Yo uso una biblioteca de terceros. Me pasan un POJO que, para nuestros propósitos y propósitos, probablemente se implementa de la siguiente manera:
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 muchas razones, que incluyen, entre otras, encapsular su API y facilitar las pruebas unitarias, quiero ajustar sus datos. ¡Pero no quiero que mis clases principales dependan de sus datos (nuevamente, por razones de prueba)! Así que ahora tengo algo como esto:
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;
}
}
Y luego esto:
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();
);
}
}
Esta clase de adaptador se combina con las otras pocas clases que DEBEN conocer sobre la API de terceros, lo que limita su omnipresencia en el resto de mi sistema. Sin embargo ... ¡esta solución es BRUTA! En Clean Code, página 40:
Más de tres argumentos (poliádicos) requieren una justificación muy especial, y de todos modos no deberían usarse.
Cosas que he considerado:
- Crear un objeto de fábrica en lugar de un método auxiliar estático
- No resuelve el problema de tener miles de millones de argumentos
- Crear una subclase de DataTypeOne y DataTypeTwo que tenga un constructor dependiente
- Todavía tiene un constructor poliádico protegido
- Cree implementaciones completamente separadas que se ajusten a la misma interfaz
- Múltiple de las ideas anteriores simultáneamente
¿Cómo debe manejarse esta situación?
Tenga en cuenta que esta no es una situación de capa anticorrupción . No hay nada malo con su API. Los problemas son:
- No quiero que mis estructuras de datos tengan
import com.third.party.library.SomeDataStructure;
- No puedo construir sus estructuras de datos en mis casos de prueba
- Mi solución actual da como resultado recuentos de argumentos muy muy altos. Quiero mantener bajos los argumentos, SIN pasar sus estructuras de datos.
- Esa pregunta es " ¿qué es una capa anticorrupción?". Mi pregunta es " ¿cómo puedo usar un patrón, cualquier patrón, para resolver este escenario?"
Tampoco estoy pidiendo código (de lo contrario, esta pregunta estaría en SO), solo estoy pidiendo una respuesta suficiente para que pueda escribir el código de manera efectiva (que esa pregunta no proporciona).
fuente
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.
Respuestas:
La estrategia que he usado cuando hay varios parámetros de inicialización es crear un tipo que solo contenga los parámetros para la inicialización
Luego, el constructor para DataTypeTwo toma un objeto DataTypeTwoParameters, y DataTypeTwo se construye a través de:
Esto brinda muchas oportunidades para dejar en claro cuáles son todos los parámetros que entran en DataTypeTwo y qué significan. También puede proporcionar valores predeterminados razonables en el constructor DataTypeTwoParameters para que solo los valores que deben establecerse puedan realizarse en cualquier orden que le guste al consumidor de la API.
fuente
Integer.parseInt
? ¿En un setter o fuera de la clase de parámetros?p.bar = Integer.parseInt("4")
.DataTypeTwoParameters
aDataTypeTwo
.Realmente tiene dos preocupaciones separadas aquí: ajustar una API y mantener bajo el recuento de argumentos.
Cuando se ajusta una API, la idea es diseñar la interfaz como si fuera desde cero, sin conocer nada más que los requisitos. Dices que no hay nada de malo en su API, luego, en el mismo aliento, enumera varias cosas que están mal con su API: comprobabilidad, capacidad de construcción, demasiados parámetros en un objeto, etc. Escribe la API que deseas tener. Si eso requiere varios objetos en lugar de uno, hazlo. Si requiere ajustar un nivel más alto, a los objetos que crean el POJO, hazlo.
Luego, una vez que tenga su API deseada, el recuento de parámetros ya no será un problema. Si es así, hay una serie de patrones comunes a considerar:
Tenga en cuenta que estos patrones de creación a menudo terminan llamando a un constructor poliádico, que debe considerar correcto cuando está encapsulado. El problema con los constructores poliádicos no es llamarlos una vez, es cuando te ves obligado a llamarlos cada vez que necesitas construir un objeto.
Tenga en cuenta que, por lo general, es mucho más fácil y más fácil pasar a la API subyacente almacenando una referencia al
OurData
objeto y reenviando las llamadas al método, en lugar de intentar volver a implementar sus componentes internos. Por ejemplo:fuente
OurData
Objeto": esto es lo que estoy tratando de evitar, al menos en la clase base, para garantizar que no haya dependencia.DataInterface
. Creas otra implementación para tus objetos simulados.Creo que podrías estar interpretando la recomendación del tío Bob demasiado estrictamente. Para las clases normales, con lógica y métodos y constructores y demás, un constructor poliádico se parece mucho al olor a código. Pero para algo que es estrictamente un contenedor de datos que expone campos y es generado por lo que es esencialmente un objeto Factory, no creo que sea tan malo.
Usted puede utilizar el modelo de objetos de parámetros, como se sugiere en un comentario, puede envolver estos parámetros del constructor para usted, cuál es su tipo de datos local envoltura es es ya , en esencia, un objeto de parámetro. Todo lo que hará su objeto Parameter es empaquetar los parámetros (¿Cómo lo creará? ¿Con un constructor poliádico?) Y luego desempacarlos un segundo más tarde en un objeto que sea casi idéntico.
Si no desea exponer los setters para sus campos y llamarlos, creo que está bien apegarse a un constructor poliádico dentro de una fábrica bien definida y encapsulada.
fuente