Come risolvere la dipendenza circolare?


33

Ho tre classi che sono circolari dipendenti l'una dall'altra:

TestExecuter esegue le richieste di TestScenario e salva un file di report utilizzando la classe ReportGenerator. Così:

  • TestExecuter dipende da ReportGenerator per generare il rapporto
  • ReportGenerator dipende da TestScenario e dai parametri impostati da TestExecuter.
  • TestScenario dipende da TestExecuter.

Non riesco a capire come rimuovere queste dipendenze.

public class TestExecuter {

  ReportGenerator reportGenerator;  

  public void getReportGenerator() {
     reportGenerator = ReportGenerator.getInstance();
     reportGenerator.setParams(this.params);
     /* this.params several parameters from TestExecuter class example this.owner */
  }

  public void setTestScenario (TestScenario  ts) {
     reportGenerator.setTestScenario(ts); 
  }

  public void saveReport() {
     reportGenerator.saveReport();    
  }

  public void executeRequest() {
    /* do things */
  }
}
public class ReportGenerator{
    public static ReportGenerator getInstance(){}
    public void setParams(String params){}
    public void setTestScenario (TestScenario ts){}
    public void saveReport(){}
}
public class TestScenario {

    TestExecuter testExecuter;

    public TestScenario(TestExecuter te) {
        this.testExecuter=te;
    }

    public void execute() {
        testExecuter.executeRequest();
    }
}
public class Main {
    public static void main(String [] args) {
      TestExecuter te = new TestExecuter();
      TestScenario ts = new TestScenario(te);

      ts.execute();
      te.getReportGenerator();
      te.setTestScenario(ts);
      te.saveReport()
    }
}

EDIT: in risposta a una risposta, maggiori dettagli sulla mia classe TestScenario:

public class TestScenario {
    private LinkedList<Test> testList;
    TestExecuter testExecuter;

    public TestScenario(TestExecuter te) {
        this.testExecuter=te;
    }

    public void execute() {
        for (Test test: testList) {
            testExecuter.executeRequest(test); 
        }
    }
}

public class Test {
  private String testName;
  private String testResult;
}

public class ReportData {
/*shall have all information of the TestScenario including the list of Test */
    }

Un esempio del file xml da generare in caso di uno scenario contenente due test:

<testScenario name="scenario1">
   <test name="test1">
     <result>false</result>
   </test>
   <test name="test1">
     <result>true</result>
   </test>
</testScenario >

Prova a identificare i tuoi oggetti andando indietro chiedendo di quale (oggetto) hai bisogno per far funzionare quello precedente, ad esempio:File(filename).write(Report); Report = XMLResult(ResultData).toString(); ResultData = TestSuite(SingleTestLogic).execute(TestDataIterator(TestDetailsList))
brivido

Risposte:


35

Tecnicamente, puoi risolvere qualsiasi dipendenza ciclica usando le interfacce, come mostrato nelle altre risposte. Tuttavia, ti consiglio di ripensare il tuo design. Penso che non sia improbabile che tu possa evitare completamente la necessità di ulteriori interfacce, mentre il tuo design diventa ancora più semplice.

Immagino che non sia necessario che un ReportGeneratordipenda TestScenariodirettamente da un . TestScenariosembra avere due responsabilità: viene utilizzato per l'esecuzione del test e funziona anche come contenitore per i risultati. Questa è una violazione dell'SRP. È interessante notare che, risolvendo tale violazione, eliminerai anche la dipendenza ciclica.

Quindi, invece di lasciare che il generatore di report acquisisca i dati dallo scenario di test, passa esplicitamente i dati utilizzando un oggetto valore. Ciò significa che sostituisci

   reportGenerator.setTestScenario(ts); 

con un codice come

reportGenerator.insertDataToDisplay(ts.getReportData()); 

Il metodo getReportDatadeve avere un tipo restituito come ReportData, un oggetto valore che funge da contenitore per i dati da visualizzare nel report. insertDataToDisplayè un metodo che prevede un oggetto esattamente di quel tipo.

