Hai định nghĩa trái ngược nhau về Nguyên tắc phân chia giao diện - cái nào đúng?


14

Khi đọc các bài viết về ISP, dường như có hai định nghĩa trái ngược nhau về ISP:

Theo định nghĩa đầu tiên (xem 1 , 2 , 3 ), ISP tuyên bố rằng các lớp thực hiện giao diện không nên bị buộc phải thực hiện các chức năng mà họ không cần. Do đó, giao diện chất béoIFat

interface IFat
{
     void A();
     void B();
     void C();
     void D();
}

class MyClass: IFat
{ ... }

nên được chia thành các giao diện nhỏ hơn ISmall_1ISmall_2

interface ISmall_1
{
     void A();
     void B();
}

interface ISmall_2
{
     void C();
     void D();
}

class MyClass:ISmall_2
{ ... }

vì theo cách này, tôi MyClasschỉ có thể thực hiện các phương thức mà nó cần ( D()C()), mà không bị buộc phải cung cấp các triển khai giả cho A(), B()C():

Nhưng theo định nghĩa thứ hai (xem 1 , 2 , câu trả lời của Nazar Merza ), ISP tuyên bố rằng MyClientcác phương thức gọi trên MyServicekhông nên biết về các phương thức MyServicemà nó không cần. Nói cách khác, nếu MyClientchỉ cần chức năng của C()D(), thì thay vì

class MyService 
{
    public void A();
    public void B();
    public void C();
    public void D();
}

/*client code*/      
MyService service = ...;
service.C(); 
service.D();

chúng ta nên tách biệt MyService'scác phương thức thành các giao diện dành riêng cho khách hàng :

public interface ISmall_1
{
     void A();
     void B();
}

public interface ISmall_2
{
     void C();
     void D();
}

class MyService:ISmall_1, ISmall_2 
{ ... }

/*client code*/
ISmall_2 service = ...;
service.C(); 
service.D();

Do đó, với định nghĩa trước đây, mục tiêu của ISP là " làm cho cuộc sống của các lớp thực hiện giao diện IFat trở nên dễ dàng hơn ", trong khi với mục tiêu sau của ISP là " làm cho cuộc sống của các phương thức gọi của MyService dễ dàng hơn ".

Điều nào trong hai định nghĩa khác nhau của ISP là thực sự chính xác?

@MARJAN VENema

1.

Vì vậy, khi bạn định chia IFat thành giao diện nhỏ hơn, phương thức nào kết thúc trong đó ISmallinterface sẽ được quyết định dựa trên mức độ gắn kết của các thành viên.

Mặc dù có ý nghĩa khi đặt các phương thức gắn kết trong cùng một giao diện, tôi nghĩ với mẫu ISP, nhu cầu của khách hàng được ưu tiên hơn "tính cố kết" của giao diện. Nói cách khác, tôi nghĩ với ISP, chúng ta nên gộp trong cùng một giao diện mà các phương thức cần thiết cho các khách hàng cụ thể, ngay cả khi điều đó có nghĩa là thoát khỏi giao diện đó, những phương thức nào, vì lợi ích của sự gắn kết, cũng được đưa vào cùng giao diện đó?

Do đó, nếu có rất nhiều khách hàng sẽ chỉ cần gọi CutGreens, nhưng cũng không GrillMeat, thì để tuân thủ mẫu ISP, chúng ta chỉ nên đặt CutGreensbên trong ICook, nhưng cũng không GrillMeat, mặc dù hai phương thức này rất gắn kết?!

2.

Tôi nghĩ rằng sự nhầm lẫn của bạn bắt nguồn từ một giả định ẩn trong định nghĩa đầu tiên: rằng các lớp thực hiện đã tuân theo nguyên tắc trách nhiệm duy nhất.

Bằng cách "thực hiện các lớp không tuân theo SRP", bạn đang đề cập đến các lớp thực hiện IFathoặc các lớp thực hiện ISmall_1/ ISmall_2? Tôi giả sử bạn đang đề cập đến các lớp thực hiện IFat? Nếu vậy, tại sao bạn cho rằng họ đã không theo dõi SRP?

cảm ơn


4
Tại sao không thể có nhiều định nghĩa được phục vụ theo cùng một nguyên tắc?
Bobson

5
Những định nghĩa này không mâu thuẫn với nhau.
Mike Partridge

1
Tất nhiên, nhu cầu của khách hàng không được ưu tiên hơn về tính cố kết của giao diện. Bạn có thể thực hiện cách "quy tắc" này đến tận cùng và kết thúc với các giao diện phương thức duy nhất ở khắp mọi nơi mà hoàn toàn không có ý nghĩa gì. Dừng theo các quy tắc và bắt đầu nghĩ về các mục tiêu mà các quy tắc này được tạo ra. Với "các lớp không theo SRP", tôi đã không nói về bất kỳ lớp cụ thể nào trong ví dụ của bạn hoặc rằng họ chưa theo dõi SRP. Đọc lại lần nữa. Định nghĩa đầu tiên chỉ dẫn đến việc chia tách một giao diện nếu giao diện không theo ISP và lớp đang theo SRP.
Marjan Venema

