Concrete Class 继承自 Abstract class 继承自 Generic Abstract Class

Concrete Class inherit from Abstract class which inherits from Generic Abstract Class

我是这里的一名 "old school" 程序员,正在努力使用继承来发挥我的优势。我发现自己在重复代码,它开始发出异味。我没有坚持 DRY,所以我想在这里重构一下以减少代码重复!

我正在尝试编写要在我的实体中使用的值对象 classes,这将强制执行基本不变量。我有一个通用的抽象 ValueObject class 可以像这样处理相等性和散列:

public abstract class ValueObject<T> where T : ValueObject<T>
{
    protected abstract IEnumerable<object> GetEqualityCheckAttributes();

    public override bool Equals(object other)
    {
        return Equals(other as T);
    }

    public bool Equals(T other)
    {
        if (other == null)
        {
            return false;
        }
        return GetEqualityCheckAttributes().SequenceEqual(other.GetEqualityCheckAttributes());
    }

    public static bool operator == (ValueObject<T> left, ValueObject<T> right)
    {
        return Equals(left, right);
    }

    public static bool operator != (ValueObject<T> left, ValueObject<T> right)
    {
        return !(left == right);
    }

    public override int GetHashCode()
    {
        int hash = 17;
        foreach (var obj in this.GetEqualityCheckAttributes())
        {
            hash = hash * 31 + (obj == null ? 0 : obj.GetHashCode());
        }
        return hash;
    }
}

然后我一直在创建我的值对象 classes,然后实现这个抽象 class,并提供逻辑以确保不能在无效状态下创建对象。这是我开始违反 DRY 并使用相同代码创建许多对象的时候(例如,最大长度为 50 或 30 或 10 的所需字符串)。

所以我希望将执行不变量的代码放在它自己的 class 中,并让我的具体值对象 class 继承该功能。类似的东西(这不编译,见下文):

public abstract class RequiredStringValueObject : ValueObject<string>
{
    private string _value;
    protected string _fieldName;
    protected byte _maxLength;

    public string Value
    {
        get
        {
            return _value;
        }
        protected set
        {
            if (value == null || string.IsNullOrWhiteSpace(value))
            {
                throw new ArgumentNullException(_fieldName, _fieldName + " must be supplied.");
            }
            value = value.Trim();
            if (value.Length > _maxLength)
            {
                throw new ArgumentOutOfRangeException(_fieldName, value, _fieldName + " can't be longer than " + _maxLength.ToString() + " characters.");
            }
            _value = value;
        }
    }
}

然后我可以 "use" 具体 class 所有这些功能,如下所示:

public class FirstName : RequiredStringValueObject
{
    private FirstName(string value, string FieldName, byte MaxLength)
    {
        _fieldName = FieldName;
        _maxLength = MaxLength;
        Value = value;
    }
    public static FirstName Create(string value, string FieldName, byte MaxLength)
    {
        return new FirstName(value, FieldName, MaxLength);
    }

    protected override IEnumerable<object> GetEqualityCheckAttributes()
    {
        return new List<object> { Value };
    }
}

所有这些似乎都是解决问题的合理方法(对我而言)。问题是我在 RequiredStringValueObject 声明中收到编译器错误:

The type string cannot be used as a type parameter T in the generic type or method ValueObject<T>. There is no implicit reference conversion from string to ValueObject<string>.

我不完全理解错误信息。我正在尝试做的事情可能吗?有没有办法使这项工作?还是我 could/should 正在采取另一种方法?

你的泛型类型 T 有一个 where 子句:

public abstract class ValueObject<T> where T : ValueObject<T>

这告诉编译器 T 必须从 ValueObject 派生,而 string 不是。

你想用 where T: 子句强制执行什么?你可能想忽略它。

问题源于这一行:

abstract class ValueObject<T> where T : ValueObject<T>

您要求 TValueObject<T> 继承,所以当您写:

RequiredStringValueObject : ValueObject<string>

string 不继承自 ValueObject(显然)所以你需要继承自 ValueObject<ValueObject<string>>,除了 also 违反了约束和嗯......它的乌龟一直向下。

