views:

152

answers:

6

I have a re-occurring design problem with certain classes which require one-off initialization with a parameter such as the name of an external resource such as a config file.

For example, I have a corelib project which provides application-wide logging, configuration and general helper methods. This object could use a static constructor to initialize itself but it need access to a config file which it can't find itself.

I can see a couple of solutions, but both of these don't seem quite right:

1) Use a constructor with a parameter. But then each object which requires corelib functionality should also know the name of the config file, so this has to be passed around the application. Also if I implemented corelib as a singleton I would also have to pass the config file as a parameter to the GetInstance method, which I believe is also not right.

2) Create a static property or method to pass through the config file or other external parameter.

I have sort of used the latter method and created a Load method which initializes an inner class which it passes through the config file in the constructor. Then this inner class is exposed through a public property MyCoreLib.

public static class CoreLib
{
    private static MyCoreLib myCoreLib;

    public static void Load(string configFile)
    {
        myCoreLib = new MyCoreLib(configFile);
    }

    public static MyCoreLib MyCoreLib
    {
        get { return myCoreLib; }
    }

    public class MyCoreLib
    {
        private string configFile;

        public MyCoreLib(string configFile)
        {
            this.configFile = configFile;
        }

        public void DoSomething()
        {
        }
    }
}

I'm still not happy though. The inner class is not initialized until you call the load method, so that needs to be considered anywhere the MyCoreLib is accessed. Also there is nothing to stop someone calling the load method again.

Any other patterns or ideas how to accomplish this?

A: 

You could make the class a singleton, thus ensuring there's only ever one instance of it while also allowing for the constructor parameter.

yodaj007
The code sample he posted basically IS a singleton. More concrete: a factory produced singleton.
Foxfire
A: 

There won't be much alternatives.

Either you pass stuff around (in that case I'd not pass the string around but create a concrete (non-static) CoreLib and pass that around or you do what you suggested.

Don't forget to hide the constructor for MyCoreLib. Currently it is public which is likely unintended.

Foxfire
Yes that would be useful to stop anyone else instantiating MyCoreLib elsewhere, but then making the constructor private stops the static load method in the parent class alseo from instantiating MyCoreLib.
Neil Dobson
Thats what e.g. internal is good for.
Foxfire
+1  A: 

When you have global state like this that you need to initialize and it requires outside input to complete initialization (such as a config file), then you're stuck with the outside code that knows about the input having to call Load or Initialize to initialize your global state. There's no way around this.

The issue that you've correctly observed, however, is that anybody could try to use the global state before it has been properly Initialized, which is the downside to having it exposed in this way. The way you get around this is by refactoring all of the stateful parts of your global library into an instance class and passing references to that instance through your application. Because you control when it is created and initialized, you can now ensure it has valid state before you pass it along. You trade off the convenience of global state for the better insulation you're after.

Dan Bryant
+1  A: 

You can use the .net configuration system to do this. The simplest way to do this is to use the <appsettings> element in your web.config file, or in your appname.exe.config file. Use:

ConfigurationManager.AppSettings["property_name"]

to access a property. Whether your code is running in a web context or as a windows app, the configuration system will find the config file and load the values you need.

You can also build a more complex system with type-safe config values and hierarchical data, but my needs were pretty simple, so I didn't explore this after the first headache set in :)

Ray
A: 

You need a common location to store this. You could use the app.config even if this is a seperate assembly by defining a config section in the library assembly and referencing it you proceess app.config. Or you could just add a generic setting to appSettings and reference that without using strongly typed settings. If the value is user entered then you could use isolated storage. Finally you could put it in a well known location in the registry at install time.

For code the following is encapsulted better

    public interface ICoreLib
    {
        void SomeMethod();
    }
    public static class CoreLibManager
    {
        private static ICoreLib coreLib;
        private static volatile bool initialized;
        private static readonly object lockObject = new object();
        public static ICoreLib CoreLib
        {
            get
            {
                Inititialize();
                return coreLib;
            }
        }

        /// <summary>
        /// The inititialize.
        /// </summary>
        private static void Inititialize()
        {
            if (initialized)
            {
                lock (lockObject)
                {
                    if (!initialized)
                    {
                        string configFile =  // Fech from common location
                        coreLib = new MyCoreLib(configFile);
                        initialized = true;
                    }
                }
            }
        }

        /// <summary>
        /// The my core lib.
        /// </summary>
        private class MyCoreLib : ICoreLib
        {
            public MyCoreLib(string configPath)
            {
            }
            public void SomeMethod()
            {
            }
        }
    }
csaam
Thanks csaam, you've provided a better example of code encapsulation, and I will take on board these ideas. I'm still a bit new to OOP in general and I can't yet figure out why you were able to hide the inner MyCoreLib class, yet expose the object through the CoreLib property - probably using the interface??
Neil Dobson
Unfortunately this still doesn't solve my problem with the fact that the location of an external resource file might not be known until runtime. Yet with this example we would be looking to use the app/user settings and embed this within the class project?
Neil Dobson
-OFF TOPIC- The thing I don't like about app settings is that it is not synchronized with an installer project. So if you install a WinForms app, any user-generated files could be placed in various locations: \Users or AppData, or even a custom folder. Getting this location into the app after the installer completes is quite difficult. This is why I try to write some code which determines the location at start-up and then passes the location through to CoreLib manager, rather than assuming CoreLib can take care of itself. Quite tricky, yet this scenario must happen quite often?
Neil Dobson
A: 

OK, thanks everyone for your assistance. I've refactored the CoreLib project and broken out the config handling into a separate project. Now we have a solution-wide shared class for config management. The class can take care of itself with a user setting, which is exposed via the static property ConfigFile. This property also persists the modified file location if the user changes via some config dialog. Also the initialized flag will be reset if the config file changes.

public interface IConfig
{
    void SomeMethod();
}

public static class ConfigurationManager
{
    private static IConfig config;
    private static volatile bool initialized;
    private static readonly object lockObject = new object();

    public static string ConfigFile
    {
        get { return Properties.Settings.Default.ConfigFile; }
        set
        {
            if (Properties.Settings.Default.ConfigFile == value) return;

            lock (lockObject)
            {
                Properties.Settings.Default.Save();
                initialized = false;
            }
        }
    }

    public static IConfig Config
    {
        get
        {
            Inititialize();
            return config;
        }
    }

    private static void Inititialize()
    {
        lock (lockObject)
        {
            if (initialized) return;

            config = new Configuration(Properties.Settings.Default.ConfigFile);
            initialized = true;
        }
    }
}

internal class Configuration : IConfig
{
    public ClientConfig(string configFile)
    {
        // Parse & validate config file
    }

    public void SomeMethod()
    {
    }
}

So now in the start-up we first validate the persisted ConfigFile setting, then attempt to access the Configuration instance via the manager's Config property. Any parsing exceptions can be handled here and dealt with accordingly. Then it is up to the developer to handle any exceptions for IConfig's methods.

if (!System.IO.File.Exists(ConfigurationManager.ConfigFile))
{
    // Display config file locator dialog
    ConfigurationManager.ConfigFile = someDialog.FileName;
}

try
{
    IConfig config = ConfigurationManager.Config;
}
catch
{
    // Error parsing config file
}
Neil Dobson