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 thực hành tốt?


29

Để có thể hoán đổi và kiểm tra được, thông thường các dịch vụ có logic cần phải có giao diện, ví dụ:

public class FooService: IFooService 
{ ... }

Thiết kế khôn ngoan, tôi đồng ý với điều này, nhưng một trong những điều làm phiền tôi với cách tiếp cận này là đối với một dịch vụ, bạn sẽ cần khai báo hai điều (lớp và giao diện), và trong nhóm của chúng tôi, thông thường là hai tệp (một cho lớp và một cho giao diện). Một khó chịu khác là khó khăn trong việc điều hướng vì sử dụng "Chuyển đến định nghĩa" trong IDE (VS2010) sẽ trỏ đến giao diện (vì các lớp khác tham chiếu đến giao diện), chứ không phải lớp thực tế.

Tôi đã nghĩ rằng viết IFooService trong cùng một tệp với FooService sẽ làm giảm sự kỳ lạ ở trên. Xét cho cùng, IFooService và FooService rất liên quan. Đây có phải là một thực hành tốt? Có lý do chính đáng nào để IFooService phải nằm trong tệp riêng của mình không?


3
Nếu bạn chỉ có một lần thực hiện một giao diện cụ thể thì không có nhu cầu logic thực sự nào cho giao diện đó. Tại sao không sử dụng lớp trực tiếp?
Joris Timmermans

11
@MadKeithV cho khớp nối lỏng lẻo và khả năng kiểm tra?
Louis Rhys

10
@MadKeithV: bạn đã đúng. Nhưng khi bạn viết các bài kiểm tra đơn vị cho mã dựa trên IFooService, thông thường bạn sẽ cung cấp MockFooService, đây một triển khai thứ hai của giao diện đó.
Doc Brown

5
@DocBrown - nếu bạn có nhiều triển khai thì rõ ràng bình luận sẽ biến mất, nhưng tiện ích của việc có thể đi trực tiếp đến "triển khai đơn lẻ" (vì có ít nhất hai). Sau đó, câu hỏi trở thành: thông tin nào trong triển khai cụ thể không nên có trong mô tả / tài liệu giao diện tại điểm bạn đang sử dụng giao diện?
Joris Timmermans

1
Tự quảng cáo trắng trợn: stackoverflow.com/questions/5840219/ từ
Marjan Venema

Câu trả lời:


24

Nó không phải ở trong tệp riêng của nó, nhưng nhóm của bạn nên quyết định một tiêu chuẩn và tuân theo nó.

Ngoài ra, bạn đúng rằng "Chuyển đến định nghĩa" sẽ đưa bạn đến giao diện, nhưng nếu bạn đã cài đặt Resharper , chỉ cần một cú nhấp chuột để mở danh sách các lớp / giao diện dẫn xuất từ ​​giao diện đó, vì vậy đó không phải là vấn đề lớn. Đó là lý do tại sao tôi giữ giao diện trong một tệp riêng biệt.

 


5
Tôi đồng ý với câu trả lời này, sau tất cả các IDE nên điều chỉnh theo thiết kế của chúng tôi chứ không phải theo cách khác, phải không?
marktani

1
Ngoài ra, không có Resharper, F12 và Shift + F12 cũng làm điều tương tự. Thậm chí không phải là một cú nhấp chuột phải :) (công bằng mà nói, nó sẽ chỉ đưa bạn đến việc sử dụng trực tiếp giao diện, không chắc phiên bản Resharper làm gì)
Daniel B

@DanielB: Trong Resharper, Alt-End đi đến phần triển khai (hoặc trình bày cho bạn một danh sách các triển khai có thể thực hiện).
StriplingWar Warrior

@StriplingWar Warrior thì có vẻ như đó chính xác là những gì vanilla VS làm. F12 = đi đến định nghĩa, Shift + F12 = đi đến thực hiện (Tôi khá chắc chắn điều này hoạt động mà không cần Resharper, mặc dù tôi đã cài đặt nó, vì vậy rất khó để xác nhận). Đây là một trong những phím tắt điều hướng hữu ích nhất, tôi chỉ truyền bá từ này.
Daniel B

