Bagi saya, ketika memulai, intinya hanya menjadi jelas ketika Anda berhenti memandanginya sebagai hal yang membuat kode Anda lebih mudah / lebih cepat untuk ditulis - ini bukan tujuan mereka. Mereka memiliki sejumlah kegunaan:
(Ini akan kehilangan analogi pizza, karena tidak mudah memvisualisasikan penggunaan ini)
Katakanlah Anda membuat game sederhana di layar dan ia akan memiliki makhluk yang berinteraksi dengan Anda.
A: Mereka dapat membuat kode Anda lebih mudah dipelihara di masa depan dengan memperkenalkan kopling longgar antara ujung depan dan implementasi ujung belakang Anda.
Anda dapat menulis ini sebagai permulaan, karena hanya akan ada troll:
// This is our back-end implementation of a troll
class Troll
{
void Walk(int distance)
{
//Implementation here
}
}
Paling depan:
function SpawnCreature()
{
Troll aTroll = new Troll();
aTroll.Walk(1);
}
Dua minggu kemudian, pemasaran memutuskan Anda juga membutuhkan Orc, ketika mereka membaca tentang mereka di twitter, jadi Anda harus melakukan sesuatu seperti:
class Orc
{
void Walk(int distance)
{
//Implementation (orcs are faster than trolls)
}
}
Paling depan:
void SpawnCreature(creatureType)
{
switch(creatureType)
{
case Orc:
Orc anOrc = new Orc();
anORc.Walk();
case Troll:
Troll aTroll = new Troll();
aTroll.Walk();
}
}
Dan Anda dapat melihat bagaimana ini mulai menjadi berantakan. Anda dapat menggunakan antarmuka di sini sehingga ujung depan Anda akan ditulis satu kali dan (inilah bagian penting) yang diuji, dan Anda kemudian dapat menyambungkan item ujung belakang lebih lanjut sesuai kebutuhan:
interface ICreature
{
void Walk(int distance)
}
public class Troll : ICreature
public class Orc : ICreature
//etc
Ujung depan adalah:
void SpawnCreature(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
creature.Walk();
}
Ujung depan sekarang hanya peduli dengan antarmuka ICreature - tidak peduli tentang implementasi internal troll atau orc, tetapi hanya pada kenyataan bahwa mereka menerapkan ICreature.
Poin penting yang perlu diperhatikan ketika melihat ini dari sudut pandang ini adalah Anda juga bisa dengan mudah menggunakan kelas makhluk abstrak, dan dari perspektif ini, ini memiliki efek yang sama .
Dan Anda dapat mengekstrak ciptaan ke pabrik:
public class CreatureFactory {
public ICreature GetCreature(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
return creature;
}
}
Dan ujung depan kita kemudian akan menjadi:
CreatureFactory _factory;
void SpawnCreature(creatureType)
{
ICreature creature = _factory.GetCreature(creatureType);
creature.Walk();
}
Ujung depan sekarang bahkan tidak harus memiliki referensi ke perpustakaan tempat Troll dan Orc diimplementasikan (asalkan pabrik berada di perpustakaan terpisah) - tidak perlu tahu apa-apa tentang mereka sama sekali.
B: Katakanlah Anda memiliki fungsionalitas yang hanya dimiliki oleh beberapa makhluk dalam struktur data Anda yang homogen , misalnya
interface ICanTurnToStone
{
void TurnToStone();
}
public class Troll: ICreature, ICanTurnToStone
Ujung depan bisa jadi:
void SpawnCreatureInSunlight(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
creature.Walk();
if (creature is ICanTurnToStone)
{
(ICanTurnToStone)creature.TurnToStone();
}
}
C: Penggunaan untuk injeksi ketergantungan
Sebagian besar kerangka kerja ketergantungan injeksi lebih mudah untuk dikerjakan ketika ada hubungan yang sangat longgar antara kode ujung depan dan implementasi ujung belakang. Jika kami mengambil contoh pabrik kami di atas dan meminta pabrik kami mengimplementasikan antarmuka:
public interface ICreatureFactory {
ICreature GetCreature(string creatureType);
}
Ujung depan kami kemudian dapat menyuntikkan ini (misalnya, pengontrol API MVC) melalui konstruktor (biasanya):
public class CreatureController : Controller {
private readonly ICreatureFactory _factory;
public CreatureController(ICreatureFactory factory) {
_factory = factory;
}
public HttpResponseMessage TurnToStone(string creatureType) {
ICreature creature = _factory.GetCreature(creatureType);
creature.TurnToStone();
return Request.CreateResponse(HttpStatusCode.OK);
}
}
Dengan kerangka kerja DI kami (mis. Ninject atau Autofac), kami dapat mengaturnya sehingga pada saat runtime instance CreatureFactory akan dibuat setiap kali ICreatureFactory diperlukan dalam sebuah konstruktor - ini membuat kode kami bagus dan sederhana.
Ini juga berarti bahwa ketika kita menulis unit test untuk controller kita, kita dapat memberikan ICreatureFactory yang diejek (mis. Jika implementasi konkret membutuhkan akses DB, kita tidak ingin unit test kita bergantung pada hal itu) dan dengan mudah menguji kode pada controller kita. .
D: Ada kegunaan lain misalnya Anda memiliki dua proyek A dan B yang karena alasan 'warisan' tidak terstruktur dengan baik, dan A memiliki referensi ke B.
Anda kemudian menemukan fungsionalitas dalam B yang perlu memanggil metode yang sudah dalam A. Anda tidak dapat melakukannya menggunakan implementasi konkret saat Anda mendapatkan referensi melingkar.
Anda dapat memiliki antarmuka yang dinyatakan dalam B yang diterapkan oleh kelas di A. Metode Anda dalam B dapat diberikan instance kelas yang mengimplementasikan antarmuka tanpa masalah, meskipun objek konkret adalah tipe dalam A.