如何使用flags属性继承自枚举的类

Any*_*are 6 c# oop enums domain-driven-design

如何在继承自EnumerationWith限制的类中实现多个选择?

如果我有五种计划类型:

  • 固定时间表
  • 轮流安排
  • 全时间表
  • 兼职时间表
  • 灵活可变的时间表

前两个选项是对(Fixed vs Rotated)后两个选项(FullTime vs PartTime)是对的,我的意思是计划不能fixedrotated在同一时间或fulltime and parttime在同一时间.但它可能是Fixed and FullTime例如.


固定工作时间表,包括每周工作的相同小时数和工作天数,并且一旦雇主和工人商定了小时数和天数,就会保持一致.

灵活的工作时间表,员工和雇主一起工作,以确定他们能够承诺的一周中的小时数和天数.全职工作时间表,通常需要每周37-40小时的承诺.由于工作时间长,具有全职时间表的职业有资格获得工作福利.这些福利包括休假,假期和疾病,健康保险和不同的退休计划选择.

兼职工作时间表,是任何不及全职工作的时间表.

轮换工作时间表,使员工在一天或一周,周转和夜班之间循环.这个循环有助于在所有员工之间分配不同的班次,这样就不会有人因为不太理想的时间而陷入困境.

所以我做了以下事情:

public class Schedule
{
    public Schedule()
    {

    }