@DanielB: Mặc định cho Shift-F12 là "tìm cách sử dụng." jetbrains.com/resharper/webhelp/ từ
StriplingWar

15

Tôi nghĩ bạn nên giữ chúng riêng biệt. Như bạn đã nói, ý tưởng là vẫn có thể kiểm tra và hoán đổi cho nhau. Bằng cách đặt giao diện trong cùng một tệp với triển khai của bạn, bạn đang liên kết giao diện với việc triển khai cụ thể. Nếu bạn quyết định tạo một đối tượng giả hoặc triển khai khác, giao diện sẽ không tách biệt về mặt logic với FooService.


10

Theo RẮN, không chỉ bạn nên tạo giao diện, và không chỉ nên ở trong một tệp khác, nó phải ở trong một cụm khác.

Tại sao? Bởi vì bất kỳ thay đổi nào đối với tệp nguồn biên dịch thành tập hợp đều yêu cầu biên dịch lại tập hợp và bất kỳ thay đổi nào đối với tập hợp đều yêu cầu biên dịch lại bất kỳ tập hợp phụ thuộc nào. Vì vậy, nếu mục tiêu của bạn, dựa trên RẮN, là có thể thay thế triển khai A bằng triển khai B, trong khi lớp C phụ thuộc vào giao diện tôi không phải biết sự khác biệt, bạn phải đảm bảo lắp ráp với I trong đó không thay đổi, do đó bảo vệ tập quán.

"Nhưng nó chỉ là một biên dịch lại" Tôi nghe bạn phản đối. Cũng có thể, nhưng trong ứng dụng điện thoại thông minh của bạn, băng thông dữ liệu của người dùng sẽ dễ dàng hơn; tải xuống một nhị phân đã thay đổi, hoặc tải xuống nhị phân đó và năm mã khác có mã phụ thuộc vào nó? Không phải mọi chương trình được viết để được sử dụng bởi các máy tính để bàn trên mạng LAN. Ngay cả trong trường hợp đó, nơi băng thông và bộ nhớ rẻ, các bản phát hành bản vá nhỏ hơn có thể có giá trị vì chúng không đáng kể để đẩy ra toàn bộ mạng LAN thông qua Active Directory hoặc các lớp quản lý miền tương tự; Người dùng của bạn sẽ đợi chỉ vài giây để nó được áp dụng vào lần tiếp theo họ đăng nhập thay vì vài phút để toàn bộ nội dung được cài đặt lại. Chưa kể rằng, càng ít các hội đồng phải được biên dịch lại khi xây dựng một dự án, nó sẽ xây dựng càng nhanh,

