Phương pháp xích và đóng gói


17

Có một vấn đề OOP cổ điển về phương thức xâu chuỗi so với các phương thức "điểm truy cập đơn":

main.getA().getB().getC().transmogrify(x, y)

đấu với

main.getA().transmogrifyMyC(x, y)

Điều đầu tiên dường như có lợi thế là mỗi lớp chỉ chịu trách nhiệm cho một tập hợp hoạt động nhỏ hơn và làm cho mọi thứ trở nên mô đun hơn rất nhiều - việc thêm một phương thức vào C không đòi hỏi bất kỳ nỗ lực nào trong A, B hoặc C để phơi bày nó.

Nhược điểm, tất nhiên, là đóng gói yếu hơn , mà mã thứ hai giải quyết. Bây giờ A có quyền kiểm soát mọi phương thức đi qua nó và có thể ủy thác nó cho các trường của nó nếu nó muốn.

Tôi nhận ra không có giải pháp duy nhất và tất nhiên nó phụ thuộc vào ngữ cảnh, nhưng tôi thực sự muốn nghe một số thông tin về sự khác biệt quan trọng khác giữa hai phong cách, và trong trường hợp nào tôi nên chọn một trong hai - vì ngay bây giờ, khi tôi thử để thiết kế một số mã, tôi cảm thấy như mình không sử dụng các đối số để quyết định cách này hay cách khác.

Câu trả lời:


25

Tôi nghĩ rằng Luật Demeter cung cấp một hướng dẫn quan trọng trong vấn đề này (với những ưu điểm và nhược điểm của nó, như thường lệ, nên được đo trên cơ sở từng trường hợp).

Ưu điểm của việc tuân theo Luật Demeter là phần mềm kết quả có xu hướng dễ bảo trì và thích nghi hơn. Vì các đối tượng ít phụ thuộc vào cấu trúc bên trong của các đối tượng khác, nên các thùng chứa đối tượng có thể được thay đổi mà không cần làm lại người gọi của chúng.

Một nhược điểm của Luật Demeter là đôi khi nó yêu cầu viết một số lượng lớn các phương thức "trình bao bọc" nhỏ để truyền các lệnh gọi phương thức đến các thành phần. Hơn nữa, giao diện của một lớp có thể trở nên cồng kềnh vì nó lưu trữ các phương thức cho các lớp được chứa, dẫn đến một lớp không có giao diện gắn kết. Nhưng đây cũng có thể là một dấu hiệu của thiết kế OO xấu.


Tôi quên mất luật đó, cảm ơn bạn đã nhắc nhở tôi. Nhưng những gì tôi đang hỏi ở đây chủ yếu là những lợi thế và bất lợi là gì, hay chính xác hơn là tôi nên quyết định sử dụng một phong cách như thế nào.
Sồi

@Oak, tôi đã thêm trích dẫn mô tả những lợi thế và bất lợi.
Péter Török

10

Nói chung, tôi cố gắng giữ cho phương thức xích càng hạn chế càng tốt (dựa trên Luật Demeter )

Ngoại lệ duy nhất tôi thực hiện là dành cho giao diện lưu loát / lập trình kiểu DSL nội bộ.

Martin Fowler tạo ra sự khác biệt tương tự trong Ngôn ngữ cụ thể miền nhưng vì lý do phân tách truy vấn lệnh vi phạm , trong đó nêu rõ:

rằng mọi phương thức sẽ là một lệnh thực hiện một hành động hoặc một truy vấn trả về dữ liệu cho người gọi, nhưng không phải cả hai.

Fowler trong cuốn sách của mình ở trang 70 nói:

Phân tách truy vấn lệnh là một nguyên tắc cực kỳ có giá trị trong lập trình và tôi đặc biệt khuyến khích các nhóm sử dụng nó. Một trong những hậu quả của việc sử dụng Phương thức Chuỗi trong DSL bên trong là nó thường phá vỡ nguyên tắc này - mỗi phương thức thay đổi trạng thái nhưng trả về một đối tượng để tiếp tục chuỗi. Tôi đã sử dụng nhiều decibel chê bai những người không tuân theo phân tách truy vấn lệnh và sẽ làm lại. Nhưng giao diện lưu loát tuân theo một bộ quy tắc khác, vì vậy tôi rất vui khi cho phép nó ở đây.


3

Tôi nghĩ câu hỏi là, liệu bạn có đang sử dụng một sự trừu tượng phù hợp.

Trong trường hợp đầu tiên, chúng tôi có

interface IHasGetA {
    IHasGetB getA();
}

interface IHasGetB {
    IHasGetC getB();
}

interface IHasGetC {
    ITransmogrifyable getC();
}

interface ITransmogrifyable {
    void transmogrify(x,y);
}

Trường hợp chính là loại IHasGetA. Câu hỏi là: Sự trừu tượng đó có phù hợp không. Câu trả lời không tầm thường. Và trong trường hợp này có vẻ hơi sai, nhưng dù sao nó cũng là một ví dụ lý thuyết. Nhưng để xây dựng một ví dụ khác:

main.getA(v).getB(w).getC(x).transmogrify(y, z);

