Hur löser man cirkulärt beroende?
On januari 1, 2021 by adminJag har tre klasser som är cirkulära beroende av varandra:
TestExecuter utför förfrågningar från TestScenario och sparar en rapportfil med hjälp av klassen ReportGenerator . Så:
- TestExecuter beror på ReportGenerator för att generera rapporten
- ReportGenerator beror på TestScenario och på parametrar som ställts in från TestExecuter.
- TestScenario beror på TestExecuter.
Kan inte ta reda på hur beroenden kan tas bort.
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å ett svar, mer information om min TestScenario-klass:
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 */ }
Ett exempel på xml-fil som ska genereras i händelse av ett scenario som innehåller två tester:
<testScenario name="scenario1"> <test name="test1"> <result>false</result> </test> <test name="test1"> <result>true</result> </test> </testScenario >
Kommentarer
Svar
Tekniskt sett kan du lösa alla cykliska beroende genom att använda gränssnitt, som visas i de andra svaren. Jag rekommenderar dock att du tänker igenom din design. Jag tror att det inte är osannolikt att du kan undvika behovet av ytterligare gränssnitt helt, medan din design blir ännu enklare.
Jag antar att det inte är nödvändigt att en ReportGenerator
är beroende av en TestScenario
direkt. TestScenario
verkar ha två ansvarsområden: den används för testkörning och fungerar också som en behållare för resultaten. Detta är ett brott mot SRP. Intressant är att genom att lösa denna överträdelse kommer du också att bli av med det cykliska beroendet.
Så istället för att låta rapportgeneratorn ta tag i data från testscenariot, skicka data uttryckligen genom att använda något värdeobjekt. Det betyder, ersätt
reportGenerator.setTestScenario(ts);
med någon kod som
reportGenerator.insertDataToDisplay(ts.getReportData());
Metoden getReportData
måste ha en returtyp som ReportData
, ett värdeobjekt som fungerar som en behållare för att data ska visas i rapporten. insertDataToDisplay
är en metod som förväntar sig ett objekt av exakt den typen.
På så sätt ReportGenerator
och TestScenario
beror båda på ReportData
, vilket inte beror på något annat, och de två första klasserna beror inte på varandra längre.
Som ett andra tillvägagångssätt: för att lösa SRP-överträdelsen, låt TestScenario
vara ansvarig för att hålla resultaten av en testkörning, men inte för att anropa testköraren. Överväg att omorganisera koden så att inte testscenariot får åtkomst till testköraren, utan testköraren startas utifrån och skriver resultatet tillbaka till TestScenario
-objektet. I exemplet du visade oss kommer det att vara möjligt genom att göra åtkomsten till LinkedList<Test>
inuti TestScenario
offentlig och genom att flytta execute
metod från TestScenario
till någon annanstans, kanske direkt till en TestExecuter
, kanske till en ny klass TestScenarioExecuter
.
På så sätt beror TestExecuter
på TestScenario
och ReportGenerator
, ReportGenerator
beror också på TestScenario
, men TestScenario
beror inte på något annat.
Och slutligen, ett tredje tillvägagångssätt: TestExecuter
har också för många ansvarsområden. Det ansvarar för att utföra tester såväl som för att tillhandahålla en TestScenario
till en ReportGenerator
. Fördela dessa två ansvarsområden i två separata klasser så kommer ditt cykliska beroende att försvinna igen.
Det kan finnas fler varianter för att närma dig ditt problem, men jag hoppas att du får den allmänna idén: ditt kärnproblem är klasser med för många ansvarsområden . Lös det problemet så kommer du att bli av med det cykliska beroendet automatiskt.
Kommentarer
- Tack för ditt svar, jag behöver faktiskt all information i TestScenario för att kunna generera min rapport i slutet 🙁
- @ sabrina2020: och vad hindrar dig från att lägga all den informationen till
ReportData
?Du kan överväga att redigera din fråga och förklara lite mer detaljerat vad som händer isaveReport
. - Egentligen innehåller min TestScenario en lista med test och jag vill ha alla information i en rapport xml-fil så att ReportData ska ha allt i det här fallet, jag kommer att redigera mitt svar för mer information, tack!
- +1: Du hade mig på
interfaces
. - @ sabrina2020: Jag lade till två olika tillvägagångssätt i mitt svar, välj det som passar dina behov bäst.
Svar
Genom att använda gränssnitt kan du lösa det cirkulära beroendet.
Nuvarande design:
Föreslagen design:
I den föreslagna konstruktionsbetongen klasser beror inte på andra konkreta klasser men bara på abstraktioner (gränssnitt).
Viktigt:
Du måste använda skapande mönster efter eget val (kanske en fabrik) för att undvika att bli framträdande new
av alla betongklasser i någon annan betongklass eller kallar getInstance()
. Endast fabriken har beroende av konkreta klasser. Din Main
-klass kan fungera som fabrik om du tror att en dedikerad fabrik skulle vara för hög. Du kan till exempel injicera en ReportGenerator
i TestExecuter
istället för att ringa getInstance()
eller new
.
Svar
Eftersom TestExecutor
använder endast ReportGenerator
internt, du bör kunna definiera ett gränssnitt för det och hänvisa till gränssnittet i TestScenario
. Då beror TestExecutor
på ReportGenerator
, ReportGenerator
beror på TestScenario
och TestScenario
beror på ITestExecutor
, vilket inte beror på någonting.
Helst du ”definierar gränssnitt för alla dina klasser och uttrycker beroenden genom dem, men detta är den minsta förändringen som kommer att lösa ditt problem.
File(filename).write(Report); Report = XMLResult(ResultData).toString(); ResultData = TestSuite(SingleTestLogic).execute(TestDataIterator(TestDetailsList))