Injeksi Ketergantungan & pola Desain Tunggal

92

Bagaimana kita mengidentifikasi kapan harus menggunakan injeksi ketergantungan atau pola tunggal. Saya telah membaca di banyak situs web di mana mereka mengatakan "Gunakan injeksi Ketergantungan atas pola tunggal". Tetapi saya tidak yakin apakah saya setuju sepenuhnya dengan mereka. Untuk proyek skala kecil atau menengah saya, saya pasti melihat penggunaan pola tunggal secara langsung.

Misalnya Logger. Saya bisa menggunakan Logger.GetInstance().Log(...) Tapi, alih-alih ini, mengapa saya perlu menyuntikkan setiap kelas yang saya buat, dengan contoh logger ?.

SysAdmin
sumber

Jawaban:

67

Jika Anda ingin memverifikasi apa yang dicatat dalam pengujian, Anda memerlukan injeksi ketergantungan. Selain itu, logger jarang sekali tunggal - umumnya Anda memiliki logger untuk setiap kelas Anda.

Tonton presentasi ini tentang desain berorientasi objek untuk kemudahan pengujian dan Anda akan melihat mengapa lajang itu buruk.

Masalah dengan lajang adalah mereka mewakili keadaan global yang sulit diprediksi, terutama dalam ujian.

Perlu diingat bahwa sebuah objek dapat menjadi tunggal de facto tetapi masih dapat diperoleh melalui injeksi ketergantungan, bukan melalui Singleton.getInstance().

Saya hanya mencantumkan beberapa poin penting yang dibuat oleh Misko Hevery dalam presentasinya. Setelah menontonnya, Anda akan mendapatkan perspektif penuh tentang mengapa lebih baik memiliki objek yang menentukan apa saja dependensinya, tetapi tidak menentukan cara membuatnya .

Bozho
sumber
89

Orang lajang seperti komunisme: keduanya terdengar hebat di atas kertas, tetapi pada praktiknya meledak dengan masalah.

Pola singleton memberikan penekanan yang tidak proporsional pada kemudahan mengakses objek. Ini sepenuhnya menghindari konteks dengan mengharuskan setiap konsumen menggunakan objek dengan cakupan AppDomain, tanpa meninggalkan opsi untuk berbagai implementasi. Ini menyematkan pengetahuan infrastruktur di kelas Anda (panggilan ke GetInstance()) sambil menambahkan tepat nol kekuatan ekspresif. Ini sebenarnya mengurangi kekuatan ekspresif Anda, karena Anda tidak dapat mengubah implementasi yang digunakan oleh satu kelas tanpa mengubahnya untuk semuanya . Anda tidak bisa menambahkan fungsionalitas satu kali.

Juga, ketika kelas Foobergantung pada Logger.GetInstance(), Foosecara efektif menyembunyikan ketergantungannya dari konsumen. Ini berarti Anda tidak dapat sepenuhnya memahami Fooatau menggunakannya dengan percaya diri kecuali Anda membaca sumbernya dan menemukan fakta bahwa itu bergantung Logger. Jika Anda tidak memiliki sumbernya, itu membatasi seberapa baik Anda dapat memahami dan secara efektif menggunakan kode yang Anda andalkan.

Pola tunggal, seperti yang diterapkan dengan properti / metode statis, tidak lebih dari sekadar peretasan dalam mengimplementasikan infrastruktur. Ini membatasi Anda dalam banyak cara sambil tidak menawarkan manfaat yang terlihat atas alternatifnya. Anda dapat menggunakannya sesuka Anda, tetapi karena ada alternatif yang layak yang mempromosikan desain yang lebih baik, ini tidak boleh menjadi praktik yang direkomendasikan.

