OT: One object w/ enumeration vs. multiple objects

OT: One object w/ enumeration vs. multiple objects

Old forum URL: forums.lhotka.net/forums/t/1412.aspx


SonOfPirate posted on Friday, October 06, 2006

I have the need to implements a solution requiring the use of a value that can be in one of two measurement units (for now): Liters or Cubic Centimeters.  We need to be able to convert the value between the units.  I.e. if we change from Liters to Cubic Centimeters, our value changes from 1.0 to 1000.0 and visa versa.

We are looking at two approaches to accomplish this and I am wondering what thoughts anyone has for the pro's & con's of each:

1. Develop a single struct (value type), LengthUnit, that has a enumeration property identifying the current units.  When the unit property is changed, we apply the conversion and change the value.  We would accept a value from the enumeration in the constructor and expose static Liter and CubicCentimeter methods for quick creation.

2. Develop two (2) structs, Liter and CubicCentimeter, and a custom TypeConverter assigned to both.  The TypeConverter would use the ILengthUnit interface (implemented by both) to access the necessary conversion factor and perform the conversion.

The first seems consistent with the way that Microsoft has implemented units in the framework, such as System.Web.UI.WebControls.Unit, but MS doesn't perform conversions between the units (i.e. no conversion from 'pixel' to 'point').

I like the second because it allows us to treat each unit like its own type and we derive the benefits from that; however, we see the possibility of expanding this in the future to add things like Gallon, FluidOunce, Milliliter, etc. and each of these would have to be implemented as their own struct.  The TypeConverter would not have to be changed if we used a common interface to expose what it needs to do the job, so to speak.  But, we could end up with a significant number of objects.

The other issue that I have with the second approach is code-reuse.  Since we can't inherit our unit objects from a common struct, implementation code would have to be repeated in each case whereas option #1, because it is all in one struct, we only have to do it once.

Thoughts?

 

JHurrell replied on Friday, October 06, 2006

If all you're looking for is a means of converting between units, why not just create a static class with a static method that accepts the unit from, unit to and amount. It would just return the converted value.

Once you have that class, you can store whatever unit and amount you want in you BO and allow either the UI to display the amount in a different/specific unit or provide a method in you BO that accepts a ToUnit and then gives the converted value.

The static unit converter class would be the only one that would need to change if you decide to provide new unit conversions.

- John

RockfordLhotka replied on Friday, October 06, 2006

Use responsibility/behavior to define your objects.

You have the concept of something that contains a value - which includes both the value itself and the UOM for that value. I'd suggest that this is a value type.

Then you have the concept of something that converts values - like a specialized form of operator. I'd suggest that this is some other object.

Microsoft does this with the Convert.Blah() Shared/static methods to convert between the various types. And this concept is integrated directly into the language in the case of VB (CStr(), CInt(), etc).

So I think you have two objects with different responsibilities. One has responsibility for representing a value/UOM and the other has responsibility for converting between various UOM's.

SonOfPirate replied on Friday, October 06, 2006

Thanks for the feedback.  It sounds like the second approach is the right one.  We'll have to deal with the short-comings using struct's instead of classes (so we're dealing with value types).  But, I agree that having the conversion aspect handled in a TypeConverter class isolates that responsibility and makes the model more flexible.

And, since the framework makes use of the TypeConverter natively, we can switch between types by simply casting (I think) or by exposing, like you said, a static method that takes advantage of the TypeConverter to do the work.

I'll have to look into some helper classes for some of the methods that are in common to obtain better code reuse than is available by nature with structs but otherwise, I think its a good approach.  Thanks again.

ajj3085 replied on Friday, October 06, 2006

Casting won't work unless you implement explicit or implicit opearators.

        public static implicit operator SmartDecimal( decimal val ) {
            return new SmartDecimal( val );
        }

RockfordLhotka replied on Saturday, October 07, 2006

SonOfPirate:

And, since the framework makes use of the TypeConverter natively, we can switch between types by simply casting (I think) or by exposing, like you said, a static method that takes advantage of the TypeConverter to do the work.



Again, I don't see why you need multiple types. If the behavior is value/UOM, then you only need one type to express that concept. Not one type per UOM. In that case you don't need a TypeConverter, because you only have a single type - with a set of Convert.Blah() methods to convert between the various UOM's.

SonOfPirate replied on Monday, October 09, 2006

Yea, I misunderstood what you were saying previously but have come back to the same conclusion when evaluating how to implement a Parse function that would accept both the magnitude and units of measure in the string.  If we have different types, then the parser has to possess all of the knowledge about the acceptable string portions in order to instantiate the correct type; where as going with a single type allows us to encapsulate this within the single Parse method.  This really put the brakes on having a separate type for each unit of measure.

We were looking to be able to have the following:

CubicCentimer ccVal = new CubicCentimeter(1000.0);
Liter literVal = (Liter)ccVal;      // literVal = 1.0 L;

But, I think in order to have the full range of behaviors we want, we'll have to settle for:

Volume ccVal = Volume.CubicCentimeter(1000.0);
Volume literVal = ccVal.ConvertTo(Liter);

Which is not a problem, just have to have a separate enumeration to track the possible units of measure as we originally thought would be the case.

 

Copyright (c) Marimer LLC