vendredi 10 janvier 2020

Single instance of a helper object with parameters

One of our projects makes use of key-value pairs where certain runtime parameters - that do not change per instance of the program - determine the value gotten. For example:

Program run in test mode with the parameter "Municipal":

Key: "testMunicipalDirectory"
Value: "C:\Foo\Bar\"

Program run with the parameter "State":

Key: "StateDirectory"
Value: "C:\Bar\Baz\"

To make it slightly more complicated, if there is no matching key for, say "testMunicipalImagesDirectory", there is a fallback of "defaultImagesDirectory":

Key: "testMunicipalImagesDirectory" ?? "defaultImagesDirectory"
Value: "C:\Foo\Bar\Images" ?? "C:\Images"

Currently, there's a lot of code duplication/inefficiencies, and room for error. Every time one of these is referenced there's string concatenation and null-coalescing and other stuff going on.

It seems like this would benefit from a single-instance object that is passed certain parameters on initialization (test or not, "State" or "Municipal", etc), that will return the correct values for each different property the keys represent.

Many answers I found to questions asking how to use the singleton design pattern with parameters basically boil down to "if it uses parameters, you probably do not want a singleton". In my case, it is invalid to attempt to initialize the object with different values, and an exception should be thrown if this happens.

This is how I would accomplish this goal (pseudo-C#) (lazy-loading is not a requirement but is used here):

public sealed class Helper
{
    // how can we enforce that Init has been called?
    private static readonly Lazy<Helper> lazyLoader = new Lazy<Helper>(() => new Helper(name, test));

    public static Helper Instance { get { return lazyLoader.Value; } }

    public static void Init(string name, bool test)
    {
        // if it has already been initalized
        throw new InvalidOperationException("This has already been initalized.");
        // else initalize it
    }

    private string Name { get; set; }
    private bool   Test { get; set; }

    private Helper(string name, bool test) { } // assign to properties, any other ctor logic

    public string Directory
        { get { return ValueGetter.Get((this.Test ? "test" : "") + this.Name + "Directory"); } }
}

public static class ValueGetter
{
    public static string Get(string key, string fallbackKey)
    {
        if (Keys.Any(k => k == key))
            return Keys[key].Value;
        else
            return Keys[fallbackKey].Value;
    }
}

But as you can see, there are questions remaining. How can it enforce calling Init before using the Instance, but not require those parameters to be passed every time Instance is accessed?

Is this the correct direction to go, or is there a better design pattern to use?

Aucun commentaire:

Enregistrer un commentaire