Bây giờ, từ chối trách nhiệm: Điều này không phải lúc nào cũng có thể hoặc khả thi để làm. Cách dễ nhất để làm điều này là tạo một dự án "giao diện" tập trung. Điều này có nhược điểm riêng của nó; mã trở nên ít sử dụng lại vì dự án giao diện VÀ dự án triển khai phải được tham chiếu trong các ứng dụng khác sử dụng lại lớp kiên trì hoặc các thành phần chính khác trong ứng dụng của bạn. Bạn có thể khắc phục vấn đề đó bằng cách chia các giao diện thành các cụm được ghép chặt chẽ hơn, nhưng sau đó bạn có nhiều dự án hơn trong ứng dụng của mình, điều này khiến việc xây dựng đầy đủ trở nên rất đau đớn. Chìa khóa là sự cân bằng và duy trì thiết kế lỏng lẻo; bạn thường có thể di chuyển các tệp xung quanh khi cần thiết, vì vậy khi bạn thấy rằng một lớp sẽ cần nhiều thay đổi hoặc việc triển khai giao diện mới sẽ cần thường xuyên (có lẽ để giao tiếp với các phiên bản mới được hỗ trợ của phần mềm khác,


8

Tại sao có các tập tin riêng biệt là một khó chịu? Đối với tôi, nó gọn gàng và sạch sẽ hơn nhiều. Việc tạo một thư mục con có tên "Giao diện" và dán các tệp IFooServer.cs của bạn ở đó là điều phổ biến, nếu bạn muốn xem ít tệp hơn trong Solution Explorer.

Lý do giao diện được định nghĩa trong tệp riêng của nó là vì lý do tương tự rằng các lớp thường được định nghĩa trong tệp riêng của chúng: quản lý dự án đơn giản hơn khi cấu trúc logic và cấu trúc tệp của bạn giống hệt nhau, vì vậy bạn luôn biết tệp nào được xác định lớp nào trong. Điều này có thể làm cho cuộc sống của bạn dễ dàng hơn khi gỡ lỗi (dấu vết ngăn xếp ngoại lệ thường cung cấp cho bạn số tệp và số dòng) hoặc khi hợp nhất mã nguồn trong kho lưu trữ kiểm soát nguồn.


6

Nó thường là một thực hành tốt để cho các tệp mã của bạn chỉ chứa một lớp hoặc một giao diện duy nhất. Nhưng các thực tiễn mã hóa này là một phương tiện để kết thúc - để cấu trúc tốt hơn mã của bạn làm cho nó dễ làm việc hơn. Nếu bạn và nhóm của bạn, thấy dễ dàng làm việc hơn nếu các lớp được giữ cùng với giao diện của chúng, bằng mọi cách hãy làm như vậy.

Cá nhân tôi thích có giao diện và lớp trong cùng một tệp khi chỉ có một lớp thực hiện giao diện, chẳng hạn như trong trường hợp của bạn.

Liên quan đến các vấn đề bạn gặp phải khi điều hướng, tôi rất có thể khuyên dùng ReSharper . Nó chứa một số phím tắt rất hữu ích để chuyển trực tiếp đến phương thức thực hiện một phương thức giao diện cụ thể.


3
+1 để chỉ ra rằng các thực tiễn của nhóm nên ra lệnh cho cấu trúc tệp và có cách tiếp cận trái với cách tiếp cận thông thường.

3

Có nhiều lúc bạn muốn giao diện của mình không chỉ trong một tệp riêng biệt với lớp, mà ngay cả trong một cụm riêng biệt hoàn toàn.

Ví dụ: giao diện Hợp đồng dịch vụ WCF có thể được chia sẻ bởi cả khách hàng và dịch vụ nếu bạn có quyền kiểm soát cả hai đầu dây. Bằng cách di chuyển giao diện vào lắp ráp riêng, nó sẽ có ít phụ thuộc lắp ráp hơn. Điều này giúp khách hàng dễ dàng tiêu thụ hơn, nới lỏng khớp nối với việc thực hiện.


2

Rất hiếm khi có một cách thực hiện giao diện 1 . Nếu bạn đặt một giao diện chung và một lớp công khai triển khai giao diện đó vào cùng một tệp, rất có thể là bạn không cần giao diện.

Khi lớp mà bạn cùng định vị với giao diện là trừu tượng và bạn biết rằng tất cả các cài đặt của giao diện của bạn sẽ kế thừa lớp trừu tượng đó, định vị hai trong cùng một tệp có ý nghĩa. Bạn vẫn nên xem xét kỹ quyết định của mình để sử dụng một giao diện: hãy tự hỏi liệu một lớp trừu tượng có ổn không, và bỏ giao diện nếu câu trả lời là khẳng định.

Tuy nhiên, nói chung, bạn nên tuân theo chiến lược "một lớp công khai / giao diện tương ứng với một tệp": nó rất dễ thực hiện và nó làm cho cây nguồn của bạn dễ điều hướng hơn.


1 Một ngoại lệ đáng chú ý là khi bạn cần có giao diện cho mục đích thử nghiệm, bởi vì lựa chọn khung mô phỏng của bạn đặt yêu cầu bổ sung này vào mã của bạn.


3
Trên thực tế, đây là một mẫu khá bình thường khi có một giao diện cho một lớp trong .NET, vì nó cho phép các bài kiểm tra đơn vị thay thế các phụ thuộc bằng các giả, cuống, spys hoặc các phép thử khác.
Pete

2
@Pete Tôi đã chỉnh sửa câu trả lời của mình để đưa nó vào đây như một ghi chú. Tôi sẽ không gọi mẫu này là "bình thường", vì nó cho phép mối quan tâm kiểm tra của bạn "rò rỉ" vào cơ sở mã chính của bạn. Các khung mô phỏng hiện đại giúp bạn khắc phục vấn đề này ở một mức độ lớn, nhưng có một giao diện ở đó chắc chắn đơn giản hóa mọi thứ rất nhiều.
dasblinkenlight

2
@KeithS Một lớp không có giao diện chỉ có trong thiết kế của bạn khi bạn chết - chắc chắn rằng nó không có ý nghĩa gì để phân lớp nó và bạn tạo lớp đó sealed. Nếu bạn nghi ngờ rằng một lúc nào đó trong tương lai bạn có thể thêm một lớp con thứ hai, bạn sẽ đặt ngay vào một giao diện. PS Một trợ lý tái cấu trúc chất lượng tốt có thể là của bạn với mức giá khiêm tốn của một giấy phép ReSharper duy nhất :)
dasblinkenlight

