2010-03-31 13 views
5

Tôi đã đọc về điều này "Luật Demeter", và nó (và tinh khiết "wrapper" các lớp học nói chung) dường như nói chung là mô hình chống. Hãy xem xét một lớp thực hiện:Máy đóng gói/luật của demeter có vẻ là một mẫu chống

class FluidSimulator { 
    void reset() { /* ... */ } 
} 

Bây giờ xem xét hai hiện thực khác nhau của lớp khác:

class ScreenSpaceEffects1 { 
    private FluidSimulator _fluidDynamics; 
    public FluidSimulator getFluidSimulator() { return _fluidDynamics; } 
} 

class ScreenSpaceEffects2 { 
    private FluidSimulator _fluidDynamics; 
    public void resetFluidSimulation() { _fluidDynamics.reset(); } 
} 

Và cách để gọi cho biết phương pháp:

callingMethod() { 
    effects1.getFluidSimulator().reset(); // Version 1 
    effects2.resetFluidSimulation();  // Version 2 
} 

Tại blush đầu tiên, phiên bản 2 có vẻ đơn giản hơn một chút, và tuân theo "quy tắc của Demeter", ẩn triển khai của Foo, v.v. Nhưng điều này liên kết bất kỳ thay đổi nào trong FluidSimulator với ScreenSpaceEffects. Ví dụ, nếu một tham số được thêm vào thiết lập lại, sau đó chúng ta có:

class FluidSimulator { 
    void reset(bool recreateRenderTargets) { /* ... */ } 
} 

class ScreenSpaceEffects1 { 
    private FluidSimulator _fluidDynamics; 
    public FluidSimulator getFluidSimulator() { return _fluidDynamics; } 
} 

class ScreenSpaceEffects2 { 
    private FluidSimulator _fluidDynamics; 
    public void resetFluidSimulation(bool recreateRenderTargets) { _fluidDynamics.reset(recreateRenderTargets); } 
} 

callingMethod() { 
    effects1.getFluidSimulator().reset(false); // Version 1 
    effects2.resetFluidSimulation(false);  // Version 2 
} 

Trong cả hai phiên bản, callingMethod cần phải thay đổi, nhưng trong phiên bản 2, ScreenSpaceEffects cũng cần phải được thay đổi. Ai đó có thể giải thích lợi thế của việc có một wrapper/mặt tiền (với ngoại lệ của adapter hoặc gói một API bên ngoài hoặc lộ một nội bộ).

EDIT: Một trong nhiều ví dụ thực tế mà tôi đã chạy vào ví dụ này thay vì một ví dụ nhỏ.

+0

Bạn có nghĩa là "phiên bản 2 có vẻ đơn giản hơn một chút" không? –

+0

Vâng, xin lỗi, sẽ thay đổi –

+0

Phiên bản 1 không tuân theo quy tắc của Demeter. Mistype? – Corwin

Trả lời

13

Sự khác biệt chính là trong phiên bản 1, là nhà cung cấp của trừu tượng Bar, bạn không có quyền kiểm soát cách hiển thị Foo. Mọi thay đổi trong số Foo sẽ được tiếp xúc với khách hàng của bạn và họ sẽ phải chịu đựng điều đó.

Với phiên bản 2, là nhà cung cấp trừu tượng Bar, bạn có thể quyết định xem bạn có muốn phơi bày diễn biến hay không. Nó sẽ chỉ phụ thuộc vào sự trừu tượng Bar và không phụ thuộc vào số Foo. Trong ví dụ của bạn, trừu tượng Bar của bạn có thể đã biết số nguyên nào để chuyển làm đối số và do đó bạn sẽ có thể cho phép người dùng của bạn sử dụng phiên bản Foo mới một cách minh bạch mà hoàn toàn không hề thay đổi.

