คำตอบของ Doc Brownแสดงให้เห็นถึงการใช้ตำราแบบคลาสสิกของ Law of Demeter - และการสร้างความรำคาญ / ความไม่เป็นระเบียบ - รหัส - การขยายตัวของการเพิ่มวิธีการหลายสิบวิธีที่อาจเป็นเหตุผลว่าทำไมผมถึงโปรแกรมเมอร์เอง
มีวิธีอื่นในการแยกลำดับชั้นของวัตถุ:
เปิดเผยinterface
ประเภทมากกว่าclass
ประเภทผ่านวิธีการและคุณสมบัติของคุณ
ในกรณีของโปสเตอร์ดั้งเดิม (OP's) encoder->WaitEncoderFrame()
จะคืนค่าIEncoderFrame
แทนที่จะเป็น a Frame
และจะกำหนดการทำงานที่อนุญาต
แนวทางแก้ไข 1
ในกรณีที่ง่ายที่สุดFrame
และEncoder
คลาสมีทั้งภายใต้การควบคุมของคุณIEncoderFrame
เป็นชุดย่อยของวิธีเฟรมที่เปิดเผยต่อสาธารณะและEncoder
คลาสไม่สนใจสิ่งที่คุณทำกับวัตถุนั้น จากนั้นการใช้งานมีความสำคัญ ( รหัสใน 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; } }
...
}
แนวทางแก้ไข 2
ในกรณีที่เป็นสื่อกลางที่Frame
นิยามไม่อยู่ภายใต้การควบคุมของคุณหรือมันจะไม่เหมาะสมที่จะเพิ่มIEncoderFrame
's วิธีการที่จะFrame
แล้วทางออกที่ดีเป็นอะแดปเตอร์ นั่นคือสิ่งที่คำตอบของ CandiedOrangeพูดถึงnew FrameHandler( frame )
คือ สำคัญ: หากคุณทำเช่นนี้มันมีความยืดหยุ่นมากขึ้นถ้าคุณแสดงว่ามันเป็นอินเตอร์เฟซที่ไม่เป็นชั้น Encoder
มีความรู้เกี่ยวกับแต่ลูกค้าจะต้องรู้ว่าclass FrameHandler
interface IFrameHandler
หรือตามที่ฉันตั้งชื่อinterface IEncoderFrame
- เพื่อระบุว่าเป็นFrameเฉพาะที่เห็นจาก POV of 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 ); } }
...
}
ต้นทุน: การจัดสรรและ GC ของวัตถุใหม่ EncoderFrameWrapper ทุกครั้งที่encoder.TheFrame
ถูกเรียก (คุณสามารถแคชกระดาษห่อหุ้มนั้น แต่เพิ่มรหัสเพิ่มเติมและเป็นรหัสง่าย ๆ เท่านั้นหากฟิลด์เฟรมตัวเข้ารหัสไม่สามารถแทนที่ด้วยเฟรมใหม่ได้)
แนวทางแก้ไข 3
ในกรณีที่ยากมากขึ้นกระดาษห่อใหม่จะต้องรู้เกี่ยวกับทั้งสองและEncoder
Frame
วัตถุนั้นจะละเมิด LoD - มันกำลังจัดการความสัมพันธ์ระหว่าง Encoder และ Frame ที่ควรเป็นความรับผิดชอบของ Encoder - และอาจเป็นความเจ็บปวดที่จะทำให้ถูกต้อง นี่คือสิ่งที่สามารถเกิดขึ้นได้หากคุณเริ่มต้นบนถนนสายนั้น:
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;
...
}
มันช่างน่าเกลียด มีการใช้งานที่ซับซ้อนน้อยลงเมื่อ wrapper ต้องการสัมผัสรายละเอียดของผู้สร้าง / เจ้าของ (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 ...
}
...
}
จริงอยู่ถ้าฉันรู้ว่าฉันจะจบที่นี่ฉันอาจไม่ทำเช่นนี้ สามารถเขียนวิธี LoD และทำได้ด้วยวิธีนี้ ไม่จำเป็นต้องกำหนดส่วนต่อประสาน ในทางกลับกันฉันชอบที่ส่วนต่อประสานตัดวิธีการที่เกี่ยวข้องเข้าด้วยกัน ฉันชอบความรู้สึกที่ได้ทำ "การทำงานเหมือนเฟรม" กับสิ่งที่รู้สึกเหมือนเฟรม
ความคิดเห็นสุดท้าย
พิจารณาสิ่งนี้: หากผู้ดำเนินการEncoder
รู้สึกว่าการเปิดเผยFrame frame
เหมาะสมกับสถาปัตยกรรมโดยรวมหรือ "ง่ายกว่าการใช้ LoD" ก็จะปลอดภัยกว่ามากหากพวกเขาทำตัวอย่างข้อมูลแรกที่ฉันแสดง - เปิดเผยชุดย่อยที่ จำกัด ของ Frame เป็นส่วนต่อประสาน จากประสบการณ์ของฉันซึ่งมักจะเป็นวิธีแก้ปัญหาได้ เพียงเพิ่มวิธีการในอินเทอร์เฟซตามต้องการ (ฉันกำลังพูดถึงสถานการณ์ที่เรา "รู้" กรอบมีวิธีการที่จำเป็นอยู่แล้วหรือพวกเขาจะง่ายและไม่ขัดแย้งที่จะเพิ่มงาน "ใช้งาน" สำหรับแต่ละวิธีคือการเพิ่มหนึ่งบรรทัดในคำจำกัดความอินเทอร์เฟซ) และ รู้ว่าแม้ในสถานการณ์ที่เลวร้ายที่สุดในอนาคตก็เป็นไปได้ที่จะทำให้ API นั้นทำงานได้ - ที่นี่IEncoderFrame
Frame
Encoder
.
นอกจากนี้ยังทราบว่าถ้าคุณไม่ได้รับอนุญาตให้เพิ่มIEncoderFrame
การFrame
หรือวิธีการที่จำเป็นไม่พอดีดีเพื่อทั่วไปFrame
ชั้นเรียนและวิธีการแก้ปัญหา # 2 ไม่เหมาะกับคุณบางทีอาจเป็นเพราะของวัตถุเสริมสร้างและการทำลายล้าง วิธีการแก้ปัญหา # 3 สามารถมองเห็นเป็นเพียงวิธีการจัดระเบียบวิธีการEncoder
เพื่อให้บรรลุ LoD อย่าเพิ่งผ่านหลายสิบวิธี ล้อมรอบพวกเขาในอินเทอร์เฟซและใช้ "การใช้อินเทอร์เฟซที่ชัดเจน" (ถ้าคุณอยู่ใน c #) เพื่อให้พวกเขาสามารถเข้าถึงได้เฉพาะเมื่อวัตถุถูกดูผ่านอินเทอร์เฟซนั้น
อีกจุดหนึ่งที่ฉันต้องการเน้นคือการตัดสินใจที่จะเปิดเผยฟังก์ชันการทำงานเป็นอินเทอร์เฟซที่จัดการกับทั้ง 3 สถานการณ์ที่อธิบายไว้ข้างต้น ในครั้งแรกIEncoderFrame
เป็นเพียงส่วนย่อยของการFrame
ทำงานของ ในวินาทีIEncoderFrame
คืออะแดปเตอร์ ในสามIEncoderFrame
คือพาร์ติชันในการEncoder
ทำงานของ ไม่สำคัญว่าความต้องการของคุณจะเปลี่ยนแปลงไปอย่างไรในสถานการณ์ทั้งสามนี้: API ยังคงเหมือนเดิม