Hoe circulaire afhankelijkheid oplossen?
Geplaatst op januari 1, 2021 door adminIk heb drie klassen die circulair afhankelijk van elkaar zijn:
TestExecuter voert verzoeken van TestScenario uit en sla een rapportbestand op met behulp van de ReportGenerator-klasse . Dus:
- TestExecuter is afhankelijk van ReportGenerator om het rapport te genereren
- ReportGenerator is afhankelijk van TestScenario en van parameters die zijn ingesteld vanuit TestExecuter.
- TestScenario is afhankelijk van TestExecuter.
Kan “niet achterhalen hoe deze afhankelijkheden te verwijderen.
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() } }
BEWERKEN: in reactie op een antwoord, meer details over mijn 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 */ }
Een voorbeeld van de xml-bestand dat moet worden gegenereerd in het geval van een scenario met twee tests:
<testScenario name="scenario1"> <test name="test1"> <result>false</result> </test> <test name="test1"> <result>true</result> </test> </testScenario >
Opmerkingen
Antwoord
Technisch gezien kun je elke cyclische afhankelijkheid oplossen door interfaces te gebruiken, zoals weergegeven in de andere antwoorden. Ik raad echter aan om uw ontwerp opnieuw te bekijken. Ik denk dat het niet onwaarschijnlijk is dat je de behoefte aan extra interfaces volledig kunt vermijden, terwijl je ontwerp nog eenvoudiger wordt.
Ik denk dat het niet nodig is dat een ReportGenerator
rechtstreeks afhankelijk is van een TestScenario
. TestScenario
lijkt twee verantwoordelijkheden te hebben: het wordt gebruikt voor het uitvoeren van tests en het werkt ook als een container voor de resultaten. Dit is in strijd met de SRP. Interessant is dat door die overtreding op te lossen, je ook de cyclische afhankelijkheid wegneemt.
Dus in plaats van de rapportgenerator gegevens uit het testscenario te laten halen, geef je de gegevens expliciet door met behulp van een waardeobject. Dat betekent, vervang
reportGenerator.setTestScenario(ts);
door een code zoals
reportGenerator.insertDataToDisplay(ts.getReportData());
De methode getReportData
moet een retourtype hebben zoals ReportData
, een waardeobject dat werkt als een container voor de gegevens die in het rapport moeten worden weergegeven. insertDataToDisplay
is een methode die een object van precies dat type verwacht.
Op deze manier ReportGenerator
en TestScenario
zullen beide afhangen van ReportData
, dat van niets anders afhangt, en de eerste twee klassen zijn niet meer van elkaar afhankelijk.
Als tweede benadering: om de SRP-overtreding op te lossen, laat TestScenario
verantwoordelijk zijn voor het bewaren van de resultaten van een testuitvoering, maar niet voor het aanroepen van de testuitvoerder. Overweeg om de code te reorganiseren, zodat niet het testscenario toegang krijgt tot de testuitvoerder, maar de testuitvoerder van buitenaf wordt gestart en de resultaten terugschrijft naar het TestScenario
-object. In het voorbeeld dat u ons heeft laten zien, is dat mogelijk door de toegang tot LinkedList<Test>
binnen TestScenario
openbaar te maken en door de execute
methode van TestScenario
naar ergens anders, misschien rechtstreeks in een TestExecuter
, misschien in een nieuwe klas TestScenarioExecuter
.
Op die manier is TestExecuter
afhankelijk van TestScenario
en ReportGenerator
, ReportGenerator
zijn ook afhankelijk van TestScenario
, maar TestScenario
zal van niets anders afhangen.
En tot slot, een derde benadering: TestExecuter
heeft ook te veel verantwoordelijkheden. Het is verantwoordelijk voor het uitvoeren van tests en voor het leveren van een TestScenario
aan een ReportGenerator
. Zet deze twee verantwoordelijkheden in twee aparte klassen, en je cyclische afhankelijkheid zal weer verdwijnen.
Er kunnen meer varianten zijn om je probleem te benaderen, maar ik hoop dat je het algemene idee krijgt: je kernprobleem zijn klassen met te veel verantwoordelijkheden . Los dat probleem op, en je raakt automatisch van de cyclische afhankelijkheid af.
Opmerkingen
- Bedankt voor je antwoord, eigenlijk heb ik alle informatie in TestScenario nodig om mijn rapport aan het einde te kunnen genereren 🙁
- @ sabrina2020: en wat weerhoudt je ervan al die informatie in
ReportData
te plaatsen?U kunt overwegen uw vraag aan te passen en een beetje gedetailleerder uit te leggen wat er gebeurt insaveReport
. - Eigenlijk bevat mijn TestScenario een lijst met Test en ik wil alles informatie in een rapport xml-bestand, zodat de ReportData het in dit geval allemaal heeft, ik zal mijn antwoord bewerken voor meer details, bedankt!
- +1: Je had me op
interfaces
. - @ sabrina2020: ik heb twee verschillende benaderingen aan mijn antwoord toegevoegd, kies degene die het beste bij je past.
Antwoord
Door interfaces te gebruiken kun je de circulaire afhankelijkheid oplossen.
Huidig ontwerp:
Voorgesteld ontwerp:
In het voorgestelde ontwerpbeton klassen zijn niet “afhankelijk van andere concrete klassen, maar alleen van abstracties (interfaces).
Belangrijk:
U moet het creatieve patroon van uw keuze gebruiken (misschien een fabriek) om te voorkomen dat new
van concrete klassen binnen een andere concrete klasse of die getInstance()
aanroept. Alleen de fabriek zal afhankelijk zijn van betonklassen. Je klasse Main
zou als fabriek kunnen dienen als je denkt dat een speciale fabriek overdreven zou zijn. U kunt bijvoorbeeld een ReportGenerator
in TestExecuter
injecteren in plaats van getInstance()
of new
.
Antwoord
Sinds TestExecutor
gebruikt alleen ReportGenerator
intern, je zou er een interface voor moeten kunnen definiëren, en verwijzen naar de interface in TestScenario
. Vervolgens is TestExecutor
afhankelijk van ReportGenerator
, ReportGenerator
hangt af van TestScenario
, en TestScenario
is afhankelijk van ITestExecutor
, die nergens van afhankelijk is.
Idealiter ben jij “Ik zou interfaces definiëren voor al je klassen en afhankelijkheden ermee uitdrukken, maar dit is de kleinste wijziging die je probleem zal oplossen.
File(filename).write(Report); Report = XMLResult(ResultData).toString(); ResultData = TestSuite(SingleTestLogic).execute(TestDataIterator(TestDetailsList))