Giao diện với lớp cơ sở


767

Khi nào tôi nên sử dụng giao diện và khi nào tôi nên sử dụng lớp cơ sở?

Nó có phải luôn luôn là một giao diện nếu tôi không thực sự muốn xác định một triển khai cơ bản của các phương thức không?

Nếu tôi có một lớp Dog và Cat. Tại sao tôi muốn triển khai IPet thay vì PetBase? Tôi có thể hiểu rằng có các giao diện cho IShbed hoặc IBarks (IMakesNaty?), Bởi vì chúng có thể được đặt trên thú cưng bằng cơ sở vật nuôi, nhưng tôi không hiểu nên sử dụng cho Pet chung.


11
Chỉ cần một điểm tôi nghĩ bạn nên cân nhắc - giao diện có thể đặt ra một số giới hạn mà bạn có thể không nhận thức được cho đến giai đoạn rất muộn. Ví dụ: với .NET, bạn không thể tuần tự hóa một biến thành viên giao diện, vì vậy nếu bạn có một Zoo Zoo lớp và một mảng biến thành viên của IAnimals, bạn sẽ không thể tuần tự hóa Zoo (và điều đó có nghĩa là viết WebService hoặc những thứ khác yêu cầu tuần tự hóa sẽ là một nỗi đau).
synhershko

1
Câu hỏi này có thể giúp hiểu khái niệm về giao diện. stackoverflow.com/q/8531292/1055241
gprathour

Tôi chỉ tò mò thôi. Tôi đã gặp trong CLR thông qua C # đoạn trích sau : I tend to prefer using the interface technique over the base type technique because the base type technique doesn’t allow the developer to choose the base type that works best in a particular situation.. Tôi không thể nắm bắt được ý nghĩa của đoạn trích. Chúng ta có thể tạo một vài loại cơ sở và tạo một loại dẫn xuất cho bất kỳ loại nào trong số chúng, vì vậy nhà phát triển có thể chọn loại cơ sở. Ai đó có thể giải thích, xin vui lòng, tôi đang thiếu gì? Tôi tin rằng nó có thể là một phần của câu hỏi này. Hay tôi nên đăng một bài khác về đoạn trích cụ thể?
qqqqqqq

Câu trả lời:


501

Hãy lấy ví dụ về lớp Chó và Mèo và hãy minh họa bằng C #:

Cả một con chó và một con mèo đều là động vật, cụ thể là động vật có vú bốn chân (động vật là waaay quá chung chung). Chúng ta hãy giả sử rằng bạn có một lớp Động vật có vú trừu tượng, cho cả hai:

public abstract class Mammal

Lớp cơ sở này có thể sẽ có các phương thức mặc định như:

  • Cho ăn
  • Người bạn đời

Tất cả đều là hành vi có ít nhiều cùng thực hiện giữa hai loài. Để xác định điều này, bạn sẽ có:

public class Dog : Mammal
public class Cat : Mammal

Bây giờ hãy giả sử có những động vật có vú khác, mà chúng ta thường thấy trong một sở thú:

public class Giraffe : Mammal
public class Rhinoceros : Mammal
public class Hippopotamus : Mammal

Điều này vẫn sẽ hợp lệ bởi vì cốt lõi của chức năng Feed()Mate()vẫn sẽ giống nhau.

Tuy nhiên, hươu cao cổ, tê giác và hà mã không phải là động vật chính xác mà bạn có thể làm cho thú cưng thoát khỏi. Đó là nơi một giao diện sẽ hữu ích:

public interface IPettable
{
    IList<Trick> Tricks{get; set;}
    void Bathe();
    void Train(Trick t);
}

Việc thực hiện hợp đồng trên sẽ không giống nhau giữa mèo và chó; đưa các triển khai của họ vào một lớp trừu tượng để kế thừa sẽ là một ý tưởng tồi.

Định nghĩa Chó và Mèo của bạn bây giờ sẽ giống như:

public class Dog : Mammal, IPettable
public class Cat : Mammal, IPettable

Về mặt lý thuyết bạn có thể ghi đè chúng từ lớp cơ sở cao hơn, nhưng về cơ bản, giao diện cho phép bạn chỉ thêm vào những thứ bạn cần vào một lớp mà không cần kế thừa.

Do đó, vì bạn thường chỉ có thể kế thừa từ một lớp trừu tượng (trong hầu hết các ngôn ngữ OO được nhập tĩnh là ... ngoại lệ bao gồm C ++) nhưng có thể thực hiện nhiều giao diện, cho phép bạn xây dựng các đối tượng theo cơ sở theo yêu cầu .


149
Tôi không nghĩ nó đơn giản. Bạn đã thay đổi một chút câu hỏi (yêu cầu) để giao diện có ý nghĩa hơn. Bạn nên luôn tự hỏi liệu bạn đang xác định hợp đồng (giao diện) hay triển khai chung (lớp cơ sở).
David Pokluda

18
Giao diện là một hợp đồng. Bạn chỉ tiết lộ phần hợp đồng mà dịch vụ yêu cầu. Nếu bạn có 'PettZoo', bạn chắc chắn không muốn tiết lộ 'Mate'-ing cho người dùng!
Anthony Mastrean

10
@David Touche, mặc dù tôi đã làm điều đó để minh họa rõ hơn về giao diện là gì và một lớp trừu tượng là gì để hiểu được sự hiểu biết của anh ấy. Một con chó và một con mèo dường như không phải là một yêu cầu cứng nhắc!
Jon Limjap

