繁体   English   中英

具体类继承自抽象类,抽象类继承自通用抽象类

[英]Concrete Class inherit from Abstract class which inherits from Generic Abstract Class

我是这里的“老派”程序员,正在努力利用继承来发挥我的优势。 我发现自己在重复代码,它开始发臭。 我没有坚持 DRY,所以我试图在这里重构一下以减少代码重复!

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

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

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

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

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

然后我可以在具体类中“使用”所有这些功能,如下所示:

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 声明中遇到编译器错误:

类型string不能用作泛型类型或方法ValueObject<T>的类型参数T 没有从stringValueObject<string>隐式引用转换。

我不完全理解错误消息。 我正在尝试做的可能吗? 有没有办法使这项工作? 或者我可以/应该采取另一种方法吗?

你的泛型类型 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>>继承,除了这违反了约束,好吧......它的海龟一直向下。

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

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

感谢您的所有帮助,这是最终的工作解决方案:

值对象

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

和具体的实现,名字(一个必需的基于字符串的最大长度的值对象):

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

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

}

正如 80 年代的孩子会说的,“完全管状!”

谢谢!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM