Pindahkan basis kode secara bertahap ke wadah injeksi ketergantungan

9

Saya memiliki basis kode besar dengan banyak lajang "anti-pola", kelas utilitas dengan metode statis, dan kelas membuat ketergantungan mereka sendiri menggunakan newkata kunci. Itu membuat kode sangat sulit untuk diuji.

Saya ingin secara bertahap memigrasi kode ke wadah injeksi ketergantungan (dalam kasus saya ini Guice, karena ini adalah GWTproyek). Dari pemahaman saya tentang injeksi ketergantungan, semuanya atau tidak sama sekali. Entah semua kelas dikelola oleh Spring / Guice atau tidak ada. Karena basis kode besar, saya tidak dapat mengubah kode pada malam hari. Jadi saya perlu cara untuk melakukannya secara bertahap.

Masalahnya adalah ketika saya mulai dengan kelas yang perlu disuntikkan ke kelas lain, saya tidak bisa menggunakan yang sederhana @Injectdi kelas-kelas itu, karena kelas-kelas itu belum dikelola oleh wadah. Jadi ini menciptakan rantai panjang hingga kelas "atas" yang tidak disuntikkan di mana pun.

Satu-satunya cara saya melihat adalah membuat Injectorkonteks / aplikasi tersedia secara global melalui singleton untuk saat ini, sehingga kelas-kelas lain dapat memperoleh kacang yang dikelola darinya. Tetapi itu bertentangan dengan ide penting untuk tidak mengungkapkan composition rootke aplikasi.

Pendekatan lain adalah bottom-up: untuk memulai dengan kelas "tingkat tinggi", memasukkan mereka ke dalam wadah ketergantungan injeksi dan perlahan-lahan pindah ke kelas "lebih kecil". Tetapi kemudian saya harus menunggu lama, karena saya dapat menguji kelas-kelas yang lebih kecil yang masih tergantung pada global / statika.

Apa yang akan menjadi cara untuk mencapai migrasi bertahap seperti itu?

PS Pertanyaan Pendekatan bertahap untuk injeksi ketergantungan serupa dalam judul, tetapi tidak menjawab pertanyaan saya.

damluar
sumber
1
Apakah Anda ingin langsung dari memiliki kelas kopling keras untuk menggunakan wadah injeksi ketergantungan? Pertama-tama Anda harus membuat beberapa refactoring untuk memisahkan kelas, menggunakan antarmuka bahkan sebelum berpikir untuk menikah dengan kerangka kerja atau alat injeksi ketergantungan.
Tulains Córdova
Cukup adil. Jadi saya memiliki beberapa kelas utilitas A yang digunakan di 30 kelas lainnya (salah satunya adalah kelas B), membuat mereka tidak dapat dites. Saya berpikir untuk menjadikan kelas A sebagai ketergantungan konstruktor dari kelas B. Saya harus mengubah semua panggilan ke konstruktor dan memasukkan A ke dalam. Tapi dari mana saya harus mendapatkannya?
damluar
Jika B adalah unit terkecil yang dapat Anda gunakan DI untuk memulai, maka saya tidak melihat ada salahnya membangun A di mana pun Anda membangun B untuk saat ini - asalkan A tidak memiliki dependensi. Setelah bola bergulir, Anda dapat kembali dan refactor.
Andy Hunt
@damluar Sebuah pabrik untuk memulai?
Tulains Córdova
1
@damluar kelas tidak menjadi tidak dapat diuji hanya karena bergantung pada kelas utilitas. Ini berarti unit Anda sedikit lebih besar dari yang Anda inginkan. Jika Anda dapat menguji kelas utilitas Anda sendiri, maka Anda dapat menggunakannya dalam tes untuk semua 30 kelas lainnya tanpa khawatir. Baca blog Martin Fowlers tentang pengujian unit.
gbjbaanb

Jawaban:

2

Maaf C#adalah bahasa pilihan saya, saya bisa membaca Javatetapi mungkin akan membantai sintaks mencoba untuk menulisnya ... Konsep yang sama berlaku antara C#dan Javameskipun, jadi mudah-mudahan ini akan menunjukkan langkah-langkah bagaimana Anda secara bertahap dapat memindahkan basis kode Anda menjadi lebih dapat diuji.

Diberikan:

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = new Foo();
        foo.DoStuff();
    }
}

public class Foo
{

    public void DoStuff()
    {
        Bar bar = new Bar();
        bar.DoSomethingElse();
    }

}

public class Bar
{
    public void DoSomethingElse();
}

dapat dengan mudah di refactored untuk menggunakan DI, tanpa menggunakan wadah IOC - dan Anda bahkan berpotensi memecahnya menjadi beberapa langkah:

(potensial) Langkah pertama - menerima dependensi tetapi tidak ada perubahan kode panggilan (UI):

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = new Foo();
        foo.DoStuff();
    }
}

public class Foo
{

    private IBar _iBar;

    // Leaving this constructor for step one, 
    // so that calling code can stay as is without impact
    public Foo()
    {
        _iBar = new Bar();
    }

    // simply because we now have a constructor that take's in the implementation of the IBar dependency, 
    // Foo can be much more easily tested.
    public Foo(IBar iBar)
    {
        _iBar = iBar;
    }

    public void DoStuff()
    {
        _iBar.DoSomethingElse();
    }

}

public interface IBar
{
    void DoSomethingElse();
}

public class Bar
{
    public void DoSomethingElse();
}

Refactor 2 (atau refactor pertama jika menerapkan wadah IOC dan segera mengubah kode panggilan):

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = null // use your IOC container to resolve the dependency
        foo.DoStuff();
    }
}

public class Foo
{

    private IBar _iBar;

    // note we have now dropped the "default constructor" - this is now a breaking change as far as the UI is concerned.
    // You can either do this all at once (do only step 2) or in a gradual manner (step 1, then step 2)

    // Only entry into class - requires passing in of class dependencies (IBar)
    public Foo(IBar iBar)
    {
        _iBar = iBar;
    }

    public void DoStuff()
    {
        _iBar.DoSomethingElse();
    }

}

Langkah 2 secara teknis dapat dilakukan dengan sendirinya - tetapi (berpotensi) akan menjadi lebih banyak pekerjaan - tergantung pada berapa banyak kelas yang saat ini "baru" fungsi yang Anda cari ke DI.

Pertimbangkan untuk pergi dengan langkah 1 -> langkah 2 rute - Anda dapat membuat tes unit untuk Foo, terlepas dari Bar. Sedangkan sebelum refactor langkah 1 itu tidak mudah dicapai tanpa menggunakan implementasi sebenarnya dari kedua kelas. Melakukan langkah 1 -> langkah 2 (daripada langkah 2 dengan segera) akan memungkinkan perubahan yang lebih kecil dari waktu ke waktu, dan Anda akan sudah memulai uji coba untuk memastikan bahwa refactor Anda berfungsi tanpa konsekuensi.

Kritner
sumber
0

Konsepnya sama apakah Anda menggunakan Java, PHP, atau bahkan C #. Pertanyaan ini ditangani dengan cukup baik oleh Gemma Anible di video YouTube ini:

https://www.youtube.com/watch?v=Jccq_Ti8Lck (PHP, maaf!)

Anda mengganti kode yang tidak dapat diuji dengan "fasad" (karena tidak ada istilah yang lebih baik) yang memanggil kode baru yang dapat diuji. Kemudian secara bertahap Anda dapat mengganti panggilan lama dengan layanan yang disuntikkan. Saya telah melakukan ini di masa lalu dan itu bekerja dengan cukup baik.

Kevin G.
sumber