Giả sử bây giờ Foo phát triển và yêu cầu người dùng gọi foo.init() trước khi có bất kỳ cuộc gọi nào tới doSomething. Với phiên bản 1, tất cả người dùng của Bar sẽ cần phải thấy rằng Foo đã thay đổi và điều chỉnh mã của họ. Với phiên bản 2, chỉ cần thay đổi Bar, doSomething gọi init nếu cần. Điều này dẫn đến ít lỗi hơn (chỉ có tác giả trừu tượng Bar phải biết và hiểu trừu tượng Foo và ít khớp nối giữa các lớp.

+0

Tôi không tin rằng đây là giá trị phức tạp bổ sung (đặc biệt là nếu các lớp học đã được ghép đôi), nhưng cảm ơn cho câu trả lời anyway :-). –

+0

Hầu như bất cứ lúc nào một cái gì đó thay đổi, một cái gì đó khác sẽ phải thay đổi. Điều tốt nhất có thể làm là cố gắng sắp xếp mọi thứ để những phần mà người ta muốn duy trì liên tục sẽ có thể duy trì liên tục ngay cả khi những thứ có khả năng thay đổi, làm như vậy. Mỗi thiết kế được cung cấp sẽ có thể chứa một số loại thay đổi tiềm năng trong tương lai của API dễ dàng hơn so với loại khác; thiết kế nào là tốt hơn phụ thuộc vào một số biện pháp như thế nào một trong những thẩm phán khả năng của các thay đổi trong tương lai có thể khác nhau. – supercat

2

Đây rõ ràng là một ví dụ nhân tạo.Trong nhiều trường hợp thực tế, gọiMethod (trong thực tế, có thể nhiều Ví dụ, nếu tôi sử dụng một API in ổn định, tôi không phải lo lắng về việc phần mềm máy in của tôi thêm hỗ trợ cho việc in ấn bóng loáng. mã in màu trắng vẫn tiếp tục hoạt động Tôi cho rằng bạn sẽ nhóm điều này dưới "bộ điều hợp", mà tôi nghĩ nó phổ biến hơn bạn ngụ ý.

Bạn có quyền mà đôi khi gọiMethod phải được thay đổi ed quá. Nhưng khi Luật Demeter được sử dụng đúng cách, điều này sẽ chỉ xảy ra hiếm khi, thường là để tận dụng chức năng mới (khác biệt với giao diện mới).

EDIT: Có vẻ như khá có thể là callMethod không quan tâm liệu các mục tiêu render có được tái tạo hay không (tôi giả định đây là câu hỏi về hiệu suất chính xác v). Sau khi tất cả, "Chúng ta nên quên đi hiệu quả nhỏ, nói khoảng 97% thời gian" (Knuth). Vì vậy, ScreenSpaceEffects2 có thể thêm một phương pháp resetFluidSimulation(bool), nhưng có resetFluidSimulation() tiếp tục làm việc (không thay đổi để gọiMethod) bằng cách gọi _fluidDynamics.reset(true) phía sau hậu trường.

+0

Thực ra, tôi đang hỏi câu hỏi này bởi vì nó đến WAY quá nhiều lần trong dự án sở thích của tôi –

+1

Điều gì xảy ra? Có lẽ một ví dụ thực sự, thay vì tất cả 'Foo' này và 'Bar' đó? –

+1

Đã thêm một ví dụ thực tế. –

2

Câu hỏi đặt ra là: callingMethod() có cần phải tạo lại các bảng hiển thị không?

Giả sử một số thực thi nhất định callingMethod() thực hiện hoặc không cần phải tạo lại các bảng hiển thị. Trong trường hợp đó bạn mở rộng trình bao bọc bằng một phương thức mới. Sau đó, bạn chỉ cần gọi phương thức mới từ các trường hợp thích hợp là callingMethod().

class ScreenSpaceEffects2 { 
    private FluidSimulator _fluidDynamics; 
    public void resetFluidSimulation() { _fluidDynamics.reset(false); } 
    public void resetFluidSimulationWithRecreate() { _fluidDynamics.reset(true); } 
} 

Ngoài ra các quyết định để tái tạo có thể thuộc về một nơi khác hoàn toàn ...

class ScreenSpaceEffects2 { 
    private FluidSimulator _fluidDynamics; 
    public void resetFluidSimulation() { 
      _fluidDynamics.reset(someRuleEngine.getRecreateRenderTables()); } 
} 

... trong whch trường hợp không có gì trong callingMethod() cần thay đổi chút nào.