Dê este artigo ao Dr. Dobbs e o Padrão do construtor em particular, como lidamos com o caso de subclassificar um construtor? Tomando uma versão resumida do exemplo em que queremos incluir uma subclasse para adicionar rotulagem de OGM, uma implementação ingênua seria:
public class NutritionFacts {
private final int calories;
public static class Builder {
private int calories = 0;
public Builder() {}
public Builder calories(int val) { calories = val; return this; }
public NutritionFacts build() { return new NutritionFacts(this); }
}
protected NutritionFacts(Builder builder) {
calories = builder.calories;
}
}
Subclasse:
public class GMOFacts extends NutritionFacts {
private final boolean hasGMO;
public static class Builder extends NutritionFacts.Builder {
private boolean hasGMO = false;
public Builder() {}
public Builder GMO(boolean val) { hasGMO = val; return this; }
public GMOFacts build() { return new GMOFacts(this); }
}
protected GMOFacts(Builder builder) {
super(builder);
hasGMO = builder.hasGMO;
}
}
Agora, podemos escrever código assim:
GMOFacts.Builder b = new GMOFacts.Builder();
b.GMO(true).calories(100);
Mas, se errarmos o pedido, tudo falhará:
GMOFacts.Builder b = new GMOFacts.Builder();
b.calories(100).GMO(true);
É claro que o problema NutritionFacts.Builder
retorna um NutritionFacts.Builder
, não um GMOFacts.Builder
, então como resolvemos esse problema ou existe um padrão melhor para usar?
Nota: esta resposta a uma pergunta semelhante oferece as classes que tenho acima; minha pergunta é sobre o problema de garantir que as chamadas do construtor estejam na ordem correta.
java
design-patterns
Ken YN
fonte
fonte
build()
produz a saídab.GMO(true).calories(100)
?Respostas:
Você pode resolvê-lo usando genéricos. Eu acho que isso é chamado de "padrões genéricos curiosamente recorrentes"
Torne o tipo de retorno dos métodos do construtor da classe base um argumento genérico.
Agora instancie o construtor de base com o construtor de classe derivado como o argumento genérico.
fonte
implements
vez deextends
ou (c) jogar tudo fora. Agora tenho um erro de compilação estranho, ondeleafBuilder.leaf().leaf()
eleafBuilder.mid().leaf()
é OK, masleafBuilder.leaf().mid().leaf()
não ...return (T) this;
resulta em umunchecked or unsafe operations
aviso. Isso é impossível de evitar, certo?unchecked cast
aviso, consulte a solução sugerida abaixo entre as outras respostas: stackoverflow.com/a/34741836/3114959Builder<T extends Builder>
na verdade, é um tipo bruto - deve serBuilder<T extends Builder<T>>
.Builder
forGMOFacts
também precisa ser genéricoBuilder<B extends Builder<B>> extends NutritionFacts.Builder<Builder>
- e esse padrão pode continuar abaixo dos níveis necessários. Se você declarar um construtor não genérico, não poderá estender o padrão.Apenas para o registro, para se livrar do
para a
return (T) this;
declaração como @dimadima e @Thomas N. falam, a solução a seguir se aplica em certos casos.Faça
abstract
o construtor que declara o tipo genérico (T extends Builder
neste caso) e declareprotected abstract T getThis()
o método abstrato da seguinte maneira:Consulte http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ205 para obter mais detalhes.
fonte
build()
método está retornando os NutrutionFacts aqui?public GMOFacts build() { return new GMOFacts(this); }
BuilderC extends BuilderB
eBuilderB extends BuilderA
quandoBuilderB
não éabstract
Com base em uma postagem de blog , essa abordagem exige que todas as classes não-folha sejam abstratas e todas as classes de folha devem ser finais.
Então, você tem alguma classe intermediária que estende essa classe e seu construtor, e quantas mais você precisar:
E, finalmente, uma classe de folhas de concreto que pode chamar todos os métodos do construtor em qualquer um de seus pais em qualquer ordem:
Em seguida, você pode chamar os métodos em qualquer ordem, a partir de qualquer uma das classes na hierarquia:
fonte
B
, sempre é a classe base.<T extends SomeClass, B extends SomeClass.Builder<T,B>> extends SomeClassParent.Builder<T,B>
padrão que a classe intermediária SecondLevel, mas declara tipos específicos. Você não pode instalar uma classe até chegar à folha usando os tipos específicos, mas, uma vez que o fizer, não poderá mais estendê-la porque você está usando os tipos específicos e abandonou o Padrão de Modelo Curiosamente Recorrente. Este link pode ajudar: angelikalanger.com/GenericsFAQ/FAQSections/…Você pode substituir também o
calories()
método e deixá-lo retornar o construtor de extensão. Isso é compilado porque o Java suporta tipos de retorno covariantes .fonte
Há também outra maneira de criar classes de acordo com o
Builder
padrão, que está de acordo com "Preferir composição sobre herança".Defina uma interface que a classe pai
Builder
herdará:A implementação de
NutritionFacts
é quase a mesma (exceto paraBuilder
implementar a interface 'FactsBuilder'):A
Builder
classe de uma criança deve estender a mesma interface (exceto implementação genérica diferente):Observe que esse
NutritionFacts.Builder
é um campo dentroGMOFacts.Builder
(chamadobaseBuilder
). O método implementado a partir do método deFactsBuilder
chamadas de interfacebaseBuilder
com o mesmo nome:Há também uma grande mudança no construtor de
GMOFacts(Builder builder)
. A primeira chamada no construtor para o construtor da classe pai deve passar apropriadaNutritionFacts.Builder
:A implementação completa da
GMOFacts
classe:fonte
Um exemplo completo de três níveis de herança de vários construtores seria assim :
(Para a versão com um construtor de cópias para o construtor, veja o segundo exemplo abaixo)
Primeiro nível - pai (potencialmente abstrato)
Segundo nível
Terceiro nivel
E um exemplo de uso
Uma versão um pouco mais longa, com um construtor de cópias para o construtor:
Primeiro nível - pai (potencialmente abstrato)
Segundo nível
Terceiro nivel
E um exemplo de uso
fonte
Se você não quer colocar os olhos em um colchete angular ou três, ou talvez não sinta você ... umm ... quero dizer ... tosse ... o resto de sua equipe rapidamente compreenderá com curiosidade padrão genérico recorrente, você pode fazer isso:
apoiado por
e o tipo pai:
Pontos chave:
EDITAR:
Eu encontrei uma maneira de contornar a criação de objetos espúrios. Primeiro adicione isso a cada construtor:
Em seguida, no construtor para cada construtor:
O custo é um arquivo de classe extra para a
new Object(){}
classe interna anônimafonte
Uma coisa que você pode fazer é criar um método estático de fábrica em cada uma de suas classes:
Esse método estático de fábrica retornaria o construtor apropriado. Você pode ter uma
GMOFacts.Builder
extensãoNutritionFacts.Builder
, isso não é um problema. O problema aqui será lidar com a visibilidade ...fonte
A seguinte contribuição do IEEE, Refined Fluent Builder em Java, fornece uma solução abrangente para o problema.
Ele disseca a pergunta original em dois subproblemas de deficiência de herança e quase invariância e mostra como uma solução para esses dois subproblemas abre para o suporte à herança com reutilização de código no padrão clássico do construtor em Java.
fonte
Criei uma classe de construtor genérico abstrata pai que aceita dois parâmetros de tipo formal. Primeiro é para o tipo de objeto retornado por build (), o segundo é o tipo retornado por cada configurador de parâmetro opcional. Abaixo estão as classes pai e filho para fins ilustrativos:
Este atendeu minhas necessidades de satisfação.
fonte