1
@KeithS Và vâng, tất cả các lớp của bạn không được thiết kế riêng cho phân lớp nên được niêm phong. Trong thực tế, các lớp mong muốn được niêm phong theo mặc định, buộc bạn phải chỉ định các lớp để kế thừa một cách rõ ràng, giống như cách mà ngôn ngữ hiện đang buộc bạn chỉ định các hàm để ghi đè bằng cách đánh dấu chúng virtual.
dasblinkenlight

2
@KeithS: Điều gì xảy ra khi một triển khai của bạn trở thành hai? Giống như khi thực hiện của bạn thay đổi; bạn thay đổi mã để phản ánh sự thay đổi đó. YAGNI áp dụng ở đây. Bạn sẽ không bao giờ biết điều gì sẽ thay đổi trong tương lai, vì vậy hãy làm điều đơn giản nhất có thể. (Thật không may khi thử nghiệm các mớ hỗn độn với câu châm ngôn này)
Groky

2

Các giao diện thuộc về khách hàng của họ không thuộc về việc triển khai của họ, như được định nghĩa trong Nguyên tắc, Thực tiễn và Mô hình Agile của Robert C. Martin. Vì vậy, kết hợp giao diện và thực hiện ở cùng một nơi là trái với nguyên tắc của nó.

Mã khách hàng phụ thuộc vào giao diện. Điều này cho phép khả năng biên dịch và triển khai mã máy khách và giao diện mà không cần thực hiện. Hơn bạn có thể có các triển khai khác nhau hoạt động như các plugin cho mã máy khách.

CẬP NHẬT: Đây không phải là một nguyên tắc chỉ nhanh nhẹn ở đây. Gang of Four trong các mẫu thiết kế, trở lại năm 94, đã nói về các khách hàng tuân thủ các giao diện và lập trình với các giao diện. Quan điểm của họ là tương tự nhau.


1
Việc sử dụng Giao diện vượt xa phạm vi mà Agile sở hữu. Agile là một phương pháp phát triển sử dụng các cấu trúc ngôn ngữ theo những cách cụ thể. Giao diện là một cấu trúc ngôn ngữ.

1
Có, nhưng giao diện vẫn thuộc về khách hàng và không phải là triển khai, bất kể thực tế là chúng ta đang nói về sự nhanh nhẹn hay không.
Patkos Csaba

Tôi ở đấy cùng bạn.
Marjan Venema

0

Cá nhân tôi không thích "tôi" trước giao diện. Là người dùng như vậy, tôi không muốn biết đây có phải là giao diện hay không. Loại là điều thú vị. Về mặt phụ thuộc, FooService của bạn là MỘT triển khai IFooService có thể. Giao diện nên ở gần nơi sử dụng. Mặt khác, việc thực hiện phải ở một nơi có thể dễ dàng thay đổi mà không ảnh hưởng đến khách hàng. Vì vậy, tôi muốn hai tập tin - bình thường.