2
Định nghĩa thứ hai không quan tâm đến những người thực hiện. Nó xác định các giao diện theo quan điểm của người gọi và không đưa ra bất kỳ giả định nào về việc người triển khai đã tồn tại hay chưa. Nó có thể giả định rằng khi bạn theo dõi ISP và đến để thực hiện các giao diện đó, tất nhiên, bạn sẽ theo SRP khi tạo chúng.
Marjan Venema

2
Làm thế nào để bạn biết trước những khách hàng nào sẽ tồn tại và họ cần những phương pháp nào? Bạn không thể. Những gì bạn có thể biết trước khi ra tay là giao diện của bạn gắn kết như thế nào.
Tulains Córdova

Câu trả lời:


6

Cả hai đều đúng

Cách tôi đọc nó, mục đích của ISP (Nguyên tắc phân chia giao diện) là giữ cho các giao diện nhỏ và tập trung: tất cả các thành viên giao diện nên có sự gắn kết rất cao. Cả hai định nghĩa đều nhằm tránh các giao diện "jack-of-all-giao dịch-master-of-none".

Phân biệt giao diện và SRP (Nguyên tắc trách nhiệm đơn) có cùng một mục tiêu: đảm bảo các thành phần phần mềm nhỏ, có tính gắn kết cao. Họ bổ sung cho nhau. Phân biệt giao diện đảm bảo rằng các giao diện nhỏ, tập trung và có tính gắn kết cao. Theo nguyên tắc trách nhiệm duy nhất đảm bảo rằng các lớp học nhỏ, tập trung và gắn kết cao.

Định nghĩa đầu tiên bạn đề cập tập trung vào người thực hiện, thứ hai về khách hàng. Mà trái với @ user61852, tôi coi là người dùng / người gọi giao diện chứ không phải người thực hiện.

Tôi nghĩ rằng sự nhầm lẫn của bạn bắt nguồn từ một giả định ẩn trong định nghĩa đầu tiên: rằng các lớp thực hiện đã tuân theo nguyên tắc trách nhiệm duy nhất.

Đối với tôi định nghĩa thứ hai, với các khách hàng là người gọi giao diện, là một cách tốt hơn để đạt được mục tiêu dự định.

Cách ly

Trong câu hỏi của bạn, bạn nêu:

vì cách này, MyClass của tôi chỉ có thể triển khai các phương thức mà nó cần (D () và C ()), mà không bị buộc phải cung cấp các triển khai giả cho A (), B () và C ():

Nhưng điều đó đang làm đảo lộn thế giới.

  • Một lớp thực hiện một giao diện không ra lệnh những gì nó cần trong giao diện mà nó đang thực hiện.
  • Các giao diện chỉ ra những phương thức mà một lớp triển khai sẽ cung cấp.
  • Những người gọi của một giao diện thực sự là những người quyết định chức năng nào họ cần giao diện để cung cấp cho họ và do đó những gì người thực hiện nên cung cấp.

Vì vậy, khi bạn định chia IFatthành giao diện nhỏ hơn, phương thức nào ISmallsẽ được quyết định dựa trên giao diện nào dựa trên mức độ gắn kết của các thành viên.

Hãy xem xét giao diện này:

interface IEverythingButTheKitchenSink
{
     void DoDishes();
     void CleanSink();
     void CutGreens();
     void GrillMeat();
}

Những phương pháp bạn sẽ đưa vào ICookvà tại sao? Bạn có muốn kết CleanSinkhợp với GrillMeatchỉ vì bạn tình cờ có một lớp chỉ có điều đó và một vài thứ khác nhưng không giống bất kỳ phương thức nào khác không? Hoặc bạn sẽ chia nó thành hai giao diện gắn kết hơn, chẳng hạn như:

interface IClean
{
     void DoDishes();
     void CleanSink();
}

interface ICook
{
     void CutGreens();
     void GrillMeat();
}

Giao diện khai báo

Một định nghĩa giao diện tốt nhất nên là riêng trong một đơn vị riêng biệt, nhưng nếu nó thực sự cần phải sống với người gọi hoặc người thực hiện, thì nó thực sự nên có với người gọi. Mặt khác, người gọi nhận được sự phụ thuộc ngay lập tức vào người thực hiện đang hoàn toàn đánh bại mục đích của giao diện. Xem thêm: Khai báo giao diện trong cùng một tệp với lớp cơ sở, đó có phải là một cách thực hành tốt không? trên các lập trình viên và tại sao chúng ta nên đặt giao diện với các lớp sử dụng chúng thay vì các giao diện triển khai chúng? trên StackOverflow.