Bryan Watts
sumber
9
@BryanWatts semua yang mengatakan, Singletons masih jauh lebih cepat dan lebih sedikit rawan kesalahan dalam aplikasi skala menengah normal. Biasanya saya tidak memiliki lebih dari satu kemungkinan implementasi untuk Logger (kustom) jadi mengapa ** harus: 1. Membuat antarmuka untuk itu dan mengubahnya setiap kali saya perlu menambah / mengubah anggota publik 2. Menjaga a DI konfigurasi 3. Menyembunyikan fakta bahwa ada satu objek jenis ini di seluruh sistem 4. Ikat diri saya ke fungsionalitas ketat yang disebabkan oleh Separation Of Concerns yang prematur.
Uri Abramson
@UriAbramson: Sepertinya Anda sudah membuat keputusan tentang pengorbanan yang Anda sukai, jadi saya tidak akan mencoba meyakinkan Anda.
Bryan Watts
@BryanWatts Bukan itu masalahnya, mungkin komentar saya agak terlalu kasar tetapi sebenarnya sangat menarik bagi saya untuk mendengar apa yang Anda katakan tentang poin yang saya kemukakan ...
Uri Abramson
2
@UriAbramson: Cukup adil. Apakah Anda setidaknya setuju bahwa pertukaran implementasi untuk isolasi selama pengujian itu penting?
Bryan Watts
6
Penggunaan lajang dan injeksi ketergantungan tidak eksklusif satu sama lain. Sebuah singleton dapat mengimplementasikan sebuah antarmuka, oleh karena itu dapat digunakan untuk memenuhi ketergantungan pada kelas lain. Fakta bahwa ini adalah singleton tidak memaksa setiap konsumen untuk mendapatkan referensi melalui metode / properti "GetInstance".
Oliver
19

Yang lain telah menjelaskan dengan sangat baik masalah dengan lajang secara umum. Saya hanya ingin menambahkan catatan tentang kasus spesifik Logger. Saya setuju dengan Anda bahwa biasanya bukan masalah untuk mengakses Logger (atau root logger, tepatnya) sebagai seorang tunggal, melalui statik getInstance()atau getRootLogger()metode. (kecuali jika Anda ingin melihat apa yang dicatat oleh kelas yang Anda uji - tetapi dalam pengalaman saya, saya hampir tidak dapat mengingat kasus seperti itu di mana hal ini diperlukan. Kemudian lagi, bagi orang lain ini mungkin menjadi masalah yang lebih mendesak).

Biasanya IMO logger tunggal tidak perlu dikhawatirkan, karena tidak berisi status apa pun yang relevan dengan kelas yang Anda uji. Artinya, status logger (dan kemungkinan perubahannya) tidak berpengaruh apa pun pada status kelas yang diuji. Jadi itu tidak membuat pengujian unit Anda lebih sulit.

Alternatifnya adalah memasukkan logger melalui konstruktor, ke (hampir) setiap kelas dalam aplikasi Anda. Untuk konsistensi antarmuka, itu harus dimasukkan bahkan jika kelas yang dimaksud tidak mencatat apa pun saat ini - alternatifnya adalah ketika Anda menemukan di beberapa titik bahwa sekarang Anda perlu mencatat sesuatu dari kelas ini, Anda memerlukan pencatat, jadi Anda perlu menambahkan parameter konstruktor untuk DI, melanggar semua kode klien. Saya tidak menyukai kedua opsi ini, dan saya merasa bahwa menggunakan DI untuk logging hanya akan mempersulit hidup saya untuk mematuhi aturan teoretis, tanpa manfaat konkret.

Jadi intinya adalah: kelas yang digunakan (hampir) secara universal, tetapi tidak berisi status yang relevan dengan aplikasi Anda, dapat dengan aman diimplementasikan sebagai Singleton .