2
Là một người dùng, tôi muốn biết liệu một loại là một giao diện, vì ví dụ, điều đó có nghĩa là tôi không thể sử dụng newvới nó, nhưng mặt khác, tôi có thể sử dụng nó theo cách khác hoặc ngược lại. Và đó Ilà một quy ước đặt tên được thiết lập trong .Net, vì vậy đó là một lý do tốt để tuân thủ nó, nếu chỉ để thống nhất với các loại khác.
Svick

Bắt đầu với Igiao diện chỉ là phần giới thiệu cho các lập luận của tôi về sự tách biệt trong hai tệp. Mã này dễ đọc hơn và nó làm cho sự đảo ngược của các phụ thuộc rõ ràng hơn.
ollins

4
Dù muốn hay không, sử dụng 'I' trước tên giao diện là một tiêu chuẩn thực tế trong .NET. Theo quan điểm của tôi, việc không tuân theo tiêu chuẩn trong một dự án .NET sẽ là vi phạm "nguyên tắc ít ngạc nhiên nhất".
Pete

Điểm chính của tôi là: riêng biệt trong hai tập tin. Một cho sự trừu tượng và một cho việc thực hiện. Nếu việc sử dụng Ilà bình thường trong môi trường của bạn: Sử dụng nó! (Nhưng tôi không thích nó :))
ollins

Và trong trường hợp của P.SE, tiền tố I ở phía trước một giao diện làm cho rõ ràng hơn rằng người đăng đang nói về một giao diện, không kế thừa từ một lớp khác.

0

Mã hóa cho các giao diện có thể là xấu, trên thực tế được coi là một mô hình chống đối với các bối cảnh nhất định và không phải là một luật. Thông thường một ví dụ điển hình về mã hóa giao diện chống mẫu là khi bạn có một giao diện chỉ có một triển khai trong ứng dụng của bạn. Một cách khác là nếu tệp giao diện trở nên khó chịu đến mức bạn phải ẩn khai báo của nó trong tệp của lớp đã triển khai.


chỉ có một triển khai trong ứng dụng giao diện của bạn không nhất thiết là điều xấu; người ta có thể bảo vệ ứng dụng chống lại sự thay đổi công nghệ. Ví dụ: giao diện cho công nghệ lưu trữ dữ liệu. Bạn sẽ chỉ có một triển khai cho công nghệ duy trì dữ liệu hiện tại trong khi bảo vệ ứng dụng nếu điều đó thay đổi. Vì vậy, sẽ chỉ có một imp tại thời điểm đó.
TSmith

0

Đặt một lớp và một giao diện vào cùng một tệp sẽ không có giới hạn về cách sử dụng. Một giao diện vẫn có thể được sử dụng cho giả, v.v ... theo cùng một cách, ngay cả khi nó nằm trong cùng một tệp với một lớp thực hiện nó. Câu hỏi có thể được coi hoàn toàn là một trong những tiện lợi của tổ chức, trong trường hợp đó tôi sẽ nói làm bất cứ điều gì làm cho cuộc sống của bạn dễ dàng hơn!

Tất nhiên, người ta có thể lập luận rằng việc có hai cái trong cùng một tệp có thể dẫn đến việc không muốn coi chúng được ghép nối theo chương trình nhiều hơn so với thực tế, đó là một rủi ro.

Cá nhân, nếu tôi bắt gặp một codebase sử dụng một quy ước khác, tôi sẽ không quá quan tâm.


Đó không chỉ là "sự tiện lợi của tổ chức" ... mà còn là vấn đề quản lý rủi ro, triển khai mgmt, truy xuất mã nguồn, kiểm soát thay đổi, kiểm soát nguồn, tiếng ồn bảo mật. Có nhiều góc độ cho câu hỏi này.
TSmith
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.