Pergunta

Estou criando uma série de construtores para limpar a sintaxe que cria classes de domínio para o meu simulações como parte de melhorar os nossos testes de unidade global. Meus construtores essencialmente preencher uma classe de domínio (como um Schedule) com alguns valores determinados invocando o WithXXX apropriado e encadeando-os.

Eu encontrei algo em comum entre os meus construtores e quero abstrato que afastado em uma classe base para aumentar a reutilização de código. Infelizmente o que eu acabar com aparência:

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

Tome nota especial do protected abstract BLDR This { get; }.

A implementação de exemplo de um construtor de classe de domínio é:

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

    // ...
}

Porque BLDR não é do tipo BaseBuilder eu não posso usar return this no método WithId(int) de BaseBuilder.

É expor o tipo de criança com a propriedade abstract BLDR This { get; } minha única opção aqui, ou estou faltando algum truque sintaxe?

Update (desde que eu posso mostrar por que estou fazendo isto um pouco mais claro):

O resultado final é ter construtores que classes de domínio construir perfilado que seria de esperar para recuperar do banco de dados em um [programador] formato legível. Não há nada de errado com ...

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

como isso é muito legível já. A sintaxe construtor alternativa é:

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

a vantagem Estou procurando fora de usar construtores (e implementação de todos esses métodos WithXXX) é abstrair criação propriedade afastado complexo (expandir automaticamente os nossos valores de pesquisa de banco de dados com o Lookup.KnownValues correta sem bater o banco de dados, obviamente) e tendo o construtor fornecer perfis de teste comumente reutilizáveis ??para classes de domínio ...

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

Solução

Tudo o que posso dizer é que, se houver é uma maneira de fazê-lo, eu quero saber sobre isso também - eu uso exatamente este padrão no meu Protocol Buffers porta . Na verdade, eu estou contente de ver que alguém tenha recorrido a ele - isso significa que estamos pelo menos um pouco provável que seja certo

Outras dicas

Eu sei que isto é uma questão de idade, mas eu acho que você pode usar um molde simples para evitar o abstract BLDR This { get; }

O código resultante seria então:

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

    // ...
}

Claro que você pode encapsular o construtor com

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

Esta é uma boa estratégia de implementação para C #.

Algumas outras línguas (não pode pensar em nome da linguagem de pesquisa que eu vi isso em) têm sistemas do tipo que quer o apoio de um "eu" covariante / "isto" diretamente, ou ter outras formas inteligentes de expressar esse padrão, mas com sistema de tipo C # 's, este é um bom (apenas?) solução.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top