Come risolvere la dipendenza circolare?

28

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

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

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

Impossibile capire come rimuovere le 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()
    }
}

MODIFICA: in risposta a una risposta, ulteriori 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 >
    
posta sabrina2020 04.01.2016 - 15:37
fonte

3 risposte

31

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

Suppongo che non sia necessario che ReportGenerator dipenda direttamente da TestScenario . TestScenario sembra avere due responsabilità: è usato per l'esecuzione del test e funziona anche come contenitore per i risultati. Questa è una violazione dell'SRP. È interessante notare che, risolvendo questa violazione, si eliminerà anche la dipendenza ciclica.

Quindi, anziché consentire al generatore di report di acquisire i dati dallo scenario di test, passare esplicitamente i dati utilizzando un oggetto valore. Ciò significa, sostituire

   reportGenerator.setTestScenario(ts); 

da un codice come

reportGenerator.insertDataToDisplay(ts.getReportData()); 

Il metodo getReportData deve avere un tipo di ritorno come ReportData , un oggetto valore che funziona come contenitore per i dati da visualizzare nel report. insertDataToDisplay è un metodo che si aspetta un oggetto esattamente di quel tipo.

In questo modo, ReportGenerator e TestScenario dipendono entrambi da 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 SRP lasciare che TestScenario sia responsabile della conservazione dei risultati dell'esecuzione di un test, ma non per chiamare l'execester di test. Si consiglia di riorganizzare il codice in modo che lo scenario di test non acceda all'esecutore di test, ma l'execester di test viene avviato dall'esterno e scrive nuovamente i risultati nell'oggetto TestScenario . Nell'esempio che ci hai mostrato, ciò sarà possibile rendendo l'accesso a LinkedList<Test> all'interno di TestScenario pubblico e spostando il metodo execute da TestScenario a qualche altra parte, magari direttamente in TestExecuter , forse in una nuova classe TestScenarioExecuter .

In questo modo, TestExecuter dipenderà da TestScenario e ReportGenerator , ReportGenerator dipenderà anche da TestScenario , ma TestScenario dipenderà da nient'altro.

Infine, un terzo approccio: TestExecuter ha troppe responsabilità. È responsabile dell'esecuzione dei test e della fornitura di TestScenario 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 problema, ma spero che tu abbia un'idea generale: il tuo problema principale sono le classi con troppe responsabilità . Risolvi il problema e ti libererai automaticamente dalla dipendenza ciclica.

    
risposta data 04.01.2016 - 17:03
fonte
7

Usando le interfacce puoi risolvere la dipendenza circolare.

Design attuale:

Designproposto:

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

Importante:

Devi utilizzare il modello di creatività di tua scelta (forse una factory) per evitare di perfezionare new di qualsiasi classe concreta all'interno di qualsiasi altra classe concreta o chiamando getInstance() . Solo la fabbrica avrà dipendenze da classi concrete. La tua classe Main potrebbe fungere da fabbrica se pensi che uno stabilimento dedicato sarebbe eccessivo. Ad esempio, puoi inserire un ReportGenerator in TestExecuter invece di chiamare getInstance() o new .

    
risposta data 04.01.2016 - 17:29
fonte
3

Poiché TestExecutor utilizza solo ReportGenerator internamente, dovresti essere in grado di definirne un'interfaccia e fare riferimento all'interfaccia in TestScenario . Quindi TestExecutor dipende da ReportGenerator , ReportGenerator dipende da TestScenario e TestScenario dipende da ITestExecutor , che non dipende da nulla.

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

    
risposta data 04.01.2016 - 16:07
fonte