2008-10-28 17 views
16

Je crée une série de builders pour nettoyer la syntaxe qui crée des classes de domaine pour mes mocks dans le cadre de l'amélioration de nos tests unitaires globaux. Mes constructeurs remplissent essentiellement une classe de domaine (telle qu'un Schedule) avec certaines valeurs déterminées en appelant le WithXXX approprié et en les enchaînant ensemble.Motif de conception Builder avec héritage: existe-t-il un meilleur moyen?

J'ai rencontré quelques points communs parmi mes constructeurs et je veux abstraire cela dans une classe de base pour augmenter la réutilisation du code. Malheureusement, ce que je finis avec ressemble:

public abstract class BaseBuilder<T,BLDR> where BLDR : BaseBuilder<T,BLDR> 
              where T : new() 
{ 
    public abstract T Build(); 

    protected int Id { get; private set; } 
    protected abstract BLDR This { get; } 

    public BLDR WithId(int id) 
    { 
     Id = id; 
     return This; 
    } 
} 

Prenez note spéciale du protected abstract BLDR This { get; }.

Un exemple d'implémentation d'un constructeur de classe de domaine est:

public class ScheduleIntervalBuilder : 
    BaseBuilder<ScheduleInterval,ScheduleIntervalBuilder> 
{ 
    private int _scheduleId; 
    // ... 

    // UG! here's the problem: 
    protected override ScheduleIntervalBuilder This 
    { 
     get { return this; } 
    } 

    public override ScheduleInterval Build() 
    { 
     return new ScheduleInterval 
     { 
      Id = base.Id, 
      ScheduleId = _scheduleId 
        // ... 
     }; 
    } 

    public ScheduleIntervalBuilder WithScheduleId(int scheduleId) 
    { 
     _scheduleId = scheduleId; 
     return this; 
    } 

    // ... 
} 

Parce que BLDR est pas de type BaseBuilder Je ne peux pas utiliser return this dans la méthode WithId(int) de BaseBuilder.

Est-ce que l'exposition du type enfant avec la propriété abstract BLDR This { get; } est ma seule option ici, ou est-ce que je manque un peu de syntaxe?

Mise à jour (depuis que je peux montrer pourquoi je fais cela un peu plus clairement):

Le résultat final est d'avoir les constructeurs qui construisent des classes de domaine profilent que l'on peut attendre d'extraire de la base de données dans un [ programmeur] format lisible. Il n'y a rien de mal à ...

mock.Expect(m => m.Select(It.IsAny<int>())).Returns(
    new Schedule 
    { 
     ScheduleId = 1 
     // ... 
    } 
); 

comme c'est déjà très lisible. La syntaxe constructeur alternative est:

mock.Expect(m => m.Select(It.IsAny<int>())).Returns(
    new ScheduleBuilder() 
     .WithId(1) 
     // ... 
     .Build() 
); 

l'avantage que je suis à la recherche sur l'utilisation des constructeurs (et mettre en œuvre toutes ces WithXXX méthodes) est abstraite création de propriété loin complexe (automatiquement décompressés nos valeurs de recherche de base de données avec le Lookup.KnownValues correct sans frapper la base de données évidemment) et ayant le constructeur fournit généralement des profils de test réutilisables pour les classes de domaine ...

mock.Expect(m => m.Select(It.IsAny<int>())).Returns(
    new ScheduleBuilder() 
     .AsOneDay() 
     .Build() 
); 

Répondre

11

Tout ce que je peux dire est que s'il est une façon de le faire, je veux savoir aussi - j'utilise exactement ce modèle dans mon Protocol Buffers port. En fait, je suis heureux de voir que quelqu'un d'autre a eu recours à cela - cela signifie que nous sommes au moins quelque peu susceptibles d'avoir raison!

+0

Yup semble être coincé avec "abstract T This {get;}" Je vais accepter et fermer puisque je pense que la solution est tout ce qui est disponible. – cfeduke

+0

J'essaie de trouver un moyen relativement clair de créer des constructeurs et a trouvé cet article (http://www.codeproject.com/Articles/240756/Hierarchically-Implementing-the-Bolchs-Builder-Pat). Il offre de le lancer une fois. (Protected BLDR _this; _this = (BLDR) this; return _this;), mais je ne comprends pas pourquoi l'auteur a ajouté la classe WindowBuilder: WindowBuilder {} (Voir exemple complet à le bas de l'article). Dans ce post je vois presque le même modèle, mais cfeduke utilise la classe ScheduleIntervalBuilder: BaseBuilder ... –

+0

Personnellement, j'implémenterais probablement chaque Builder sans la classe de base. J'essaie toujours de ne pas utiliser ce * modèle auto-générique *: http://blogs.msdn.com/b/ericlippert/archive/2011/02/03/curiouser-and-curiouser.aspx – Pellared

2

Ceci est une bonne stratégie de mise en œuvre pour C#. D'autres langues (je ne peux pas penser au nom de la langue de recherche dans laquelle j'ai vu cela) ont des systèmes de type qui supportent un "moi"/"ceci" covariant directement, ou qui ont d'autres façons intelligentes d'exprimer ce modèle , mais avec le système de type C#, c'est une bonne (seulement?) solution.

+0

Merci pour la contribution, je pense que "abstract T This {get;}" n'est pas un prix élevé à payer. :) – cfeduke

3

Je sais que c'est une vieille question, mais je pense que vous pouvez utiliser un simple casting pour éviter la abstract BLDR This { get; }

Le code résultant serait alors:

public abstract class BaseBuilder<T, BLDR> where BLDR : BaseBuilder<T, BLDR> 
              where T : new() 
{ 
    public abstract T Build(); 

    protected int Id { get; private set; } 

    public BLDR WithId(int id) 
    { 
     _id = id; 
     return (BLDR)this; 
    } 
} 

public class ScheduleIntervalBuilder : 
    BaseBuilder<ScheduleInterval,ScheduleIntervalBuilder> 
{ 
    private int _scheduleId; 
    // ... 

    public override ScheduleInterval Build() 
    { 
     return new ScheduleInterval 
     { 
       Id = base.Id, 
       ScheduleId = _scheduleId 
        // ... 
     }; 
    } 

    public ScheduleIntervalBuilder WithScheduleId(int scheduleId) 
    { 
     _scheduleId = scheduleId; 
     return this; 
    } 

    // ... 
} 

Bien sûr, vous pourriez résumer le constructeur avec

protected BLDR This 
{ 
    get 
    { 
     return (BLDR)this; 
    } 
}