Chaining of Composition è il giusto approccio?

2

Sto lavorando su un codice dove appare la struttura:

Class A {
  Client client;
  B b;
  void process(request) {
    data = fetchData(client, data);
    b.process(data);
  }
}

class B {
  C c;
  void process(data) {
    data2 = someProcessing(data);
    c.process(data2);
  }
}

class C {
  D d;
  SqsClient sqs;
  void process(data2) {
    data3 = someProcessing(data2);
    d.saveToDB(data3);
    sqs.sendData(data3);
}

Si noti che tutti i metodi di processo sono nulli (poiché non prevedono alcun valore di ritorno). L'idea è di prendere alcuni dati di input, trasformarli, salvarli in DB e accodarli. Il modo più semplice che posso pensare di fare è usare Composition, ma ora sembra una catena di dipendenze. Stavo guardando Chain of Responsibility, ma in questo caso voglio che ogni gestore della catena faccia sempre qualcosa, quindi non credo che vada bene. Quale sarebbe l'approccio giusto per organizzare il mio codice?

    
posta MadN 12.11.2017 - 18:03
fonte

2 risposte

1

Sono d'accordo con @Zapadlo che questo non è un gran problema dato che le classi sono coese. In una configurazione più generale, però, potresti voler essere più flessibile con quel tipo di calcoli dipendenti.

In questo caso, puoi esaminare la composizione tradizionale delle funzioni per arrivare ad un'astrazione più generale:

Considera la tua prima classe B ( A sembra iniziare la catena recuperando inizialmente i dati). Prende data come input, diciamo di tipo T . Fa alcuni effetti collaterali e calcola una nuova vista dei dati, chiamata data2 nel codice, di tipo S . La stessa struttura è presente per C . In termini di funzioni questo è fondamentalmente il Function<T, S> composto con Function<S, U> .

Normalmente, un Function dovrebbe essere privo di effetti collaterali. In particolare, quando si entra nel regno della programmazione funzionale. A seconda dell'affinità tua e del tuo team con questo argomento, puoi semplicemente utilizzare un nome diverso per l'interfaccia sottostante. Supponiamo, un DataHandler<T,S> che prende data di tipo T , ne fa qualcosa e restituisce alcuni dati aggiornati di tipo S . È fondamentalmente la stessa cosa però. (Il nome DataHandler non è però molto buono). D'altra parte, se hai familiarità con FP, puoi prendere la strada divertente e strappare le parti con effetti collaterali usando le monadi.

In ogni caso, l'astrazione della parte di gestione dati e di modifica nella propria interfaccia separata rimuove le dipendenze dirette tra queste classi. L'idea alla base di questa operazione è il principio di inversione delle dipendenze (DIP). Il codice chiamante risultante è quindi responsabile della composizione effettiva, quindi ci sarà un singolo posto dedicato nel codice che crea la catena di dipendenze B->C->D , mentre le singole classi non sono infastidite da quella catena.

Inoltre, ci sono buone probabilità che dopo il refactoring basato sul DIP, scoprirai che il codice risultante che gestisce la trasformazione e l'inoltro dei dati a una dipendenza sembra molto simile o addirittura identico nelle diverse classi. Quindi puoi continuare a fare il refactoring seguendo il principio Non ripetere te stesso (ASCIUTTO) se ritieni che valga la pena.

    
risposta data 13.11.2017 - 14:27
fonte
1

In questo caso il CdR sarebbe eccessivo. Potresti farlo in quel modo, ovviamente, ma il codice sarebbe più complicato, senza un chiaro vantaggio. Le tue classi dipendono chiaramente l'una dall'altra. Potresti disaccoppiarli un po 'usando le interfacce, ma se non hai intenzione di avere più di un'implementazione di ogni interfaccia, allora anche questo è eccessivo.

Vorrei solo suggerire una modifica, però. In base al tuo codice, non hai davvero bisogno di avere un membro B di classe A, o membro C di classe B. A meno che non ci siano altre ragioni per questo (che non vedo da questo codice), è un accoppiamento completamente inutile e uno spreco di spazio. Potresti semplicemente creare quelle variabili locali, come questa:

Class A {
  Client client;

  void process(request) {
    data = fetchData(client, data);
    B b = new B();
    b.process(data);
  }
}

class B {

  void process(data) {
    data2 = someProcessing(data);
    C c = new C();
    c.process(data2);
  }
}

class C {
  SqsClient sqs;
  void process(data2) {
    data3 = someProcessing(data2);
    D d = new D();
    d.saveToDB(data3);
    sqs.sendData(data3);
  }
}
    
risposta data 13.11.2017 - 15:17
fonte