Jak rozwiązać zależność cykliczną?
On 1 stycznia, 2021 by adminMam trzy klasy, które są cyklicznie zależne od siebie:
TestExecuter wykonuje żądania TestScenario i zapisuje plik raportu przy użyciu klasy ReportGenerator . Tak więc:
- TestExecuter zależy od ReportGenerator, aby wygenerować raport.
- ReportGenerator zależy od TestScenario i parametrów ustawionych przez TestExecuter.
- TestScenario zależy od TestExecuter.
Nie mogę dowiedzieć się, jak usunąć te zależności.
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() } }
EDYTUJ: w odpowiedzi na odpowiedź, więcej szczegółów na temat mojej klasy 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 */ }
Przykład xml do wygenerowania w przypadku scenariusza zawierającego dwa testy:
<testScenario name="scenario1"> <test name="test1"> <result>false</result> </test> <test name="test1"> <result>true</result> </test> </testScenario >
Komentarze
Odpowiedź
Z technicznego punktu widzenia każdą cykliczną zależność można rozwiązać za pomocą interfejsów, jak pokazano w innych odpowiedziach. Polecam jednak przemyśleć swój projekt. Myślę, że nie jest nieprawdopodobne, abyś mógł uniknąć potrzeby stosowania dodatkowych interfejsów, a projekt stanie się jeszcze prostszy.
Wydaje mi się, że ReportGenerator
nie jest konieczne, aby bezpośrednio zależało od TestScenario
. TestScenario
wydaje się mieć dwa zadania: jest używany do wykonywania testów i działa również jako pojemnik na wyniki. To jest naruszenie SRP. Co ciekawe, usuwając to naruszenie, pozbędziesz się również cyklicznej zależności.
Więc zamiast pozwalać generatorowi raportów pobierać dane ze scenariusza testowego, przekaż dane jawnie, używając obiektu wartości. Oznacza to, że zastąp
reportGenerator.setTestScenario(ts);
jakimś kodem, takim jak
reportGenerator.insertDataToDisplay(ts.getReportData());
Metoda getReportData
musi mieć zwracany typ, taki jak ReportData
, obiekt wartości, który działa jako kontener dla danych do wyświetlenia w raporcie. insertDataToDisplay
to metoda, która oczekuje obiektu dokładnie tego typu.
W ten sposób ReportGenerator
i TestScenario
będą zależały od ReportData
, co nie zależy od niczego innego, a pierwsze dwie klasy nie zależą już od siebie.
Jako drugie podejście: aby rozwiązać naruszenie SRP, pozwól TestScenario
być odpowiedzialnym za przechowywanie wyników wykonania testu, ale nie za wywoływanie programu wykonawczego testu. Rozważ zreorganizowanie kodu, aby scenariusz testowy nie uzyskiwał dostępu do modułu wykonawczego testów, ale program wykonywalny testów był uruchamiany z zewnątrz i zapisywał wyniki z powrotem do obiektu TestScenario
. W przykładzie, który nam pokazałeś, będzie to możliwe, jeśli dostęp do LinkedList<Test>
wewnątrz TestScenario
będzie publiczny i przeniesiesz execute
z TestScenario
do innego miejsca, może bezpośrednio do TestExecuter
, być może do nowej klasy TestScenarioExecuter
.
W ten sposób TestExecuter
będzie zależało od TestScenario
i ReportGenerator
, ReportGenerator
będzie zależało również od TestScenario
, ale TestScenario
nie będzie zależało od niczego innego.
I wreszcie trzecie podejście: TestExecuter
ma również zbyt wiele obowiązków. Jest odpowiedzialny za wykonywanie testów, a także za dostarczanie TestScenario
do ReportGenerator
. Umieść te dwa obowiązki w dwóch osobnych klasach, a twoja cykliczna zależność znowu zniknie.
Może być więcej wariantów podejścia do twojego problemu, ale mam nadzieję, że zrozumiesz ogólny pomysł: twoim głównym problemem są klasy z za dużo obowiązków . Rozwiąż ten problem, a automatycznie pozbędziesz się cyklicznej zależności.
Komentarze
- Dziękuję za odpowiedź, właściwie potrzebuję wszystkich informacji w TestScenario aby móc wygenerować raport na końcu 🙁
- @ sabrina2020: a co przeszkadza Ci umieścić wszystkie te informacje w
ReportData
?Możesz rozważyć edycję swojego pytania i nieco bardziej szczegółowe wyjaśnienie, co dzieje się wewnątrzsaveReport
. - Właściwie mój scenariusz testowy zawiera listę testów i chcę wszystko informacje w pliku raportu xml, aby w tym przypadku ReportData zawierało wszystko. Zmienię odpowiedź, aby uzyskać więcej informacji, dzięki!
- +1: Miałeś mnie pod adresem
interfaces
. - @ sabrina2020: Dodałem dwa różne podejścia do mojej odpowiedzi, wybierz to, które najlepiej odpowiada Twoim potrzebom.
Odpowiedź
Używając interfejsów, możesz rozwiązać zależność cykliczną.
Obecny projekt:
Proponowany projekt:
W proponowanym projekcie betonu klasy nie zależą od innych konkretnych klas, ale tylko od abstrakcji (interfejsów).
Ważne:
Musisz użyć wybranego wzorca kreacyjnego (może to być fabryka), aby uniknąć wykonania new
dowolnych konkretnych klas wewnątrz dowolnej innej konkretnej klasy lub wywołujące getInstance()
. Tylko fabryka będzie miała zależności od konkretnych klas. Twoja Main
klasa może służyć jako fabryka, jeśli uważasz, że dedykowana fabryka byłaby przesadą. Na przykład możesz wstrzyknąć ReportGenerator
do TestExecuter
zamiast wywoływać getInstance()
lub new
.
Odpowiedź
Od TestExecutor
używa tylko ReportGenerator
wewnętrznie, powinieneś być w stanie zdefiniować dla niego interfejs i odnieść się do interfejsu w TestScenario
. Wtedy TestExecutor
zależy od ReportGenerator
, ReportGenerator
zależy od TestScenario
i TestScenario
zależy od ITestExecutor
, który nie zależy od niczego.
Idealnie „Zdefiniowałbym interfejsy dla wszystkich twoich klas i wyrażał za ich pośrednictwem zależności, ale jest to najmniejsza zmiana, która rozwiąże twój problem.
File(filename).write(Report); Report = XMLResult(ResultData).toString(); ResultData = TestSuite(SingleTestLogic).execute(TestDataIterator(TestDetailsList))