Cum se rezolvă dependența circulară?
On ianuarie 1, 2021 by adminAm trei clase care sunt circulare dependente unele de altele:
TestExecuter execută solicitările TestScenario și salvează un fișier de raport folosind clasa ReportGenerator . Deci:
- TestExecuter depinde de ReportGenerator pentru a genera raportul
- ReportGenerator depinde de TestScenario și de parametrii setați din TestExecuter.
- TestScenario depinde de TestExecuter.
Nu se poate afla cum să eliminați aceste dependențe.
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: ca răspuns la un răspuns, mai multe detalii despre clasa mea 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 exemplu de fișier XML care va fi generat în cazul unui scenariu care conține două teste:
<testScenario name="scenario1"> <test name="test1"> <result>false</result> </test> <test name="test1"> <result>true</result> </test> </testScenario >
Comentarii
Răspuns
Din punct de vedere tehnic, puteți rezolva orice dependență ciclică utilizând interfețe, așa cum se arată în celelalte răspunsuri. Cu toate acestea, vă recomand să vă regândiți designul. Cred că nu este puțin probabil să poți evita nevoia de interfețe suplimentare complet, în timp ce designul tău devine și mai simplu.
Cred că nu este necesar ca un ReportGenerator
să depindă direct de un TestScenario
. TestScenario
pare să aibă două responsabilități: este utilizat pentru executarea testelor și funcționează și ca un container pentru rezultate. Aceasta este o încălcare a SRP. Interesant este că, rezolvând această încălcare, veți scăpa și de dependența ciclică.
Deci, în loc să lăsați generatorul de rapoarte să preia date din scenariul de testare, transmiteți datele în mod explicit utilizând un obiect de valoare. Asta înseamnă, înlocuiți
reportGenerator.setTestScenario(ts);
cu un cod cum ar fi
reportGenerator.insertDataToDisplay(ts.getReportData());
Metoda getReportData
trebuie să aibă un tip de returnare ca ReportData
, un obiect valoare care funcționează ca un container pentru datele care trebuie afișate în raport. insertDataToDisplay
este o metodă care așteaptă un obiect exact de acel tip.
În acest fel, ReportGenerator
și TestScenario
ambele vor depinde de ReportData
, care nu depinde de nimic altceva, iar primele două clase nu mai depind unele de altele.
Ca a doua abordare: pentru a rezolva încălcarea SRP, lăsați TestScenario
să fie responsabil pentru deținerea rezultatelor unei execuții de test, dar nu pentru apelarea executorului de test. Luați în considerare reorganizarea codului, astfel încât scenariul de test să nu acceseze executorul de testare, dar executorul de testare este pornit din exterior și scrie rezultatele înapoi în obiectul TestScenario
. În exemplul pe care ni l-ați arătat, acest lucru va fi posibil făcând public accesul la LinkedList<Test>
din TestScenario
și mutând execute
metoda de la TestScenario
la altundeva, poate direct într-o TestExecuter
, poate într-o nouă clasă TestScenarioExecuter
.
În acest fel, TestExecuter
va depinde de TestScenario
și ReportGenerator
, ReportGenerator
va depinde și de TestScenario
, dar TestScenario
nu va depinde de nimic altceva.
Și, în cele din urmă, o a treia abordare: TestExecuter
are prea multe responsabilități. Este responsabil pentru executarea testelor, precum și pentru furnizarea unui TestScenario
unui ReportGenerator
. Puneți aceste două responsabilități în două clase separate, iar dependența dvs. ciclică va dispărea din nou.
S-ar putea să existe mai multe variante pentru abordarea problemei dvs., dar sper că veți avea ideea generală: problema dvs. de bază sunt clasele cu prea multe responsabilități . Rezolvați această problemă și veți scăpa automat de dependența ciclică.
Comentarii
- Vă mulțumim pentru răspuns, de fapt am nevoie de toate informațiile din TestScenario pentru a putea genera raportul meu la final 🙁
- @ sabrina2020: și ce vă împiedică să introduceți toate aceste informații în
ReportData
?S-ar putea să vă gândiți să vă editați întrebarea și să explicați puțin mai detaliat ce se întâmplă însaveReport
. - De fapt, TestScenario-ul meu conține o listă de teste și vreau toate informații într-un fișier XML raport, astfel încât ReportData să aibă totul în acest caz, îmi voi edita răspunsul pentru mai multe detalii, mulțumesc!
- +1: M-ai avut la
interfaces
. - @ sabrina2020: Am adăugat două abordări diferite la răspunsul meu, alegeți cea care se potrivește cel mai bine nevoilor dvs.
Răspuns
Prin utilizarea interfețelor puteți rezolva dependența circulară.
Proiectare curentă:
Proiectare propusă:
În betonul de proiectare propus clasele nu depind de alte clase concrete, ci doar de abstracții (interfețe).
Important:
Trebuie să utilizați model de creație la alegere (poate o fabrică) pentru a evita realizarea new
al oricăror clase concrete din interiorul oricărei alte clase concrete sau apelând getInstance()
. Doar fabrica va avea dependențe de clasele concrete. Clasa dvs. Main
ar putea servi ca fabrică dacă credeți că o fabrică dedicată ar fi exagerată. De exemplu, puteți injecta un ReportGenerator
în TestExecuter
în loc să apelați getInstance()
sau new
.
Răspuns
Deoarece TestExecutor
utilizează numai ReportGenerator
intern, ar trebui să puteți defini o interfață pentru aceasta și să consultați interfața din TestScenario
. Apoi TestExecutor
depinde de ReportGenerator
, ReportGenerator
depinde de TestScenario
și TestScenario
depinde de ITestExecutor
, care nu depinde de nimic.
În mod ideal, tu „Ați fi definirea interfețelor pentru toate clasele dvs. și exprimarea dependențelor prin intermediul acestora, dar aceasta este cea mai mică schimbare care vă va rezolva problema.
File(filename).write(Report); Report = XMLResult(ResultData).toString(); ResultData = TestSuite(SingleTestLogic).execute(TestDataIterator(TestDetailsList))