Anda harus menggunakan teknik untuk memecahkan masalah yang mereka pecahkan saat Anda memiliki masalah tersebut. Ketergantungan inversi dan injeksi tidak berbeda.
Inversi ketergantungan atau injeksi adalah teknik yang memungkinkan kode Anda untuk memutuskan implementasi metode apa yang dipanggil saat dijalankan. Ini memaksimalkan manfaat dari ikatan yang terlambat. Teknik ini diperlukan ketika bahasa tidak mendukung penggantian fungsi non-instance waktu berjalan. Misalnya, Java tidak memiliki mekanisme untuk mengganti panggilan ke metode statis dengan panggilan ke implementasi yang berbeda; kontras dengan Python, di mana semua yang diperlukan untuk mengganti pemanggilan fungsi adalah untuk mengikat nama ke fungsi yang berbeda (menugaskan kembali variabel yang memegang fungsi).
Mengapa kita ingin memvariasikan implementasi fungsi? Ada dua alasan utama:
- Kami ingin menggunakan palsu untuk tujuan pengujian. Ini memungkinkan kita untuk menguji kelas yang bergantung pada pengambilan basis data tanpa benar-benar terhubung ke database.
- Kita perlu mendukung banyak implementasi. Sebagai contoh, kita mungkin perlu mengatur sistem yang mendukung database MySQL dan PostgreSQL.
Anda mungkin juga ingin mencatat inversi wadah kontrol. Ini adalah teknik yang dimaksudkan untuk membantu Anda menghindari pohon konstruksi besar yang kusut yang terlihat seperti kodesemu ini:
thing5 = new MyThing5();
thing3 = new MyThing3(thing5, new MyThing10());
myApp = new MyApp(
new MyAppDependency1(thing5, thing3),
new MyAppDependency2(
new Thing1(),
new Thing2(new Thing3(thing5, new Thing4(thing5)))
),
...
new MyAppDependency15(thing5)
);
Ini memungkinkan Anda mendaftarkan kelas Anda dan kemudian melakukan konstruksi untuk Anda:
injector.register(Thing1); // Yes, you'd need some kind of actual class reference.
injector.register(Thing2);
...
injector.register(MyAppDepdency15);
injector.register(MyApp);
myApp = injector.create(MyApp); // The injector fills in all the construction parameters.
Perhatikan bahwa ini paling sederhana jika kelas yang didaftarkan dapat menjadi lajang tanpa kewarganegaraan .
Kata hati-hati
Perhatikan bahwa inversi dependensi seharusnya bukan jawaban masuk Anda untuk logika decoupling. Cari peluang untuk menggunakan parameterisasi sebagai gantinya. Pertimbangkan metode pseudocode ini misalnya:
myAverageAboveMin()
{
dbConn = new DbConnection("my connection string");
dbQuery = dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", 5);
dbQuery.Execute();
myData = dbQuery.getAll();
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
Kami dapat menggunakan inversi ketergantungan untuk beberapa bagian dari metode ini:
class MyQuerier
{
private _dbConn;
MyQueries(dbConn) { this._dbConn = dbConn; }
fetchAboveMin(min)
{
dbQuery = this._dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", min);
dbQuery.Execute();
return dbQuery.getAll();
}
}
class Averager
{
private _querier;
Averager(querier) { this._querier = querier; }
myAverageAboveMin(min)
{
myData = this._querier.fetchAboveMin(min);
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
Tetapi kita seharusnya tidak, setidaknya tidak sepenuhnya. Perhatikan bahwa kami telah membuat kelas stateful dengan Querier
. Sekarang memegang referensi ke beberapa objek koneksi dasarnya global. Ini menciptakan masalah seperti kesulitan dalam memahami keadaan keseluruhan program dan bagaimana kelas yang berbeda berkoordinasi satu sama lain. Perhatikan juga bahwa kita terpaksa memalsukan querier atau koneksi jika kita ingin menguji logika rata-rata. Lebih lanjut Pendekatan yang lebih baik adalah meningkatkan parameterisasi :
class MyQuerier
{
fetchAboveMin(dbConn, min)
{
dbQuery = dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", min);
dbQuery.Execute();
return dbQuery.getAll();
}
}
class Averager
{
averageData(myData)
{
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
class StuffDoer
{
private _querier;
private _averager;
StuffDoer(querier, averager)
{
this._querier = querier;
this._averager = averager;
}
myAverageAboveMin(dbConn, min)
{
myData = this._querier.fetchAboveMin(dbConn, min);
return this._averager.averageData(myData);
}
}
Dan koneksi akan dikelola pada tingkat yang lebih tinggi yang bertanggung jawab untuk operasi secara keseluruhan dan tahu apa yang harus dilakukan dengan output ini.
Sekarang kita dapat menguji logika rata-rata sepenuhnya terlepas dari kueri, dan terlebih lagi kita dapat menggunakannya dalam berbagai situasi yang lebih luas. Kita mungkin mempertanyakan apakah kita bahkan membutuhkan MyQuerier
dan Averager
objek, dan mungkin jawabannya adalah kita tidak melakukannya jika kita tidak berniat untuk pengujian unit StuffDoer
, dan bukan pengujian unit StuffDoer
akan sangat masuk akal karena begitu erat dengan database. Mungkin lebih masuk akal untuk membiarkan tes integrasi menutupinya. Dalam hal ini, kita mungkin bisa membuat fetchAboveMin
dan averageData
menjadi metode statis.