关于枚举类(Enumeration Class )

9 篇文章 0 订阅
6 篇文章 0 订阅

        在微软的微服务架构案例eshopOnContainer中,对于订单的状态是这样的:

public class OrderStatus
    : Enumeration
{
    public static OrderStatus Submitted = new OrderStatus(1, nameof(Submitted).ToLowerInvariant());
    public static OrderStatus AwaitingValidation = new OrderStatus(2, nameof(AwaitingValidation).ToLowerInvariant());
    public static OrderStatus StockConfirmed = new OrderStatus(3, nameof(StockConfirmed).ToLowerInvariant());
    public static OrderStatus Paid = new OrderStatus(4, nameof(Paid).ToLowerInvariant());
    public static OrderStatus Shipped = new OrderStatus(5, nameof(Shipped).ToLowerInvariant());
    public static OrderStatus Cancelled = new OrderStatus(6, nameof(Cancelled).ToLowerInvariant());

    public OrderStatus(int id, string name)
        : base(id, name)
    {
    }
}

        微软没有直接使用传统的Enum关键字,而是自定了以一个抽象类:Enumeration。最开始,我有些不解,为什么么要“舍近求远”?其实微软官方在此文中提到了枚举类,理由是启动面向对象语言的所有丰富功能。解释的很晦涩,虽然给出了代码案例,但是还是让人很难体会到其中奥妙。直到最近又看了这个博客,似乎才深入理解了一些。结合一个例子说明,可能更好!

目录

1.使用枚举

2.enumeration类

3.拓展



1.使用枚举

        假设你要去买咖啡,正常情况下会有“大、中、小”三种规格,所以一般情况下会让你给出尺寸需求,不同的大小价格自然不同,出于节约,默认是小杯。好了我们应该怎么实现呢?

首先定义一个枚举类型,表明所要分量:

    public enum CoffeCupType
    {
        Middle,
        Large,
        SuperLarge
    }

        然后我们定义咖啡类如下:

    public class Coffee
    {
        const double CoffeBeans = 10.0;
        public CoffeCupType Type { get; set; } = default;

        public double Pay()
        {
            return Type switch
            {
                CoffeCupType.Middle => CoffeBeans * 1.0,
                CoffeCupType.Large => CoffeBeans * 1.5,
                CoffeCupType.SuperLarge => CoffeBeans * 2.0,
                _ => 0,
            };
        }
        public void SetType(int t)
        {
            Type = (CoffeCupType)t;
        }
    }

可以看到我们设计了一个很简单的咖啡类,咖啡的费用需要根据类型判断,看起来好像没什么问题,但是还是有些不完美的地方:

  • 与枚举相关的行为会分部到应用的多个地方
  • 如果要增加新的枚举值,可能要对代码进行
  • ,枚举类型不符合开闭原则

添加新的枚举值,就需要在Pay函数中添加新的Switch分支,让默认分支变得有防御性,但是新的枚举值可能导致错误的逻辑。

        其实,对于上面的例子,我们将杯子的属性和价格倍率拆分了,使得在Coffe类中显得耦合性过强。其实价格倍率本身应该是CoffeeCupType的附加属性,所以,如果我们能定义一个类集合,那么上面的逻辑就会简化很多。