1
Bạn có thể xem bản cập nhật tôi đã thực hiện?
EdvRusj

"người gọi nhận được sự phụ thuộc ngay lập tức vào người triển khai " ... chỉ khi bạn vi phạm DIP (nguyên tắc đảo ngược phụ thuộc), nếu các biến nội bộ của tham số, tham số, giá trị trả về, v.v. thuộc loại ICookthay vì loại SomeCookImplementor, như ủy quyền của DIP, thì nó không 'phải phụ thuộc vào SomeCookImplementor.
Tulains Córdova

@ user61852: Nếu khai báo giao diện và người triển khai trong cùng một đơn vị, tôi ngay lập tức nhận được sự phụ thuộc vào người triển khai đó. Không nhất thiết là trong thời gian chạy, nhưng chắc chắn nhất là ở cấp độ dự án, đơn giản là bởi thực tế là nó ở đó. Dự án không còn có thể biên dịch mà không có nó hoặc bất cứ thứ gì nó sử dụng. Ngoài ra, Dependency Injection không giống như Nguyên tắc đảo ngược phụ thuộc. Bạn có thể quan tâm DIP trong tự nhiên
Marjan Venema

Tôi đã sử dụng lại các ví dụ mã của bạn trong câu hỏi này lập trình viên.stackexchange.com/a/271142/61852 , cải thiện nó sau khi nó đã được chấp nhận. Tôi đã cho bạn tín dụng do các ví dụ.
Tulains Córdova

Cool @ user61852 :) (và cảm ơn về khoản tín dụng)
Marjan Venema

14

Bạn nhầm lẫn từ "khách hàng" như được sử dụng trong tài liệu của Gang of Four với "khách hàng" như người tiêu dùng dịch vụ.

Một "khách hàng", theo dự định của Gang of Four, là một lớp thực hiện một giao diện. Nếu lớp A thực hiện giao diện B, thì họ nói A là khách hàng của B. Nếu không, cụm từ "khách hàng không nên bị buộc phải thực hiện giao diện mà họ không sử dụng" sẽ không có ý nghĩa vì "khách hàng" (như trong người tiêu dùng) không Không thực hiện bất cứ điều gì. Cụm từ chỉ có ý nghĩa khi bạn xem "khách hàng" là "người thực hiện".

Nếu "client" có nghĩa là một lớp "tiêu thụ" (gọi) các phương thức của một lớp khác thực hiện giao diện lớn, thì bằng cách gọi hai phương thức bạn quan tâm và bỏ qua phần còn lại, sẽ đủ để bạn tách rời khỏi phần còn lại của phương pháp bạn không sử dụng.

Tinh thần của nguyên tắc là tránh "khách hàng" (lớp thực hiện giao diện) phải thực hiện các phương thức giả để tuân thủ toàn bộ giao diện khi nó chỉ quan tâm đến một tập hợp các phương thức có liên quan.

Ngoài ra, nó nhằm mục đích có số lượng khớp nối càng ít càng tốt để những thay đổi được thực hiện ở một nơi gây ra ít tác động hơn. Bằng cách tách biệt các giao diện, bạn giảm khớp nối.

Vấn đề đó xuất hiện khi giao diện làm quá nhiều và có các phương thức nên được chia thành nhiều giao diện thay vì chỉ một.

Cả hai ví dụ mã của bạn đều ổn . Chỉ có điều trong phần hai bạn giả sử "client" có nghĩa là "một lớp tiêu thụ / gọi các dịch vụ / phương thức được cung cấp bởi một lớp khác".

Tôi thấy không có mâu thuẫn trong các khái niệm được giải thích trong ba liên kết bạn đưa ra.

Chỉ cần nói rõ rằng "khách hàng" là người triển khai , trong cuộc nói chuyện RẮN.


Nhưng theo @pdr, trong khi các ví dụ mã trong tất cả các liên kết tuân thủ ISP, định nghĩa của ISP lại thiên về "cách ly máy khách (một lớp gọi các phương thức của lớp khác) khỏi biết nhiều hơn về dịch vụ" hơn là về " ngăn chặn từ khách hàng (người thực hiện) bị buộc phải thực hiện các giao diện họ không sử dụng. "
EdvRusj

1
@EdvRusj Câu trả lời của tôi dựa trên các tài liệu trên trang web Object Mentor (doanh nghiệp Bob Martin), được viết bởi chính Martin khi anh ấy ở trong Gang of Four nổi tiếng. Như bạn đã biết Gnag of Four là một nhóm các kỹ sư phần mềm, bao gồm Martin, đã đặt ra từ viết tắt RẮN, xác định và ghi lại các nguyên tắc. docs.google.com/a/cleancoder.com/file/d/ từ
Tulains Córdova

