Eu criei o que, para mim, é uma grande melhoria em relação ao Builder Pattern de Josh Bloch. Sem dizer que é "melhor", apenas que, em uma situação muito específica , fornece algumas vantagens - a maior delas é que desacopla o construtor de sua classe a ser construída.
Documentei completamente essa alternativa abaixo, que chamo de Padrão de construtor cego.
Padrão de Design: Construtor Cego
Como alternativa ao Builder Pattern de Joshua Bloch (item 2 em Java efetivo, 2ª edição), criei o que chamo de "Blind Builder Pattern", que compartilha muitos dos benefícios do Bloch Builder e, além de um único personagem, é usado exatamente da mesma maneira. Construtores cegos têm a vantagem de
- dissociando o construtor de sua classe anexa, eliminando uma dependência circular,
- reduz bastante o tamanho do código fonte (o que não é mais ) da classe envolvente e
- permite que a
ToBeBuilt
classe seja estendida sem precisar estender seu construtor .
Nesta documentação, vou me referir à classe que está sendo criada como " ToBeBuilt
" a classe.
Uma classe implementada com um Bloch Builder
Um Bloch Builder é um public static class
contido dentro da classe que ele cria. Um exemplo:
classe pública UserConfig {
private final String sName;
final privado int iAge;
final privado String sFavColor;
public UserConfig (UserConfig.Cfg uc_c) {// CONSTRUTOR
//transferir
experimentar {
sName = uc_c.sName;
} captura (NullPointerException rx) {
lança nova NullPointerException ("uc_c");
}
iAge = uc_c.iAge;
sFavColor = uc_c.sFavColor;
// VALIDAR TODOS OS CAMPOS AQUI
}
public String toString () {
retornar "nome =" + sName + ", idade =" + iAge + ", sFavColor =" + sFavColor;
}
//builder...START
classe estática pública Cfg {
private String sName;
private int iAge;
private String sFavColor;
Cfg público (String s_name) {
sName = s_name;
}
// setters com retorno automático ... START
idade pública do Cfg (int i_age) {
iAge = i_age;
devolva isso;
}
Cfg público favoriteColor (String s_color) {
sFavColor = s_color;
devolva isso;
}
// setters com retorno automático ... END
public UserConfig build () {
return (new UserConfig (this));
}
}
//builder...END
}
Instanciando uma classe com um Bloch Builder
UserConfig uc = new UserConfig.Cfg ("Kermit"). Age (50) .favoriteColor ("green"). Build ();
A mesma classe, implementada como um Blind Builder
Há três partes em um Blind Builder, cada uma delas em um arquivo de código-fonte separado:
- A
ToBeBuilt
classe (neste exemplo UserConfig
:)
- Sua
Fieldable
interface " "
- O construtor
1. A classe a ser construída
A classe a ser construída aceita sua Fieldable
interface como seu único parâmetro construtor. O construtor define todos os campos internos e valida cada um. Mais importante ainda, esta ToBeBuilt
classe não tem conhecimento de seu construtor.
classe pública UserConfig {
private final String sName;
final privado int iAge;
final privado String sFavColor;
public UserConfig (UserConfig_Fieldable uc_f) {// CONSTRUCTOR
//transferir
experimentar {
sName = uc_f.getName ();
} captura (NullPointerException rx) {
lança nova NullPointerException ("uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// VALIDAR TODOS OS CAMPOS AQUI
}
public String toString () {
retornar "nome =" + sName + ", idade =" + iAge + ", sFavColor =" + sFavColor;
}
}
Conforme observado por um comentarista inteligente (que excluiu inexplicavelmente sua resposta), se a ToBeBuilt
classe também implementar sua Fieldable
, seu único construtor pode ser usado como construtor primário e de cópia (uma desvantagem é que os campos são sempre validados, mesmo que sabe-se que os campos no original ToBeBuilt
são válidos).
2. A Fieldable
interface " "
A interface de campo é a "ponte" entre a ToBeBuilt
classe e seu construtor, definindo todos os campos necessários para construir o objeto. Essa interface é requerida pelo ToBeBuilt
construtor de classes e é implementada pelo construtor. Como essa interface pode ser implementada por outras classes que não o construtor, qualquer classe pode instanciar facilmente a ToBeBuilt
classe, sem ser forçada a usar seu construtor. Isso também facilita a extensão da ToBeBuilt
classe, quando a extensão do construtor não é desejável ou necessária.
Conforme descrito na seção abaixo, eu não documento as funções nesta interface.
interface pública UserConfig_Fieldable {
String getName ();
int getAge ();
String getFavoriteColor ();
}
3. O construtor
O construtor implementa a Fieldable
classe. Ele não faz nenhuma validação e, para enfatizar esse fato, todos os seus campos são públicos e mutáveis. Embora essa acessibilidade pública não seja um requisito, prefiro e recomendo, porque reforça o fato de que a validação não ocorre até que o ToBeBuilt
construtor seja chamado. Isso é importante, porque é possível que outro encadeamento manipule ainda mais o construtor, antes de ser passado para o ToBeBuilt
construtor. A única maneira de garantir a validade dos campos - assumindo que o construtor não possa "bloquear" seu estado - é que a ToBeBuilt
classe faça a verificação final.
Finalmente, como na Fieldable
interface, não documento nenhum de seus getters.
classe pública UserConfig_Cfg implementa UserConfig_Fieldable {
public String sName;
public int iAge;
public String sFavColor;
public UserConfig_Cfg (String s_name) {
sName = s_name;
}
// setters com retorno automático ... START
idade pública de UserConfig_Cfg (int i_age) {
iAge = i_age;
devolva isso;
}
public UserConfig_Cfg favoriteColor (String s_color) {
sFavColor = s_color;
devolva isso;
}
// setters com retorno automático ... END
//getters...START
public String getName () {
return sName;
}
public int getAge () {
return iAge;
}
public String getFavoriteColor () {
return sFavColor;
}
//getters...END
public UserConfig build () {
return (new UserConfig (this));
}
}
Instanciando uma classe com um Blind Builder
UserConfig uc = new UserConfig_Cfg ("Kermit"). Age (50) .favoriteColor ("green"). Build ();
A única diferença é " UserConfig_Cfg
" em vez de " UserConfig.Cfg
"
Notas
Desvantagens:
- Os cegos não podem acessar membros particulares de sua
ToBeBuilt
classe,
- Eles são mais detalhados, pois agora são necessários getters no construtor e na interface.
- Tudo para uma única aula não está mais em apenas um lugar .
Compilar um construtor cego é direto:
ToBeBuilt_Fieldable
ToBeBuilt
ToBeBuilt_Cfg
A Fieldable
interface é totalmente opcional
Para uma ToBeBuilt
classe com poucos campos obrigatórios - como esta UserConfig
classe de exemplo, o construtor pode simplesmente ser
public UserConfig (String s_name, int i_age, String s_favColor) {
E chamou o construtor com
public UserConfig build () {
return (novo UserConfig (getName (), getAge (), getFavoriteColor ()));
}
Ou mesmo eliminando completamente os getters (no construtor):
return (novo UserConfig (sName, iAge, sFavoriteColor));
Ao passar os campos diretamente, a ToBeBuilt
classe é tão "cega" (desconhece seu construtor) quanto na Fieldable
interface. No entanto, para ToBeBuilt
classes que e se destinam a ser "estendida e sub-estendido muitas vezes" (que está no título deste post), quaisquer alterações a quaisquer necessita de campo mudanças em todos os sub-classe, em cada construtor e ToBeBuilt
construtor. À medida que o número de campos e subclasses aumenta, torna-se impraticável de manter.
(De fato, com poucos campos necessários, o uso de um construtor pode ser um exagero. Para os interessados, aqui está uma amostra de algumas das interfaces Fieldable maiores na minha biblioteca pessoal.)
Classes secundárias no subpacote
Eu escolho ter todo o construtor e as Fieldable
classes, para todos os Construtores Cegos, em um subpacote de sua ToBeBuilt
classe. O subpacote é sempre chamado " z
". Isso evita que essas classes secundárias atravancem a lista de pacotes JavaDoc. Por exemplo
library.class.my.UserConfig
library.class.my.z.UserConfig_Fieldable
library.class.my.z.UserConfig_Cfg
Exemplo de validação
Como mencionado acima, toda validação ocorre no ToBeBuilt
construtor do. Aqui está o construtor novamente com o código de validação de exemplo:
public UserConfig (UserConfig_Fieldable uc_f) {
//transferir
experimentar {
sName = uc_f.getName ();
} captura (NullPointerException rx) {
lança nova NullPointerException ("uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// validar (realmente deve pré-compilar os padrões ...)
experimentar {
if (! Pattern.compile ("\\ w +"). matcher (sName) .matches ()) {
throw new IllegalArgumentException ("uc_f.getName () (\" "+ sName +" \ ") pode não estar vazio e deve conter apenas letras dígitos e sublinhados.");
}
} captura (NullPointerException rx) {
lança nova NullPointerException ("uc_f.getName ()");
}
if (iAge <0) {
throw new IllegalArgumentException ("uc_f.getAge () (" + iAge + ") é menor que zero.");
}
experimentar {
if (! Pattern.compile ("(?: vermelho | azul | verde | rosa quente)"). matcher (sFavColor) .matches ()) {
throw new IllegalArgumentException ("uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +" \ ") não é vermelho, azul, verde ou rosa quente.");
}
} captura (NullPointerException rx) {
lança nova NullPointerException ("uc_f.getFavoriteColor ()");
}
}
Documentando Construtores
Esta seção é aplicável aos Bloch Builders e Blind Builders. Ele demonstra como documento as classes neste design, fazendo com que os setters (no construtor) e seus getters (na ToBeBuilt
classe) sejam diretamente referenciados entre si - com um único clique do mouse e sem que o usuário precise saber onde essas funções realmente residem - e sem que o desenvolvedor precise documentar algo de forma redundante.
Getters: ToBeBuilt
Somente nas aulas
Os getters são documentados apenas na ToBeBuilt
classe. Os getters equivalentes nas classes _Fieldable
e
_Cfg
são ignorados. Eu não os documento.
/ **
<P> A idade do usuário. </P>
@return Um int representando a idade do usuário.
@see UserConfig_Cfg # age (int)
@see getName ()
** /
public int getAge () {
return iAge;
}
O primeiro @see
é um link para seu setter, que está na classe construtora.
Setters: Na classe de construtor
O setter está documentado como se está na ToBeBuilt
classe , e também como se ele faz a validação (que realmente é feito pelo ToBeBuilt
construtor 's). O asterisco (" *
") é uma pista visual que indica que o destino do link está em outra classe.
/ **
<P> Defina a idade do usuário. </P>
@param i_age Não pode ser inferior a zero. Entre com {@code UserConfig # getName () getName ()} *.
@see #favoriteColor (String)
** /
idade pública de UserConfig_Cfg (int i_age) {
iAge = i_age;
devolva isso;
}
Outras informações
Juntando tudo: a fonte completa do exemplo do Blind Builder, com documentação completa
UserConfig.java
importar java.util.regex.Pattern;
/ **
<P> Informações sobre um usuário - <I> [construtor: UserConfig_Cfg] </I> </P>
<P> A validação de todos os campos ocorre neste construtor de classes. No entanto, cada requisito de validação é um documento apenas nas funções de configuração do construtor. </P>
<P> {@code java xbn.z.xmpl.lang.builder.finalv.UserConfig} </P>
** /
classe pública UserConfig {
público estático final void main (String [] igno_red) {
UserConfig uc = new UserConfig_Cfg ("Kermit"). Age (50) .favoriteColor ("green"). Build ();
System.out.println (uc);
}
private final String sName;
final privado int iAge;
final privado String sFavColor;
/ **
<P> Crie uma nova instância. Isso define e valida todos os campos. </P>
@param uc_f Pode não ser {@code null}.
** /
public UserConfig (UserConfig_Fieldable uc_f) {
//transferir
experimentar {
sName = uc_f.getName ();
} captura (NullPointerException rx) {
lança nova NullPointerException ("uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
//validar
experimentar {
if (! Pattern.compile ("\\ w +"). matcher (sName) .matches ()) {
throw new IllegalArgumentException ("uc_f.getName () (\" "+ sName +" \ ") pode não estar vazio e deve conter apenas letras dígitos e sublinhados.");
}
} captura (NullPointerException rx) {
lança nova NullPointerException ("uc_f.getName ()");
}
if (iAge <0) {
throw new IllegalArgumentException ("uc_f.getAge () (" + iAge + ") é menor que zero.");
}
experimentar {
if (! Pattern.compile ("(?: vermelho | azul | verde | rosa quente)"). matcher (sFavColor) .matches ()) {
throw new IllegalArgumentException ("uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +" \ ") não é vermelho, azul, verde ou rosa quente.");
}
} captura (NullPointerException rx) {
lança nova NullPointerException ("uc_f.getFavoriteColor ()");
}
}
//getters...START
/ **
<P> O nome do usuário. </P>
@return Uma string não - {@ code null}, não vazia.
@see UserConfig_Cfg # UserConfig_Cfg (String)
@see #getAge ()
@see #getFavoriteColor ()
** /
public String getName () {
return sName;
}
/ **
<P> A idade do usuário. </P>
@return Um número maior que ou igual a zero.
@see UserConfig_Cfg # age (int)
@see #getName ()
** /
public int getAge () {
return iAge;
}
/ **
<P> A cor favorita do usuário. </P>
@return Uma string não - {@ code null}, não vazia.
@see UserConfig_Cfg # age (int)
@see #getName ()
** /
public String getFavoriteColor () {
return sFavColor;
}
//getters...END
public String toString () {
retornar "getName () =" + getName () + ", getAge () =" + getAge () + ", getFavoriteColor () =" + getFavoriteColor ();
}
}
UserConfig_Fieldable.java
/ **
<P> Requerido pelo construtor {@link UserConfig} {@code UserConfig # UserConfig (UserConfig_Fieldable)}. </P>
** /
interface pública UserConfig_Fieldable {
String getName ();
int getAge ();
String getFavoriteColor ();
}
UserConfig_Cfg.java
importar java.util.regex.Pattern;
/ **
<P> Construtor para {@link UserConfig}. </P>
<P> A validação de todos os campos ocorre no construtor <CODE> UserConfig </CODE>. No entanto, cada requisito de validação é um documento apenas nessas funções do configurador de classes. </P>
** /
classe pública UserConfig_Cfg implementa UserConfig_Fieldable {
public String sName;
public int iAge;
public String sFavColor;
/ **
<P> Crie uma nova instância com o nome do usuário. </P>
@param s_name Não pode ser {@code null} ou vazio e deve conter apenas letras, dígitos e sublinhados. Conheça {@code UserConfig # getName () getName ()} {@ code ()} .
** /
public UserConfig_Cfg (String s_name) {
sName = s_name;
}
// setters com retorno automático ... START
/ **
<P> Defina a idade do usuário. </P>
@param i_age Não pode ser inferior a zero. Conheça {@code UserConfig # getName () getName ()} {@ code ()} .
@see #favoriteColor (String)
** /
idade pública de UserConfig_Cfg (int i_age) {
iAge = i_age;
devolva isso;
}
/ **
<P> Defina a cor favorita do usuário. </P>
@param s_color Deve ser {@code "red"}, {@code "blue"}, {@code green} ou {@code "hot pink"}. Conheça {@code UserConfig # getName () getName ()} {@ code ()} *.
@see #age (int)
** /
public UserConfig_Cfg favoriteColor (String s_color) {
sFavColor = s_color;
devolva isso;
}
// setters com retorno automático ... END
//getters...START
public String getName () {
return sName;
}
public int getAge () {
return iAge;
}
public String getFavoriteColor () {
return sFavColor;
}
//getters...END
/ **
<P> Crie o UserConfig, conforme configurado. </P>
@return <CODE> (novo {@link UserConfig # UserConfig (UserConfig_Fieldable) UserConfig} (este)) </CODE>
** /
public UserConfig build () {
return (new UserConfig (this));
}
}