Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing a static property of a child in a parent method - Design considerations

I have a similar problem to the post Accessing a static property of a child in a parent method. The preferred answer hints that the design of the classes is faulty and more information is needed to discuss the problem.

Here is the situation I want to discuss with you.

I want to implement some unit aware datatypes like length, mass, current, ... There should be an implicit cast to create the instances from a given string. As example "1.5 m" should give the same as "150 cm", or "20 in" should be treated correctly.

To be able to convert between different units, I need quantity specific conversion constants. My idea was to create an abstract base class with some static translation methods. Those should use class specific statically defined dictionary to do their job. So have a look at the example.

public class PhysicalQuantities
{
    protected static Dictionary<string, double> myConvertableUnits;

    public static double getConversionFactorToSI(String baseUnit_in)
    {
        return myConvertableUnits[baseUnit_in];
    }
}

public class Length : PhysicalQuantities
{
    protected static Dictionary<string, double> myConvertableUnits = new Dictionary<string, double>()
    {
        { "in", 0.0254 }, { "ft", 0.3048 }
    };
}

class Program
{
    static void Main(string[] args)
    {
        Length.getConversionFactorToSI("in");
    }
}

I think this gives a rather intuitive usage and keeps the code compact and quite readable and extendable. But of course I ran into the same problems the referenced post describes.

Now my question is: How can I avoid this problems by design?

like image 425
Andreas Walter Avatar asked May 16 '11 16:05

Andreas Walter


3 Answers

I think this could be solved with generics to still look readable. Refined as per Slaks suggestion to fit the registration into the static constructor to make it thread safe per se.

So if I am not mistaken:

  • thread safe (all work on dictionary in the static constructor)
  • syntax still easy to use and readable SIConversion<Length>.GetFactor() (1 char more)
  • code needed to implement on derived classes very boilerplate register(string,double); (actually shorter than your dictionary definition)

    interface ISIConversionSubscriber
    {
        void Register(Action<string, double> regitration);
    }
    
    static class SIConversion<T> where T : ISIConversionSubscriber, new()
    {
    
        private static Dictionary<string, double> myConvertableUnits = new Dictionary<string, double>();
    
        static SIConversion() {
            T subscriber = new T();
            subscriber.Register(registrationAction);
        }
    
        public static double GetFactor(string baseUnit)
        {
            return myConvertableUnits[baseUnit];
        }
    
        private static void registrationAction(string baseUnit, double value)
        {
            myConvertableUnits.Add(baseUnit, value);
        }
    
    }
    
    abstract class PhysicalQuantities : ISIConversionSubscriber
    {
        public abstract void Register(Action<string, double> register);
    }
    
    class Length : PhysicalQuantities
    {
        public override void Register(Action<string, double> register)
        {
            // for each derived type register the type specific values in this override
            register("in", 1);
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(SIConversion<Length>.GetFactor("in"));
        }
    }
    

Output: 1

In case you wonder why I made PhysicalQuantities abstract: to avoid using it with SIConversion<PhysicalQuantities>.GetFactor() since we don't have a conversion for a base class. You probably don't need instances of a base class like this anyway - it is not a full representation of a quantity, so it will probably contain reusable methods only.

Another suggestion would be to use an Enum for the baseUnit instead of a string. Since everybody is striving for type safety and crying foul on magic strings, it probably is a good path to follow :))

like image 200
Marino Šimić Avatar answered Nov 10 '22 14:11

Marino Šimić


The best option here, typically, tends to be to avoid the static nature of your design, and work on instances instead. I have a similar library I've developed, but the usage tends to be more like:

static void Main()
{
    // Since I'm working with instances, I tend to pass the actual 
    // measured amount in as well...  However, you could leave this out (or pass 1)
    var len = Length.Create(42, "in"); 
    double conversionFactory = len.ConversionFactorToSI;
}

I developed it this way so that I could define a dictionary per type, statically, but use a factory method that passes this to the base class constructor (which is protected). This allows the base class to be instantiated with a reference to the specific type's dictionary, which works very cleanly.

like image 22
Reed Copsey Avatar answered Nov 10 '22 14:11

Reed Copsey


I have found that test-driven-development has often driven me toward better designs too. In your case, the 'translation methods' are an important piece you will want to test independently of their use in your classes. I would suggest encapsulating that logic in its own type.

Then you can focus on instances as Reed suggests with the knowledge that your translation logic is well-tested. Your abstract base class then simply serves as a common-root that knows how to get the right translator.

like image 24
n8wrl Avatar answered Nov 10 '22 15:11

n8wrl