Vì vậy, bạn không đồng ý với @pdr và do đó bạn tìm thấy định nghĩa đầu tiên về ISP (xem bài viết gốc của tôi) dễ chịu hơn?
EdvRusj

@EdvRusj Tôi nghĩ cả hai đều đúng. Nhưng điều thứ hai thêm vào sự nhầm lẫn không cần thiết bằng cách sử dụng phép ẩn dụ của máy khách / máy chủ. Nếu tôi phải chọn một, tôi sẽ đi với Gang of Four chính thức, đây là lần đầu tiên. Nhưng điều quan trọng là làm giảm sự ghép đôi và các phụ thuộc không cần thiết, đó là tinh thần của các nguyên tắc RẮN sau tất cả. Không quan trọng cái nào đúng. Điều quan trọng là bạn nên tách biệt các giao diện theo bahaviors. Đó là tất cả. Nhưng khi nghi ngờ, chỉ cần đi đến nguồn ban đầu.
Tulains Córdova

3
Tôi rất không đồng ý với khẳng định của bạn rằng "khách hàng" là người triển khai trong cuộc nói chuyện RẮN. Đối với một người, thật vô nghĩa khi gọi một nhà cung cấp (người thực hiện) một khách hàng về những gì họ đang cung cấp (thực hiện). Tôi cũng chưa thấy bài viết nào về RẮN cố gắng truyền đạt điều này, nhưng đơn giản là tôi có thể đã bỏ lỡ điều đó. Quan trọng nhất mặc dù nó thiết lập trình triển khai của một giao diện là người quyết định những gì sẽ có trong giao diện. Và điều đó không có ý nghĩa với tôi. Người gọi / người dùng của một giao diện xác định những gì họ cần từ một giao diện và những người triển khai (số nhiều) của giao diện đó bị ràng buộc để cung cấp nó.
Marjan Venema

5

ISP là tất cả về cách ly khách hàng khỏi biết nhiều hơn về dịch vụ mà họ cần biết (ví dụ: bảo vệ nó trước những thay đổi không liên quan). Định nghĩa thứ hai của bạn là chính xác. Theo tôi đọc, chỉ có một trong ba bài viết đó gợi ý khác ( bài đầu tiên ) và nó hoàn toàn sai. (Chỉnh sửa: Không, không sai, chỉ gây hiểu nhầm.)

Định nghĩa đầu tiên được liên kết chặt chẽ hơn nhiều với LSP.


3
Trong ISP, khách hàng không nên bị ép buộc TIÊU DÙNG các thành phần giao diện mà họ không sử dụng. Trong LSP, DỊCH VỤ không nên bị buộc phải thực hiện phương thức D vì mã gọi yêu cầu phương thức A. Chúng không mâu thuẫn, chúng bổ sung cho nhau.
pdr

2
@EdvRusj, đối tượng thực hiện InterfaceA mà ClientA gọi trên thực tế có thể là cùng một đối tượng thực hiện InterfaceB cần thiết cho Client B. Trong những trường hợp hiếm hoi mà cùng một khách hàng cần xem cùng một đối tượng như các Class khác nhau, mã sẽ không thường là "chạm". Bạn sẽ xem nó như một A cho một mục đích và B cho mục đích khác.
Amy Blankenship

1
@EdvRusj: Nó có thể hữu ích nếu bạn suy nghĩ lại về định nghĩa của bạn về giao diện ở đây. Nó không phải luôn luôn là một giao diện theo thuật ngữ C # / Java. Bạn có thể có một dịch vụ phức tạp với một số lớp đơn giản bao quanh nó, như vậy máy khách A sử dụng lớp bọc AX để "giao diện" với dịch vụ X. Vì vậy, khi bạn thay đổi X theo cách ảnh hưởng đến A và AX, bạn không buộc phải ảnh hưởng đến BX và B.
pdr

1
@EdvRusj: Sẽ chính xác hơn khi nói rằng A và B không quan tâm nếu cả hai đều gọi X hoặc một người đang gọi Y và người kia gọi Z. THAT là điểm cơ bản của ISP. Vì vậy, bạn có thể chọn thực hiện mà bạn đi, và dễ dàng thay đổi tâm trí của bạn sau này. ISP không ủng hộ tuyến này hay tuyến kia, nhưng LSP và SRP có thể.
pdr

1
@EdvRusj Không, khách hàng A sẽ có thể thay thế Dịch vụ X bằng Dịch vụ y, cả hai đều thực hiện giao diện AX. X và / hoặc Y có thể triển khai các Giao diện khác, nhưng khi Khách hàng gọi chúng là AX, nó không quan tâm đến các Giao diện khác đó.
Amy Blankenship
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.