2017-02-07 1 views
1

Je suis un programmeur "old school" ici qui a du mal à utiliser l'héritage à mon avantage. Je me suis retrouvé à répéter le code et ça a commencé à sentir. Je n'étais pas adhérant à DRY, donc j'essaie de refactoriser un peu ici pour réduire la duplication de code! J'essaie d'écrire des classes d'objets de valeur à utiliser dans mes entités, qui imposeront des invariants de base. J'ai une classe abstraite ValueObject générique qui gère l'égalité et hash comme ceci:Concrete Class hériter de la classe Abstract qui hérite de la classe Generic Abstract

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

J'ai été alors crée mes classes d'objets de valeur qui mettent alors en œuvre cette classe abstraite, et fournir la logique pour vous assurer que l'objet ne peut pas être créé dans un état invalide. C'est à ce moment-là que j'ai commencé à violer DRY, et que je créais de nombreux objets avec le même code (par exemple, une chaîne obligatoire avec une longueur maximale de 50, 30 ou 10). Je souhaite donc mettre le code qui impose l'invariant dans sa propre classe, et faire en sorte que ma classe d'objets de valeur concrète hérite de cette capacité. Quelque chose comme (cela ne compile pas, voir ci-dessous):

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

alors je pourrais « utiliser » toutes ces fonctionnalités dans la classe concrète comme ceci:

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

Tout cela semble comme un moyen raisonnable de résoudre le problème (pour moi). Le problème est que je reçois une erreur de compilation dans la déclaration RequiredStringValueObject:

Le type string ne peut pas être utilisé comme un paramètre de type T dans le type générique ou méthode ValueObject<T>. Il n'y a pas de conversion de référence implicite de string à ValueObject<string>.

Je ne comprends pas exactement le message d'erreur. Est ce que j'essaye de faire possible? Y a-t-il un moyen de faire ce travail? Ou y a-t-il une autre approche que je pourrais/devrait prendre?

+0

Je ne suis pas sûr, mais pourquoi avez-vous le 'où T: ValueObject ' – TryingToImprove

+1

Erreur est due à la façon dont la classe est définie. Vous avez 'la classe abstraite publique ValueObject où T: ValueObject '. Essentiellement, vous dites - je déclare une classe générique avec T étant limité aux instances de cette même classe. Sort de définition circulaire. 'String' ne satisfait pas à ce critère. – Sherlock

Répondre

2

Le problème vient de cette ligne:

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

Vous exigez T hériter de ValueObject<T> alors quand vous écrivez:

RequiredStringValueObject : ValueObject<string> 

string ne possèdes pas de ValueObject (évidemment) et vous besoin d'hériter de ValueObject<ValueObject<string>>, sauf que aussi viole la contrainte et bien ... ses tortues tout le chemin vers le bas.

La solution facile consiste à supprimer la contrainte de type; Il semble que votre code est la plupart du temps mis en place pour traiter object de toutes les manières que vous ne devriez pas en avoir besoin. Mettre n'importe quel type de contrainte de type "récursif" va juste vous causer des problèmes dans cette configuration. Si vous avez vraiment besoin d'une telle chose, vous pourriez avoir besoin d'aller avec la composition à la place, quelque chose comme:

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

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

Et vous pouvez passer dans l'ensemble des méthodes à utiliser comme un objet distinct.

+0

La suppression de la contrainte achemine ensuite les problèmes vers ma classe abstraite ValueObject. Dans la méthode Equals, j'obtiens alors "Le paramètre de type 'T' ne peut pas être utilisé avec l'opérateur 'as' car il n'a pas de contrainte de type classe ni de 'classe'." Donc je passe de 'autre comme T' à 'autre comme objet'? – Scuzzlebutt

+0

Aussi, other.GetEqualityCheckAttributes() a alors un problème: "'T' ne contient pas de définition pour 'GetEqualityCheckAttributes' et aucune méthode d'extension 'GetEqualityCheckAtributes' acceptant un premier argument de type 'T' pourrait être trouvée" – Scuzzlebutt

+1

@Scuzzlebutt probablement devoir retravailler 'GetEqualityCheckAttributes' avec la suggestion d'interface ci-dessus. En ce qui concerne le premier, vous pouvez mettre une contrainte 'where T: class' mais je doute sérieusement que vous le vouliez * car il semblerait que vous vouliez qu'il fonctionne avec int, short, etc. – BradleyDotNET

4

Vous avez une clause where sur votre type générique T:

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

Ceci indique au compilateur que T doit provenir de ValueObject, et la chaîne ne fonctionne pas.

Qu'est-ce que vous essayez d'appliquer avec cela où T: clause? Vous voulez probablement l'omettre.

2

En accord avec ce que dit @BradleyDotNET. Une solution possible pourrait ressembler à ce qui suit:

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

Merci pour l'idée! Je n'y aurais jamais pensé par moi-même. – Scuzzlebutt

+0

Et cela a aidé à compléter ce que @BradleyDotNET a commencé ... – Scuzzlebutt

0

Merci à tous de votre aide, voici la solution de travail finale:

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

Une variation sur le thème, la 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 }; 
    } 
} 

Et la mise en œuvre concrète, la FirstName (un objet de valeur en fonction de chaîne requise avec la longueur maximale):

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

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

} 

Comme un enfant des années 80 disaient: « Totalement tubulaire! »

Merci!