Domanda

Sto creando una serie di costruttori per ripulire la sintassi che crea classi di dominio per i miei mock come parte del miglioramento dei nostri test unitari complessivi. I miei costruttori essenzialmente popolano una classe di dominio (come un Schedule ) con alcuni valori determinati invocando l'appropriato WithXXX e concatenandoli insieme.

Ho riscontrato un po 'di comunanza tra i miei costruttori e voglio sottrarlo in una classe base per aumentare il riutilizzo del codice. Sfortunatamente quello che finisco sembra:

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

Prendi nota dell'estratto protetto BLDR Questo {get; } .

Un'implementazione di esempio di un generatore di classi di domini è:

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

    // ...
}

Poiché BLDR non è di tipo BaseBuilder, non posso usare restituirlo nel metodo WithId (int) di BaseBuilder .

Sta esponendo il tipo figlio con la proprietà abstract BLDR Questo {get; } la mia unica opzione qui, o mi sto perdendo qualche trucco di sintassi?

Aggiornamento (poiché posso mostrare perché lo sto facendo un po 'più chiaramente):

Il risultato finale è avere costruttori che costruiscono classi di dominio profilate che ci si aspetterebbe di recuperare dal database in un formato leggibile da [programmatore]. Non c'è niente di sbagliato in ...

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

dato che è già abbastanza leggibile. La sintassi del generatore alternativo è:

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

il vantaggio che sto cercando di utilizzare i costruttori (e implementare tutti questi metodi WithXXX ) è quello di sottrarre la creazione di proprietà complesse (espandere automaticamente i valori di ricerca del database con la ricerca corretta .KnownValues ?? senza colpire il database ovviamente) e avere il builder che fornisce profili di test comunemente riutilizzabili per le classi di dominio ...

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

Soluzione

Tutto quello che posso dire è che se è un modo per farlo, voglio saperlo anche io - uso esattamente questo schema nel mio Porta buffer protocollo . In effetti, sono felice di vedere che qualcun altro ha fatto ricorso ad esso - significa che almeno in qualche modo avremo ragione!

Altri suggerimenti

So che questa è una vecchia domanda, ma penso che puoi usare un cast semplice per evitare il abstract BLDR This {get; }

Il codice risultante sarebbe quindi:

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

    // ...
}

Ovviamente potresti incapsulare il builder con

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

Questa è una buona strategia di implementazione per C #.

Alcune altre lingue (non riesco a pensare al nome del linguaggio di ricerca in cui ho visto questo) hanno sistemi di tipi che supportano una covariante "auto" / ";" direttamente o hanno altri modi intelligenti per esprimere questo modello, ma con il sistema di tipi di C #, questa è una buona (unica?) soluzione.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top