konteks ambient vs injeksi konstruktor

9

Saya sudah banyak kelas inti yang memerlukan ISessionContext dari database, ILogManager untuk log dan IService digunakan untuk berkomunikasi dengan layanan lain. Saya ingin menggunakan injeksi dependensi untuk kelas ini yang digunakan oleh semua kelas inti.

Saya sudah dua kemungkinan implementasi. Kelas inti yang menerima IAmbientContext dengan semua tiga kelas atau menyuntikkan untuk semua kelas tiga kelas.

public interface ISessionContext 
{
    ...
}

public class MySessionContext: ISessionContext 
{
    ...
}

public interface ILogManager 
{

}

public class MyLogManager: ILogManager 
{
    ...
}

public interface IService 
{
    ...
}

public class MyService: IService
{
    ...
}

Solusi pertama:

public class AmbientContext
{
    private ISessionContext sessionContext;
    private ILogManager logManager;
    private IService service;

    public AmbientContext(ISessionContext sessionContext, ILogManager logManager, IService service)
    {
        this.sessionContext = sessionContext;
        this.logManager = logManager;
        this.service = service;
    }
}


public class MyCoreClass(AmbientContext ambientContext)
{
    ...
}

solusi kedua (tanpa ambientcontext)

public MyCoreClass(ISessionContext sessionContext, ILogManager logManager, IService service)
{
    ...
}

Apakah solusi terbaik dalam hal ini?

pengguna3401335
sumber
Apa yang " IServicedigunakan untuk berkomunikasi dengan layanan lain?" Jika IServicemewakili ketergantungan samar pada layanan lain maka itu terdengar seperti pencari lokasi dan seharusnya tidak ada. Kelas Anda harus bergantung pada antarmuka yang secara eksplisit menggambarkan apa yang akan dilakukan konsumen dengan mereka. Tidak ada kelas yang membutuhkan layanan untuk menyediakan akses ke layanan. Kelas membutuhkan ketergantungan yang melakukan sesuatu yang spesifik yang dibutuhkan kelas.
Scott Hannen

Jawaban:

4

"Terbaik" terlalu subyektif di sini. Seperti yang biasa terjadi pada keputusan semacam itu, ini merupakan pertukaran antara dua cara yang sama validnya untuk mencapai sesuatu.

Jika Anda membuat AmbientContextdan menyuntikkan itu ke banyak kelas, Anda berpotensi memberikan informasi lebih lanjut untuk masing-masing dari mereka perlu (misalnya kelas Foohanya dapat menggunakan ISessionContext, tapi sedang diberitahu tentang ILogManagerdan ISessionjuga).

Jika Anda mengirimkan masing-masing melalui parameter, maka Anda memberi tahu setiap kelas hanya tentang hal-hal yang perlu diketahui. Tetapi, jumlah parameter dapat dengan cepat bertambah dan Anda mungkin mendapati Anda memiliki terlalu banyak konstruktor dan metode dengan banyak parameter, yang diulang-ulang, yang dapat disederhanakan melalui kelas konteks.

Jadi ini adalah kasus menyeimbangkan keduanya dan memilih yang sesuai untuk keadaan Anda. Jika Anda hanya memiliki satu kelas dan tiga parameter, saya pribadi tidak akan peduli AmbientContext. Bagi saya, titik kritis kemungkinan adalah empat parameter. Tapi itu pendapat murni. Titik kritis Anda mungkin akan berbeda dengan saya, jadi ikuti dengan apa yang terasa benar bagi Anda.

David Arno
sumber
4

Terminologi dari pertanyaan tidak benar-benar cocok dengan kode contoh. Ini Ambient Contextadalah pola yang digunakan untuk mengambil ketergantungan dari kelas mana saja di modul apa pun semudah mungkin, tanpa mencemari setiap kelas untuk menerima antarmuka ketergantungan, tetapi tetap mempertahankan gagasan inversi kontrol. Ketergantungan seperti itu biasanya didedikasikan untuk logging, keamanan, manajemen sesi, transaksi, caching, audit sehingga untuk masalah lintas sektoral dalam aplikasi itu. Ini entah bagaimana menjengkelkan untuk menambahkan ILogging, ISecurity, ITimeProvideruntuk konstruktor dan sebagian besar waktu tidak semua kelas perlu semua pada saat yang sama, jadi saya mengerti kebutuhan Anda.