    private ICollection<ScheduleDetail> _assignedWeeks;
    public int Id { get; set; }
    public string Name { get; set; }
    public int WorkingGroupId { get; set; }
    public ScheduleType ScheduleType { get; set; }
    public bool IsFixed { get; }
    public bool IsFlexible { get; }
    public bool IsFullTime { get; }
    public ICollection<ScheduleDetail> AssignedWeeks { get => _assignedWeeks; set => _assignedWeeks = value; }
}
Run Code Online (Sandbox Code Playgroud)
public abstract class ScheduleType : Enumeration
    {
        protected ScheduleType(int value, string displayName) : base(value, displayName)
        {
        }
        public static readonly ScheduleType Fixed
       = new FixedType();
        public static readonly ScheduleType Flexible
            = new FlexibleType();
        public static readonly ScheduleType FullTime
            = new FullTimeType();
        public static readonly ScheduleType PartTime
           = new PartTimeType();
        public static readonly ScheduleType Rotated
           = new RotatedType();



        private class FixedType : ScheduleType
        {
            public FixedType() : base(1, "Fixed Work Schedule")
            {
            }
        }

        private class FlexibleType : ScheduleType
        {
            public FlexibleType() : base(2, "Flexible Work Schedule")
            {
            }
        }

        private class FullTimeType : ScheduleType
        {
            public FullTimeType() : base(3, "Full Time Work Schedule")
            {
            }
        }

        private class PartTimeType : ScheduleType
        {
            public PartTimeType() : base(4, "Part Time Work Schedule")
            {
            }
        }
        private class RotatedType : ScheduleType
        {
            public RotatedType() : base(5, "Rotated Work Schedule")
            {
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)
public abstract class Enumeration : IComparable
    {
        private readonly int _value;
        private readonly string _displayName;

        protected Enumeration()
        {
        }

        protected Enumeration(int value, string displayName)
        {
            _value = value;
            _displayName = displayName;
        }

        public int Value
        {
            get { return _value; }
        }

        public string DisplayName
        {
            get { return _displayName; }
        }

        public override string ToString()
        {
            return DisplayName;
        }

        public static IEnumerable<T> GetAll<T>() where T : Enumeration, new()
        {
            var type = typeof(T);
            var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly);

            foreach (var info in fields)
            {
                var instance = new T();
                var locatedValue = info.GetValue(instance) as T;

                if (locatedValue != null)
                {
                    yield return locatedValue;
                }
            }
        }

        public override bool Equals(object obj)
        {
            var otherValue = obj as Enumeration;

            if (otherValue == null)
            {
                return false;
            }

            var typeMatches = GetType().Equals(obj.GetType());
            var valueMatches = _value.Equals(otherValue.Value);

            return typeMatches && valueMatches;
        }

        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 FromDisplayName<T>(string displayName) where T : Enumeration, new()
        {
            var matchingItem = parse<T, string>(displayName, "display name", item => item.DisplayName == displayName);
            return matchingItem;
        }

        private static T parse<T, K>(K value, string description, Func<T, bool> predicate) where T : Enumeration, new()
        {
            var matchingItem = GetAll<T>().FirstOrDefault(predicate);

            if (matchingItem == null)
            {
                var message = string.Format("'{0}' is not a valid {1} in {2}", value, description, typeof(T));
                throw new ApplicationException(message);
            }

            return matchingItem;
        }

        public int CompareTo(object other)
        {
            return Value.CompareTo(((Enumeration)other).Value);
        }
    }
Run Code Online (Sandbox Code Playgroud)

因此,根据用户对特定选项或选项集的选择,我必须调用一个方法来(IsFixed,...)在Schedule类中设置标志来控制scheduledetails类(固定和旋转)和小时数(全天和兼职)


我会对任何建议或建议表示感谢?

Boz*_*eff 10

你太复杂了.我怀疑的第一个问题是你(或你的业务分析师)对业务主题没有足够的把握 - 即转移.你在这里有两个不同的枚举:

public enum ScheduleType 
{
    Unknown = 0,
    Fixed,
    Rotated
}

public enum ScheduleLoad 
{
    Unknown = 0,
    FullTime,
    PartTime
}
Run Code Online (Sandbox Code Playgroud)

接下来,在UI中,您需要两个不同的下拉框/单选按钮组,以允许用户排列移位布局,然后将其保存在对象的两个不同属性中.

但是,如果您坚持在一个枚举中使用它,因此一个带有标记枚举值的属性,则需要在将标志保存到存储之前验证用户输入.

[Flags]
public enum ShiftLayout
{
    Unknown = 0,
    Fixed = 1,
    Rotated = 2,
    FullTime = 4,
    PartTime = 8,
    Flexible = 16
}
Run Code Online (Sandbox Code Playgroud)

然后验证执行如下:

public bool IsShiftLayoutValid(ShiftLayout layout)
{
    var isValid = layout.HasFlag(ShiftLayout.Flexible) 
        && (layout & ~ShiftLayout.Flexible) == ShiftLayout.Unknown;

    if (!isValid && !layout.HasFlag(ShiftLayout.Flexible))
    {
        var hasValidSchedule = (layout.HasFlag(ShiftLayout.Fixed) && !layout.HasFlag(ShiftLayout.Rotated))
            || layout.HasFlag(ShiftLayout.Rotated);

        var hasValidTime = (layout.HasFlag(ShiftLayout.FullTime) && !layout.HasFlag(ShiftLayout.PartTime))
            || layout.HasFlag(ShiftLayout.PartTime);

        isValid = hasValidSchedule && hasValidTime;
    }

    return isValid;
}
Run Code Online (Sandbox Code Playgroud)

  • 我认为主要问题是OP试图"跟随DDD",因此希望使用类而不是简单的枚举(将业务逻辑合并到其中). (2认同)

Nko*_*osi 5

以下ScheduleType示例能够保存与使用位字段类似的多种类型.请注意用于类型值的十六进制值,这些值允许逻辑操作确定构成当前值的类型.

public class ScheduleType : FlagsValueObject<ScheduleType> {
    public static readonly ScheduleType Fixed = new ScheduleType(0x01, "Fixed");
    public static readonly ScheduleType Flexible = new ScheduleType(0x02, "Flexible");
    public static readonly ScheduleType FullTime = new ScheduleType(0x04, "Full Time");
    public static readonly ScheduleType PartTime = new ScheduleType(0x08, "Part Time");
    public static readonly ScheduleType Rotated = new ScheduleType(0x10, "Rotated");

    protected ScheduleType(int value, string name)
        : base(value, name) {
    }

    private ScheduleType(ScheduleType a, ScheduleType b) {
        foreach (var kvp in a.Types.Union(b.Types)) {
            Types[kvp.Key] = kvp.Value;
        }            
        Name = string.Join(", ", Types.OrderBy(kvp => kvp.Value).Select(kvp => kvp.Value)) + " Work Schedule";
        Value = Types.Keys.Sum();
    }

    protected override ScheduleType Or(ScheduleType other) {
        var result=new ScheduleType(this, other);

        //Applying validation rules on new combination
        if (result.HasFlag(Fixed) && result.HasFlag(Rotated))
            throw new InvalidOperationException("ScheduleType cannot be both Fixed and Rotated");

        if (result.HasFlag(FullTime) && result.HasFlag(PartTime))
            throw new InvalidOperationException("ScheduleType cannot be both FullTime and PartTime");

        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

使用HasFlag确定标志内存在的组合,可以应用所需的业务规则.

例如

//Applying validation rules on new combination
if (result.HasFlag(Fixed) && result.HasFlag(Rotated))
    throw new InvalidOperationException("ScheduleType cannot be both Fixed and Rotated");

if (result.HasFlag(FullTime) && result.HasFlag(PartTime))
    throw new InvalidOperationException("ScheduleType cannot be both FullTime and PartTime");
Run Code Online (Sandbox Code Playgroud)

组合标志时应用这些规则以防止创建任何不需要的组合.

它源自以下支持值对象

public abstract class FlagsValueObject<T> : EnumValueObject where T : FlagsValueObject<T> {
    protected readonly IDictionary<int, string> Types = new SortedDictionary<int, string>();

    protected FlagsValueObject(int value, string name)
        : base(value, name) {
        Types[value] = name;
    }

    protected FlagsValueObject() {

    }

    public static T operator |(FlagsValueObject<T> left, T right) {
        return left.Or(right);
    }

    protected abstract T Or(T other);

    public virtual bool HasFlag(T flag) {
        return flag != null && (Value & flag.Value) == flag.Value;
    }

    public virtual bool HasFlagValue(int value) {
        return (Value & value) == value;
    }
}

public class EnumValueObject : IEquatable<EnumValueObject>, IComparable<EnumValueObject> {

    protected EnumValueObject(int value, string name) {
        Value = value;
        Name = name;
    }

    protected EnumValueObject() {

    }

    public virtual string Name { get; protected set; }

    public virtual int Value { get; protected set; }

    public static bool operator ==(EnumValueObject left, EnumValueObject right) {
        return Equals(left, right);
    }

    public static bool operator !=(EnumValueObject left, EnumValueObject right) {
        return !Equals(left, right);
    }

    public int CompareTo(EnumValueObject other) {
        return Value.CompareTo(other.Value);
    }

    public bool Equals(EnumValueObject other) {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Value.Equals(other.Value);
    }

    public override bool Equals(object obj) {
        return obj is EnumValueObject && Equals((EnumValueObject)obj);
    }

    public override int GetHashCode() {
        return Value.GetHashCode();
    }

    public override string ToString() {
        return Name;
    }
}
Run Code Online (Sandbox Code Playgroud)

简单的单元测试示例.

[TestClass]
public class ScheduleTypeValueObjectTests {
    [TestMethod]
    public void Should_Merge_Names() {
        //Arrange
        var fixedSchedult = ScheduleType.Fixed; //Fixed Work Schedule
        var fullTime = ScheduleType.FullTime; // Full Time Work Schedule
        var type = fixedSchedult | fullTime;

        //Act
        var actual = type.Name;

        //Assert
        actual.Should().Be("Fixed, Full Time Work Schedule");
    }


    [TestMethod]
    [ExpectedException(typeof(InvalidOperationException))]
    public void Should_Fail_Bitwise_Combination() {
        //Arrange
        var fullTime = ScheduleType.FullTime; // Full Time Work Schedule
        var partTime = ScheduleType.PartTime;

        var value = fullTime | partTime;
    }
}
Run Code Online (Sandbox Code Playgroud)

HasFlag属性允许检查标志中存在哪些类型,如以下示例所示.

public class Schedule {
    public Schedule(
        //...
        ScheduleType scheduleType
        //...
        ) {

        //...

        ScheduleType = scheduleType;
    }

    //...

    public ScheduleType ScheduleType { get; set; }
    public bool IsFixed {
        get {
            return ScheduleType != null && ScheduleType.HasFlag(ScheduleType.Fixed);
        }
    }
    public bool IsFlexible {
        get {
            return
                ScheduleType != null && ScheduleType.HasFlag(ScheduleType.Flexible);
        }
    }
    public bool IsFullTime {
        get {
            return
                ScheduleType != null && ScheduleType.HasFlag(ScheduleType.FullTime);
        }
    }

    //...
}
Run Code Online (Sandbox Code Playgroud)