【问题标题】:What pattern should I use to express a Hierarchical Enum?我应该使用什么模式来表达分层枚举?
【发布时间】:2015-07-07 00:25:07
【问题描述】:

我正在尝试使用 API 在给定时间发布值(值和时间的元组)。这些样本将由数据查看器(例如图表)使用。

我想将值与数量和单位相关联,例如以米为单位的长度。这样我的“观众”可以适当地缩放它。

我正在寻找一种分层枚举,如下所示:

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

但这在 C# 中不存在。

我不确定表达这一点的最佳模式,我想出了以下内容。是否有公认的或更好的方法来做到这一点?

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);
        }        
    }    
}

【问题讨论】:

标签: c# architecture enums design-patterns


【解决方案1】:

我认为最好为计量单位创建一个Unit 类和一个将计量单位与数量相关联的Quantity 类。看看Quantity pattern 的想法。由于您还想记录计量单位的“类型”,您可以创建一个记录该信息的UnitType 类:

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;
  }
}

(您应该通过覆盖EqualsGetHashCode 使它们成为正确的值类型)

Unit 类可以扩展以提供例如转换、复合单位、格式化和解析。

然后,您可以在类中定义常见情况:

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);
  // ...
}

或者用静态类定义你的“层次结构”:

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 ...

Quantity 类也将是不可变的值类型(仅显示其用法):

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

或者你可以使用扩展方法来创建Quantitys:

var eniacWeight = 27.Tons();

(来自ENIAC

【讨论】:

  • 感谢 Fowler 模式的链接,非常有趣。
【解决方案2】:

这是不可能的。枚举是原始类型,不能从其他枚举继承,因为继承是对象的属性。

【讨论】:

    【解决方案3】:

    如上所述,分层枚举是不可能的。但是,如果您只使用度量标准,则可以使用标准前缀(如果有帮助的话)。

    enum MeasurementUnits
    {
        Gram,
        Metre,
        Litre,
        Hectare
        // etc
    }
    
    enum MeasurementPrefix
    {
        Milli,
        Natural,
        Kilo,
        Mega
        // etc
    }
    

    这可能不是您想要的,但它会提供您可能正在寻找的“分组”类型(例如,通过检查其“单位”值来分组关于长度、重量等的测量值)。

    【讨论】:

    • 这是个好主意,我喜欢。 +1
    【解决方案4】:

    您建议的方法对我来说似乎是合理的,我在我的项目中使用了类似的方法。但是,我保留了对象的实际值部分,并且我使用struct 而不是class,因为它们自然是值类型。这里不需要继承(无论如何,对于结构来说也不可能),所以我使用接口来创建合同并在需要时充当约束(我称之为IUnitOfMeasure)。

    我不建议创建一个包含各种测量类型的所有单位的枚举;验证单位以确保有人在使用长度时没有引用质量单位是地狱。

    public interface IUnitOfMeasure<TThis>
        where TThis : IUnitOfMeasure<TThis>
    {
        TThis ConvertTo(TThis value);
    }
    
    public struct Mass : IUnitOfMeasure<Mass>
    {
        public enum Units
        {
            Gram,
            Kilogram
        }
    
        private double _value;
        private Mass.Units _unit;
    
        public double Value { get { return _value; } }
    
        public Mass.Units Unit { get { return _unit; } }
    
        public Mass(double value, Mass.Units unit)
        {
            _value = value;
            _unit = unit;
        }
    
        public Mass ConvertTo(Mass value)
        {
            switch(value.Unit)
            {
                case Units.Gram:
                    return new Mass(Unit == Units.Gram ? Value : Value/1000, Units.Gram);
                case Units.Kilogram:
                    return new Mass(Unit == Units.Gram ? Value*1000 : Value, Units.Kilogram);
                default:
                    throw new NotImplementedException();
            }
        }
    
        public override string ToString()
        {
            return string.Format("{0} {1}", Value, Unit);
        }
    
        public static readonly Mass G = new Mass(0, Units.Gram);
        public static readonly Mass Kg = new Mass(0, Units.Kilogram);
    }
    

    用法:

    var kg = new Mass(5.0, Mass.Units.Kilogram);
    
    Console.WriteLine(kg); // writes "5 Kilogram"
    
    var g = kg.ConvertTo(Mass.G);
    
    Console.WriteLine(g); // writes ".005 Gram"
    

    如果您不关心保留值,而只想将枚举/静态值保留在中心位置:

    public static class UnitOfMeasure
    {
        public enum Mass
        {
          Gram,
          Kilogram
        }
    
        public enum Length
        {
           Meter,
           Kilometer
        }
        // etc.
    }
    

    用法:var unit = UnitOfMeasure.Mass.Kilogram;

    【讨论】:

    • 我不能使用你帖子中的最后一个想法,感谢界面的想法。 +1
    【解决方案5】:

    你不能用枚举引入继承。枚举只是一种方便的机制,允许您在代码中使用有意义的文本标识符。从您拥有的代码中,我建议您使用类似的枚举;

    public enum UnitOfMeasure
    {
        MassGrams,
        MassKg,
        LengthMM,
        LengthCM,
        . . .
    }
    

    或者将其拆分到合适的位置,例如分别定义质量和长度。

    “继承”只是你在思考这个问题时引入的东西,但它不是你的解决方案所必需的。当你想处理 Mass 时,你只需要查看适合 mass 的标志/枚举。

    【讨论】:

      猜你喜欢
      • 2018-05-30
      • 1970-01-01
      • 2011-06-17
      • 2011-06-29
      • 2012-09-21
      • 1970-01-01
      • 2014-08-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多