Berikut adalah skenario umum yang selalu membuat saya frustrasi untuk berurusan.
Saya memiliki model objek dengan objek induk. Induk berisi beberapa objek anak. Sesuatu seperti ini.
public class Zoo
{
public List<Animal> Animals { get; set; }
public bool IsDirty { get; set; }
}
Setiap objek anak memiliki berbagai data dan metode
public class Animal
{
public string Name { get; set; }
public int Age { get; set; }
public void MakeMess()
{
...
}
}
Ketika anak berubah, dalam hal ini ketika metode MakeMess dipanggil, beberapa nilai dalam orangtua perlu diperbarui. Katakanlah ketika ambang Hewan tertentu telah membuat kekacauan, maka bendera IsDirty Kebun Binatang perlu ditetapkan.
Ada beberapa cara untuk menangani skenario ini (yang saya tahu).
1) Setiap Hewan dapat memiliki referensi Kebun Binatang induk untuk mengkomunikasikan perubahan.
public class Animal
{
public Zoo Parent { get; set; }
...
public void MakeMess()
{
Parent.OnAnimalMadeMess();
}
}
Ini terasa seperti pilihan terburuk karena ia memasangkan Animal ke objek induknya. Bagaimana jika saya menginginkan binatang yang tinggal di rumah?
2) Pilihan lain, jika Anda menggunakan bahasa yang mendukung acara (seperti C #) adalah membuat orang tua berlangganan untuk mengubah acara.
public class Animal
{
public event OnMakeMessDelegate OnMakeMess;
public void MakeMess()
{
OnMakeMess();
}
}
public class Zoo
{
...
public void SubscribeToChanges()
{
foreach (var animal in Animals)
{
animal.OnMakeMess += new OnMakeMessDelegate(OnMakeMessHandler);
}
}
public void OnMakeMessHandler(object sender, EventArgs e)
{
...
}
}
Ini tampaknya berhasil tetapi dari pengalaman semakin sulit untuk dipertahankan. Jika Hewan pernah mengubah Kebun Binatang, Anda harus berhenti berlangganan acara di Kebun Binatang lama dan berlangganan di Kebun Binatang baru. Ini hanya menjadi lebih buruk karena pohon komposisi semakin dalam.
3) Pilihan lainnya adalah memindahkan logika ke induk.
public class Zoo
{
public void AnimalMakesMess(Animal animal)
{
...
}
}
Ini tampaknya sangat tidak wajar dan menyebabkan duplikasi logika. Misalnya, jika saya memiliki objek House yang tidak membagikan warisan pewarisan bersama dengan Zoo ..
public class House
{
// Now I have to duplicate this logic
public void AnimalMakesMess(Animal animal)
{
...
}
}
Saya belum menemukan strategi yang baik untuk menghadapi situasi ini. Apa lagi yang tersedia? Bagaimana ini bisa dibuat lebih sederhana?
sumber
Jawaban:
Saya harus berurusan dengan ini beberapa kali. Pertama kali saya menggunakan opsi 2 (peristiwa) dan seperti yang Anda katakan itu menjadi sangat rumit. Jika Anda menggunakan rute itu, saya sangat menyarankan Anda perlu unit test yang sangat teliti untuk memastikan acara tersebut dilakukan dengan benar dan Anda tidak meninggalkan referensi yang menggantung, jika tidak, akan sangat sulit untuk melakukan debug.
Kedua kalinya, saya baru saja menerapkan properti induk sebagai fungsi anak-anak, jadi simpan
Dirty
properti pada setiap hewan, dan biarkanAnimal.IsDirty
kembalithis.Animals.Any(x => x.IsDirty)
. Itu ada dalam model. Di atas model ada Pengendali, dan tugas pengontrol adalah untuk mengetahui bahwa setelah saya mengubah model (semua tindakan pada model dilewatkan melalui pengontrol sehingga tahu bahwa ada sesuatu yang berubah), maka ia tahu harus memanggil kembali -Fungsi evaluasi, seperti memicuZooMaintenance
departemen untuk memeriksa apakahZoo
kotor lagi. Atau saya bisa menekanZooMaintenance
cek sampai beberapa waktu kemudian dijadwalkan (setiap 100 ms, 1 detik, 2 menit, 24 jam, apa pun yang diperlukan).Saya menemukan bahwa yang terakhir jauh lebih mudah untuk dipertahankan, dan ketakutan saya terhadap masalah kinerja tidak pernah terwujud.
Edit
Cara lain untuk mengatasi ini adalah pola Bus Pesan . Daripada menggunakan
Controller
seperti dalam contoh saya, Anda menyuntikkan setiap objek denganIMessageBus
layanan. TheAnimal
kelas maka dapat mempublikasikan pesan, seperti "Mess Terbuat" dan AndaZoo
kelas dapat berlangganan pesan "Mess Terbuat". Layanan bus pesan akan memberitahukanZoo
kapan ada hewan yang mempublikasikan salah satu pesan itu, dan ia dapat mengevaluasi kembaliIsDirty
propertinya.Ini memiliki keuntungan yang
Animals
tidak lagi membutuhkanZoo
referensi, danZoo
tidak perlu khawatir tentang berlangganan dan berhenti berlangganan dari setiap acaraAnimal
. Hukumannya adalah bahwa semuaZoo
kelas yang berlangganan pesan tersebut harus mengevaluasi kembali properti mereka, bahkan jika itu bukan salah satu dari hewan-hewannya. Itu mungkin atau mungkin bukan masalah besar. Jika hanya ada satu atau duaZoo
contoh, itu mungkin baik-baik saja.Edit 2
Jangan mengabaikan kesederhanaan opsi 1. Siapa pun yang mengunjungi kembali kode tidak akan kesulitan memahaminya. Ini akan menjadi jelas bagi seseorang yang melihat
Animal
kelas bahwa ketikaMakeMess
dipanggil akan menyebarkan pesan ke kelasZoo
dan itu akan menjadi jelas bagiZoo
kelas dari mana pesan itu berasal. Ingat bahwa dalam pemrograman berorientasi objek, panggilan metode dulu disebut "pesan". Bahkan, satu-satunya waktu yang masuk akal untuk keluar dari opsi 1 adalah jika lebih dari sekadarZoo
harus diberitahukan jikaAnimal
membuat berantakan. Jika ada lebih banyak objek yang perlu diberitahukan, maka saya mungkin akan pindah ke bus pesan atau pengontrol.sumber
Saya telah membuat diagram kelas sederhana yang menggambarkan domain Anda:
Masing-masing
Animal
memilikiHabitat
itu kacau.Tidak
Habitat
peduli apa atau berapa banyak hewan yang dimilikinya (kecuali jika secara fundamental bagian dari desain Anda yang dalam hal ini Anda uraikan bukan).Tetapi yang
Animal
peduli, karena akan berperilaku berbeda di setiapHabitat
.Diagram ini mirip dengan Diagram UML dari pola desain strategi , tetapi kami akan menggunakannya secara berbeda.
Berikut adalah beberapa contoh kode di Java (Saya tidak ingin membuat kesalahan spesifik C #).
Tentu saja Anda dapat membuat tweak sendiri untuk desain, bahasa, dan persyaratan ini.
Ini adalah antarmuka Strategi:
Contoh beton
Habitat
. tentu saja setiapHabitat
subclass dapat mengimplementasikan metode ini secara berbeda.Tentu saja Anda dapat memiliki beberapa subkelas hewan, di mana masing-masing mengacaukannya secara berbeda:
Ini adalah kelas klien, ini pada dasarnya menjelaskan bagaimana Anda dapat menggunakan desain ini.
Tentu saja dalam aplikasi nyata Anda, Anda dapat memberi
Habitat
tahu dan mengelolaAnimal
jika Anda membutuhkannya.sumber
Saya sudah cukup sukses dengan arsitektur seperti pilihan Anda 2 di masa lalu. Ini adalah opsi paling umum dan akan memberikan fleksibilitas terbesar. Tetapi, jika Anda memiliki kendali atas pendengar Anda dan tidak mengelola banyak jenis langganan, Anda dapat berlangganan acara dengan lebih mudah dengan membuat antarmuka.
Opsi antarmuka memiliki keuntungan yang hampir sesederhana opsi 1 Anda, tetapi juga memungkinkan Anda untuk menempatkan hewan dengan mudah di dalam
House
atauFairlyLand
.sumber
Dwelling
dan menyediakanMakeMess
metode di atasnya. Itu memutus ketergantungan sirkuler. Lalu ketika hewan itu membuat berantakan, iadwelling.MakeMess()
juga memanggil .Dalam semangat lex parsimoniae , saya akan memilih yang ini, walaupun saya mungkin akan menggunakan solusi rantai di bawah ini, mengenal saya. (Ini hanya model yang sama dengan yang disarankan @Benjamin Albert.)
Perhatikan bahwa jika Anda memodelkan tabel basis data relasional, relasinya akan menuju ke arah lain: Hewan akan memiliki referensi ke Kebun Binatang, dan koleksi Hewan untuk Kebun Binatang akan menjadi hasil permintaan.
Messable
, dan di setiap item yang bisa dikirim, sertakan referensinext
. Setelah membuat kekacauan, panggilMakeMess
item berikutnya.Jadi Zoo di sini terlibat dalam membuat kekacauan, karena menjadi berantakan juga. Memiliki:
Jadi sekarang Anda memiliki rantai hal-hal yang mendapatkan pesan bahwa kekacauan telah dibuat.
Opsi 2, model terbitkan / berlangganan dapat bekerja di sini, tetapi terasa sangat berat. Objek dan wadah memiliki hubungan yang diketahui, sehingga tampaknya sedikit berat menggunakan sesuatu yang lebih umum dari itu.
Opsi 3: Dalam kasus khusus ini, menelepon
Zoo.MakeMess(animal)
atauHouse.MakeMess(animal)
sebenarnya bukan pilihan yang buruk, karena sebuah rumah mungkin memiliki semantik berbeda untuk menjadi berantakan daripada Kebun Binatang.Bahkan jika Anda tidak melalui rute rantai, sepertinya ada dua masalah di sini: 1) masalahnya adalah tentang menyebarkan perubahan dari suatu objek ke wadahnya, 2) Kedengarannya seperti Anda ingin mematikan antarmuka untuk wadah untuk abstrak di mana binatang bisa hidup.
...
Jika Anda memiliki fungsi kelas satu, Anda dapat meneruskan suatu fungsi (atau mendelegasikan) ke Animal untuk dipanggil setelah itu membuat kekacauan. Itu sedikit seperti ide berantai, kecuali dengan fungsi bukan antarmuka.
Saat hewan bergerak, cukup tentukan delegasi baru.
sumber
Saya akan menggunakan 1, tapi saya akan membuat hubungan orangtua-anak bersama dengan notifikasi logika menjadi bungkus terpisah. Ini menghilangkan ketergantungan Animal on Zoo dan memungkinkan manajemen otomatis hubungan orangtua-anak. Tapi ini mengharuskan Anda untuk membuat ulang objek dalam hierarki menjadi antarmuka / kelas abstrak terlebih dahulu dan menulis pembungkus spesifik untuk setiap antarmuka. Tapi itu bisa dihapus menggunakan pembuatan kode.
Sesuatu seperti :
Ini sebenarnya bagaimana beberapa ORM melakukan pelacakan perubahan pada entitas. Mereka membuat pembungkus di sekitar entitas dan membuat Anda bekerja dengan itu. Pembungkus tersebut biasanya dibuat menggunakan refleksi dan pembuatan kode dinamis.
sumber
Dua opsi yang sering saya gunakan. Anda dapat menggunakan pendekatan kedua dan menempatkan logika untuk memasang acara di koleksi itu sendiri pada induknya.
Pendekatan alternatif (yang sebenarnya dapat digunakan dengan salah satu dari tiga opsi) adalah dengan menggunakan penahanan. Buat AnimalContainer (atau bahkan koleksi) yang dapat tinggal di rumah atau kebun binatang atau apa pun. Ini menyediakan fungsionalitas pelacakan yang terkait dengan hewan, tetapi menghindari masalah warisan karena dapat dimasukkan dalam objek apa pun yang membutuhkannya.
sumber
Anda mulai dengan kegagalan dasar: objek anak tidak seharusnya tahu tentang orang tua mereka.
Apakah string tahu bahwa mereka ada dalam daftar? Tidak. Apakah tanggal tahu mereka ada di kalender? Tidak.
Pilihan terbaik adalah mengubah desain Anda sehingga skenario semacam ini tidak ada.
Setelah daripada, pertimbangkan inversi kontrol. Alih-alih
MakeMess
padaAnimal
dengan efek samping atau acara, lulusZoo
ke metode. Opsi 1 baik-baik saja jika Anda perlu melindungi invarian yangAnimal
selalu perlu tinggal di suatu tempat. Itu bukan orangtua, tetapi asosiasi teman sebaya.Kadang-kadang 2 dan 3 akan berjalan dengan baik, tetapi prinsip arsitektur utama yang harus diikuti adalah bahwa anak-anak tidak tahu tentang orang tua mereka.
sumber