Péter Török
sumber
1
Meski begitu, seorang lajang bisa menjadi sakit. Jika Anda menyadari bahwa logger Anda memerlukan beberapa parameter tambahan untuk beberapa kasus, Anda dapat membuat metode baru (dan membiarkan logger menjadi lebih jelek), atau menghancurkan semua konsumen logger, meskipun mereka tidak peduli.
kyoryu
4
@kyoryu, saya berbicara tentang kasus "biasa", yang IMHO menyiratkan menggunakan kerangka kerja logging standar (de facto). (Yang biasanya dapat dikonfigurasi melalui file property / XML btw.) Tentu saja ada pengecualian - seperti biasa. Jika saya tahu aplikasi saya luar biasa dalam hal ini, saya memang tidak akan menggunakan Singleton. Tapi rekayasa berlebihan mengatakan "ini mungkin berguna suatu saat" hampir selalu merupakan upaya yang sia-sia.
Péter Török
Jika Anda sudah menggunakan DI, itu bukan rekayasa ekstra. (BTW, saya tidak setuju dengan Anda, saya upvote). Banyak logger memerlukan beberapa informasi "kategori" atau semacamnya, dan menambahkan parameter tambahan dapat menyebabkan kesulitan. Menyembunyikannya di balik antarmuka dapat membantu menjaga kode konsumsi tetap bersih, dan dapat dengan mudah beralih ke kerangka kerja logging yang berbeda.
kyoryu
1
@kyoryu, maaf atas anggapan yang salah. Saya melihat suara negatif dan komentar, jadi saya menghubungkan titik-titiknya - dengan cara yang salah, ternyata :-( Saya tidak pernah mengalami kebutuhan untuk beralih ke kerangka kerja logging yang berbeda, tetapi sekali lagi, saya mengerti itu mungkin sah perhatian dalam beberapa proyek.
Péter Török
5
Koreksi saya jika saya salah, tetapi singletonnya adalah untuk LogFactory, bukan logger. Ditambah LogFactory yang kemungkinan besar adalah apache commons atau fasad logging Slf4j. Jadi, mengganti implementasi logging tidak merepotkan. Bukankah rasa sakit yang sebenarnya dengan menggunakan DI untuk menyuntikkan LogFactory adalah fakta bahwa Anda harus membuka applicationContext sekarang untuk membuat setiap instance di aplikasi Anda?
HDave
9

Ini sebagian besar, tetapi tidak seluruhnya tentang tes. Singlet populer karena mudah dikonsumsi, tetapi ada sejumlah kerugian bagi lajang.

  • Sulit untuk diuji. Artinya bagaimana cara memastikan logger melakukan hal yang benar.
  • Sulit untuk diuji. Artinya jika saya menguji kode yang menggunakan logger, tetapi itu bukan fokus pengujian saya, saya masih perlu memastikan env pengujian saya mendukung logger
  • Terkadang Anda tidak menginginkan singlton, tetapi lebih fleksibel

DI memberi Anda konsumsi kelas dependen yang mudah - cukup taruh di argumen konstruktor, dan sistem menyediakannya untuk Anda - sambil memberi Anda fleksibilitas pengujian dan konstruksi.

Scott Weinstein
sumber
Tidak juga. Ini tentang keadaan bersama yang bisa berubah dan ketergantungan statis yang membuat hal-hal menyakitkan dalam jangka panjang. Pengujian hanyalah contoh yang jelas, dan seringkali yang paling menyakitkan.
kyoryu
2

Tentang satu-satunya saat Anda harus menggunakan Singleton daripada Dependency Injection adalah jika Singleton mewakili nilai yang tidak dapat diubah, seperti List.Empty atau sejenisnya (dengan asumsi daftar yang tidak dapat diubah).

Pemeriksaan usus untuk Singleton harus "apakah saya akan baik-baik saja jika ini adalah variabel global, bukan Singleton?" Jika tidak, Anda menggunakan pola Singleton untuk menjelaskan variabel global, dan harus mempertimbangkan pendekatan yang berbeda.

kyoryu
sumber
1

Baru saja memeriksa artikel Monostate - ini adalah alternatif bagus untuk Singleton, tetapi memiliki beberapa properti yang aneh:

class Mono{
    public static $db;
    public function setDb($db){
       self::$db = $db;
    }

}

class Mapper extends Mono{
    //mapping procedure
    return $Entity;

    public function save($Entity);//requires database connection to be set
}

class Entity{
public function save(){
    $Mapper = new Mapper();
    $Mapper->save($this);//has same static reference to database class     
}

$Mapper = new Mapper();
$Mapper->setDb($db);

$User = $Mapper->find(1);
$User->save();

Bukankah ini menakutkan - karena Pemeta benar-benar bergantung pada koneksi database untuk melakukan save () - tetapi jika pembuat peta lain telah dibuat sebelumnya - ia dapat melewati langkah ini dalam memperoleh dependensinya. Meski rapi, tapi juga agak berantakan bukan?

sunwukung
sumber