Câu trả lời của Doc Brown cho thấy một triển khai sách giáo khoa cổ điển của Law of Demeter - và sự phiền toái / vô tổ chức của việc thêm hàng tá phương pháp có lẽ là lý do tại sao các lập trình viên, bao gồm cả tôi, thường không bận tâm làm điều đó, ngay cả khi họ nên làm.
Có một cách khác để tách rời cấu trúc phân cấp của các đối tượng:
Vạch trần interface
các loại, chứ không phải class
các loại, thông qua phương pháp và tài sản của bạn.
Trong trường hợp của Poster gốc (OP), encoder->WaitEncoderFrame()
sẽ trả về IEncoderFrame
thay vì Frame
và sẽ xác định thao tác nào được phép.
GIẢI PHÁP 1
Trong trường hợp dễ nhất Frame
và Encoder
các lớp đều nằm dưới sự kiểm soát của bạn, IEncoderFrame
là một tập hợp con các phương thức Khung đã được hiển thị công khai và Encoder
lớp không thực sự quan tâm bạn làm gì với đối tượng đó. Sau đó, việc thực hiện là không đáng kể ( mã trong 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; } }
...
}
GIẢI PHÁP 2
Trong trường hợp trung gian, trong đó Frame
định nghĩa không nằm trong tầm kiểm soát của bạn hoặc không phù hợp để thêm IEncoderFrame
phương thức vào Frame
, thì một giải pháp tốt là Bộ điều hợp . Đó là những gì câu trả lời của CandiedOrange thảo luận, như new FrameHandler( frame )
. QUAN TRỌNG: Nếu bạn làm điều này, nó sẽ linh hoạt hơn nếu bạn trưng bày nó dưới dạng giao diện , không phải là một lớp . Encoder
phải biết về class FrameHandler
, nhưng khách hàng chỉ cần biết interface IFrameHandler
. Hoặc như tôi đã đặt tên cho nó, interface IEncoderFrame
- để chỉ ra rằng đó là Khung cụ thể như được thấy từ POV của 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 ); } }
...
}
CHI PHÍ: Phân bổ và GC của một đối tượng mới, EncoderFrameWrapper, mỗi lần encoder.TheFrame
được gọi. (Bạn có thể lưu trữ trình bao bọc đó, nhưng điều đó thêm nhiều mã hơn. Và chỉ dễ mã hóa một cách đáng tin cậy nếu trường khung của bộ mã hóa không thể được thay thế bằng một khung mới.)
GIẢI PHÁP 3
Trong trường hợp khó khăn hơn, trình bao bọc mới sẽ cần biết về cả hai Encoder
và Frame
. Bản thân đối tượng đó sẽ vi phạm LoD - nó đang thao túng mối quan hệ giữa Encoder và Frame nên là trách nhiệm của Encoder - và có lẽ là một nỗi đau để có được quyền. Đây là những gì có thể xảy ra nếu bạn bắt đầu con đường đó:
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;
...
}
Điều đó trở nên xấu xí. Có một triển khai ít phức tạp hơn, khi trình bao bọc cần chạm vào chi tiết của người tạo / chủ sở hữu của nó (Bộ mã hóa):
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 ...
}
...
}
Cấp, nếu tôi biết tôi sẽ kết thúc ở đây, tôi có thể không làm điều này. Chỉ có thể viết các phương thức LoD, và được thực hiện với nó. Không cần xác định một giao diện. Mặt khác, tôi thích rằng giao diện kết hợp các phương thức liên quan với nhau. Tôi thích cảm giác khi thực hiện "các thao tác giống như khung" với cảm giác giống như một khung.
Ý KIẾN CUỐI CÙNG
Hãy xem xét điều này: Nếu người triển khai Encoder
cảm thấy rằng việc phơi bày Frame frame
phù hợp với kiến trúc tổng thể của họ hoặc "dễ dàng hơn nhiều so với thực hiện LoD", thì sẽ an toàn hơn nhiều nếu họ thay vào đó là đoạn trích đầu tiên tôi trình bày - phơi bày một tập hợp con giới hạn của Khung, như một giao diện. Theo kinh nghiệm của tôi, đó thường là một giải pháp hoàn toàn khả thi. Chỉ cần thêm phương thức vào giao diện khi cần thiết. (Tôi đang nói về một kịch bản mà chúng ta "biết" Khung đã có các phương thức cần thiết hoặc chúng sẽ dễ dàng và không gây tranh cãi để thêm vào. Công việc "triển khai" cho mỗi phương thức là thêm một dòng vào định nghĩa giao diện.) Và biết rằng ngay cả trong trường hợp xấu nhất trong tương lai, vẫn có thể giữ API đó hoạt động - tại đây,IEncoderFrame
Frame
Encoder
.
Cũng lưu ý rằng nếu bạn không có quyền thêm IEncoderFrame
vào Frame
hoặc các phương thức cần thiết không phù hợp với Frame
lớp chung và giải pháp số 2 không phù hợp với bạn, có lẽ do việc tạo và hủy đối tượng thêm, giải pháp số 3 có thể được xem đơn giản là một cách để tổ chức các phương pháp Encoder
, để thực hiện LoD. Đừng chỉ thông qua hàng tá phương pháp. Gói chúng trong một Giao diện và sử dụng "triển khai giao diện rõ ràng" (nếu bạn ở c #), để chúng chỉ có thể được truy cập khi đối tượng được xem qua giao diện đó.
Một điểm khác tôi muốn nhấn mạnh là quyết định phơi bày chức năng như một giao diện , xử lý cả 3 tình huống được mô tả ở trên. Đầu tiên, IEncoderFrame
chỉ đơn giản là một tập hợp con của Frame
chức năng. Trong thứ hai, IEncoderFrame
là một bộ chuyển đổi. Trong phần ba, IEncoderFrame
là một phân vùng thành Encoder
chức năng s. Sẽ không có vấn đề gì nếu nhu cầu của bạn thay đổi giữa ba tình huống này: API vẫn giữ nguyên.