2
Cần lưu ý rằng trong các môi trường được giải thích, JIT (đặc biệt là JVM), các cuộc gọi phương thức ảo nhanh hơn đáng kể so với các cuộc gọi phương thức giao diện , tùy thuộc vào ngữ cảnh. Tôi nhấn mạnh "bối cảnh" vì JVM thường có thể tối ưu hóa việc tìm kiếm phương thức chậm. (vd
Philip Guin

14
Ngoài ra, một giao diện cho phép một hòn đá thú cưng, cho phép nó được tắm và được dạy các thủ thuật, nhưng việc cho ăn và giao phối sẽ không được hỗ trợ bởi vì điều đó sẽ vô lý đối với một hòn đá.
eclux 9/12/2015

146

Chà, Josh Bloch đã tự nói mình trong Java 2d hiệu quả :

Thích giao diện hơn các lớp trừu tượng

Một số điểm chính:

  • Các lớp hiện tại có thể dễ dàng được trang bị thêm để thực hiện một giao diện mới . Tất cả những gì bạn phải làm là thêm các phương thức cần thiết nếu chúng chưa tồn tại và thêm một mệnh đề thực hiện vào khai báo lớp.

  • Giao diện là lý tưởng để xác định mixin . Nói một cách lỏng lẻo, một mixin là một loại mà một lớp có thể thực hiện cùng với loại chính loại chính của nó để tuyên bố rằng nó cung cấp một số hành vi tùy chọn. Ví dụ, so sánh là một giao diện mixin cho phép một lớp khai báo rằng các thể hiện của nó được sắp xếp theo các đối tượng so sánh lẫn nhau khác.

  • Các giao diện cho phép xây dựng các kiểu khung không phân cấp . Kiểu phân cấp là tuyệt vời để tổ chức một số thứ, nhưng những thứ khác không nằm gọn trong một hệ thống phân cấp cứng nhắc.

  • Các giao diện cho phép cải tiến chức năng an toàn, mạnh mẽ thông qua thành ngữ bao bọc mỗi lớp. Nếu bạn sử dụng các lớp trừu tượng để xác định các loại, bạn sẽ để lập trình viên muốn thêm chức năng mà không có sự thay thế nào ngoài việc sử dụng tính kế thừa.

Hơn nữa, bạn có thể kết hợp các ưu điểm của giao diện và các lớp trừu tượng bằng cách cung cấp một lớp triển khai khung xương trừu tượng để đi với mỗi giao diện không cần thiết mà bạn xuất.

Mặt khác, giao diện rất khó phát triển. Nếu bạn thêm một phương thức vào một giao diện, nó sẽ phá vỡ tất cả các triển khai của nó.

Tái bút: Mua sách. Nó chi tiết hơn rất nhiều.


71
Bất cứ khi nào cần thay đổi giao diện, cách không phá vỡ là tạo giao diện mới kế thừa từ giao diện cũ. Điều này bảo tồn các triển khai hiện có và cho phép bạn làm bất cứ điều gì bạn muốn trong cái mới.
Scott Lawrence

5
chúng tôi có "Nguyên tắc phân chia giao diện". Nguyên tắc này dạy chúng ta quan tâm đến cách chúng ta viết các giao diện của mình. Khi chúng ta viết các giao diện của mình, chúng ta nên chú ý chỉ thêm các phương thức nên có. Nếu chúng ta thêm các phương thức không nên có thì các lớp thực hiện giao diện cũng sẽ phải thực hiện các phương thức đó. Ví dụ: nếu chúng ta tạo một giao diện gọi là Công nhân và thêm giờ nghỉ trưa, tất cả các công nhân sẽ phải thực hiện nó. Điều gì sẽ xảy ra nếu công nhân là một robot? Như một kết luận Các giao diện có chứa các phương thức không dành riêng cho nó được gọi là các giao diện bị ô nhiễm hoặc chất béo.
Eklavyaa

Kể từ Java 8, các phương thức mặc định cho phép bạn thêm chức năng mới vào các giao diện và đảm bảo khả năng tương thích ngược cho các lớp hiện có thực hiện giao diện đó. Các phương thức mặc định được gọi theo mặc định, nếu chúng không bị ghi đè trong lớp thực hiện. Tất cả các lớp triển khai có thể ghi đè các phương thức mặc định hoặc chúng có thể gọi chúng trực tiếp bằng cách sử dụng instance.defaultMethod ()
Arpit Rastogi

118

Các giao diện và các lớp cơ sở đại diện cho hai dạng quan hệ khác nhau.

Kế thừa (các lớp cơ sở) thể hiện mối quan hệ "is-a". Ví dụ: một con chó hoặc một con mèo "là - một" thú cưng. Mối quan hệ này luôn thể hiện mục đích (duy nhất) của lớp (kết hợp với "nguyên tắc trách nhiệm duy nhất" ).

Giao diện , mặt khác, đại diện cho tính năng bổ sung của một lớp. Tôi gọi đó là mối quan hệ "là", như trong " Foolà dùng một lần", do đó IDisposablegiao diện trong C #.


10
Trong số tất cả các câu trả lời, câu trả lời này mang lại hỗn hợp ngắn gọn nhất mà không mất đi sự rõ ràng
Matt Wilko

Ai đó đã từng nói với tôi sử dụng một giao diện khi có mối quan hệ "có-có". Tôi không chắc chắn nếu điều đó luôn luôn đúng; Máy tính xách tay của tôi có màn hình, vậy máy tính xách tay nên triển khai IScreen hay có thuộc tính Màn hình? Cái sau có vẻ tự nhiên hơn với tôi.
Berend

1
@berend buồn cười là bạn viết điều đó bởi vì các màn hình được triển khai thông qua các giao diện - VGA, HDMI, v.v.
Konstantin

Chỉ vì đó là OOP, chúng ta phải kéo dài trí tưởng tượng của mình để theo dõi các tình huống thực tế. Nó không luôn luôn áp dụng.
Crismogram

110

Phong cách hiện đại là xác định IPet PetBase.

Ưu điểm của giao diện là các mã khác có thể sử dụng nó mà không có bất kỳ ràng buộc nào với mã thực thi khác. Hoàn toàn "sạch". Ngoài ra giao diện có thể được trộn lẫn.

Nhưng các lớp cơ sở rất hữu ích cho việc triển khai đơn giản và các tiện ích chung. Vì vậy, cung cấp một lớp cơ sở trừu tượng là tốt để tiết kiệm thời gian và mã.


Lấy phần bánh của mình đi!
Darren Kopp

3
Một giao diện xác định cách các lớp khác có thể sử dụng mã của bạn. Một lớp cơ sở giúp người triển khai thực hiện giao diện của bạn. Hai điều khác nhau cho hai mục đích khác nhau.
Bill Michell

27
Không có gì "hiện đại" ở đây. Lớp cơ sở và giao diện với cùng API đơn giản là dự phòng. Bạn có thể sử dụng phương pháp này trong một số trường hợp nhưng bạn không nên khái quát!
smentek

3
Nhận xét được hỗ trợ nhiều nhất cho câu trả lời được hỗ trợ thứ hai thực sự không đồng ý với câu trả lời, thật thú vị. Tôi phải nói rằng tôi thấy nhiều ví dụ về giao diện và lớp cơ sở cùng tồn tại. Theo nghĩa đó, đó là cách "hiện đại". Ví dụ, trong mẫu MVVM, thực sự có lớp ViewModelBase thực hiện INotifyPropertyChanged. Nhưng khi đồng nghiệp của tôi đã hỏi tôi tại sao tôi lại có lớp cơ sở, thay vì thực hiện các giao diện trong tất cả các mô hình điểm, tôi không biết làm thế nào để thuyết phục anh ta
tete

1
Chính xác. Đây không phải là câu hỏi của 1 hay khác. Chúng tồn tại để giải quyết 2 vấn đề rất khác nhau. Các giao diện là các hợp đồng mà một lớp thực hiện phải giải quyết. Họ đã tìm thấy sự ưu ái gần đây (đôi khi cuồng tín) cho các khía cạnh IoC và TDD. Các lớp trừu tượng / cơ sở phục vụ cho nhóm các thuộc tính và logic phổ biến theo cấp bậc. Nó làm giảm mã trùng lặp, từ đó làm tăng khả năng bảo trì giải pháp và làm cho nó ít bị lỗi hơn.
Đến vào

63

Giao diện

  • Xác định hợp đồng giữa 2 mô-đun. Không thể có bất kỳ thực hiện.
  • Hầu hết các ngôn ngữ cho phép bạn thực hiện nhiều giao diện
  • Sửa đổi một giao diện là một thay đổi đột phá. Tất cả các triển khai cần phải được biên dịch lại / sửa đổi.
  • Tất cả các thành viên là công khai. Việc thực hiện phải thực hiện tất cả các thành viên.
  • Giao diện giúp đỡ trong việc tách rời. Bạn có thể sử dụng các khung giả để giả lập bất cứ thứ gì đằng sau một giao diện
  • Các giao diện thường chỉ ra một loại hành vi
  • Giao diện triển khai được tách rời / tách biệt với nhau

Các lớp cơ sở

  • Cho phép bạn thêm một số triển khai mặc định mà bạn nhận được miễn phí bằng cách tạo đạo hàm
  • Ngoại trừ C ++, bạn chỉ có thể xuất phát từ một lớp. Ngay cả khi có thể từ nhiều lớp, nó thường là một ý tưởng tồi.
  • Thay đổi lớp cơ sở là tương đối dễ dàng. Đạo hàm không cần làm gì đặc biệt
  • Các lớp cơ sở có thể khai báo các hàm được bảo vệ và công khai có thể được truy cập bằng các đạo hàm
  • Các lớp cơ sở trừu tượng không thể bị chế giễu dễ dàng như các giao diện
  • Các lớp cơ sở thường biểu thị phân cấp kiểu (IS A)
  • Các dẫn xuất lớp có thể phụ thuộc vào một số hành vi cơ bản (có kiến ​​thức phức tạp về việc thực hiện của phụ huynh). Mọi thứ có thể lộn xộn nếu bạn thay đổi cách thực hiện cơ sở cho một anh chàng và phá vỡ những người khác.

Lưu ý: hướng dẫn thiết kế khung khuyến nghị sử dụng các lớp cơ sở (trái ngược với giao diện) vì chúng phiên bản tốt hơn. Thêm một phương thức mới vào một lớp cơ sở trừu tượng trong vNext là một thay đổi không phá vỡ ..
Gishu

59

Nói chung, bạn nên ưu tiên các giao diện hơn các lớp trừu tượng. Một lý do để sử dụng một lớp trừu tượng là nếu bạn có triển khai chung giữa các lớp cụ thể. Tất nhiên, bạn vẫn nên khai báo một giao diện (IPet) và có một lớp trừu tượng (PetBase) thực hiện giao diện đó. Sử dụng các giao diện nhỏ, khác biệt, bạn có thể sử dụng bội số để cải thiện tính linh hoạt hơn nữa. Các giao diện cho phép mức độ linh hoạt và tính di động tối đa của các loại qua các ranh giới. Khi chuyển tham chiếu qua các ranh giới, luôn luôn vượt qua giao diện và không phải là loại cụ thể. Điều này cho phép kết thúc nhận để xác định thực hiện cụ thể và cung cấp sự linh hoạt tối đa. Điều này là hoàn toàn đúng khi lập trình theo kiểu TDD / BDD.

Gang of Four đã nêu trong cuốn sách của họ "Bởi vì quyền thừa kế tiết lộ một lớp con cho các chi tiết về việc thực hiện của cha mẹ, nên người ta thường nói rằng" sự kế thừa phá vỡ sự đóng gói ". Tôi tin rằng điều này là đúng.


Diệp. Cá nhân, tôi tin rằng đây là ass ngược. Các giao diện nên giữ chức năng tối thiểu cho một loại và các lớp cơ sở sẽ cung cấp một khung công tác phong phú mà theo đó có thể tùy chỉnh được xây dựng. Đặt nó trong một giao diện và bạn chỉ làm cho nó rất khó để thực hiện.

Không ai nói giao diện của bạn cần phải rất lớn. Nhỏ hơn, nhiều giao diện và các lớp cơ sở phong phú hơn tạo nên một API tuyệt vời.
Kilhoffer

3
Có phải chỉ tôi, hoặc hầu hết các lớp "công nhân chung" chia sẻ một cách thực hiện chung? Trong bối cảnh đó, nó đi ngược lại quy tắc chung của bạn về các giao diện ưa thích. Tôi sẽ giới thiệu khái quát của bạn thành 2 khái quát: Những lớp phổ biến không chứa hoặc ít logic sẽ thực hiện một giao diện. Các lớp phổ biến có chứa một lượng logic "phong nha" sẽ xuất phát từ một lớp cơ sở (vì rất có thể chúng sẽ chia sẻ chức năng).
goku_da_master

@Kilhoffer "Các giao diện cho phép mức độ linh hoạt và tính di động tối đa của các loại qua các ranh giới", vui lòng xây dựng tuyên bố này.
JAVA

49

Điều này khá cụ thể về .NET, nhưng cuốn sách Hướng dẫn thiết kế khung lập luận rằng trong các lớp học nói chung cho phép linh hoạt hơn trong một khung phát triển. Khi một giao diện được xuất xưởng, bạn không có cơ hội thay đổi giao diện mà không vi phạm mã đã sử dụng giao diện đó. Tuy nhiên, với một lớp, bạn có thể sửa đổi nó và không phá vỡ mã liên kết đến nó. Miễn là bạn thực hiện các sửa đổi phù hợp, bao gồm thêm chức năng mới, bạn sẽ có thể mở rộng và phát triển mã của mình.

Krzysztof Cwalina nói trên trang 81:

Trong suốt ba phiên bản của .NET Framework, tôi đã nói về hướng dẫn này với khá nhiều nhà phát triển trong nhóm của chúng tôi. Nhiều người trong số họ, bao gồm cả những người ban đầu không đồng ý với hướng dẫn, đã nói rằng họ rất tiếc vì đã chuyển một số API dưới dạng giao diện. Tôi chưa từng nghe về một trường hợp nào mà ai đó hối hận vì họ đã chuyển một lớp học.

Điều đó đang được nói rằng chắc chắn có một nơi cho các giao diện. Như một hướng dẫn chung luôn cung cấp một triển khai lớp cơ sở trừu tượng của một giao diện nếu không có gì khác làm ví dụ về cách thực hiện giao diện. Trong trường hợp tốt nhất, lớp cơ sở sẽ tiết kiệm rất nhiều công việc.


19

Juan,

Tôi thích nghĩ về các giao diện như một cách để mô tả một lớp. Một lớp giống chó đặc biệt, nói là YorkshireTerrier, có thể là hậu duệ của lớp chó mẹ, nhưng nó cũng thực hiện IFurry, IStubby và IYippieDog. Vì vậy, lớp định nghĩa lớp là gì nhưng giao diện cho chúng ta biết những điều về nó.

Ưu điểm của việc này là cho phép tôi, ví dụ, thu thập tất cả các IYippieDog và ném chúng vào bộ sưu tập Đại dương của tôi. Vì vậy, bây giờ tôi có thể tiếp cận với một nhóm đối tượng cụ thể và tìm những đối tượng đáp ứng các tiêu chí mà tôi đang xem mà không kiểm tra lớp quá chặt chẽ.

Tôi thấy rằng các giao diện thực sự nên định nghĩa một tập hợp con của hành vi công khai của một lớp. Nếu nó định nghĩa tất cả các hành vi công khai cho tất cả các lớp thực hiện thì nó thường không cần tồn tại. Họ không nói với tôi điều gì hữu ích.

Suy nghĩ này đi ngược lại với ý tưởng rằng mỗi lớp nên có một giao diện và bạn nên viết mã cho giao diện đó. Điều đó tốt, nhưng bạn kết thúc với rất nhiều giao diện một đến một lớp và nó làm cho mọi thứ trở nên khó hiểu. Tôi hiểu rằng ý tưởng là nó không thực sự tốn kém gì để làm và bây giờ bạn có thể trao đổi mọi thứ một cách dễ dàng. Tuy nhiên, tôi thấy rằng tôi hiếm khi làm điều đó. Hầu hết thời gian tôi chỉ sửa đổi lớp hiện tại và có các vấn đề chính xác giống như tôi luôn làm nếu giao diện chung của lớp đó cần thay đổi, ngoại trừ bây giờ tôi phải thay đổi nó ở hai nơi.

Vì vậy, nếu bạn nghĩ như tôi, bạn chắc chắn sẽ nói rằng Mèo và Chó là IPable. Đó là một đặc tính phù hợp với cả hai.

Một phần khác của điều này là họ có nên có cùng một lớp cơ sở không? Câu hỏi đặt ra là họ có cần được đối xử rộng rãi như vậy không. Chắc chắn cả hai đều là Động vật, nhưng điều đó phù hợp với cách chúng ta sẽ sử dụng chúng cùng nhau.

Nói rằng tôi muốn tập hợp tất cả các lớp Động vật và đặt chúng vào thùng chứa Ark của tôi.

Hay họ cần phải là động vật có vú? Có lẽ chúng ta cần một số loại nhà máy vắt sữa động vật?

Họ thậm chí có cần phải được liên kết với nhau không? Là đủ để chỉ cần biết cả hai đều có thể IP?

Tôi thường cảm thấy mong muốn có được một hệ thống phân cấp toàn bộ lớp khi tôi thực sự chỉ cần một lớp. Tôi làm điều đó trong dự đoán một ngày nào đó tôi có thể cần nó và thường thì tôi không bao giờ làm điều đó. Ngay cả khi tôi làm, tôi thường thấy tôi phải làm rất nhiều để sửa nó. Đó là bởi vì lớp đầu tiên tôi tạo ra không phải là Chó, tôi không may mắn, thay vào đó là Thú mỏ vịt. Bây giờ toàn bộ hệ thống phân cấp lớp của tôi dựa trên trường hợp kỳ quái và tôi có rất nhiều mã bị lãng phí.

Tại một thời điểm nào đó, bạn cũng có thể thấy rằng không phải tất cả Mèo đều có thể IPable (giống như con không có lông). Bây giờ bạn có thể di chuyển Giao diện đó đến tất cả các lớp phái sinh phù hợp. Bạn sẽ thấy rằng một sự thay đổi ít phá vỡ hơn mà tất cả những con Mèo bất ngờ không còn xuất phát từ PfortBase.


18

Dưới đây là định nghĩa cơ bản và đơn giản của giao diện và lớp cơ sở:

  • Lớp cơ sở = kế thừa đối tượng.
  • Giao diện = kế thừa chức năng.

chúc mừng


12

Tôi khuyên bạn nên sử dụng thành phần thay vì kế thừa bất cứ khi nào có thể. Sử dụng giao diện nhưng sử dụng các đối tượng thành viên để thực hiện cơ sở. Bằng cách đó, bạn có thể định nghĩa một nhà máy xây dựng các đối tượng của bạn để hành xử theo một cách nhất định. Nếu bạn muốn thay đổi hành vi thì bạn tạo một phương thức nhà máy mới (hoặc nhà máy trừu tượng) tạo ra các loại đối tượng phụ khác nhau.

Trong một số trường hợp, bạn có thể thấy rằng các đối tượng chính của bạn hoàn toàn không cần giao diện, nếu tất cả các hành vi có thể thay đổi được xác định trong các đối tượng trợ giúp.

Vì vậy, thay vì IPet hoặc PetBase, bạn có thể kết thúc với Pet có tham số IFurBehavior. Tham số IFurBehavior được đặt theo phương thức CreateDog () của PetFactory. Đó là tham số này được gọi cho phương thức shed ().

Nếu bạn làm điều này, bạn sẽ thấy mã của mình linh hoạt hơn nhiều và hầu hết các đối tượng đơn giản của bạn xử lý các hành vi toàn hệ thống rất cơ bản.

Tôi đề nghị mô hình này ngay cả trong các ngôn ngữ đa kế thừa.


12

Nó được giải thích tốt trong bài viết về Thế giới Java này .

Cá nhân, tôi có xu hướng sử dụng các giao diện để xác định các giao diện - tức là các phần của thiết kế hệ thống xác định cách thức một cái gì đó nên được truy cập.

Không có gì lạ khi tôi sẽ có một lớp thực hiện một hoặc nhiều giao diện.

Các lớp trừu tượng tôi sử dụng làm cơ sở cho một cái gì đó khác.

Sau đây là một trích từ bài viết được đề cập ở trên bài viết JavaWorld.com, tác giả Tony Sintes, 04/20/01


Giao diện so với lớp trừu tượng

Chọn giao diện và các lớp trừu tượng không phải là một hoặc / hoặc mệnh đề. Nếu bạn cần thay đổi thiết kế của mình, hãy biến nó thành một giao diện. Tuy nhiên, bạn có thể có các lớp trừu tượng cung cấp một số hành vi mặc định. Các lớp trừu tượng là những ứng cử viên xuất sắc bên trong các khung ứng dụng.

Các lớp trừu tượng cho phép bạn xác định một số hành vi; họ buộc các lớp con của bạn cung cấp cho người khác. Ví dụ: nếu bạn có khung ứng dụng, một lớp trừu tượng có thể cung cấp các dịch vụ mặc định như xử lý sự kiện và thông báo. Những dịch vụ này cho phép ứng dụng của bạn cắm vào khung ứng dụng của bạn. Tuy nhiên, có một số chức năng dành riêng cho ứng dụng mà chỉ ứng dụng của bạn mới có thể thực hiện. Chức năng này có thể bao gồm các tác vụ khởi động và tắt máy, thường phụ thuộc vào ứng dụng. Vì vậy, thay vì cố gắng tự xác định hành vi đó, lớp cơ sở trừu tượng có thể khai báo các phương thức khởi động và tắt máy trừu tượng. Lớp cơ sở biết rằng nó cần các phương thức đó, nhưng một lớp trừu tượng cho phép lớp của bạn thừa nhận rằng nó không biết cách thực hiện các hành động đó; nó chỉ biết rằng nó phải bắt đầu các hành động. Khi đến lúc bắt đầu, lớp trừu tượng có thể gọi phương thức khởi động. Khi lớp cơ sở gọi phương thức này, Java gọi phương thức được xác định bởi lớp con.

Nhiều nhà phát triển quên rằng một lớp định nghĩa một phương thức trừu tượng cũng có thể gọi phương thức đó. Các lớp trừu tượng là một cách tuyệt vời để tạo ra hệ thống phân cấp thừa kế theo kế hoạch. Chúng cũng là một lựa chọn tốt cho các lớp không điếc trong hệ thống phân cấp lớp.

Lớp so với giao diện

Một số người nói rằng bạn nên định nghĩa tất cả các lớp theo giao diện, nhưng tôi nghĩ rằng khuyến nghị có vẻ hơi cực. Tôi sử dụng các giao diện khi tôi thấy rằng một cái gì đó trong thiết kế của tôi sẽ thay đổi thường xuyên.

Ví dụ: mẫu Chiến lược cho phép bạn trao đổi các thuật toán và quy trình mới vào chương trình của mình mà không thay đổi các đối tượng sử dụng chúng. Một trình phát đa phương tiện có thể biết cách phát các tệp CD, MP3 và wav. Tất nhiên, bạn không muốn mã hóa các thuật toán phát lại đó vào trình phát; điều đó sẽ gây khó khăn cho việc thêm một định dạng mới như AVI. Hơn nữa, mã của bạn sẽ được lấp đầy với các báo cáo trường hợp vô dụng. Và để thêm sự xúc phạm đến thương tích, bạn sẽ cần cập nhật những tuyên bố trường hợp đó mỗi khi bạn thêm một thuật toán mới. Nói chung, đây không phải là một cách rất hướng đối tượng để lập trình.

Với mẫu Chiến lược, bạn chỉ có thể gói gọn thuật toán đằng sau một đối tượng. Nếu bạn làm điều đó, bạn có thể cung cấp trình cắm phương tiện mới bất cứ lúc nào. Hãy gọi lớp MediaStrargety của trình cắm thêm. Đối tượng đó sẽ có một phương thức: playStream (Stream s). Vì vậy, để thêm một thuật toán mới, chúng tôi chỉ cần mở rộng lớp thuật toán của chúng tôi. Bây giờ, khi chương trình bắt gặp loại phương tiện mới, nó chỉ đơn giản là ủy thác việc phát luồng cho chiến lược truyền thông của chúng tôi. Tất nhiên, bạn sẽ cần một số hệ thống ống nước để khởi tạo đúng các chiến lược thuật toán bạn sẽ cần.

Đây là một nơi tuyệt vời để sử dụng một giao diện. Chúng tôi đã sử dụng mẫu Chiến lược, trong đó chỉ rõ một vị trí trong thiết kế sẽ thay đổi. Vì vậy, bạn nên xác định chiến lược là một giao diện. Nói chung, bạn nên ưu tiên các giao diện hơn kế thừa khi bạn muốn một đối tượng có một kiểu nhất định; trong trường hợp này, MediaStrargety. Dựa vào sự kế thừa cho danh tính loại là nguy hiểm; nó khóa bạn vào một hệ thống phân cấp thừa kế cụ thể. Java không cho phép nhiều kế thừa, vì vậy bạn không thể mở rộng thứ gì đó mang lại cho bạn cách triển khai hữu ích hoặc nhận dạng kiểu nhiều hơn.


2
+1. "Dựa vào sự kế thừa cho danh tính loại là nguy hiểm; nó khóa bạn vào một hệ thống phân cấp thừa kế cụ thể." Câu đó mô tả hoàn hảo lý do của tôi để thích giao diện.
Kỹ sư

Và hơn thế nữa, thay vì mở rộng, hãy soạn thảo việc thực hiện đằng sau mỗi phương thức của giao diện của bạn.
Kỹ sư

10

Ngoài ra, hãy nhớ đừng để bị cuốn vào OO ( xem blog ) và luôn luôn mô hình các đối tượng dựa trên hành vi cần thiết, nếu bạn đang thiết kế một ứng dụng trong đó hành vi duy nhất bạn yêu cầu là tên chung và loài cho động vật thì bạn chỉ cần một lớp Động vật với một tài sản cho tên, thay vì hàng triệu lớp cho mọi động vật có thể có trên thế giới.


10

Tôi có một quy tắc thô

Chức năng: có khả năng khác nhau ở tất cả các phần: Giao diện.

Dữ liệu và chức năng, các phần sẽ giống nhau, các phần khác nhau: lớp trừu tượng.

Dữ liệu và chức năng, thực sự hoạt động, nếu chỉ được mở rộng với những thay đổi nhỏ: lớp thông thường (cụ thể)

Dữ liệu và chức năng, không có thay đổi theo kế hoạch: lớp thông thường (cụ thể) với công cụ sửa đổi cuối cùng.

Dữ liệu và có thể là chức năng: chỉ đọc: thành viên enum.

Điều này rất thô sơ và sẵn sàng và hoàn toàn không được xác định rõ ràng, nhưng có một phổ từ các giao diện nơi mọi thứ được dự định thay đổi thành enums trong đó mọi thứ được cố định một chút giống như một tệp chỉ đọc.


7

Các giao diện nên nhỏ. Thực sự nhỏ. Nếu bạn thực sự phá vỡ các đối tượng của mình, thì giao diện của bạn có thể sẽ chỉ chứa một vài phương thức và thuộc tính rất cụ thể.

Các lớp trừu tượng là các phím tắt. Có những thứ mà tất cả các dẫn xuất của PetBase chia sẻ mà bạn có thể mã hóa một lần và được thực hiện với? Nếu có, thì đó là thời gian cho một lớp trừu tượng.

Các lớp trừu tượng cũng hạn chế. Trong khi họ cung cấp cho bạn một lối tắt tuyệt vời để tạo các đối tượng con, bất kỳ đối tượng cụ thể nào cũng chỉ có thể thực hiện một lớp trừu tượng. Nhiều lần, tôi thấy đây là một hạn chế của các lớp Trừu tượng và đây là lý do tại sao tôi sử dụng nhiều giao diện.

Các lớp trừu tượng có thể chứa một số giao diện. Lớp trừu tượng PetBase của bạn có thể triển khai IPet (thú cưng có chủ) và IDigestion (thú cưng ăn, hoặc ít nhất là chúng nên). Tuy nhiên, PetBase có thể sẽ không thực hiện IMammal, vì không phải tất cả thú cưng đều là động vật có vú và không phải tất cả động vật có vú đều là thú cưng. Bạn có thể thêm MammalPetBase mở rộng PetBase và thêm IMammal. FishBase có thể có PetBase và thêm IFish. IFish sẽ có ISwim và IUnderwaterBreather làm giao diện.

Vâng, ví dụ của tôi quá phức tạp đối với ví dụ đơn giản, nhưng đó là một phần của điều tuyệt vời về cách các giao diện và các lớp trừu tượng làm việc cùng nhau.


7

Nguồn : http://jasonroell.com/2014/12/09/interfaces-vs-abab-groupes-what-should-you-use/

C # là một ngôn ngữ tuyệt vời đã trưởng thành và phát triển trong 14 năm qua. Điều này rất tốt cho các nhà phát triển của chúng tôi bởi vì một ngôn ngữ trưởng thành cung cấp cho chúng tôi rất nhiều tính năng ngôn ngữ theo ý của chúng tôi.

Tuy nhiên, với nhiều quyền lực trở thành nhiều trách nhiệm. Một số tính năng này có thể bị sử dụng sai, hoặc đôi khi thật khó hiểu tại sao bạn lại chọn sử dụng một tính năng này hơn một tính năng khác. Trong những năm qua, một tính năng mà tôi đã thấy nhiều nhà phát triển đấu tranh là khi nào nên chọn sử dụng giao diện hoặc chọn sử dụng một lớp trừu tượng. Cả hai đều có ưu điểm và nhược điểm và thời gian và địa điểm sử dụng chính xác. Nhưng làm thế nào để chúng ta quyết định ???

Cả hai đều cung cấp cho việc tái sử dụng chức năng chung giữa các loại. Sự khác biệt rõ ràng nhất ngay lập tức là các giao diện không cung cấp triển khai cho chức năng của chúng trong khi các lớp trừu tượng cho phép bạn thực hiện một số hành vi mặc định cơ sở của cơ sở dữ liệu và sau đó có khả năng ghi đè lên hành vi mặc định này với các loại dẫn xuất lớp nếu cần thiết .

Đây là tất cả tốt và tốt và cung cấp cho việc tái sử dụng mã tuyệt vời và tuân thủ nguyên tắc phát triển phần mềm DRY (Đừng lặp lại chính mình). Các lớp trừu tượng rất tuyệt để sử dụng khi bạn có một mối quan hệ là một mối quan hệ.

Ví dụ: Một chú chó tha mồi vàng là một loại chó. Poodle cũng vậy. Cả hai đều có thể sủa, như tất cả các con chó có thể. Tuy nhiên, bạn có thể muốn nói rằng công viên poodle khác biệt đáng kể so với tiếng sủa của con chó mặc định. Do đó, nó có thể có ý nghĩa đối với bạn để thực hiện một cái gì đó như sau:

public abstract class Dog
{
      public virtual void Bark()
      {
        Console.WriteLine("Base Class implementation of Bark");
      }
}

public class GoldenRetriever : Dog
{
   // the Bark method is inherited from the Dog class
}

public class Poodle : Dog
{
  // here we are overriding the base functionality of Bark with our new implementation
  // specific to the Poodle class
  public override void Bark()
  {
     Console.WriteLine("Poodle's implementation of Bark");
  }
}

// Add a list of dogs to a collection and call the bark method.

void Main()
{
    var poodle = new Poodle();
    var goldenRetriever = new GoldenRetriever();

    var dogs = new List<Dog>();
    dogs.Add(poodle);
    dogs.Add(goldenRetriever);

    foreach (var dog in dogs)
    {
       dog.Bark();
    }
}

// Output will be:
// Poodle's implementation of Bark
// Base Class implementation of Bark

// 

Như bạn có thể thấy, đây sẽ là một cách tuyệt vời để giữ mã DRY của bạn và cho phép thực hiện lớp cơ sở khi bất kỳ loại nào chỉ có thể dựa vào Bark mặc định thay vì triển khai trường hợp đặc biệt. Các lớp như GoldenRetriever, Boxer, Lab đều có thể thừa hưởng Bark mặc định (lớp bass) Bark miễn phí chỉ vì họ triển khai lớp trừu tượng Dog.

Nhưng tôi chắc rằng bạn đã biết điều đó.

Bạn ở đây vì bạn muốn hiểu lý do tại sao bạn có thể muốn chọn một giao diện trên một lớp trừu tượng hoặc ngược lại. Vâng, một lý do bạn có thể muốn chọn một giao diện trên một lớp trừu tượng là khi bạn không có hoặc muốn ngăn chặn việc triển khai mặc định. Điều này thường là do các loại đang triển khai giao diện không liên quan trong một trò chơi là một mối quan hệ. Trên thực tế, họ không cần phải liên quan gì cả, ngoại trừ thực tế là mỗi loại CỰC có thể có hoặc có thể là một người khó tính để làm một cái gì đó hoặc có một cái gì đó.

Bây giờ cái quái đó có nghĩa là gì? Chà, ví dụ: Con người không phải là con vịt và con vịt không phải là con người. Khá rõ ràng. Tuy nhiên, cả một con vịt và một con người đều có khả năng bơi lội (cho rằng con người đã vượt qua bài học bơi của mình ở lớp 1 :)). Ngoài ra, vì một con vịt không phải là con người hay ngược lại, đây không phải là một trò chơi thực sự, mà thay vào đó, một mối quan hệ có thể có mối quan hệ và chúng ta có thể sử dụng một giao diện để minh họa rằng:

// Create ISwimable interface
public interface ISwimable
{
      public void Swim();
}

// Have Human implement ISwimable Interface
public class Human : ISwimable

     public void Swim()
     {
        //Human's implementation of Swim
        Console.WriteLine("I'm a human swimming!");
     }

// Have Duck implement ISwimable interface
public class Duck: ISwimable
{
     public void Swim()
     {
          // Duck's implementation of Swim
          Console.WriteLine("Quack! Quack! I'm a Duck swimming!")
     }
}

//Now they can both be used in places where you just need an object that has the ability "to swim"

public void ShowHowYouSwim(ISwimable somethingThatCanSwim)
{
     somethingThatCanSwim.Swim();
}

public void Main()
{
      var human = new Human();
      var duck = new Duck();

      var listOfThingsThatCanSwim = new List<ISwimable>();

      listOfThingsThatCanSwim.Add(duck);
      listOfThingsThatCanSwim.Add(human);

      foreach (var something in listOfThingsThatCanSwim)
      {
           ShowHowYouSwim(something);
      }
}

 // So at runtime the correct implementation of something.Swim() will be called
 // Output:
 // Quack! Quack! I'm a Duck swimming!
 // I'm a human swimming!

Sử dụng các giao diện như đoạn mã trên sẽ cho phép bạn truyền một đối tượng vào một phương thức mà có khả năng là để có thể làm gì đó. Mã không quan tâm đến cách thức hoạt động của nó. Tất cả những gì nó biết là nó có thể gọi phương thức Bơi trên đối tượng đó và đối tượng đó sẽ biết hành vi nào thực hiện trong thời gian chạy dựa trên loại của nó.

