Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What pattern should I use to express a Hierarchical Enum?

I'm experimenting with an API for publishing values at a given time (tuples of value and time). These samples will be used by a data viewer (e.g. a graph).

I want to associate the value with a Quantity and a Unit, for example length in meters. That way my "viewer" can scale it appropriately.

I'm looking for a sort of hierarchical enum, like this:

enum Quantity
{
   Mass.Kg,
   Mass.g,
   Length.m,
   Length.mm
}

But this doesn't exist in C#.

I'm not sure the best pattern to express this and I've come up with the following. Is there a recognised, or better way to do this?

using System;
using Moq;

namespace ConsoleApplication26
{
    class Program
    {
        static void Main(string[] args)
        {
            //use a Mock to play with the API
            Mock<ITelemetryPublisherFactory> mockTelemetryPublisherFactory = new Mock<ITelemetryPublisherFactory>();
            var telemetryPublisherFactory = mockTelemetryPublisherFactory.Object;

            //example usages
            var massTelemetryPublisher = telemetryPublisherFactory.GetChannelSamplePublisher<Double>("My Mass", Mass.Kg);
            massTelemetryPublisher.PublishChannelSampleAtTimeNow(83.4);                        

            var lengthTelemetryPublisher = telemetryPublisherFactory.GetChannelSamplePublisher<Int32>("My Height", Length.μm);
            lengthTelemetryPublisher.PublishChannelSampleAtTimeNow(1800000);      

            //10 years time..
            lengthTelemetryPublisher.PublishChannelSampleAtTimeNow(1800000);
            massTelemetryPublisher.PublishChannelSampleAtTimeNow(120.1);                        
        }
    }

    public interface ITelemetryPublisherFactory
    {
        ITelemetryPublisher<T> GetChannelSamplePublisher<T>(String channelName, Quantity quantity);
    }

    public interface ITelemetryPublisher<T>
    {
        void PublishChannelSampleAtTimeNow(T sampleValue);
    }

    public abstract class Quantity {}

    public class Mass : Quantity
    {
        private enum Unit
        {
            g,
            Kg
        }

        private readonly Unit _unit;

        private Mass(Unit unit)
        {
            _unit = unit;
        }

        public static Quantity Kg {get { return new Mass(Unit.Kg); }}
        public static Quantity g { get { return new Mass(Unit.g); } }

        public override string ToString()
        {
            return String.Format("Mass.{0}", _unit);
        }
    }

    public class Length : Quantity
    {
        private enum Unit
        {
            m,
            mm,
            μm,
            beardSecond
        }

        private readonly Unit _unit;

        private Length(Unit unit)
        {
            _unit = unit;
        }

        public static Quantity m { get { return new Length(Unit.m); } }
        public static Quantity mm { get { return new Length(Unit.mm); } }
        public static Quantity μm { get { return new Length(Unit.μm); } }
        public static Quantity beardSecond { get { return new Length(Unit.beardSecond); } }

        public override string ToString()
        {
            return String.Format("Length.{0}", _unit);
        }        
    }    
}
like image 733
Daniel James Bryars Avatar asked May 28 '12 22:05

Daniel James Bryars


2 Answers

I think it's better to create a Unit class for the unit of measure and a Quantity class that associates a unit of measure with an amount. Look at the Quantity pattern for the idea. Since you also want to record the "type" of the unit of measure, you could create a UnitType class that records that information:

public sealed partial class UnitType {
  public string Name { get; private set; }
  public UnitType(string name) {
    Name = name;
  }
}

public sealed partial class Unit {
  public string Name { get; private set; }
  public UnitType Type { get; private set; }
  public Unit(string name, UnitType type) {
    Name = name;
    Type = type;
  }
}

(You should make them proper value types by overriding Equals and GetHashCode)

The Unit class can be extended to provide for e.g. conversions, compound units, formatting and parsing.

Then, you can define the common cases inside the classes:

public partial class UnitType {
  public static readonly UnitType Mass = new UnitType("Mass");
  public static readonly UnitType Length = new UnitType("Length");
}

public partial class Unit {
  public static readonly Unit Grams = new Unit("g", UnitType.Mass);
  public static readonly Unit Kilos = new Unit("kg", UnitType.Mass);
  // ...
}

Or define your "hierarchies" with static classes:

public static class Mass {
  public static readonly UnitType Type = new UnitType("Mass");

  public static readonly Unit Grams = new Unit("g", Type);
  public static readonly Unit Kilos = new Unit("kg", Type);
  ...
}

public static class Length ...

The Quantity class would also be an immutable value type (just showing its usage):

var eniacWeight = new Quantity(27, Mass.Tons);

Or you could use extension methods to create Quantitys:

var eniacWeight = 27.Tons();

(from ENIAC)

like image 199
Jordão Avatar answered Oct 31 '22 11:10

Jordão


This is not possible. Enums are primitive types and cannot inherit from other enums, as inheritance is a property of objects.

like image 21
Porco Avatar answered Oct 31 '22 10:10

Porco