Bagaimana jika masa pakai ISessioninstance berbeda dengan yang ILoggerada? Mungkin instance ISession harus dibuat pada setiap permintaan dan ILogger sekali. Jadi memiliki semua dependensi ini diatur oleh satu objek yang bukan wadah itu sendiri tidak terlihat pilihan yang tepat karena semua masalah ini dengan manajemen seumur hidup dan lokalisasi dan lainnya yang dijelaskan dalam utas ini.

Pertanyaan IAmbientContextdi atas tidak menyelesaikan masalah tidak mencemari setiap konstruktor. Anda masih harus menggunakannya dalam tanda tangan konstruktor, tentu saja, hanya sekali saja kali ini.

Jadi cara termudah BUKAN menggunakan injeksi konstruktor atau mekanisme injeksi lainnya untuk menangani dependensi lintas bidang, tetapi menggunakan panggilan statis . Kami sebenarnya melihat pola ini cukup sering, diimplementasikan oleh kerangka itu sendiri. Periksa Thread.CurrentPrincipal yang merupakan properti statis yang mengembalikan implementasi IPrincipalantarmuka. Ini juga dapat diatur sehingga Anda dapat mengubah implementasinya jika Anda suka, sehingga Anda tidak dapat menggunakannya.

MyCore terlihat seperti sekarang

public class MyCoreClass
{
    public void BusinessFeature(string data)
    {
        LoggerContext.Current.Log(data);

        _repository.SaveProcessedData();

        SessionContext.Current.SetData(data);
        ...etc
    }
}

Pola ini dan kemungkinan implementasi telah dijelaskan secara rinci oleh Mark Seemann dalam artikel ini . Mungkin ada implementasi yang bergantung pada wadah IoC sendiri yang Anda gunakan.

Anda ingin menghindari AmbientContext.Current.Logger, AmbientContext.Current.Sessionuntuk alasan yang sama seperti yang dijelaskan di atas.

Tetapi Anda memiliki opsi lain untuk menyelesaikan masalah ini: gunakan dekorator, intersepsi dinamis jika wadah Anda memiliki kemampuan ini atau AOP. Ambient Context harus menjadi pilihan terakhir karena kliennya menyembunyikan ketergantungan mereka melaluinya. Saya masih akan menggunakan Ambient Context jika antarmuka itu benar-benar meniru dorongan saya untuk menggunakan dependensi statis seperti DateTime.Nowatau ConfigurationManager.AppSettingsdan kebutuhan ini meningkat cukup sering. Tetapi pada akhirnya injeksi konstruktor mungkin bukan ide yang buruk untuk mendapatkan dependensi di mana-mana.

Adrian Iftode
sumber
3

Saya akan menghindari AmbientContext.

Pertama, jika kelas tergantung, AmbientContextAnda tidak benar-benar tahu apa fungsinya. Anda harus melihat penggunaan ketergantungan itu untuk mencari tahu mana dependensi bersarang yang digunakannya. Anda juga tidak dapat melihat jumlah dependensi dan mengetahui apakah kelas Anda melakukan terlalu banyak karena salah satu dependensi itu sebenarnya mewakili beberapa dependensi bersarang.

Kedua, jika Anda menggunakannya untuk menghindari beberapa dependensi konstruktor, maka pendekatan ini akan mendorong pengembang lain (termasuk Anda) untuk menambahkan anggota baru ke kelas konteks sekitar. Kemudian masalah pertama diperparah.

Ketiga, mengejek ketergantungan pada AmbientContextlebih sulit karena Anda harus mencari tahu dalam setiap kasus apakah mengolok-olok semua anggotanya atau hanya yang Anda butuhkan, dan kemudian membuat tiruan yang mengembalikan mengejek itu (atau menguji ganda.) Itu membuat unit Anda lebih sulit untuk menulis, membaca, dan memelihara.

Keempat, tidak memiliki kohesi dan melanggar Prinsip Tanggung Jawab Tunggal. Itu sebabnya ia memiliki nama seperti "AmbientContext," karena ia melakukan banyak hal yang tidak berhubungan dan tidak ada cara untuk memberi nama sesuai dengan fungsinya.

Dan itu mungkin melanggar Prinsip Segregasi Antarmuka dengan memperkenalkan anggota antarmuka ke dalam kelas yang tidak membutuhkannya.

Scott Hannen
sumber
2

Yang kedua (tanpa pembungkus antarmuka)

Kecuali jika ada beberapa interaksi antara berbagai layanan yang perlu dienkapsulasi dalam kelas perantara, itu hanya mempersulit kode Anda dan membatasi fleksibilitas ketika Anda memperkenalkan 'antarmuka antarmuka'

Ewan
sumber