Một lần nữa, điều này giúp mã của bạn giữ được DRY để bạn không phải viết nhiều phương thức đang gọi đối tượng để tạo thành cùng một hàm cốt lõi (ShowHowHumanSwims (con người), ShowHowDuckSwims (vịt), v.v.)

Sử dụng một giao diện ở đây cho phép các phương thức gọi không phải lo lắng về loại nào hoặc cách thức thực hiện hành vi. Nó chỉ biết rằng được cung cấp giao diện, mỗi đối tượng sẽ phải thực hiện phương thức Bơi để an toàn khi gọi nó theo mã riêng và cho phép hành vi của phương thức Bơi được xử lý trong lớp riêng của nó.

Tóm lược:

Vì vậy, nguyên tắc chính của tôi là sử dụng một lớp trừu tượng khi bạn muốn triển khai chức năng mặc định của chế độ ăn mặc cho một hệ thống phân cấp lớp hoặc / và các lớp hoặc loại bạn đang làm việc với chia sẻ một mối quan hệ (ví dụ: poodle. Loại chó của chó).

Mặt khác, hãy sử dụng một giao diện khi bạn không có mối quan hệ với mối quan hệ tình cảm nhưng có các loại chia sẻ, khả năng có thể làm gì đó hoặc có một cái gì đó (ví dụ: Vịt không phải là con người. Tuy nhiên, vịt và con người chia sẻ Có khả năng bơi lội).

