2009-08-05 35 views
30

Trong khi một số nguyên tắc nhất định bạn nên sử dụng giao diện khi bạn muốn xác định hợp đồng cho lớp không thừa kế (IDomesticated) và thừa kế khi lớp là phần mở rộng của lớp khác (Cat : Mammal, Snake : Reptile), có trường hợp (theo ý kiến ​​của tôi) những hướng dẫn này đi vào một khu vực màu xám.Khi nào sử dụng giao diện hoặc lớp trừu tượng? Khi nào sử dụng cả hai?

Ví dụ: giả sử triển khai của tôi là Cat : Pet. Pet là một lớp trừu tượng. Nếu được mở rộng thành Cat : Mammal, IDomesticated trong đó Mammal là lớp trừu tượng và IDomesticated là giao diện? Hoặc tôi có xung đột với nguyên tắc KISS/YAGNI (mặc dù tôi không chắc chắn liệu sẽ có một lớp học Wolf trong tương lai không thể kế thừa từ Pet)?

Di chuyển khỏi ẩn dụ Cat s và Pet s, giả sử tôi có một số lớp đại diện cho nguồn cho dữ liệu đến. Tất cả họ đều cần phải thực hiện cùng một cơ sở bằng cách nào đó. Tôi có thể thực hiện một số mã chung trong một lớp trừu tượng Source và kế thừa từ nó. Tôi cũng có thể chỉ cần thực hiện một giao diện ISource (mà cảm thấy nhiều hơn "đúng" với tôi) và tái triển khai mã chung trong mỗi lớp (ít trực quan hơn). Cuối cùng, tôi có thể "ăn bánh và ăn nó" bằng cách tạo cả lớp trừu tượng và giao diện. Điều gì là tốt nhất?

Hai trường hợp này mang lại điểm để chỉ sử dụng lớp trừu tượng, chỉ có giao diện và sử dụng cả lớp trừu tượng và giao diện. Có phải tất cả các lựa chọn hợp lệ này hay có "quy tắc" cho thời điểm nào nên được sử dụng trên một quy tắc khác không?


Tôi muốn làm rõ rằng bằng cách "sử dụng cả một lớp trừu tượng và một giao diện" bao gồm các trường hợp khi họ về cơ bản đại diện cho điều tương tự (SourceISource cả hai đều có cùng các thành viên), nhưng lớp thêm chức năng chung trong khi giao diện chỉ định hợp đồng.

Cũng đáng lưu ý là câu hỏi này chủ yếu là cho các ngôn ngữ không hỗ trợ đa kế thừa (chẳng hạn như .NET và Java).

+1

Tôi muốn lưu ý rằng tôi đã thấy một số câu hỏi "Giao diện so với lớp trừu tượng", nhưng trong câu hỏi này, tôi quan tâm nhất khi sử dụng * cả giao diện * và lớp trừu tượng (nếu đây là một điều hợp lệ để làm.) – Blixt

+0

