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!