2017-02-07 19 views
1

Я программист «старой школы», который борется с использованием наследования в свою пользу. Я обнаружил, что повторяю код, и он начал пахнуть. Я не придерживался 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 не может быть использован в качестве параметра T типа в универсальном типе или методе ValueObject<T>. Нет никакого неявного ссылочного преобразования от string до ValueObject<string>.

Я не понимаю сообщение об ошибке точно. Это то, что я пытаюсь сделать возможным? Есть ли способ сделать эту работу? Или есть другой подход, который я мог бы/должен был взять?

+0

Я не уверен, но почему у вас есть 'where T: ValueObject ' – TryingToImprove

+1

Ошибка связана с тем, как определяется класс. У вас есть открытый публичный класс ValueObject , где T: ValueObject '. По сути, вы говорите - я объявляю общий класс, когда T ограничивается экземплярами этого самого класса. Сортировка кругового определения. 'String' не удовлетворяет этим критериям. – Sherlock

ответ

2

Проблема проистекает из этой линии:

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

Вы требующих T наследовать от ValueObject<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) 
{ 
} 

И тогда вы можете перейти в набор методов для использования в качестве отдельного объекта.

+0

Удаление ограничения затем стекает проблемы в моем абстрактном классе ValueObject. В методе Equals я получаю: «Параметр типа« T »не может использоваться с оператором« as », потому что у него нет ограничения типа класса или ограничения класса.» Итак, я перехожу от «другого как Т» к «другому как объект»? – Scuzzlebutt

+0

Кроме того, у other.GetEqualityCheckAttributes() есть проблема: «« T »не содержит определения для« GetEqualityCheckAttributes », и никакой метод расширения« GetEqualityCheckAtributes », принимающий первый аргумент типа« T », не может быть найден« – Scuzzlebutt

+1

@Scuzzlebutt вы будете вероятно, придется переработать 'GetEqualityCheckAttributes', возможно, с помощью предложения интерфейса сверху. Что касается первого, вы можете поместить ограничение 'where T: class', но я серьезно сомневаюсь, что вы * хотите *, что, как кажется, вы хотите, чтобы он работал с int, short и т. Д. – BradleyDotNET

4

У вас есть где пункт о вашем шаблонного типа T:

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

Это говорит компилятору, что T должен быть унаследован от ValueObject, и строка не делает.

Что вы пытаетесь обеспечить с помощью этого условия, где T: clause? Возможно, вы захотите его опустить.

2

В соответствии с тем, что сказал @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; 
    } 
} 
+0

Спасибо за идею! Никогда бы об этом не подумал сам. – Scuzzlebutt

+0

И это помогло завершить то, что начал @BradleyDotNET ... – Scuzzlebutt

0

Спасибо всем вашей помощи, вот окончательный рабочий раствор:

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-х годов было бы сказать, «Полностью трубчатым!»

Спасибо!