Modèle de conception de générateur avec héritage: existe-t-il un meilleur moyen?

StackOverflow https://stackoverflow.com/questions/244772

  •  05-07-2019
  •  | 
  •  

Question

Je crée une série de générateurs pour nettoyer la syntaxe, ce qui crée des classes de domaine pour mes simulacres dans le cadre de l'amélioration de nos tests unitaires globaux. Mes constructeurs peuplent essentiellement une classe de domaine (telle qu'un Planification ) avec des valeurs déterminées en appelant le WithXXX approprié et en les chaînant.

J'ai rencontré des points communs chez mes constructeurs et je souhaite les résumer dans une classe de base pour augmenter la réutilisation du code. Malheureusement, mon résultat final est le suivant:

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 du résumé abstrait protégé BLDR This {get; } .

Voici un exemple d'implémentation d'un générateur de classe de domaine:

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

    // ...
}

Etant donné que BLDR n'est pas du type BaseBuilder, je ne peux pas utiliser renvoyer ce dans la méthode WithId (int) de BaseBuilder .

Expose le type enfant avec la propriété abstract BLDR This {get; } ma seule option ici, ou manque-t-il une astuce de syntaxe?

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

Le résultat final est d’avoir des générateurs qui construisent des classes de domaine profilées que l’on s'attendrait à extraire de la base de données dans un format lisible par [programmeur]. 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à assez lisible. La syntaxe de générateur alternative est la suivante:

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

L’avantage que j’attends des générateurs (et de l’implémentation de toutes ces méthodes WithXXX ) est d’abstraire la création de propriétés complexes (développer automatiquement les valeurs de recherche dans la base de données avec le code correct . .KnownValues ?? sans toucher évidemment la base de données) et au constructeur de fournir des profils de test couramment réutilisables pour les classes de domaine ...

mock.Expect(m => m.Select(It.IsAny<int>())).Returns(
    new ScheduleBuilder()
        .AsOneDay()
        .Build()
);
Était-ce utile?

La solution

Tout ce que je peux dire, c’est que si il y a une manière de le faire, je veux aussi en savoir plus. J'utilise exactement ce modèle dans mon port des tampons de protocole . En fait, je suis heureux de voir que quelqu'un d'autre y a eu recours - cela signifie que nous avons au moins un peu de chance d'avoir raison!

Autres conseils

Je sais que c’est une vieille question, mais je pense que vous pouvez utiliser un casting simple pour éviter le 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 pouvez encapsuler le générateur avec

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

C’est une bonne stratégie de mise en œuvre pour C #.

Certaines autres langues (je ne vois pas le nom de la langue de recherche dans laquelle j'ai vu cela) ont des systèmes de types qui supportent un covariant "soi" / "ceci". directement, ou utilisez d’autres moyens astucieux pour exprimer ce motif, mais avec le système de types de C #, c’est une bonne solution (seulement?).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top