2.enumeration类

        这里作者定义了一个抽象类:

 public abstract class Enumeration
    {
        private readonly int _value;
        private readonly string _name;

        protected Enumeration()
        { }

        protected Enumeration(int value, string name)
        {
            _value = value;
            _name = name;
        }
        public int Value { get { return _value; } }
        public string Name { get { return _name; } }
        public override string ToString()
        {
            return Name;
        }
        public static IEnumerable<T> GetAll<T>() where T:Enumeration
        {
            var type=typeof(T);
            var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly);
            return fields.Select(f=> f.GetValue(null)).Cast<T>();
        }
        public override bool Equals(object? obj)
        {
            if (obj is not Enumeration otherObj)
                return false;
            var typeMatcher=GetType().Equals(obj.GetType());
            var valueMatcher=_value.Equals(otherObj.Value);
            return typeMatcher && valueMatcher;
        }
        public override int GetHashCode()
        {
            return Value.GetHashCode();
        }
        public static int AbsoluteDifference(Enumeration firstValue,Enumeration secondValue)
        {
            var absoluteDifference=Math.Abs(firstValue.Value - secondValue.Value);
            return absoluteDifference;
        }
        public static T FromValue<T>(int value) where T:Enumeration,new()
        {
            var matchingItem = Parse<T, int>(value, "value", item => item.Value == value);
            return matchingItem;
        }
        public static T FromValue<T>(string name) where T:Enumeration,new()
        {
            var matchingItem=Parse<T, string>(name, "name", item => item.Name == name);
            return matchingItem;
        }

        public static T Parse<T,K>(K value,string description,Func<T,bool> predicate) where T:Enumeration
        {
            var matchingItem=GetAll<T>().FirstOrDefault(predicate);
            if(matchingItem == null)
            {
                throw new InvalidOperationException($"'{value}' is not a valid {description} in {typeof(T)}");
            }
            return matchingItem;
        }
        public int CompareTo(object other)=>Value.CompareTo(((Enumeration)other).Value);
    }

 具体代码我就不讲解了,成员属性都很好理解。有了上面的枚举类,我们可以将CoffeeCupType类派生于此,然后定义三个静态成员,以此限定范围。

 public class CoffeeCupType2:Enumeration
    {
        public static readonly CoffeeCupType2 Middle = new CoffeeCupType2(0, "中杯",1.0);
        public static readonly CoffeeCupType2 Large = new CoffeeCupType2(1, "大杯",1.5);
        public static readonly CoffeeCupType2 SuperLarge = new CoffeeCupType2(2, "特大杯",2.0);
        public CoffeeCupType2() { }
        public CoffeeCupType2(int value,string displayName,double rate):base(value, displayName)
        {
            _rate = rate; 
        }

        private readonly double _rate;
        public static IEnumerable<CoffeeCupType2> List()=>
            new[] {Middle,Large,SuperLarge};
        public static CoffeeCupType2 FromName(string name)
        {
            var state= List().Single(s => string.Equals(s.Name, name, StringComparison.OrdinalIgnoreCase));
            if(state==null)
            {
                throw new InvalidDataException("Invalid Name!");
            }
            return state;
        }
        public static CoffeeCupType2 FromValue(int value)
        {
            var state = List().Single(s => s.Value==value);
            if (state == null)
            {
                throw new InvalidDataException("Invalid Value!");
            }
            return state;
        }

        public  double GetRate()=>_rate;
        public  void EntryWord() => Console.WriteLine($"你现在换成了{Name}");
    }

        这下,就可以将枚举当成一个完整的类来使用,这个类很像DDD中定义的“值对象”,然后获取支付订单的函数就变为:

        public double GetPay()
        {
            return CoffeCupType_2!.GetRate() * CoffeBeans;
        }

        优化支付函数,避免Switch分支语句只是其中的一个好处,你还可以在CoffeCupType2中添加一些行为函数,让其作为应用中作为“状态”时,变成“充血模型”。

        你还可以创建子类型:

        private class GlassBottle:CoffeeCupType2
        {
            public GlassBottle() : base(3, "畅饮杯", 3.0) { }
            public new void EntryWord() => Console.WriteLine($"只有尊贵的VIP才可以买{Name}装咖啡,可以无限续杯");

        }

        public static readonly CoffeeCupType2 Free = new GlassBottle();

这个类型不会暴露给外面。如果你看过里面的代码,就注意到了我还添加了一个函数:EntryWord,也就是带有一定内建行为,我想在替换杯子类型时,能够出发这个行为:

因此在Coffe中,给类型赋值时会调用这个函数:

        private CoffeeCupType2? _coffeCupType2;
        public CoffeeCupType2? CoffeCupType_2 
        {
            get { return _coffeCupType2; }
            set
            {
                if(value is CoffeeCupType2 c)
                {
                    _coffeCupType2 = c;
                    _coffeCupType2.EntryWord();
                }
                else
                {
                    _coffeCupType2 = CoffeeCupType2.Middle;
                }
            } } 

        枚举类在你需要构建充血模型时很有用,你可以让这些枚举值附带特定的行为逻辑,这对于构建DDD中的领域模型时很有帮助的,但是并不是什么时候都需要枚举类。在某些简单的场景,还是用普通枚举即可。

3.拓展

        值得一提的是,枚举类已经有人实现了这个类库,而且相当强大,这样就不要自己写Enumeration抽象类了。具体可以在github上搜SmartEnum。

这里贴一个官方例子感受一下:

using Ardalis.SmartEnum;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SmartEnumStudy
{
    /// <summary>
    /// 可以实现对状态的控制
    /// </summary>
    public abstract class OrderStatus:SmartEnum<OrderStatus>
    {
        public static readonly OrderStatus New = new NewStatus();
        public static readonly OrderStatus Accepted = new NewStatus();
        public static readonly OrderStatus Paid = new NewStatus();
        public static readonly OrderStatus Cancelled = new NewStatus();
        private OrderStatus(string name, int value) : base(name, value) { }
        public abstract bool CanTransitionTo(OrderStatus next);

        private sealed class NewStatus : OrderStatus
        {
            public NewStatus() : base("New", 0)
            {
            }

            public override bool CanTransitionTo(OrderStatus next) =>
                next == OrderStatus.Accepted || next == OrderStatus.Cancelled;
        }
        private sealed class AcceptedStatus : OrderStatus
        {
            public AcceptedStatus() : base("Accepted", 1)
            {
            }

            public override bool CanTransitionTo(OrderStatus next) =>
                next == OrderStatus.Paid || next == OrderStatus.Cancelled;
        }

        private sealed class PaidStatus : OrderStatus
        {
            public PaidStatus() : base("Paid", 2)
            {
            }

            public override bool CanTransitionTo(OrderStatus next) =>
                next == OrderStatus.Cancelled;
        }

        private sealed class CancelledStatus : OrderStatus
        {
            public CancelledStatus() : base("Cancelled", 3)
            {
            }

            public override bool CanTransitionTo(OrderStatus next) =>
                false;
        }
    }
}

Nuget地址:SmartEnum

本文示例代码:EnumerationClass

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值