Hvordan løse sirkulær avhengighet?
On januar 1, 2021 by adminJeg har tre klasser som er sirkulære avhengige av hverandre:
TestExecuter utfører forespørsler fra TestScenario og lagrer en rapportfil ved hjelp av ReportGenerator-klassen . Så:
- TestExecuter er avhengig av ReportGenerator for å generere rapporten
- ReportGenerator er avhengig av TestScenario og av parametere angitt fra TestExecuter.
- TestScenario er avhengig av TestExecuter.
Kan ikke finne ut hvordan du fjerner avhengighetene.
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: som svar på et svar, mer informasjon om min TestScenario-klasse:
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 */ }
Et eksempel på xml-fil som skal genereres i tilfelle et scenario som inneholder to tester:
<testScenario name="scenario1"> <test name="test1"> <result>false</result> </test> <test name="test1"> <result>true</result> </test> </testScenario >
Kommentarer
Svar
Teknisk sett kan du løse enhver syklisk avhengighet ved å bruke grensesnitt, som vist i de andre svarene. Imidlertid anbefaler jeg å revurdere designet ditt. Jeg tror det ikke er usannsynlig at du kan unngå behovet for flere grensesnitt helt, mens designet ditt blir enda enklere.
Jeg antar at det ikke er nødvendig for en ReportGenerator
å være avhengig av en TestScenario
direkte. TestScenario
ser ut til å ha to ansvar: den brukes til testutførelse, og den fungerer også som en beholder for resultatene. Dette er et brudd på SRP. Interessant, ved å løse dette bruddet, vil du også bli kvitt den sykliske avhengigheten.
Så i stedet for å la rapportgeneratoren hente data fra testscenariet, send dataene eksplisitt ved å bruke noe verdiobjekt. Det betyr, erstatt
reportGenerator.setTestScenario(ts);
med noen kode som
reportGenerator.insertDataToDisplay(ts.getReportData());
Metoden getReportData
må ha en returtype som ReportData
, et verdiobjekt som fungerer som en beholder for dataene som skal vises i rapporten. insertDataToDisplay
er en metode som forventer et objekt av akkurat den typen.
På denne måten ReportGenerator
og TestScenario
vil begge avhenge av ReportData
, som ikke er avhengig av noe annet, og de to første klassene er ikke avhengige av hverandre lenger.
Som en annen tilnærming: la TestScenario
være ansvarlig for å holde resultatene av en testutførelse, men ikke for å ringe testutføreren for å løse SRP-bruddet. Vurder å omorganisere koden, slik at ikke testscenariet får tilgang til testutføreren, men testutføreren startes utenfra og skriver resultatene tilbake til TestScenario
-objektet. I eksemplet du viste oss, vil det være mulig ved å gjøre tilgangen til LinkedList<Test>
inne i TestScenario
offentlig, og ved å flytte execute
metode fra TestScenario
til et annet sted, kanskje direkte inn i en TestExecuter
, kanskje inn i en ny klasse TestScenarioExecuter
.
På den måten vil TestExecuter
avhenge av TestScenario
og ReportGenerator
, ReportGenerator
vil også avhenge av TestScenario
, men TestScenario
vil ikke være avhengig av noe annet.
Og til slutt, en tredje tilnærming: TestExecuter
har også for mange ansvarsoppgaver. Den er ansvarlig for å utføre tester så vel som å gi en TestScenario
til en ReportGenerator
. Sett disse to ansvarsoppgavene i to separate klasser, og din sykliske avhengighet forsvinner igjen.
Det kan være flere varianter for å nærme deg problemet ditt, men jeg håper du får den generelle ideen: ditt kjerneproblem er klasser med for mange ansvarsoppgaver . Løs det problemet, så blir du kvitt den sykliske avhengigheten automatisk.
Kommentarer
- Takk for svaret, faktisk trenger jeg all informasjon i TestScenario for å kunne generere rapporten min til slutt 🙁
- @ sabrina2020: og hva hindrer deg i å legge all den informasjonen inn i
ReportData
?Du kan vurdere å redigere spørsmålet ditt og forklare litt mer detaljert hva som skjer inne isaveReport
. - Egentlig inneholder TestScenario en liste over test, og jeg vil ha alt informasjon i en rapport xml-fil så ReportData skal ha alt i dette tilfellet, jeg vil redigere svaret mitt for mer informasjon, takk!
- +1: Du hadde meg på
interfaces
. - @ sabrina2020: Jeg la til to forskjellige tilnærminger til svaret mitt, velg den som passer best for dine behov.
Svar
Ved å bruke grensesnitt kan du løse den sirkulære avhengigheten.
Gjeldende design:
Foreslått design:
I den foreslåtte designbetongen klasser avhenger ikke av andre konkrete klasser, men bare av abstraksjoner (grensesnitt).
Viktig:
Du må bruke skapelsesmønster etter eget valg (kanskje en fabrikk) for å unngå perfeksjon new
av alle konkrete klasser i en hvilken som helst annen konkret klasse eller kaller getInstance()
. Bare fabrikken vil ha avhengighet av betongklasser. Main
-klassen din kan tjene som fabrikk hvis du tror at en dedikert fabrikk vil være for mye. For eksempel kan du injisere en ReportGenerator
i TestExecuter
i stedet for å ringe getInstance()
eller new
.
Svar
Siden TestExecutor
bruker bare ReportGenerator
internt, du bør kunne definere et grensesnitt for det, og referere til grensesnittet i TestScenario
. Da er TestExecutor
avhengig av ReportGenerator
, ReportGenerator
avhenger av TestScenario
og TestScenario
avhenger av ITestExecutor
, som ikke er avhengig av noe.
Ideelt sett er du «definerer grensesnitt for alle klassene dine og uttrykker avhengighet gjennom dem, men dette er den minste endringen som løser problemet ditt.
File(filename).write(Report); Report = XMLResult(ResultData).toString(); ResultData = TestSuite(SingleTestLogic).execute(TestDataIterator(TestDetailsList))