Hvordan løses cirkulær afhængighed?
On januar 1, 2021 by adminJeg har tre klasser, der er cirkulære afhængige af hinanden:
TestExecuter udfører anmodninger fra TestScenario og gemmer en rapportfil ved hjælp af ReportGenerator-klassen . Så:
- TestExecuter afhænger af ReportGenerator for at generere rapporten
- ReportGenerator afhænger af TestScenario og af parametre, der er indstillet fra TestExecuter.
- TestScenario afhænger af TestExecuter.
Kan ikke finde ud af, hvordan man fjerner disse afhængigheder.
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, flere detaljer 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, der skal genereres i tilfælde af et scenarie, der indeholder to tests:
<testScenario name="scenario1"> <test name="test1"> <result>false</result> </test> <test name="test1"> <result>true</result> </test> </testScenario >
Kommentarer
Svar
Teknisk set kan du løse enhver cyklisk afhængighed ved hjælp af grænseflader, som vist i de andre svar. Jeg anbefaler dog at genoverveje dit design. Jeg synes, det er ikke usandsynligt, at du kan undgå behovet for yderligere grænseflader fuldstændigt, mens dit design bliver endnu enklere.
Jeg tror det ikke er nødvendigt for en ReportGenerator
at være afhængig af en TestScenario
direkte. TestScenario
ser ud til at have to ansvarsområder: det bruges til testudførelse, og det fungerer også som en container til resultaterne. Dette er en krænkelse af SRP. Interessant nok, ved at løse denne overtrædelse, slipper du også med den cykliske afhængighed.
Så i stedet for at lade rapportgeneratoren hente data fra testscenariet, videregive dataene eksplicit ved hjælp af et værdiobjekt. Det betyder, udskift
reportGenerator.setTestScenario(ts);
med en kode som
reportGenerator.insertDataToDisplay(ts.getReportData());
Metoden getReportData
skal have en returtype som ReportData
, et værdiobjekt, der fungerer som en container for de data, der skal vises i rapporten. insertDataToDisplay
er en metode, der forventer et objekt af nøjagtig den type.
På denne måde ReportGenerator
og TestScenario
afhænger begge af ReportData
, som ikke afhænger af noget andet, og de to første klasser er ikke mere afhængige af hinanden.
Som en anden tilgang: For at løse SRP-overtrædelsen lad TestScenario
være ansvarlig for at holde resultaterne af en testudførelse, men ikke for at kalde testudføreren. Overvej at omorganisere koden, så ikke testscenariet får adgang til testkøreren, men testkøreren startes udefra og skriver resultaterne tilbage til TestScenario
-objektet. I det eksempel, du viste os, vil det være muligt ved at gøre adgangen til LinkedList<Test>
inde i TestScenario
offentlig og ved at flytte execute
metode fra TestScenario
til et andet sted, måske direkte ind i en TestExecuter
, måske til en ny klasse TestScenarioExecuter
.
På den måde vil TestExecuter
afhænge af TestScenario
og ReportGenerator
, ReportGenerator
afhænger også af TestScenario
, men TestScenario
afhænger ikke af noget andet.
Og endelig har en tredje tilgang: TestExecuter
også for mange ansvarsområder. Det er ansvarligt for udførelse af tests såvel som for at levere en TestScenario
til en ReportGenerator
. Sæt disse to ansvarsområder i to separate klasser, og din cykliske afhængighed forsvinder igen.
Der kan være flere varianter til at nærme dig dit problem, men jeg håber, du får den generelle idé: dit kerneproblem er klasser med for mange ansvarsområder . Løs dette problem, så slipper du automatisk af den cykliske afhængighed.
Kommentarer
- Tak for dit svar, faktisk har jeg brug for alle oplysninger i TestScenario for at kunne generere min rapport i slutningen 🙁
- @ sabrina2020: og hvad forhindrer dig i at lægge alle disse oplysninger i
ReportData
?Du kan overveje at redigere dit spørgsmål og forklare lidt mere detaljeret, hvad der sker inde isaveReport
. - Faktisk indeholder min TestScenario en liste over test, og jeg vil have alle oplysninger i en rapport xml-fil, så ReportData skal have det hele i dette tilfælde, jeg vil redigere mit svar for flere detaljer, tak!
- +1: Du havde mig på
interfaces
. - @ sabrina2020: Jeg tilføjede to forskellige tilgange til mit svar, vælg den der passer bedst til dine behov.
Svar
Ved at bruge grænseflader kan du løse den cirkulære afhængighed.
Nuværende design:
Forslag til design:
I det foreslåede designbeton klasser afhænger ikke af andre konkrete klasser, men kun af abstraktioner (grænseflader).
Vigtigt:
Du skal bruge det skabelsesmønster efter eget valg (måske en fabrik) for at undgå at dukke new
af alle konkrete klasser inden for enhver anden betonklasse eller kalder getInstance()
. Kun fabrikken har afhængighed af konkrete klasser. Din Main
-klasse kan fungere som fabrik, hvis du tror, at en dedikeret fabrik ville være for stor. For eksempel kan du indsprøjte en ReportGenerator
i TestExecuter
i stedet for at ringe til getInstance()
eller new
.
Svar
Siden TestExecutor
bruger kun ReportGenerator
internt, du skal kunne definere en grænseflade til den og henvise til grænsefladen i TestScenario
. Derefter afhænger TestExecutor
af ReportGenerator
, ReportGenerator
afhænger af TestScenario
og TestScenario
afhænger af ITestExecutor
, hvilket ikke afhænger af noget.
Ideelt set dig “definerer grænseflader til alle dine klasser og udtrykker afhængigheder gennem dem, men dette er den mindste ændring, der løser dit problem.
File(filename).write(Report); Report = XMLResult(ResultData).toString(); ResultData = TestSuite(SingleTestLogic).execute(TestDataIterator(TestDetailsList))