Eu uso uma biblioteca de terceiros. Eles me passam um POJO que, para nossos propósitos e propósitos, provavelmente é implementado assim:
public class OurData {
private String foo;
private String bar;
private String baz;
private String quux;
// A lot more than this
// IMPORTANT: NOTE THAT THIS IS A PACKAGE PRIVATE CONSTRUCTOR
OurData(/* I don't know what they do */) {
// some stuff
}
public String getFoo() {
return foo;
}
// etc.
}
Por muitas razões, incluindo, entre outras, encapsular sua API e facilitar o teste de unidade, desejo agrupar seus dados. Mas não quero que minhas classes principais dependam de seus dados (novamente, por razões de teste)! Então agora eu tenho algo parecido com isto:
public class DataTypeOne implements DataInterface {
private String foo;
private int bar;
private double baz;
public DataTypeOne(String foo, int bar, double baz) {
this.foo = foo;
this.bar = bar;
this.baz = baz;
}
}
public class DataTypeTwo implements DataInterface {
private String foo;
private int bar;
private double baz;
public DataTypeOne(String foo, int bar, double baz, String quux) {
this.foo = foo;
this.bar = bar;
this.baz = baz;
this.quux = quux;
}
}
E então isso:
public class ThirdPartyAdapter {
public static makeMyData(OurData data) {
if(data.getQuux() == null) {
return new DataTypeOne(
data.getFoo(),
Integer.parseInt(data.getBar()),
Double.parseDouble(data.getBaz()),
);
} else {
return new DataTypeTwo(
data.getFoo(),
Integer.parseInt(data.getBar()),
Double.parseDouble(data.getBaz()),
data.getQuux();
);
}
}
Essa classe de adaptador é acoplada às outras poucas classes que DEVEM saber sobre a API de terceiros, limitando sua abrangência pelo restante do meu sistema. No entanto ... esta solução é GROSS! No Código Limpo, página 40:
Mais de três argumentos (poládicos) exigem justificativa muito especial - e, portanto, não devem ser usados de qualquer maneira.
Coisas que eu considerei:
- Criando um objeto de fábrica em vez de um método auxiliar estático
- Não resolve o problema de ter um bajilhão de argumentos
- Criando uma subclasse de DataTypeOne e DataTypeTwo que possui um construtor dependente
- Ainda possui um construtor protegido poládico
- Crie implementações totalmente separadas que estejam em conformidade com a mesma interface
- Múltiplas das idéias acima simultaneamente
Como essa situação deve ser tratada?
Observe que essa não é uma situação de camada anticorrupção . Não há nada errado com a API deles. Os problemas são:
- Não quero que minhas estruturas de dados tenham
import com.third.party.library.SomeDataStructure;
- Não consigo construir suas estruturas de dados nos meus casos de teste
- Minha solução atual resulta em contagens de argumentos muito muito altas. Eu quero manter a contagem de argumentos baixa, SEM passar em suas estruturas de dados.
- Essa pergunta é "o que é uma camada anticorrupção?". Minha pergunta é " como posso usar um padrão, qualquer padrão, para resolver esse cenário?"
Também não estou pedindo código (caso contrário, essa pergunta estaria no SO), apenas pedindo uma resposta suficiente para que eu possa escrever o código efetivamente (o que essa pergunta não fornece).
fonte
The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification — and then shouldn’t be used anyway.
Respostas:
A estratégia que usei quando existem vários parâmetros de inicialização é criar um tipo que apenas contenha os parâmetros para inicialização
Em seguida, o construtor para DataTypeTwo pega um objeto DataTypeTwoParameters e DataTypeTwo é construído via:
Isso oferece muitas oportunidades para deixar claro quais são todos os parâmetros do DataTypeTwo e o que eles significam. Você também pode fornecer padrões sensíveis no construtor DataTypeTwoParameters, para que apenas os valores que precisam ser configurados possam ser executados em qualquer ordem que o consumidor da API gostar.
fonte
Integer.parseInt
? Em um setter, ou fora da classe de parâmetros?p.bar = Integer.parseInt("4")
.DataTypeTwoParameters
paraDataTypeTwo
.Você realmente tem duas preocupações separadas aqui: agrupar uma API e manter a contagem de argumentos baixa.
Ao agrupar uma API, a idéia é projetar a interface como se fosse do zero, sem conhecer nada além dos requisitos. Você diz que não há nada de errado com a API deles; em seguida, na mesma lista, várias coisas erradas com a API: testabilidade, construtibilidade, muitos parâmetros em um objeto, etc. Escreva a API que você deseja ter. Se isso exigir vários objetos em vez de um, faça isso. Se precisar envolver um nível mais alto, nos objetos que criam o POJO, faça isso.
Depois que você tiver a API desejada, a contagem de parâmetros poderá não ser mais um problema. Se for, há vários padrões comuns a serem considerados:
Observe que esses padrões de criação geralmente acabam chamando um construtor poládico, que você deve considerar aceitável quando encapsulado. O problema com os construtores poliadicos não é chamá-los uma vez, é quando você é forçado a chamá-los toda vez que precisa construir um objeto.
Observe que geralmente é muito mais fácil e mais sustentável passar para a API subjacente armazenando uma referência ao
OurData
objeto e encaminhando as chamadas de método, em vez de tentar reimplementar seus componentes internos. Por exemplo:fonte
OurData
objeto" - é isso que estou tentando evitar, pelo menos na classe base, para garantir que não haja dependência.DataInterface
. Você cria outra implementação para seus objetos simulados.Eu acho que você pode estar interpretando estritamente a recomendação do tio Bob. Para classes normais, com lógica, métodos e construtores, um construtor poládico realmente se parece muito com o cheiro do código. Mas para algo que é estritamente um contêiner de dados que expõe campos e é gerado pelo que já é essencialmente um objeto Factory, não acho tão ruim assim.
Você pode usar o padrão Parameter Object, conforme sugerido em um comentário, pode agrupar esses parâmetros do construtor para você, qual é o seu wrapper de tipo de dados local já é , essencialmente, um objeto Parameter. Tudo o que seu objeto Parameter fará é empacotar os parâmetros (como você o criará? Com um construtor poládico?) E depois descompactá-los um segundo depois em um objeto quase idêntico.
Se você não deseja expor os setters para seus campos e chamá-los, acho que seguir um construtor poládico dentro de uma fábrica bem definida e encapsulada é bom.
fonte