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:
Ekspos interfacetipe, bukan classtipe, melalui metode dan properti Anda.
Dalam kasus Original Poster (OP), encoder->WaitEncoderFrame()akan mengembalikan IEncoderFramebukan Frame, dan akan menentukan operasi apa yang diizinkan.
SOLUSI 1
Dalam kasus termudah, Framedan Encoderkelas keduanya berada di bawah kendali Anda, IEncoderFrameadalah bagian dari metode yang sudah diekspos oleh Frame, dan Encoderkelas sebenarnya tidak peduli apa yang Anda lakukan pada objek itu. Kemudian, implementasi bersifat sepele ( kode dalam c # ):
interface IEncoderFrame {
void DoOrGetSomething();
}
class Frame : IEncoderFrame {
// A method that already exists in Frame.
public void DoOrGetSomething() { ... }
}
class Encoder {
private Frame _frame;
public IEncoderFrame TheFrame { get { return _frame; } }
...
}
SOLUSI 2
Dalam kasus menengah, di mana Framedefinisi tidak berada di bawah kendali Anda, atau tidak sesuai untuk menambahkan IEncoderFramemetode Frame, maka solusi yang baik adalah Adaptor . Itulah yang jawabannya CandiedOrange ini membahas, sebagai new FrameHandler( frame ). PENTING: Jika Anda melakukan ini, itu lebih fleksibel jika Anda mengeksposnya sebagai antarmuka , bukan sebagai kelas . Encoderharus tahu class FrameHandler, tetapi klien hanya perlu tahu interface IFrameHandler. Atau seperti yang saya sebutkan, interface IEncoderFrame- untuk menunjukkan bahwa itu adalah Frame khusus seperti yang terlihat dari POV dari Encoder :
interface IEncoderFrame {
void DoOrGetSomething();
}
// Adapter pattern. Appropriate if no access needed to Encoder.
class EncoderFrameWrapper : IEncoderFrame {
Frame _frame;
public EncoderFrameWrapper( Frame frame ) {
_frame = frame;
}
public void DoOrGetSomething() {
_frame....;
}
}
class Encoder {
private Frame _frame;
// Adapter pattern. Appropriate if no access needed to Encoder.
public IEncoderFrame TheFrame { get { return new EncoderFrameWrapper( _frame ); } }
...
}
BIAYA: Alokasi dan GC dari objek baru, EncoderFrameWrapper, setiap kali encoder.TheFramedipanggil. (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 Encoderdan Frame. 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:
interface IEncoderFrame {
void DoOrGetSomething();
}
// *** You will end up regretting this. See next code snippet instead ***
class EncoderFrameWrapper : IEncoderFrame {
Encoder _owner;
Frame _frame;
public EncoderFrameWrapper( Encoder owner, Frame frame ) {
_owner = owner; _frame = frame;
}
public void DoOrGetSomething() {
_frame.DoOrGetSomething();
// Hmm, maybe this wrapper class should be nested inside Encoder...
_owner... some work inside owner; maybe should be owner-internal details ...
}
}
class Encoder {
private Frame _frame;
...
}
Itu jadi jelek. Ada implementasi yang tidak terlalu berbelit-belit, ketika pembungkus perlu menyentuh detail pencipta / pemiliknya (Encoder):
interface IEncoderFrame {
void DoOrGetSomething();
}
class Encoder : IEncoderFrame {
private Frame _frame;
// HA! Client gets to think of this as "the frame object",
// but its really me, intercepting it.
public IEncoderFrame TheFrame { get { return this; } }
// This is the method that the LoD approach suggests writing,
// except that we are exposing it only when the instance is accessed as an IEncoderFrame,
// to avoid extending Encoder's already large API surface.
public void IEncoderFrame.DoOrGetSomething() {
_frame.DoOrGetSomething();
... make some change within current Encoder instance ...
}
...
}
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 Encodermerasa bahwa mengekspos Frame framesesuai 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,IEncoderFrameFrameEncoder.
Juga mencatat bahwa jika Anda tidak memiliki izin untuk menambahkan IEncoderFrameke Frame, atau metode yang diperlukan tidak cocok untuk umum Framekelas, dan solusi # 2 tidak sesuai dengan Anda, mungkin karena objek ekstra penciptaan-dan-kehancuran, solusi # 3 dapat dilihat hanya sebagai cara untuk mengatur metode Encoder, 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, IEncoderFramehanyalah sebagian dari Framefungsionalitas. Yang kedua, IEncoderFrameadalah adaptor. Yang ketiga, IEncoderFrameadalah fungsi partisi ke Encoders. Tidak masalah jika kebutuhan Anda berubah di antara ketiga situasi ini: API tetap sama.