Saya memahami motif di balik prinsip paling sedikit pengetahuan , tetapi saya menemukan beberapa kelemahan jika saya mencoba menerapkannya dalam desain saya.
Salah satu contoh dari prinsip ini (sebenarnya bagaimana tidak menggunakannya), yang saya temukan dalam buku Head First Design Patterns menentukan bahwa salah memanggil metode pada objek yang dikembalikan dari memanggil metode lain, dalam hal prinsip ini .
Namun sepertinya terkadang sangat diperlukan untuk menggunakan kemampuan tersebut.
Sebagai contoh: Saya memiliki beberapa kelas: kelas video-capture, kelas encoder, kelas streamer, dan mereka semua menggunakan beberapa kelas dasar lainnya, VideoFrame, dan karena mereka berinteraksi satu sama lain, mereka dapat melakukan misalnya sesuatu seperti ini:
streamer
kode kelas
...
frame = encoder->WaitEncoderFrame()
frame->DoOrGetSomething();
....
Seperti yang Anda lihat, prinsip ini tidak diterapkan di sini. Dapatkah prinsip ini diterapkan di sini, atau apakah prinsip ini tidak selalu dapat diterapkan dalam desain seperti ini?
sumber
Jawaban:
Prinsip yang Anda bicarakan (lebih dikenal sebagai Law of Demeter ) untuk fungsi dapat diterapkan dengan menambahkan metode pembantu lain ke kelas streamer Anda seperti
Sekarang, setiap fungsi hanya "berbicara dengan teman", bukan dengan "teman teman".
IMHO ini adalah pedoman kasar yang dapat membantu menciptakan metode yang mengikuti prinsip tanggung jawab tunggal dengan lebih ketat. Dalam kasus sederhana seperti yang di atas mungkin sangat disarankan jika ini benar-benar layak kerumitan dan jika kode yang dihasilkan benar-benar "bersih", atau jika itu hanya akan memperluas kode Anda secara formal tanpa keuntungan yang nyata.
sumber
Prinsip ilmu paling tidak atau Hukum Demeter adalah peringatan agar tidak melibatkan kelas Anda dengan detail kelas lain yang melintasi lapisan demi lapisan. Ini memberi tahu Anda bahwa lebih baik berbicara hanya dengan "teman" Anda dan bukan dengan "teman teman".
Bayangkan Anda telah diminta untuk mengelas perisai ke patung ksatria berbaju zirah. Anda dengan hati-hati memposisikan perisai di lengan kiri sehingga terlihat alami. Anda perhatikan ada tiga tempat kecil di lengan bawah, siku, dan lengan atas tempat perisai menyentuh armor. Anda mengelas ketiga tempat karena Anda ingin memastikan koneksi kuat. Sekarang bayangkan bos Anda marah karena dia tidak bisa menggerakkan siku bajunya. Anda mengira baju besi itu tidak akan pernah bergerak dan karena itu menciptakan koneksi tidak bergerak antara lengan bawah dan lengan atas. Perisai seharusnya hanya terhubung ke temannya, lengan. Bukan untuk mempersenjatai teman. Bahkan jika Anda harus menambahkan sepotong logam untuk membuatnya tersentuh.
Metafora itu bagus tapi apa yang sebenarnya kita maksud dengan teman? Apa pun yang diketahui cara membuat atau menemukan objek adalah teman. Atau, suatu objek hanya bisa meminta untuk menyerahkan objek lain , yang hanya mengetahui antarmuka. Ini tidak dianggap sebagai teman karena tidak ada harapan tentang cara mendapatkannya. Jika objek tidak tahu dari mana asalnya karena ada sesuatu yang lewat / disuntikkan maka itu bukan teman teman, itu bahkan bukan teman. Ini adalah sesuatu yang hanya diketahui oleh objek yang digunakan. Itu hal yang baik.
Ketika mencoba menerapkan prinsip-prinsip seperti ini, penting untuk dipahami bahwa prinsip-prinsip itu tidak pernah melarang Anda mencapai sesuatu. Mereka adalah peringatan bahwa Anda mungkin lalai untuk melakukan lebih banyak pekerjaan untuk mencapai desain yang lebih baik yang mencapai hal yang sama.
Tidak ada yang ingin melakukan pekerjaan tanpa alasan, jadi penting untuk memahami apa yang Anda dapatkan dari mengikuti ini. Dalam hal ini membuat kode Anda fleksibel. Anda dapat membuat perubahan dan memiliki sedikit kelas lain yang terpengaruh oleh perubahan yang perlu dikhawatirkan. Itu kedengarannya bagus tetapi tidak membantu Anda memutuskan apa yang harus dilakukan kecuali Anda menganggapnya sebagai semacam doktrin agama.
Alih-alih secara membabi buta mengikuti prinsip ini, ambillah versi sederhana dari masalah ini. Tulis solusi yang tidak mengikuti prinsip dan yang tidak. Sekarang setelah Anda memiliki dua solusi, Anda dapat membandingkan seberapa reseptif masing-masing terhadap perubahan dengan mencoba membuatnya di keduanya.
Jika Anda TIDAK BISA menyelesaikan masalah saat mengikuti prinsip ini, ada kemungkinan Anda kehilangan keahlian lain.
Salah satu solusi untuk masalah khusus Anda adalah dengan menyuntikkan
frame
sesuatu (kelas atau metode) yang tahu cara berbicara dengan bingkai sehingga Anda tidak harus menyebarkan semua detail frame chatting di kelas Anda, yang sekarang hanya tahu bagaimana dan kapan harus dapatkan bingkai.Itu sebenarnya mengikuti prinsip lain: Pisahkan penggunaan dari konstruksi.
Dengan menggunakan kode ini, Anda bertanggung jawab untuk memperoleh suatu
Frame
. Anda belum memikul tanggung jawab untuk berbicara dengan seorangFrame
.Sekarang Anda harus tahu cara berbicara dengan
Frame
, tetapi gantilah dengan:Dan sekarang Anda hanya perlu tahu cara berbicara dengan teman Anda, FrameHandler.
Ada banyak cara untuk mencapai ini dan ini, mungkin, bukan yang terbaik tetapi itu menunjukkan bagaimana mengikuti prinsip tidak membuat masalah tidak dapat diselesaikan. Itu hanya menuntut lebih banyak pekerjaan dari Anda.
Setiap aturan yang baik memiliki pengecualian. Contoh terbaik yang saya tahu adalah Bahasa Spesifik Domain internal . Sebuah DSL s rantai metodetampaknya menyalahgunakan Hukum Demeter sepanjang waktu karena Anda terus-menerus memanggil metode yang mengembalikan berbagai jenis dan menggunakannya secara langsung. Kenapa ini oke? Karena dalam DSL semua yang dikembalikan adalah teman yang dirancang dengan hati-hati sehingga Anda harus berbicara langsung. Sesuai desain, Anda telah diberi hak untuk mengharapkan rantai metode DSL tidak berubah. Anda tidak memiliki hak itu jika Anda hanya secara acak mempelajari rantai basis kode bersama-sama apa pun yang Anda temukan. DSL terbaik adalah representasi atau antarmuka yang sangat tipis ke objek lain yang kemungkinan besar tidak perlu Anda selidiki. Saya hanya menyebutkan ini karena saya menemukan saya memahami hukum demeter jauh lebih baik setelah saya mengetahui mengapa DSL adalah desain yang baik. Beberapa orang bahkan mengatakan DSL tidak melanggar hukum demeter yang sebenarnya.
Solusi lain adalah membiarkan sesuatu yang lain menyuntikkan
frame
ke Anda. Jikaframe
datang dari setter atau lebih disukai seorang konstruktor maka Anda tidak bertanggung jawab untuk membangun atau mendapatkan bingkai. Ini berarti Anda berperan di sini lebih sepertiFrameHandlers
yang akan terjadi. Alih-alih sekarang kaulah yang suka mengobrolFrame
dan membuat sesuatu yang lain mencari cara untuk mendapatkan.Frame
Dengan cara ini adalah solusi yang sama dengan perubahan perspektif sederhana.The SOLID prinsip adalah orang-orang besar saya mencoba mengikuti. Dua yang dihormati di sini adalah Prinsip Tanggung Jawab Tunggal dan Pembalikan Ketergantungan. Sangat sulit untuk menghormati kedua orang ini dan akhirnya melanggar Hukum Demeter.
Mentalitas yang masuk ke dalam pelanggaran Demeter adalah seperti makan di restoran prasmanan di mana Anda hanya mengambil apa pun yang Anda inginkan. Dengan sedikit kerja dimuka Anda dapat menyediakan diri Anda dengan menu dan server yang akan membawakan apa pun yang Anda suka. Duduk, santai, dan tip dengan baik.
sumber
Apakah desain fungsional lebih baik daripada desain berorientasi objek? Tergantung.
Apakah MVVM lebih baik daripada MVC? Tergantung.
Amos & Andy atau Martin dan Lewis? Tergantung.
Apa itu tergantung? Pilihan yang Anda buat tergantung pada seberapa baik masing-masing teknik atau teknologi memenuhi persyaratan fungsional dan non-fungsional perangkat lunak Anda, sementara memuaskan tujuan desain, kinerja, dan pemeliharaan Anda.
Ketika Anda membaca ini di buku atau blog, evaluasi klaim berdasarkan kemampuannya; yaitu, tanyakan mengapa. Tidak ada teknik benar atau salah dalam pengembangan perangkat lunak, hanya ada "seberapa baik teknik ini memenuhi tujuan saya? Apakah efektif atau tidak efektif? Apakah itu menyelesaikan satu masalah tetapi membuat yang baru? Bisakah itu dipahami dengan baik oleh seluruh tim pengembangan, atau terlalu kabur? "
Dalam kasus khusus ini - tindakan memanggil metode pada objek yang dikembalikan oleh metode lain - karena ada pola desain aktual yang mengkodifikasi praktik ini (Pabrik), sulit untuk membayangkan bagaimana seseorang dapat membuat pernyataan bahwa itu adalah kategori. salah.
Alasannya disebut "Prinsip Pengetahuan Paling Kurang" adalah karena "Kopling Rendah" adalah kualitas sistem yang diinginkan. Objek yang tidak terikat satu sama lain bekerja lebih mandiri, dan karenanya lebih mudah untuk mempertahankan dan memodifikasi secara individual. Tetapi seperti yang ditunjukkan contoh Anda, ada kalanya kopling tinggi lebih diinginkan, sehingga objek dapat lebih efektif mengoordinasikan upaya mereka.
sumber
Jawaban Doc Brown menunjukkan implementasi buku teks klasik Law of Demeter - dan kekesalan / kekacauan kode-menambahkan beberapa metode yang mungkin mengapa programmer, termasuk saya, sering tidak repot-repot melakukannya, bahkan jika mereka harus.
Ada cara alternatif untuk memisahkan hirarki objek:
Dalam kasus Original Poster (OP),
encoder->WaitEncoderFrame()
akan mengembalikanIEncoderFrame
bukanFrame
, dan akan menentukan operasi apa yang diizinkan.SOLUSI 1
Dalam kasus termudah,
Frame
danEncoder
kelas keduanya berada di bawah kendali Anda,IEncoderFrame
adalah bagian dari metode yang sudah diekspos oleh Frame, danEncoder
kelas sebenarnya tidak peduli apa yang Anda lakukan pada objek itu. Kemudian, implementasi bersifat sepele ( kode dalam c # ):SOLUSI 2
Dalam kasus menengah, di mana
Frame
definisi tidak berada di bawah kendali Anda, atau tidak sesuai untuk menambahkanIEncoderFrame
metodeFrame
, maka solusi yang baik adalah Adaptor . Itulah yang jawabannya CandiedOrange ini membahas, sebagainew FrameHandler( frame )
. PENTING: Jika Anda melakukan ini, itu lebih fleksibel jika Anda mengeksposnya sebagai antarmuka , bukan sebagai kelas .Encoder
harus tahuclass FrameHandler
, tetapi klien hanya perlu tahuinterface IFrameHandler
. Atau seperti yang saya sebutkan,interface IEncoderFrame
- untuk menunjukkan bahwa itu adalah Frame khusus seperti yang terlihat dari POV dari Encoder :BIAYA: Alokasi dan GC dari objek baru, EncoderFrameWrapper, setiap kali
encoder.TheFrame
dipanggil. (Anda dapat menembolok pembungkus itu, tetapi itu menambahkan lebih banyak kode. Dan hanya mudah untuk kode yang andal jika bidang bingkai pembuat kode tidak dapat diganti dengan bingkai baru.)SOLUSI 3
Dalam kasus yang lebih sulit, bungkus baru perlu tahu tentang keduanya
Encoder
danFrame
. Objek itu sendiri akan melanggar LoD - itu memanipulasi hubungan antara Encoder dan Frame yang seharusnya menjadi tanggung jawab Encoder - dan mungkin menjadi rasa sakit untuk mendapatkan yang benar. Inilah yang dapat terjadi jika Anda memulai jalan itu:Itu jadi jelek. Ada implementasi yang tidak terlalu berbelit-belit, ketika pembungkus perlu menyentuh detail pencipta / pemiliknya (Encoder):
Memang, jika saya tahu saya akan berakhir di sini, saya mungkin tidak akan melakukan ini. Bisa saja menulis metode LoD, dan selesai dengan itu. Tidak perlu mendefinisikan antarmuka. Di sisi lain, saya suka bahwa antarmuka membungkus metode terkait bersama. Saya suka bagaimana rasanya melakukan "operasi seperti bingkai" dengan apa yang terasa seperti bingkai.
KOMENTAR AKHIR
Pertimbangkan ini: Jika implementor
Encoder
merasa bahwa mengeksposFrame frame
sesuai dengan arsitektur keseluruhan mereka, atau "jauh lebih mudah daripada mengimplementasikan LoD", maka itu akan jauh lebih aman jika mereka melakukan potongan pertama yang saya tunjukkan - memperlihatkan subset terbatas dari Bingkai, sebagai antarmuka. Dalam pengalaman saya, itu sering merupakan solusi yang sepenuhnya bisa diterapkan. Cukup tambahkan metode ke antarmuka sesuai kebutuhan. (Saya berbicara tentang skenario di mana kita "tahu" Frame sudah memiliki metode yang diperlukan, atau mereka akan mudah dan non-kontroversial untuk ditambahkan. Pekerjaan "implementasi" untuk setiap metode menambahkan satu baris ke definisi antarmuka.) Dan tahu bahwa bahkan dalam skenario terburuk di masa mendatang, dimungkinkan untuk menjaga agar API tetap berfungsi - di sini,IEncoderFrame
Frame
Encoder
.Juga mencatat bahwa jika Anda tidak memiliki izin untuk menambahkan
IEncoderFrame
keFrame
, atau metode yang diperlukan tidak cocok untuk umumFrame
kelas, dan solusi # 2 tidak sesuai dengan Anda, mungkin karena objek ekstra penciptaan-dan-kehancuran, solusi # 3 dapat dilihat hanya sebagai cara untuk mengatur metodeEncoder
, untuk mencapai LoD. Jangan hanya melewati lusinan metode. Bungkus mereka dalam suatu Interface, dan gunakan "implementasi antarmuka eksplisit" (jika Anda berada di c #), sehingga mereka hanya dapat diakses ketika objek dilihat melalui antarmuka itu.Poin lain yang ingin saya tekankan adalah keputusan untuk mengekspos fungsi sebagai antarmuka , menangani semua 3 situasi yang dijelaskan di atas. Yang pertama,
IEncoderFrame
hanyalah sebagian dariFrame
fungsionalitas. Yang kedua,IEncoderFrame
adalah adaptor. Yang ketiga,IEncoderFrame
adalah fungsi partisi keEncoder
s. Tidak masalah jika kebutuhan Anda berubah di antara ketiga situasi ini: API tetap sama.sumber