Come si fa a mantenere basso il numero di argomenti mantenendo ancora separate le dipendenze di terze parti?

13

Uso una libreria di terze parti. Mi hanno passato un POJO che, a nostro avviso, è probabilmente implementato in questo modo:

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.
}

Per molti motivi, incluso ma non limitato a incapsulare la loro API e facilitare i test delle unità, voglio avvolgere i loro dati. Ma non voglio che le mie classi principali siano dipendenti dai loro dati (di nuovo, per ragioni di test)! Quindi adesso ho qualcosa di simile a questo:

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 poi questo:

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();
      );
  }
}

Questa classe dell'adattatore è accoppiata con le altre poche classi che DEVONO conoscere l'API di terze parti, limitando la sua pervasività attraverso il resto del mio sistema. Tuttavia ... questa soluzione è GROSS! In Codice pulito, pagina 40:

More than three arguments (polyadic) requires very special justification--and then shouldn't be used anyway.

Cose che ho considerato:

  • Creazione di un oggetto factory anziché di un metodo helper statico
    • Non risolve il problema di avere argomenti bajillion
  • Creazione di una sottoclasse di DataTypeOne e DataTypeTwo con un costruttore dipendente
    • Ha ancora un costruttore protetto poliadico
  • Crea implementazioni completamente separate conformi alla stessa interfaccia
  • Molteplici delle idee precedenti contemporaneamente

Come dovrebbe essere gestita questa situazione?

Nota questo non è un strato anti-corruzione situazione. Non c'è niente di sbagliato nella loro API. I problemi sono:

  • Non voglio che le mie strutture dati abbiano import com.third.party.library.SomeDataStructure;
  • Non riesco a costruire le loro strutture dati nei miei casi di test
  • La mia soluzione attuale si traduce in un numero di argomenti molto elevato. Voglio mantenere basso il conteggio degli argomenti, SENZA passare nelle loro strutture dati.
  • La domanda è " che cos'è un livello anti corruzione?". La mia domanda è " come posso utilizzare un pattern, qualsiasi pattern, per risolvere questo scenario?"

Non sto nemmeno chiedendo il codice (altrimenti questa domanda sarebbe su SO), solo chiedendo abbastanza di una risposta per permettermi di scrivere il codice in modo efficace (che quella domanda non fornisce).

    
posta durron597 29.01.2015 - 18:26
fonte

3 risposte

10

La strategia che ho usato quando ci sono diversi parametri di inizializzazione è creare un tipo che contenga solo i parametri per l'inizializzazione

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

Quindi il costruttore di DataTypeTwo acquisisce un oggetto DataTypeTwoParameters e DataTypeTwo viene creato tramite:

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

DataTypeTwo dtt = new DataTypeTwo(p);

Questo offre molte opportunità per chiarire quali sono i parametri che entrano in DataTypeTwo e cosa significano. Puoi anche fornire impostazioni predefinite sensibili nel costruttore DataTypeTwoParameters in modo che solo i valori che devono essere impostati possano essere eseguiti in qualsiasi ordine desiderato dal consumatore dell'API.

    
risposta data 29.01.2015 - 18:41
fonte
14

Hai due problemi distinti: avvolgere un'API e mantenere basso il numero di argomenti.

Quando si avvolge un'API, l'idea è di progettare l'interfaccia come se fosse da zero, non conoscendo nient'altro che i requisiti. Dici che non c'è niente di sbagliato nella loro API, quindi nella stessa lista di risposte ci sono un sacco di cose sbagliate con la loro API: testabilità, costruibilità, troppi parametri in un oggetto, ecc. Scrivi l'API che desideri avete avuto. Se ciò richiede più oggetti invece di uno, fallo. Se è necessario eseguire il wrapping di un livello superiore, sugli oggetti che creano il POJO, fallo.

Quindi, una volta ottenuta l'API desiderata, il conteggio dei parametri potrebbe non essere più un problema. Se lo è, ci sono un certo numero di modelli comuni da considerare:

  • Un oggetto parametro, come in la risposta di Erik .
  • Il modello di build , in cui si crea un oggetto generatore separato, quindi si chiama un numero di setter per impostare i parametri singolarmente , quindi crea il tuo oggetto finale.
  • Il modello prototipo , dove cloni sottoclassi dell'oggetto desiderato con i campi già impostati internamente.
  • Una fabbrica con cui hai già familiarità.
  • Una combinazione di quanto sopra.

Tieni presente che questi pattern di creazione spesso finiscono con il chiamare un costruttore poliadico, che dovresti considerare ok quando è incapsulato. Il problema con i costruttori poliadici non è quello di chiamarli una volta, è quando sei costretto a chiamarli ogni volta che devi costruire un oggetto.

Si noti che in genere è molto più semplice e più gestibile passare all'API sottostante memorizzando un riferimento all'oggetto OurData e inoltrando le chiamate al metodo, piuttosto che provare a reimplementare i suoi interni. Ad esempio:

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());
  }
  ...
}
    
risposta data 29.01.2015 - 19:33
fonte
1

Penso che potresti interpretare troppo strettamente la raccomandazione dello zio Bob. Per le classi normali, con la logica, i metodi, i costruttori e così via, un costruttore poliadico sente davvero molto l'odore del codice. Ma per qualcosa che è strettamente un contenitore di dati che espone campi, ed è generato da ciò che è essenzialmente un oggetto Factory già, non penso sia troppo male.

Tu puoi usare il modello Oggetto Parametro, come suggerito in un commento, puoi racchiudere questi parametri costruttore, quello che il tuo wrapper di tipo di dati locale è già , in sostanza , un oggetto Parameter. Tutto ciò che farà il tuo oggetto Parameter è l'impacchettamento dei parametri (Come lo creerai? Con un costruttore poliadico?) E poi li spacchetterai un secondo dopo in un oggetto che è quasi identico.

Se non vuoi esporre setter per i tuoi campi e chiamarli, penso che attenersi a un costruttore poliadico all'interno di un factory ben definito e incapsulato va bene.

    
risposta data 29.01.2015 - 18:58
fonte

Leggi altre domande sui tag