Bagaimana cara mengatasi ketergantungan sirkuler?

33

Saya memiliki tiga kelas yang saling bergantung satu sama lain:

TestExecuter menjalankan permintaan dari TestScenario dan menyimpan file laporan menggunakan kelas ReportGenerator. Begitu:

  • TestExecuter bergantung pada ReportGenerator untuk menghasilkan laporan
  • ReportGenerator tergantung pada TestScenario dan pada parameter yang ditetapkan dari TestExecuter.
  • TestScenario tergantung pada TestExecuter.

Tidak dapat menemukan cara menghapus dependensi mereka.

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: sebagai tanggapan atas jawaban, detail lebih lanjut tentang kelas TestScenario saya:

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 */
    }

Contoh file xml yang akan dihasilkan jika ada skenario yang berisi dua tes:

<testScenario name="scenario1">
   <test name="test1">
     <result>false</result>
   </test>
   <test name="test1">
     <result>true</result>
   </test>
</testScenario >
sabrina2020
sumber
Coba identifikasi objek Anda yang mundur menanyakan apa (objek) yang Anda butuhkan untuk yang sebelumnya berfungsi - misalnya:File(filename).write(Report); Report = XMLResult(ResultData).toString(); ResultData = TestSuite(SingleTestLogic).execute(TestDataIterator(TestDetailsList))
gemetaran

Jawaban:

35

Secara teknis, Anda dapat menyelesaikan ketergantungan siklik dengan menggunakan antarmuka, seperti yang ditunjukkan pada jawaban lainnya. Namun, saya sarankan untuk memikirkan kembali desain Anda. Saya pikir bukan tidak mungkin Anda dapat menghindari kebutuhan untuk antarmuka tambahan sepenuhnya, sementara desain Anda menjadi lebih sederhana.

Saya kira itu tidak perlu untuk ReportGeneratorbergantung pada secara TestScenariolangsung. TestScenariotampaknya memiliki dua tanggung jawab: digunakan untuk pelaksanaan tes, dan berfungsi juga sebagai wadah untuk hasilnya. Ini merupakan pelanggaran terhadap SRP. Menariknya, dengan menyelesaikan pelanggaran itu, Anda akan menyingkirkan ketergantungan siklik juga.

Jadi alih-alih membiarkan pembuat laporan mengambil data dari skenario pengujian, berikan data secara eksplisit dengan menggunakan beberapa objek nilai. Itu artinya, ganti

   reportGenerator.setTestScenario(ts); 

oleh beberapa kode seperti

reportGenerator.insertDataToDisplay(ts.getReportData()); 

Metode getReportDataharus memiliki tipe pengembalian seperti ReportData, objek nilai yang berfungsi sebagai wadah untuk data yang akan ditampilkan dalam laporan. insertDataToDisplayadalah metode yang mengharapkan objek dengan tipe seperti itu.

Dengan cara ini, ReportGeneratordan TestScenariokeduanya akan bergantung ReportData, yang tidak bergantung pada yang lain, dan dua kelas pertama tidak lagi saling bergantung.

Sebagai pendekatan kedua: untuk menyelesaikan pelanggaran SRP, biarkan TestScenariobertanggung jawab untuk memegang hasil dari eksekusi tes, tetapi tidak untuk memanggil pelaksana tes. Pertimbangkan untuk menata ulang kode sehingga skenario pengujian tidak mengakses pelaksana tes, tetapi pelaksana pengujian dimulai dari luar dan menulis hasilnya kembali ke TestScenarioobjek. Dalam contoh yang Anda tunjukkan kepada kami, itu akan dimungkinkan dengan membuat akses ke LinkedList<Test>dalam TestScenariopublik, dan dengan memindahkan executemetode dari TestScenarioke tempat lain, mungkin langsung ke TestExecuter, mungkin ke kelas baru TestScenarioExecuter.

Dengan cara itu, TestExecuterakan bergantung pada TestScenariodan ReportGenerator, ReportGeneratorakan bergantung pada TestScenario, juga, tetapi tidak TestScenarioakan bergantung pada yang lain.

Dan akhirnya, pendekatan ketiga: TestExecutermemiliki terlalu banyak tanggung jawab juga. Ini bertanggung jawab untuk melaksanakan tes serta untuk menyediakan TestScenarioa ReportGenerator. Masukkan dua tanggung jawab ini ke dalam dua kelas yang terpisah, dan ketergantungan siklik Anda akan hilang lagi.

Mungkin ada lebih banyak varian untuk mendekati masalah Anda, tetapi saya harap Anda mendapatkan ide umum: masalah inti Anda adalah kelas dengan tanggung jawab terlalu banyak . Selesaikan masalah itu, dan Anda akan menyingkirkan ketergantungan siklik secara otomatis.

Doc Brown
sumber
Terima kasih atas jawaban Anda, sebenarnya saya membutuhkan semua informasi di TestScenario untuk dapat menghasilkan laporan saya di akhir :(
sabrina2020
@ sabrina2020: dan apa yang menghalangi Anda untuk memasukkan semua informasi itu ReportData? Anda dapat mempertimbangkan untuk mengedit pertanyaan Anda dan menjelaskan sedikit lebih rinci apa yang terjadi di dalamnya saveReport.
Doc Brown
Sebenarnya TestScenario saya berisi daftar Test dan saya ingin semua informasi dalam file xml laporan sehingga ReportData akan memiliki semuanya dalam hal ini, saya akan mengedit jawaban saya untuk lebih jelasnya, terima kasih!
sabrina2020
1
+1: Anda memiliki saya di interfaces.
Joel Etherton
@ sabrina2020: Saya menambahkan dua pendekatan berbeda untuk jawaban saya, pilih satu yang paling sesuai dengan kebutuhan Anda.
Doc Brown
8

Dengan menggunakan antarmuka Anda dapat menyelesaikan ketergantungan sirkular.

Desain saat ini:

masukkan deskripsi gambar di sini

Desain yang diusulkan:

masukkan deskripsi gambar di sini

Dalam desain yang diusulkan, kelas beton tidak bergantung pada kelas beton lain tetapi hanya pada abstraksi (antarmuka).

Penting:

Anda harus menggunakan pola kreasi pilihan Anda (mungkin pabrik) untuk menghindari perfusi newkelas beton apa pun di dalam kelas atau panggilan beton lainnya getInstance(). Hanya pabrik yang akan memiliki ketergantungan pada kelas beton. MainKelas Anda dapat berfungsi sebagai pabrik jika Anda berpikir pabrik yang berdedikasi akan berlebihan. Misalnya Anda dapat menyuntikkan ReportGeneratorke dalam TestExecuteralih-alih menelepon getInstance()atau new.

Tulains Córdova
sumber
3

Karena TestExecutorhanya menggunakan secara ReportGeneratorinternal, Anda harus dapat mendefinisikan antarmuka untuk itu, dan merujuk ke antarmuka di TestScenario. Maka TestExecutortergantung pada ReportGenerator, ReportGeneratortergantung pada TestScenario, dan TestScenariotergantung pada ITestExecutor, yang tidak tergantung pada apa pun.

Idealnya Anda akan mendefinisikan antarmuka untuk semua kelas Anda dan mengekspresikan dependensi melalui mereka, tetapi ini adalah perubahan terkecil yang akan menyelesaikan masalah Anda.

TMN
sumber