Một điểm khác biệt cần lưu ý giữa các lớp trừu tượng và giao diện là một lớp có thể thực hiện một đến nhiều giao diện nhưng một lớp chỉ có thể kế thừa từ MỘT lớp trừu tượng (hoặc bất kỳ lớp nào cho vấn đề đó). Có, bạn có thể lồng các lớp và có một hệ thống phân cấp thừa kế (mà nhiều chương trình nên và nên có) nhưng bạn không thể kế thừa hai lớp trong một định nghĩa lớp dẫn xuất (quy tắc này áp dụng cho C #. Trong một số ngôn ngữ khác, bạn có thể làm điều này, thông thường chỉ vì thiếu giao diện trong các ngôn ngữ này).

Cũng cần nhớ khi sử dụng các giao diện để tuân thủ Nguyên tắc phân chia giao diện (ISP). ISP tuyên bố rằng không có khách hàng nào bị buộc phải phụ thuộc vào các phương thức mà nó không sử dụng. Vì lý do này, các giao diện nên được tập trung vào các nhiệm vụ cụ thể và thường rất nhỏ (ví dụ: IDis Dùng một lần, IComparable).

Một mẹo khác là nếu bạn đang phát triển các bit nhỏ, ngắn gọn về chức năng, hãy sử dụng các 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.

Hy vọng điều này sẽ làm sáng tỏ mọi thứ cho một số người!

Ngoài ra nếu bạn có thể nghĩ ra bất kỳ ví dụ nào tốt hơn hoặc muốn chỉ ra điều gì đó, vui lòng làm như vậy trong các bình luận bên dưới!


6

Trường hợp cho các Lớp cơ sở trên Giao diện được giải thích rõ trong Nguyên tắc mã hóa .NET của Submain:

Các lớp cơ sở so với các giao diện Một loại giao diện là một mô tả một phần của một giá trị, có khả năng được hỗ trợ bởi nhiều loại đối tượng. Sử dụng các lớp cơ sở thay vì giao diện bất cứ khi nào có thể. Từ góc độ phiên bản, các lớp linh hoạt hơn các giao diện. Với một lớp, bạn có thể gửi Phiên bản 1.0 và sau đó trong Phiên bản 2.0 thêm một phương thức mới vào lớp. Miễn là phương thức này không trừu tượng, bất kỳ lớp dẫn xuất hiện có nào tiếp tục hoạt động không thay đổi.

Vì các giao diện không hỗ trợ kế thừa triển khai, nên mẫu áp dụng cho các lớp không áp dụng cho các giao diện. Thêm một phương thức vào một giao diện tương đương với việc thêm một phương thức trừu tượng vào một lớp cơ sở; bất kỳ lớp nào thực hiện giao diện sẽ bị hỏng vì lớp không thực hiện phương thức mới. Các giao diện phù hợp trong các tình huống sau:

  1. Một số lớp không liên quan muốn hỗ trợ giao thức.
  2. Các lớp này đã thiết lập các lớp cơ sở (ví dụ: một số là các điều khiển giao diện người dùng (UI) và một số là các dịch vụ Web XML).
  3. Tập hợp là không phù hợp hoặc thực tế. Trong tất cả các tình huống khác, kế thừa lớp là một mô hình tốt hơn.

Tôi cảm thấy câu trả lời này sẽ được chú ý nhiều hơn. Nó đi ngược lại hạt của nhiều câu trả lời, ở đây. Tôi sẽ không nói rằng tôi hoàn toàn đồng ý, nhưng có những điểm tuyệt vời ở đây.
kayleeFrye_onDeck

5

Một sự khác biệt quan trọng là bạn chỉ có thể kế thừa một lớp cơ sở, nhưng bạn có thể thực hiện nhiều giao diện. Vì vậy, bạn chỉ muốn sử dụng một lớp cơ sở nếu bạn hoàn toàn chắc chắn rằng bạn sẽ không cần phải kế thừa một lớp cơ sở khác. Ngoài ra, nếu bạn thấy giao diện của mình ngày càng lớn thì bạn nên bắt đầu tìm cách chia nó thành một vài phần logic xác định chức năng độc lập, vì không có quy tắc nào rằng lớp của bạn không thể thực hiện tất cả (hoặc bạn có thể định nghĩa khác giao diện chỉ kế thừa tất cả chúng để nhóm chúng).


4

Khi tôi mới bắt đầu tìm hiểu về lập trình hướng đối tượng, tôi đã mắc một lỗi dễ dàng và có thể phổ biến là sử dụng tính kế thừa để chia sẻ hành vi chung - ngay cả khi hành vi đó không cần thiết đối với bản chất của đối tượng.

Để tiếp tục xây dựng một ví dụ được sử dụng nhiều trong câu hỏi cụ thể này, có rất nhiều thứ dễ thương - bạn gái, xe hơi, chăn mờ ... - vì vậy tôi có thể đã có một lớp Petable cung cấp hành vi chung này và các lớp khác nhau kế thừa từ nó.

Tuy nhiên, trở thành thú cưng không phải là một phần bản chất của bất kỳ đối tượng nào trong số này. Có rất nhiều khái niệm quan trọng hơn rất cần thiết cho bản chất của họ - bạn gái là một người, xe hơi là phương tiện giao thông đường bộ, mèo là động vật có vú ...

Các hành vi nên được gán trước cho các giao diện (bao gồm giao diện mặc định của lớp) và chỉ được thăng cấp thành lớp cơ sở nếu chúng (a) chung cho một nhóm lớn các lớp con là tập hợp con của lớp lớn hơn - theo nghĩa tương tự "mèo" và "người" là tập hợp con của "động vật có vú".

Điều hấp dẫn là, sau khi bạn hiểu thiết kế hướng đối tượng đủ tốt hơn tôi đã làm lúc đầu, bạn sẽ thường làm điều này một cách tự động mà không cần nghĩ về nó. Vì vậy, sự thật trần trụi của tuyên bố "mã cho một giao diện, không phải là một lớp trừu tượng" trở nên rõ ràng đến mức bạn khó có thể tin bất cứ ai sẽ bận tâm để nói nó - và bắt đầu cố gắng đọc các ý nghĩa khác vào nó.

Một điều nữa tôi muốn nói thêm là nếu một lớp hoàn toàn trừu tượng - không có các thành viên hoặc phương thức không trừu tượng, không được kế thừa tiếp xúc với trẻ em, cha mẹ hoặc khách hàng - thì tại sao nó lại là một lớp? Nó có thể được thay thế, trong một số trường hợp bởi một giao diện và trong các trường hợp khác bởi Null.


Một lớp hoàn toàn trừu tượng có thể cung cấp các hành vi mặc định cho các phương thức. Điều này rất hữu ích khi tất cả các lớp cụ thể của bạn sẽ chia sẻ các phương thức phổ biến sẽ là dự phòng để thực hiện lại nhiều lần.
Adam Hughes

4

Thích giao diện hơn các lớp trừu tượng

Đặt vấn đề, những điểm chính cần xem xét [hai đã được đề cập ở đây] là:

  • Các giao diện linh hoạt hơn, bởi vì một lớp có thể thực hiện nhiều giao diện. Vì Java không có nhiều kế thừa, nên việc sử dụng các lớp trừu tượng sẽ ngăn người dùng của bạn sử dụng bất kỳ hệ thống phân cấp lớp nào khác. Nói chung, thích giao diện hơn khi không có cài đặt hoặc trạng thái mặc định. Các bộ sưu tập Java cung cấp các ví dụ tốt về điều này (Bản đồ, Bộ, v.v.).
  • Các lớp trừu tượng có lợi thế cho phép tương thích tốt hơn về phía trước. Khi khách hàng sử dụng giao diện, bạn không thể thay đổi giao diện đó; nếu họ sử dụng một lớp trừu tượng, bạn vẫn có thể thêm hành vi mà không vi phạm mã hiện có. Nếu khả năng tương thích là một mối quan tâm, hãy xem xét sử dụng các lớp trừu tượng.
  • Ngay cả khi bạn có triển khai mặc định hoặc trạng thái nội bộ, hãy xem xét việc cung cấp giao diện và triển khai trừu tượng về giao diện đó . Điều này sẽ hỗ trợ khách hàng, nhưng vẫn cho phép họ tự do hơn nếu muốn [1].
    Tất nhiên, chủ đề đã được thảo luận ở độ dài khác [2,3].

[1] Tất nhiên, nó thêm nhiều mã hơn, nhưng nếu sự ngắn gọn là mối quan tâm chính của bạn, có lẽ bạn nên tránh Java ngay từ đầu!

[2] Joshua Bloch, Java hiệu quả, các mục 16-18.

[3] http://www.codeproject.com/KB/ar ...


3

Nhận xét trước đây về việc sử dụng các lớp trừu tượng để thực hiện phổ biến chắc chắn là trên nhãn hiệu. Một lợi ích mà tôi chưa thấy đề cập đến là việc sử dụng các giao diện giúp cho việc thực hiện các đối tượng giả dễ dàng hơn nhiều cho mục đích thử nghiệm đơn vị. Xác định IPet và PetBase như Jason Cohen mô tả cho phép bạn dễ dàng giả lập các điều kiện dữ liệu khác nhau mà không cần cơ sở dữ liệu vật lý (cho đến khi bạn quyết định đã đến lúc kiểm tra thực tế).


3

Không sử dụng lớp cơ sở trừ khi bạn biết ý nghĩa của nó và nó được áp dụng trong trường hợp này. Nếu nó được áp dụng, sử dụng nó, nếu không, sử dụng giao diện. Nhưng lưu ý câu trả lời về giao diện nhỏ.

Quyền thừa kế công cộng được sử dụng quá mức ở OOD và thể hiện rất nhiều so với hầu hết các nhà phát triển nhận ra hoặc sẵn sàng sống theo. Xem Nguyên tắc thay thế Liskov

Nói tóm lại, nếu A "là" B thì A yêu cầu không nhiều hơn B và cung cấp không ít hơn B, cho mọi phương thức mà nó đưa ra.


3

Một lựa chọn khác cần ghi nhớ là sử dụng mối quan hệ "has-a", hay còn gọi là "được thực hiện theo nghĩa" hoặc "thành phần". Đôi khi, đây là cách dễ dàng hơn, linh hoạt hơn để cấu trúc mọi thứ hơn là sử dụng thừa kế "is-a".

Có thể không có ý nghĩa nhiều về mặt logic khi nói rằng cả Dog và Cat đều "có" Pet, nhưng nó tránh được nhiều cạm bẫy thừa kế phổ biến:

public class Pet
{
    void Bathe();
    void Train(Trick t);
}

public class Dog
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

public class Cat
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

Vâng, ví dụ này cho thấy có rất nhiều sự trùng lặp mã và thiếu thanh lịch liên quan đến việc thực hiện mọi thứ theo cách này. Nhưng người ta cũng nên đánh giá cao rằng điều này giúp giữ cho Dog và Cat tách khỏi lớp Pet (trong đó Dog và Cat không có quyền truy cập vào các thành viên riêng của Pet), và nó để lại chỗ cho Dog và Cat thừa hưởng từ một thứ khác- - có thể là lớp động vật có vú.

Thành phần được ưu tiên hơn khi không yêu cầu quyền truy cập riêng tư và bạn không cần phải tham khảo Chó và Mèo bằng cách sử dụng các tham chiếu / con trỏ Pet chung. Các giao diện cung cấp cho bạn khả năng tham chiếu chung đó và có thể giúp cắt giảm tính dài dòng của mã của bạn, nhưng chúng cũng có thể làm xáo trộn mọi thứ khi chúng được tổ chức kém. Kế thừa rất hữu ích khi bạn cần quyền truy cập của thành viên tư nhân và khi sử dụng nó, bạn cam kết sẽ kết hợp chặt chẽ các lớp Dog và Cat với lớp Pet của bạn, đây là một chi phí rất lớn phải trả.

Giữa kế thừa, thành phần và giao diện không có một cách nào luôn luôn đúng và nó giúp xem xét làm thế nào cả ba tùy chọn có thể được sử dụng hài hòa. Trong ba, kế thừa thường là tùy chọn nên được sử dụng ít nhất.


3

Về mặt khái niệm, một giao diện được sử dụng để xác định chính thức và bán chính thức một tập hợp các phương thức mà một đối tượng sẽ cung cấp. Chính thức có nghĩa là một tập hợp các tên phương thức và chữ ký, và bán chính thức có nghĩa là tài liệu có thể đọc được của con người liên quan đến các phương thức đó.

Các giao diện chỉ là mô tả của một API (xét cho cùng, API là viết tắt của giao diện lập trình ứng dụng ), chúng không thể chứa bất kỳ triển khai nào và không thể sử dụng hoặc chạy giao diện. Họ chỉ làm rõ hợp đồng về cách bạn nên tương tác với một đối tượng.

Các lớp cung cấp một triển khai và họ có thể tuyên bố rằng họ thực hiện 0, một hoặc nhiều Giao diện. Nếu một lớp được dự định kế thừa, quy ước là tiền tố tên lớp với "Base".

Có một sự phân biệt giữa một lớp cơ sở và một lớp cơ sở trừu tượng (ABC). Giao diện trộn ABC và thực hiện với nhau. Trừu tượng bên ngoài lập trình máy tính có nghĩa là "tóm tắt", đó là "trừu tượng == giao diện". Một lớp cơ sở trừu tượng sau đó có thể mô tả cả một giao diện, cũng như một thực hiện trống rỗng, một phần hoặc toàn được dự định sẽ được thừa hưởng.

Ý kiến ​​về việc khi nào nên sử dụng giao diện so với các lớp cơ sở trừu tượng so với chỉ các lớp sẽ thay đổi mạnh mẽ dựa trên cả những gì bạn đang phát triển và ngôn ngữ bạn đang phát triển. Các giao diện thường chỉ được liên kết với các ngôn ngữ được nhập tĩnh như Java hoặc C #, nhưng ngôn ngữ gõ động cũng có thể có giao diệncác lớp cơ sở trừu tượng . Ví dụ, trong Python, sự phân biệt được làm rõ giữa một Class, nó tuyên bố rằng nó thực hiện một giao diện và một đối tượng, là một thể hiện của một lớp và được cho là cung cấp giao diện đó. Có thể trong một ngôn ngữ động, hai đối tượng là cả hai thể hiện của cùng một lớp , có thể tuyên bố rằng chúng cung cấp các giao diện hoàn toàn khác nhau . Trong Python, điều này chỉ có thể cho các thuộc tính đối tượng, trong khi các phương thức được chia sẻ trạng thái giữa tất cả các đối tượng của một lớp . Tuy nhiên, trong Ruby, các đối tượng có thể có các phương thức theo thể hiện, do đó, có thể giao diện giữa hai đối tượng cùng lớp có thể khác nhau tùy theo lập trình viên mong muốn (tuy nhiên, Ruby không có cách khai báo Giao diện rõ ràng nào).

Trong các ngôn ngữ động, giao diện cho một đối tượng thường được giả định ngầm, bằng cách hướng nội một đối tượng và hỏi nó cung cấp phương thức nào ( nhìn trước khi bạn nhảy ) hoặc tốt nhất là chỉ đơn giản là cố gắng sử dụng giao diện mong muốn trên một đối tượng và bắt ngoại lệ nếu đối tượng không cung cấp giao diện đó ( dễ xin tha thứ hơn là cho phép ). Điều này có thể dẫn đến "dương tính giả" trong đó hai giao diện có cùng tên phương thức, nhưng khác nhau về mặt ngữ nghĩa. Tuy nhiên, sự đánh đổi là mã của bạn linh hoạt hơn vì bạn không cần chỉ định quá mức để dự đoán tất cả các cách sử dụng mã có thể có của mình.


2

Nó phụ thuộc vào yêu cầu của bạn. Nếu IPet đủ đơn giản, tôi muốn thực hiện điều đó. Mặt khác, nếu PetBase thực hiện một tấn chức năng mà bạn không muốn sao chép, thì hãy sử dụng nó.

Nhược điểm để thực hiện một lớp cơ sở là yêu cầu override(hoặc new) các phương thức hiện có. Điều này làm cho chúng các phương thức ảo có nghĩa là bạn phải cẩn thận về cách bạn sử dụng thể hiện đối tượng.

Cuối cùng, sự kế thừa duy nhất của .NET giết chết tôi. Một ví dụ ngây thơ: Giả sử bạn đang thực hiện kiểm soát người dùng, do đó bạn kế thừa UserControl. Nhưng, bây giờ bạn bị khóa khỏi kế thừa PetBase. Điều này buộc bạn phải tổ chức lại, chẳng hạn như để làm một PetBasethành viên trong lớp, thay vào đó.


2

Tôi thường không thực hiện hoặc cho đến khi tôi cần. Tôi thích giao diện hơn các lớp trừu tượng vì điều đó cho phép linh hoạt hơn một chút. Nếu có hành vi phổ biến trong một số lớp kế thừa, tôi sẽ chuyển nó lên và tạo một lớp cơ sở trừu tượng. Tôi không thấy sự cần thiết của cả hai, vì về cơ bản chúng phục vụ cùng một mục đích và có cả hai là mùi mã xấu (imho) rằng giải pháp đã được thiết kế quá mức.


2

Về C #, trong một số giác quan giao diện và các lớp trừu tượng có thể thay thế cho nhau. Tuy nhiên, sự khác biệt là: i) giao diện không thể thực thi mã; ii) vì điều này, các giao diện không thể gọi thêm ngăn xếp đến lớp con; và iii) chỉ có thể kế thừa lớp trừu tượng trên một lớp, trong khi nhiều giao diện có thể được thực hiện trên một lớp.


2

Theo def, giao diện cung cấp một lớp để giao tiếp với các mã khác. Tất cả các thuộc tính và phương thức công khai của một lớp theo mặc định đang thực hiện giao diện ẩn. Chúng ta cũng có thể định nghĩa một giao diện là một vai trò, khi bất kỳ lớp nào cần đóng vai trò đó, nó phải thực hiện nó cho nó các hình thức thực hiện khác nhau tùy thuộc vào lớp thực hiện nó. Do đó khi bạn nói về giao diện, bạn đang nói về đa hình và khi bạn nói về lớp cơ sở, bạn đang nói về sự kế thừa. Hai khái niệm về oops !!!


2

Tôi đã thấy rằng một mẫu Giao diện> Trừu tượng> Bê tông hoạt động trong trường hợp sử dụng sau:

1.  You have a general interface (eg IPet)
2.  You have a implementation that is less general (eg Mammal)
3.  You have many concrete members (eg Cat, Dog, Ape)

Lớp trừu tượng định nghĩa các thuộc tính được chia sẻ mặc định của các lớp cụ thể, nhưng vẫn thực thi giao diện. Ví dụ:

public interface IPet{

    public boolean hasHair();

    public boolean walksUprights();

    public boolean hasNipples();
}

Bây giờ, vì tất cả các động vật có vú đều có lông và núm vú (AFAIK, tôi không phải là nhà động vật học), chúng ta có thể cuộn nó vào lớp cơ sở trừu tượng

public abstract class Mammal() implements IPet{

     @override
     public walksUpright(){
         throw new NotSupportedException("Walks Upright not implemented");
     }

     @override
     public hasNipples(){return true}

     @override
     public hasHair(){return true}

Và sau đó các lớp cụ thể chỉ đơn thuần xác định rằng chúng đi thẳng đứng.

public class Ape extends Mammal(){

    @override
    public walksUpright(return true)
}

public class Catextends Mammal(){

    @override
    public walksUpright(return false)
}

Thiết kế này rất tuyệt khi có nhiều lớp cụ thể và bạn không muốn duy trì bản tóm tắt chỉ để lập trình cho một giao diện. Nếu các phương thức mới được thêm vào giao diện, nó sẽ phá vỡ tất cả các lớp kết quả, vì vậy bạn vẫn nhận được những lợi thế của phương pháp giao diện.

Trong trường hợp này, bản tóm tắt cũng có thể cụ thể; tuy nhiên, chỉ định trừu tượng giúp nhấn mạnh rằng mẫu này đang được sử dụng.


1

Một người thừa kế của một lớp cơ sở nên có một mối quan hệ "là một". Giao diện đại diện cho mối quan hệ "thực hiện". Vì vậy, chỉ sử dụng một lớp cơ sở khi những người thừa kế của bạn sẽ duy trì mối quan hệ.


1

Sử dụng Giao diện để thực thi một họ ACROSS hợp đồng của các lớp không liên quan. Ví dụ: bạn có thể có các phương thức truy cập phổ biến cho các lớp đại diện cho các bộ sưu tập, nhưng chứa dữ liệu hoàn toàn khác nhau, tức là một lớp có thể biểu thị một tập kết quả từ một truy vấn, trong khi lớp kia có thể biểu thị các hình ảnh trong một bộ sưu tập. Ngoài ra, bạn có thể thực hiện nhiều giao diện, do đó cho phép bạn pha trộn (và biểu thị) các khả năng của lớp.

Sử dụng Kế thừa khi các lớp có mối quan hệ chung và do đó có chữ ký cấu trúc và hành vi của similair, ví dụ: Ô tô, Xe máy, Xe tải và SUV là tất cả các loại phương tiện giao thông đường bộ có thể chứa một số bánh xe, tốc độ tối đa

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.