Come risolvere la dipendenza circolare?
Su Gennaio 1, 2021 da adminHo tre classi che dipendono in modo circolare luna dallaltra:
TestExecuter esegue le richieste di TestScenario e salva un file di report utilizzando la classe ReportGenerator . Quindi:
- 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 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 >
Commenti
Risposta
Tecnicamente, puoi risolvere qualsiasi dipendenza ciclica utilizzando le interfacce, come mostrato nelle altre risposte. Tuttavia, ti consiglio di ripensare al tuo design. Penso che non sia improbabile che tu possa evitare la necessità di interfacce aggiuntive completamente, mentre il tuo design diventa ancora più semplice.
Immagino che non sia necessario che un ReportGenerator
dipenda direttamente da un TestScenario
. TestScenario
sembra avere due responsabilità: viene utilizzato per lesecuzione del test e funziona anche come contenitore dei risultati. Questa è una violazione dellSRP. È interessante notare che, risolvendo tale violazione, si eliminerà anche la dipendenza ciclica.
Quindi, invece di lasciare che il generatore di report acquisisca i dati dallo scenario di test, passare i dati esplicitamente utilizzando un oggetto valore. Ciò significa che, sostituire
reportGenerator.setTestScenario(ts);
con un codice come
reportGenerator.insertDataToDisplay(ts.getReportData());
Il metodo getReportData
deve avere un tipo di ritorno come ReportData
, un oggetto valore che funge da contenitore per i dati da visualizzare nel rapporto. insertDataToDisplay
è un metodo che si aspetta un oggetto esattamente di quel tipo.
In questo modo, ReportGenerator
e TestScenario
dipenderanno entrambi da ReportData
, che non dipende da nientaltro, e le prime due classi non dipendono più luna dallaltra.
Come secondo approccio: per risolvere la violazione SRP lascia che TestScenario
sia responsabile della conservazione dei risultati di unesecuzione del test, ma non della chiamata dellesecutore del test. Considera di riorganizzare il codice in modo che lo scenario di test non acceda allesecutore di test, ma questultimo viene avviato dallesterno e riscrive i risultati nelloggetto TestScenario
. Nellesempio che ci hai mostrato, ciò sarà possibile rendendo pubblico laccesso a LinkedList<Test>
allinterno di TestScenario
e spostando il execute
da TestScenario
a qualche altra parte, magari direttamente in un TestExecuter
, magari in una nuova classe TestScenarioExecuter
.
In questo modo, TestExecuter
dipenderà da TestScenario
e ReportGenerator
, ReportGenerator
dipenderà anche da TestScenario
, ma TestScenario
non dipenderà da nientaltro.
E infine, un terzo approccio: TestExecuter
ha anche troppe responsabilità. È responsabile dellesecuzione dei test e della fornitura di un TestScenario
a un 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 unidea generale: il tuo problema principale sono le classi con troppe responsabilità . Risolvi il problema e ti libererai automaticamente della dipendenza ciclica.
Commenti
- Grazie per la tua risposta, in realtà ho bisogno di tutte le informazioni in TestScenario per poter generare il mio rapporto alla fine 🙁
- @ sabrina2020: e cosa ti impedisce di inserire tutte queste informazioni in
ReportData
?Puoi considerare di modificare la tua domanda e spiegare un po più dettagliatamente cosa succede allinterno disaveReport
. - In realtà il mio TestScenario contiene un elenco di Test e voglio che tutti informazioni in un file xml del rapporto in modo che ReportData contenga tutto in questo caso, modificherò la mia risposta per maggiori dettagli, grazie!
- +1: Mi avevi a
interfaces
. - @ sabrina2020: Ho aggiunto due diversi approcci alla mia risposta, scegli quello più adatto alle tue esigenze.
Risposta
Utilizzando le interfacce puoi risolvere la dipendenza circolare.
Design attuale:
Progetto proposto:
Nel calcestruzzo di progetto proposto le classi non dipendono da altre classi concrete, ma solo dalle astrazioni (interfacce).
Importante:
Devi utilizzare il pattern di creazione di tua scelta (forse una fabbrica) per evitare lesecuzione new
di qualsiasi classe concreta allinterno di qualsiasi altra classe concreta o chiamando getInstance()
. Solo la fabbrica dipenderà dalle classi concrete. La tua classe Main
potrebbe fungere da fabbrica se pensi che una fabbrica dedicata sarebbe eccessiva. Ad esempio, puoi inserire un ReportGenerator
in TestExecuter
invece di chiamare getInstance()
o new
.
Risposta
Poiché TestExecutor
usa solo ReportGenerator
internamente, dovresti essere in grado di definire uninterfaccia per esso e fare riferimento allinterfaccia in TestScenario
. Quindi TestExecutor
dipende da ReportGenerator
, ReportGenerator
dipende da TestScenario
e TestScenario
dipende da ITestExecutor
, che “t non dipende da nulla.
Idealmente tu “Definirei le interfacce per tutte le tue classi ed esprimere le dipendenze attraverso di esse, ma questa è la più piccola modifica che risolverà il tuo problema.
File(filename).write(Report); Report = XMLResult(ResultData).toString(); ResultData = TestSuite(SingleTestLogic).execute(TestDataIterator(TestDetailsList))