bản sao có thể có của [Giao diện vs Lớp trừu tượng (chung OO)] (http://stackoverflow.com/questions/761194/interface-vs-abstract-class-general-oo) –

+0

bản sao có thể có của [Khi sử dụng giao diện thay vì một lớp trừu tượng và ngược lại?] (Http://stackoverflow.com/questions/479142/when-to-use-an-interface-instead-of-an-abstract-class-and -vice-versa) – nawfal

Trả lời

33

Như quy tắc đầu tiên, tôi thích lớp trừu tượng hơn giao diện, based on the .NET Design Guidelines. Lý do áp dụng rộng hơn nhiều so với .NET, nhưng được giải thích rõ hơn trong cuốn sách Framework Design Guidelines.

Lý do chính đằng sau tùy chọn cho lớp cơ sở trừu tượng là phiên bản, vì bạn luôn có thể thêm thành viên ảo mới vào lớp cơ sở trừu tượng mà không làm hỏng các máy khách hiện có. Điều đó là không thể với các giao diện.

Có các trường hợp giao diện vẫn là lựa chọn đúng (đặc biệt khi bạn không quan tâm đến việc tạo phiên bản), nhưng nhận thức được các ưu điểm và nhược điểm cho phép bạn đưa ra quyết định đúng.

Vì vậy, như một câu trả lời từng phần trước khi tôi tiếp tục: Có cả giao diện và lớp cơ sở chỉ có ý nghĩa nếu bạn quyết định mã hóa giao diện ở vị trí đầu tiên. Nếu bạn cho phép một giao diện, bạn phải mã chống lại giao diện đó chỉ, vì nếu không bạn sẽ vi phạm Nguyên tắc thay thế Liskov. Nói cách khác, ngay cả khi bạn cung cấp một lớp cơ sở thực hiện giao diện, bạn không thể để mã của bạn tiêu thụ lớp cơ sở đó.

Nếu bạn quyết định mã hóa dựa trên lớp cơ sở, việc có giao diện sẽ không có ý nghĩa.

Nếu bạn quyết định mã chống lại một giao diện, có một lớp cơ sở cung cấp chức năng mặc định là tùy chọn. Nó không phải là cần thiết, nhưng có thể tăng tốc độ cho những người thực hiện, vì vậy bạn có thể cung cấp một cách lịch sự.

Một ví dụ lưu ý đến trong ASP.NET MVC. Đường ống yêu cầu hoạt động trên IController, nhưng có một lớp cơ sở Controller mà bạn thường sử dụng để thực hiện hành vi.

Câu trả lời cuối cùng: Nếu sử dụng lớp cơ sở trừu tượng, chỉ sử dụng lớp đó. Nếu sử dụng một giao diện, một lớp cơ sở là một lịch sự tùy chọn cho người triển khai.


Cập nhật: tôi không còn thích lớp trừu tượng trên giao diện, và tôi không có một thời gian dài; thay vào đó, tôi ủng hộ thành phần thừa kế, sử dụng SOLID làm phương châm.

(Trong khi tôi có thể chỉnh sửa văn bản ở trên trực tiếp, nó sẽ thay đổi hoàn toàn bản chất của bài đăng và vì một vài người đã thấy nó đủ giá trị để bỏ phiếu, tôi muốn để nguyên bản gốc, và thay vào đó thêm ghi chú này. các phần sau của bài vẫn có ý nghĩa, vì vậy nó sẽ là một sự xấu hổ để xóa nó, quá.)

+0

Ah cảm ơn những lời khuyên hữu ích! Tôi cảm thấy rằng các giao diện được cho là "thực hành tốt" (và vì thế tôi cố gắng tìm cách sử dụng chúng) nhưng trong thực tế chúng không thực sự giải quyết được nhiều vấn đề đó, ít nhất là trong các dự án quy mô vừa và nhỏ ... Tôi tin rằng tôi sẽ cố gắng gắn bó với các lớp cơ sở trừu tượng trong dự án tiếp theo của tôi trừ khi tôi tìm thấy một lý do cụ thể để đi với một giao diện. – Blixt

+2

Xác định giao diện và lớp trừu tượng triển khai nó. Sau đó, hầu hết các triển khai chỉ có thể mở rộng lớp trừu tượng mà không có vấn đề về phiên bản (các phương thức mới có thể được thêm vào giao diện và lớp trừu tượng), trong khi nếu không thể thực hiện được (ví dụ: khi đã có siêu khác), việc triển khai thực hiện giao diện. –

+0

Với JDK8 và các phương thức mặc định cho giao diện, đối số phiên bản xuất hiện thậm chí ít liên quan hơn. Tuy nhiên một sự khác biệt thú vị để suy nghĩ về. – mibollma

2

tôi luôn luôn sử dụng những nguyên tắc này:

  • Sử dụng giao diện cho đa kế thừa TYPE (như .NET/Java không sử dụng đa kế thừa)
  • Sử dụng lớp trừu tượng cho một việc thực hiện tái sử dụng của một loại

Quy tắc của mối quan tâm chi phối quy định rằng một lớp luôn có mối quan tâm chính và 0 hoặc nhiều người khác (xem http://citeseer.ist.psu.edu/tarr99degrees.html). Từ 0 trở lên, bạn thực hiện thông qua các giao diện, khi lớp đó triển khai tất cả các kiểu mà nó phải thực hiện (riêng của nó và tất cả các giao diện mà nó thực hiện).

Trong thế giới thừa kế nhiều triển khai (ví dụ: C++/Eiffel), một kế thừa sẽ được kế thừa từ các lớp thực hiện giao diện. (Về lý thuyết. Trong thực tế nó có thể không hoạt động tốt.)

2

Nếu bạn muốn cung cấp tùy chọn thay thế hoàn toàn việc triển khai của mình, hãy sử dụng giao diện. Điều này đặc biệt áp dụng cho các tương tác giữa các thành phần chính, chúng phải luôn được tách riêng bởi các giao diện.

Cũng có thể có các lý do kỹ thuật để thích giao diện, ví dụ: để bật chế độ nhại trong các thử nghiệm đơn vị.

Nội bộ trong một thành phần có thể là tốt để chỉ sử dụng lớp trừu tượng trực tiếp để truy cập vào một hệ thống phân cấp của các lớp.

Nếu bạn sử dụng một giao diện và có một hệ thống phân cấp thực hiện các lớp thì thực hành tốt là có một bản tóm tắt trừu tượng chứa các phần phổ biến của việc triển khai. Ví dụ.

interface Foo 
abstract class FooBase implements Foo 
class FunnyFoo extends FooBase 
class SeriousFoo extends FooBase 

Bạn cũng có thể có thêm các lớp trừu tượng kế thừa từ nhau cho một hệ thống phân cấp phức tạp hơn.

2

Ngoài ra còn có một cái gì đó gọi là nguyên tắc DRY - Không lặp lại chính mình.

Trong ví dụ về nguồn dữ liệu bạn nói có một số mã chung chung phổ biến giữa các triển khai khác nhau. Với tôi, có vẻ như cách tốt nhất để xử lý đó là có một lớp trừu tượng với mã chung trong đó và một số lớp cụ thể mở rộng nó.

Lợi thế là mọi sửa lỗi trong mã chung đều có lợi cho mọi triển khai cụ thể.

Nếu bạn chỉ sử dụng giao diện, bạn sẽ phải duy trì một số bản sao của cùng một mã yêu cầu sự cố.

Về giao diện trừu tượng + nếu không có biện minh ngay lập tức cho nó, tôi sẽ không làm điều đó. Việc trích xuất giao diện từ lớp trừu tượng là một phép tái cấu trúc dễ dàng, vì vậy tôi sẽ thực hiện nó chỉ khi nó thực sự cần thiết.

+0

Hầu hết các tình huống mà bạn có giao diện sẽ không dẫn đến "một số bản sao của cùng một mã". – RichardOD

+0

Trong trường hợp cụ thể này theo OP anh ta có một số mã phổ biến. Nếu bạn sử dụng giao diện, bạn vẫn có thể có nó ở một nơi bằng cách sử dụng một số phái đoàn, nhưng tôi sẽ không bận tâm. –

19

tôi có xu hướng sử dụng các lớp cơ sở (trừu tượng hay không) để mô tả những gì một cái gì đó , trong khi tôi sử dụng giao diện để mô tả khả năng của một đối tượng.

A Cat Động vật có vú nhưng một trong số đó là khả năng là nó có thể đặt là Pettable.

Hoặc, để đặt nó theo một cách khác, các lớp là danh từ, trong khi giao diện bản đồ gần hơn với tính từ.

12

Từ MSDN, Recommendations for Abstract Classes vs. Interfaces

  • Nếu bạn dự đoán tạo nhiều phiên bản của thành phần của bạn, tạo một lớp trừu tượng. Các lớp trừu tượng cung cấp một cách đơn giản và dễ dàng để phiên bản các thành phần của bạn. Bằng cách cập nhật lớp cơ sở, tất cả các lớp kế thừa được tự động cập nhật với thay đổi. Mặt khác, giao diện không thể thay đổi khi được tạo. Nếu cần một phiên bản giao diện mới, bạn phải tạo một giao diện hoàn toàn mới.

  • Nếu chức năng bạn đang tạo sẽ hữu ích trên nhiều đối tượng khác nhau, hãy sử dụng giao diện. Các lớp trừu tượng nên được sử dụng chủ yếu cho các đối tượng có liên quan chặt chẽ, trong khi các giao diện phù hợp nhất để cung cấp các chức năng phổ biến cho các lớp không liên quan.

  • Nếu bạn đang thiết kế các bit nhỏ, súc tích, sử dụng giao diện. Nếu bạn đang thiết kế các đơn vị chức năng lớn, hãy sử dụng một lớp trừu tượng.

  • Nếu bạn muốn cung cấp chức năng phổ biến, được triển khai trong số tất cả các triển khai của thành phần của bạn, hãy sử dụng lớp trừu tượng. Các lớp trừu tượng cho phép bạn thực hiện một phần lớp của bạn, trong khi các giao diện không chứa thực hiện cho bất kỳ thành viên nào.

0

Tham khảo dưới đây SE câu hỏi để được hướng dẫn chung chung:

Interface vs Abstract Class (general OO)

trường hợp sử dụng thực tế cho giao diện:

  1. Thực hiện Strategy_pattern: Xác định chiến lược của bạn như một giao diện. Chuyển đổi việc triển khai động với một trong những triển khai cụ thể về chiến lược trong thời gian chạy.

  2. Xác định khả năng trong số nhiều lớp không liên quan.

tình huống thực tế sử dụng cho lớp trừu tượng:

  1. Thực hiện Template_method_pattern: Xác định một bộ xương của một thuật toán. Các lớp con không thể thay đổi strucutre của algortihm nhưng chúng có thể tái xác định một phần của việc thực hiện trong các lớp con.

  2. Khi bạn muốn chia sẻ các biến không tĩnh và không phải là cuối cùng giữa nhiều lớp có liên quan với "có mối quan hệ".

Sử dụng cả hai lớp abstradt và giao diện:

Nếu bạn đang cho một lớp trừu tượng, bạn có thể di chuyển các phương pháp trừu tượng giao diện và lớp trừu tượng đơn giản có thể thực hiện giao diện đó. Tất cả các trường hợp sử dụng của các lớp trừu tượng đều có thể rơi vào thể loại này.

Các vấn đề liên quan