La dichiarazione ChildBuilder extends BaseBuilder<ChildBuilder>
indica in qualche modo un odore di codice e sembra violare DRY. In questo esempio BaseBuilder
può essere parametrizzato solo con ChildBuilder
e nient'altro, quindi dovrebbe essere ridondante.
Preferirei ripensare al fatto se davvero voglio fare un'architettura eccessiva e proverei a mettere tutti i metodi dei costruttori di bambini nello BaseBuilder
. Quindi posso semplicemente restituire this
da tutti i metodi che supportano il concatenamento.
Se continuo a pensare che trarrai vantaggio separando gruppi specifici di metodi builder nelle proprie classi, quindi darei preferenza alla composizione, perché non è consigliabile applicare l'ereditarietà solo per il riutilizzo del codice.
Supponiamo di avere due sottoclassi della BaseBuilder
:
class BuilderA extends BaseBuilder<BuilderA> {
BuilderA buildSomethingA() { return this; }
}
class BuilderB extends BaseBuilder<BuilderB> {
BuilderB buildSomethingB() { return this; }
}
E se sorge la necessità di catena buildSomethingA
e buildSomethingB
come:
builder.buildSomething().buildSomethingA().buildSomethingB();
Non saremo in grado di farlo senza spostare il metodi di sottoclasse allo BaseBuilder
; ma immagina che ci sia anche BuilderC
per cui quei metodi non hanno senso e non dovrebbero essere ereditati dallo BaseBuilder
.
Se spostiamo questi due metodi sulla superclasse e la prossima volta con altri tre metodi e la prossima volta ... ci ritroveremo con una superclasse responsabile del 90% dei compiti dell'intera gerarchia con un sacco di codice come:
if ((this instanceof BuilderB) && !flag1 && flag2) {
...
} else if ((this instanceof BuilderC) && flag1 && !flag2 && thing != null) {
...
} else if ...
la soluzione che mi piace di più è un DSL come:
builder.buildSomething1().buildSomething2()
.builderA()
.buildSomethingA1().buildSomethingA2()
.end()
.buildSomething3()
.builderB()
.buildSomethingB()
.end();
Qui end()
restituisce l'istanza builder
in modo da poter catena più dei suoi metodi o di iniziare una nuova sotto-builder.
In questo modo i (sub) builder possono ereditare da qualsiasi cosa abbiano bisogno (altrimenti devono estendere solo lo BaseBuilder
) e possono avere le loro gerarchie o composizioni significative.
Non dovrebbe essere di classe pubblica BaseBuilder perché il tipo generico si riferisce alla stessa classe? => ChildBuilder estende BaseBuilder –
6ton
@ 6ton, perché i soli tipi generici validi sono i tipi che ereditano BaseBuilder. Ad esempio, "BaseBuilder" non avrebbe senso. –
Ancora non sono sicuro del motivo per cui hai bisogno della firma - ho pubblicato una risposta con giustificazione – 6ton