Thường tốt hơn

main.superTransmogrify(v, w, x, y, z);

Bởi vì trong ví dụ sau cả thismainphụ thuộc vào các loại v, w, x, yz. Ngoài ra, mã không thực sự trông tốt hơn nhiều, nếu mỗi khai báo phương thức có nửa tá đối số.

Một định vị dịch vụ thực sự đòi hỏi cách tiếp cận đầu tiên. Bạn không muốn truy cập vào thể hiện mà nó tạo thông qua trình định vị dịch vụ.

Vì vậy, "tiếp cận" thông qua một đối tượng có thể tạo ra rất nhiều sự phụ thuộc, càng nhiều hơn nếu nó dựa trên các thuộc tính của các lớp thực tế.
Tuy nhiên, việc tạo ra một sự trừu tượng, đó là tất cả về việc cung cấp một đối tượng là một điều hoàn toàn khác.

Ví dụ: bạn có thể có:

class Main implements IHasGetA, IHasGetA, IHasGetA, ITransmogrifyable {
    IHasGetB getA() { return this; }
    IHasGetC getB() { return this; }
    ITransmogrifyable getC() { return this; }
    void transmogrify(x,y) {
        return x + y;//yeah!
    }
}

Trường hợp mainlà một ví dụ của Main. Nếu lớp biết mainlàm giảm sự phụ thuộc IHasGetAthay vì Main, bạn sẽ thấy khớp nối thực sự khá thấp. Mã gọi thậm chí không biết nó thực sự đang gọi phương thức cuối cùng trên đối tượng ban đầu, thực sự minh họa mức độ tách rời.
Bạn đi dọc theo một con đường trừu tượng ngắn gọn và trực giao, thay vì đi sâu vào bên trong của một triển khai.


Điểm rất thú vị về sự gia tăng lớn về số lượng tham số.
Sồi

2

Các Luật của Demeter , như @ Péter Török chỉ ra, cho thấy hình thức "nhỏ gọn".

Ngoài ra, càng có nhiều phương thức mà bạn đề cập rõ ràng trong mã của mình, thì càng có nhiều lớp mà lớp của bạn phụ thuộc vào, làm tăng các vấn đề bảo trì. Trong ví dụ của bạn, dạng rút gọn phụ thuộc vào hai lớp, trong khi dạng dài hơn phụ thuộc vào bốn lớp. Hình thức dài hơn không chỉ trái với Luật Demeter; nó cũng sẽ khiến bạn thay đổi mã của mình mỗi khi bạn thay đổi bất kỳ phương thức nào trong bốn phương thức được tham chiếu (trái ngược với hai phương thức ở dạng rút gọn).


Mặt khác, theo luật đó một cách mù quáng có nghĩa là số lượng phương thức Asẽ bùng nổ với rất nhiều phương thức Acó thể muốn ủy thác đi. Tuy nhiên, tôi đồng ý với các phụ thuộc - điều đó làm giảm đáng kể số lượng phụ thuộc được yêu cầu từ mã máy khách.
Sồi

1
@Oak: Làm bất cứ điều gì một cách mù quáng không bao giờ là tốt. Người ta cần xem xét ưu và nhược điểm và đưa ra quyết định dựa trên bằng chứng. Điều này bao gồm cả Luật Demeter.
CesarGon

2

Tôi đã vật lộn với vấn đề này bản thân mình. Nhược điểm của việc "tiếp cận" sâu vào các đối tượng khác nhau là khi bạn thực hiện tái cấu trúc, cuối cùng bạn sẽ phải thay đổi rất nhiều mã vì có quá nhiều phụ thuộc. Ngoài ra, mã của bạn trở nên hơi khó đọc và khó đọc hơn.

Mặt khác, việc có các lớp chỉ đơn giản là các phương thức "chuyển qua" cũng có nghĩa là chi phí phải khai báo nhiều phương thức ở nhiều nơi.

Một giải pháp giảm thiểu điều này và phù hợp trong một số trường hợp là có một lớp nhà máy xây dựng một đối tượng mặt tiền bằng cách sao chép dữ liệu / đối tượng từ các lớp bị khủng bố. Bằng cách đó bạn có thể mã hóa đối tượng mặt tiền của mình và khi bạn cấu trúc lại, bạn chỉ cần thay đổi logic của nhà máy.


1

Tôi thường thấy rằng logic của một chương trình dễ dàng hơn để tìm kiếm các phương thức xích. Đối với tôi, customer.getLastInvoice().itemCount()phù hợp với bộ não của tôi tốt hơn customer.countLastInvoiceItems().

Cho dù đó là giá trị đau đầu bảo trì của việc có thêm khớp nối là tùy thuộc vào bạn. (Tôi cũng thích các hàm nhỏ trong các lớp nhỏ, vì vậy tôi có xu hướng xâu chuỗi. Tôi không nói nó đúng - đó chỉ là những gì tôi làm.)


đó phải là khách hàng IMO.NrLastInvoices hoặc khách hàng.LastInvoice.NrItems. Chuỗi đó không quá dài nên có lẽ không đáng để làm phẳng nếu số lượng kết hợp có phần lớn
Homde
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.