¿Cómo se mantienen bajos los conteos de argumentos y se mantienen separadas las dependencias de terceros?

13

Yo uso una biblioteca de terceros. Me pasan un POJO que, para nuestros propósitos y propósitos, probablemente se implementa de esta 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 muchos motivos, incluidos, entre otros, la encapsulación de su API y la facilitación de las pruebas unitarias, quiero resumir sus datos. ¡Pero no quiero que mis clases principales sean dependientes 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 penetración en el resto de mi sistema. Sin embargo ... esta solución es GRUESA! En Clean Code, página 40:

  

Más de tres argumentos (políada) requieren una justificación muy especial y, por lo tanto, no deben usarse de todos modos.

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 un billón de argumentos
  • Creando una subclase de DataTypeOne y DataTypeTwo que tiene un constructor dependiente
    • Todavía tiene un constructor protegido poládico
  • Cree implementaciones completamente separadas que se ajusten a la misma interfaz
  • Múltiples de las ideas anteriores simultáneamente

¿Cómo debe manejarse esta situación?

Tenga en cuenta que esto no es un situación de la 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 un conteo de argumentos muy alto. Quiero mantener bajos los conteos de 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 un código (de lo contrario, esta pregunta estaría en SO), solo pediré una respuesta suficiente para que yo pueda escribir el código de manera efectiva (lo que esa pregunta no proporciona).

    
pregunta durron597 29.01.2015 - 18:26

3 respuestas

10

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

public class DataTypeTwoParameters {
    public String foo;  // use getters/setters instead if it's appropriate
    public int bar;
    public double baz;
    public String quuz;
}

Luego, el constructor para DataTypeTwo toma un objeto DataTypeTwoParameters, y DataTypeTwo se construye a través de:

DataTypeTwoParameters p = new DataTypeTwoParameters();
p.foo = "Hello";
p.bar = 4;
p.baz = 3;
p.quuz = "World";

DataTypeTwo dtt = new DataTypeTwo(p);

Esto brinda muchas oportunidades para aclarar 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 se puedan establecer los valores que se deben establecer, en cualquier orden que le guste al consumidor de la API.

    
respondido por el Erik 29.01.2015 - 18:41
14

Realmente tienes dos preocupaciones separadas aquí: envolver una API y mantener el conteo de argumentos bajo.

Al ajustar una API, la idea es diseñar la interfaz desde cero, sin saber nada más que los requisitos. Dice que no hay nada de malo en su API, luego, al mismo tiempo, enumere una serie de errores con su API: capacidad de prueba, capacidad de construcción, demasiados parámetros en un objeto, etc. Escriba la API que desea tu tenias. Si eso requiere múltiples objetos en lugar de uno, haz eso. Si requiere ajustar un nivel más alto, a los objetos que crea el POJO, haga eso.

Luego, una vez que tenga la API deseada, es posible que el número de parámetros ya no sea un problema. Si es así, hay una serie de patrones comunes a considerar:

  • Un objeto de parámetro, como en la respuesta de Erik .
  • El patrón de creación , donde crea un objeto de creación independiente, luego llama a un número de configuradores para establecer los parámetros individualmente , luego crea tu objeto final.
  • El patrón prototipo , donde clonar subclases de su objeto deseado con los campos ya establecidos internamente.
  • Una fábrica con la que ya estás familiarizado.
  • Alguna combinación de lo anterior.

Tenga en cuenta que estos patrones de creación a menudo terminan llamando un constructor poládico, que debería considerar bien cuando está encapsulado. El problema con los constructores poládicos no es llamarlos una sola vez, es cuando te ves obligado a llamarlos cada vez que necesitas construir un objeto.

Tenga en cuenta que generalmente es mucho más fácil y más fácil pasar a la API subyacente al almacenar una referencia al objeto OurData y reenviar las llamadas de método, en lugar de intentar reimplementar sus aspectos internos. Por ejemplo:

public class DataTypeTwo implements DataInterface {
  private OurData data;

  public DataTypeOne(OurData data) {
    this.data = data;
  }

   public String getFoo() {
    return data.getFoo();
  }

  public int getBar() {
    return Integer.parseInt(data.getBar());
  }
  ...
}
    
respondido por el Karl Bielefeldt 29.01.2015 - 19:33
1

Creo que podrías estar interpretando la recomendación del tío Bob de manera demasiado estricta. Para las clases normales, con lógica, métodos y constructores, el constructor poládico se parece mucho al olor del código. Pero para algo que es estrictamente un contenedor de datos que expone campos, y que es generado por lo que ya es esencialmente un objeto de Factory, no creo que sea tan malo.

Usted puede usar el patrón de Objeto de Parámetro, como se sugiere en un comentario, puede envolver estos parámetros de constructor para usted, lo que su contenedor de tipo de datos local es ya , esencialmente , un objeto Parameter. Todo lo que hará su objeto Parameter es empaquetar los parámetros (¿Cómo se creará? ¿Con un constructor poládico?) Y luego descomprimirlos un segundo después en un objeto que sea casi idéntico.

Si no desea exponer a los establecedores para sus campos y llamarlos, creo que está bien pegarse a un constructor poládico dentro de una fábrica bien definida y encapsulada.

    
respondido por el Avner Shahar-Kashtan 29.01.2015 - 18:58

Lea otras preguntas en las etiquetas