简单的解决方法是删除类型约束;看起来你的代码主要是为处理 object 任何方式而设置的,所以你不需要它。放置任何类型的 "recursive" 类型约束只会导致您在此设置中出现问题。如果你真的需要这样的东西,你可能需要使用组合,比如:

public interface IValueMethods<T>
{
   //required methods
}

//Constructor for value object
public ValueObject<T>(IValueMethods<T> commonMethods)
{
}

然后您可以传入一组方法以用作单独的对象。

同意@BradleyDotNET 所说的。可能的修复如下所示:

public abstract class ValueObjectBase
{
    public abstract IEnumerable<object> GetEqualityCheckAttributes();
}

public abstract class ValueObject<T> : ValueObjectBase where T : class
{
    public override bool Equals(object other)
    {
        if (other is ValueObjectBase)
            return Equals(other as ValueObjectBase);

        return Equals(other as T);
    }

    public bool Equals(T other)
    {

        if (other == null)
        {
            return false;
        }
        return other.Equals(this);

    }

    public bool Equals(ValueObjectBase other)
    {
        return GetEqualityCheckAttributes().SequenceEqual(other.GetEqualityCheckAttributes());
    }

    public static bool operator ==(ValueObject<T> left, ValueObject<T> right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(ValueObject<T> left, ValueObject<T> right)
    {
        return !(left == right);
    }

    public override int GetHashCode()
    {
        int hash = 17;
        foreach (var obj in this.GetEqualityCheckAttributes())
        {
            hash = hash * 31 + (obj == null ? 0 : obj.GetHashCode());
        }
        return hash;
    }
}

感谢大家的帮助,这是最终的工作解决方案:

ValueObject

public abstract class ValueObjectBase
{
    public abstract IEnumerable<object> GetEqualityCheckAttributes();
}

public abstract class ValueObject<T> : ValueObjectBase
{
    public override bool Equals(object other)
    {
        if (other is ValueObjectBase)
        {
            return Equals(other as ValueObjectBase);
        }
        return Equals(other as IEquatable<T>);
    }

    public bool Equals(T other)
    {
        if (other == null)
        {
            return false;
        }
        return other.Equals(this);
    }

    public bool Equals(ValueObjectBase other)
    {
        return GetEqualityCheckAttributes().SequenceEqual(other.GetEqualityCheckAttributes());
    }

    public static bool operator == (ValueObject<T> left, ValueObject<T> right)
    {
        return Equals(left, right);
    }

    public static bool operator != (ValueObject<T> left, ValueObject<T> right)
    {
        return !(Equals(left, right));
    }

    public override int GetHashCode()
    {
        int hash = 17;
        foreach (var obj in this.GetEqualityCheckAttributes())
        {
            hash = hash * 31 + (obj == null ? 0 : obj.GetHashCode());
        }
        return hash;
    }
}

主题的变体,RequiredStringValueObject:

public abstract class RequiredStringValueObject : ValueObject<string>
{
    private string _value;
    protected string _fieldName;
    protected byte _maxLength;

    public string Value
    {
        get
        {
            return _value;
        }
        protected set
        {
            if (value == null || string.IsNullOrWhiteSpace(value))
            {
                throw new ArgumentNullException(_fieldName, _fieldName + " must be supplied.");
            }
            value = value.Trim();
            if (value.Length > _maxLength)
            {
                throw new ArgumentOutOfRangeException(_fieldName, value, _fieldName + " can't be longer than " + _maxLength.ToString() + " characters.");
            }
            _value = value;
        }
    }

    protected RequiredStringValueObject(string fieldName, byte maxLength, string value)
    {
        _fieldName = fieldName;
        _maxLength = maxLength;
        Value = value;
    }

    public override IEnumerable<object> GetEqualityCheckAttributes()
    {
        return new List<object> { Value };
    }
}

以及具体的实现,FirstName(需要的最大长度的基于字符串的值对象):

 public class FirstName : RequiredStringValueObject
{
    private FirstName(string value) : base(nameof(FirstName),30, value) { }

    public static FirstName Create(string value)
    {
        return new FirstName(value);
    }

}

用80后的话说,"Totally tubular!"

谢谢!