In questo modo, ReportGeneratore TestScenariodipenderà entrambi ReportData, che non dipende da nient'altro, e le prime due classi non dipendono più l'una dall'altra.

Come secondo approccio: per risolvere la violazione di SRP, TestScenarioessere responsabile del mantenimento dei risultati di un'esecuzione del test, ma non della chiamata dell'esecutore del test. Considerare di riorganizzare il codice in modo che non lo scenario di test acceda all'esecutore di test, ma l'esecutore di test viene avviato dall'esterno e riscrive i risultati TestScenarionell'oggetto. Nell'esempio che ci hai mostrato, ciò sarà possibile rendendo l'accesso LinkedList<Test>all'interno del TestScenariopubblico e spostando il executemetodo da TestScenarioqualche altra parte, magari direttamente in una TestExecuter, forse in una nuova classe TestScenarioExecuter.

In questo modo, TestExecuterdipenderà TestScenarioe ReportGenerator, ReportGeneratordipenderà TestScenario, anche, ma TestScenariodipenderà da nient'altro.

E infine un terzo approccio: TestExecuterha anche troppe responsabilità. È responsabile dell'esecuzione dei test e della fornitura di TestScenarioa a ReportGenerator. Metti queste due responsabilità in due classi separate e la tua dipendenza ciclica svanirà di nuovo.

Potrebbero esserci più varianti per affrontare il tuo problema, ma spero che tu abbia un'idea generale: il tuo problema principale sono le classi con troppe responsabilità . Risolvi quel problema e ti libererai automaticamente della dipendenza ciclica.


Grazie per la risposta, in realtà ho bisogno di tutte le informazioni in TestScenario per essere in grado di generare il mio rapporto alla fine :(
sabrina2020

@ sabrina2020: e cosa ti impedisce di inserire tutte queste informazioni ReportData? Puoi considerare di modificare la tua domanda e spiegare un po 'più in dettaglio cosa succede all'interno di saveReport.
Doc Brown,

In realtà il mio TestScenario contiene un elenco di test e voglio tutte le informazioni in un file xml di report, quindi ReportData deve avere tutto in questo caso, modificherò la mia risposta per maggiori dettagli, grazie!
sabrina2020,

1
+1: mi avevi interfaces.
Joel Etherton,

@ sabrina2020: ho aggiunto due diversi approcci alla mia risposta, scegli quello che si adatta meglio alle tue esigenze.
Doc Brown,

8

Utilizzando le interfacce è possibile risolvere la dipendenza circolare.

Disegno attuale:

inserisci qui la descrizione dell'immagine

Progetto proposto:

inserisci qui la descrizione dell'immagine

Nella progettazione proposta le classi concrete non dipendono da altre classi concrete ma solo dalle astrazioni (interfacce).

Importante:

Devi usare il modello creativo di tua scelta (forse una fabbrica) per evitare il perfezionamento newdi qualsiasi classe concreta all'interno di qualsiasi altra classe concreta o chiamata getInstance(). Solo la fabbrica avrà dipendenze da classi concrete. La tua Mainclasse potrebbe servire da fabbrica se pensi che una fabbrica dedicata sarebbe eccessiva. Ad esempio, puoi inserire a ReportGeneratorin TestExecuterinvece di chiamare getInstance()o new.


3

Poiché TestExecutorutilizza solo ReportGeneratorinternamente, dovresti essere in grado di definire un'interfaccia per esso e fare riferimento all'interfaccia in TestScenario. Quindi TestExecutordipende ReportGenerator, ReportGeneratordipende TestScenarioe TestScenariodipende da ITestExecutor, che non dipende da nulla.

Idealmente, dovresti definire le interfacce per tutte le tue classi e esprimere le dipendenze attraverso di esse, ma questa è la modifica più piccola che risolverà il tuo problema.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.