Đây có thể là một câu hỏi OOP chung. Tôi muốn làm một so sánh chung giữa một giao diện và một lớp trừu tượng trên cơ sở sử dụng của chúng.
Khi nào thì người ta muốn sử dụng một giao diện và khi nào thì người ta muốn sử dụng một lớp trừu tượng ?
Đây có thể là một câu hỏi OOP chung. Tôi muốn làm một so sánh chung giữa một giao diện và một lớp trừu tượng trên cơ sở sử dụng của chúng.
Khi nào thì người ta muốn sử dụng một giao diện và khi nào thì người ta muốn sử dụng một lớp trừu tượng ?
Câu trả lời:
Tôi đã viết một bài báo về điều đó:
Các lớp và giao diện trừu tượng
Tóm tắt:
Khi chúng ta nói về các lớp trừu tượng, chúng ta đang xác định các đặc điểm của một loại đối tượng; chỉ định một đối tượng là gì .
Khi chúng ta nói về một giao diện và xác định các khả năng mà chúng ta hứa sẽ cung cấp, chúng ta đang nói về việc thiết lập một hợp đồng về những gì đối tượng có thể làm.
Interfaces do not express something like "a Doberman is a type of dog and every dog can walk" but more like "this thing can walk"
. Cảm ơn bạn
Use abstract classes and inheritance if you can make the statement “A is a B”. Use interfaces if you can make the statement “A is capable of [doing] as”
Một lớp trừu tượng có thể có trạng thái chia sẻ hoặc chức năng. Một giao diện chỉ là một lời hứa để cung cấp trạng thái hoặc chức năng. Một lớp trừu tượng tốt sẽ làm giảm số lượng mã phải viết lại vì chức năng hoặc trạng thái của nó có thể được chia sẻ. Giao diện không có thông tin xác định sẽ được chia sẻ
Cá nhân, tôi gần như không bao giờ có nhu cầu viết các lớp trừu tượng.
Hầu hết tôi thấy các lớp trừu tượng được sử dụng (mis), đó là vì tác giả của lớp trừu tượng đang sử dụng mẫu "Phương thức mẫu".
Vấn đề với "Phương thức mẫu" là gần như luôn luôn được cấp lại - lớp "dẫn xuất" biết về không chỉ phương thức "trừu tượng" của lớp cơ sở mà nó đang triển khai, mà còn về các phương thức công khai của lớp cơ sở , mặc dù hầu hết các lần không cần gọi cho họ.
(Đơn giản hóa quá mức) ví dụ:
abstract class QuickSorter
{
public void Sort(object[] items)
{
// implementation code that somewhere along the way calls:
bool less = compare(x,y);
// ... more implementation code
}
abstract bool compare(object lhs, object rhs);
}
Vì vậy, ở đây, tác giả của lớp này đã viết một thuật toán chung và dự định cho mọi người sử dụng nó bằng cách "chuyên" hóa nó bằng cách cung cấp "móc" của riêng họ - trong trường hợp này là phương pháp "so sánh".
Vì vậy, mục đích sử dụng là như thế này:
class NameSorter : QuickSorter
{
public bool compare(object lhs, object rhs)
{
// etc.
}
}
Vấn đề với điều này là bạn đã kết hợp quá mức hai khái niệm:
Về mặt lý thuyết, về mặt lý thuyết, tác giả của phương pháp "so sánh" có thể gọi lại một cách ngẫu nhiên vào phương thức "Sắp xếp" của siêu lớp ... mặc dù trong thực tế, họ sẽ không bao giờ muốn hoặc không cần phải làm điều này.
Cái giá bạn phải trả cho khớp nối không cần thiết này là rất khó để thay đổi siêu lớp và trong hầu hết các ngôn ngữ OO, không thể thay đổi nó khi chạy.
Phương pháp thay thế là sử dụng mẫu thiết kế "Chiến lược" thay thế:
interface IComparator
{
bool compare(object lhs, object rhs);
}
class QuickSorter
{
private readonly IComparator comparator;
public QuickSorter(IComparator comparator)
{
this.comparator = comparator;
}
public void Sort(object[] items)
{
// usual code but call comparator.Compare();
}
}
class NameComparator : IComparator
{
bool compare(object lhs, object rhs)
{
// same code as before;
}
}
Vì vậy, hãy chú ý ngay bây giờ: Tất cả những gì chúng ta có là các giao diện và triển khai cụ thể các giao diện đó. Trong thực tế, bạn không thực sự cần bất cứ điều gì khác để thực hiện một thiết kế OO cấp cao.
Để "che giấu" thực tế là chúng tôi đã triển khai "sắp xếp tên" bằng cách sử dụng lớp "QuickSort" và "NameComparator", chúng tôi vẫn có thể viết phương thức xuất xưởng ở đâu đó:
ISorter CreateNameSorter()
{
return new QuickSorter(new NameComparator());
}
Bất cứ khi nào bạn có một lớp trừu tượng, bạn có thể làm điều này ... ngay cả khi có mối quan hệ tái ủy quyền tự nhiên giữa lớp cơ sở và lớp dẫn xuất, nó thường trả tiền để làm cho chúng rõ ràng.
Một suy nghĩ cuối cùng: Tất cả những gì chúng ta đã làm ở trên là "soạn thảo" chức năng "NameSorting" bằng cách sử dụng chức năng "QuickSort" và chức năng "NameComparison" ... trong ngôn ngữ lập trình chức năng, phong cách lập trình này thậm chí còn tự nhiên hơn, với ít mã hơn.
Nếu bạn đang xem java là ngôn ngữ OOP,
" Giao diện không cung cấp phương thức triển khai " không còn hợp lệ với khởi chạy Java 8. Bây giờ java cung cấp thực hiện trong giao diện cho các phương thức mặc định.
Nói một cách đơn giản, tôi muốn sử dụng
giao diện: Để thực hiện hợp đồng bởi nhiều đối tượng không liên quan. Nó cung cấp " ĐÃ A khả năng ".
lớp trừu tượng: Để thực hiện cùng một hành vi hoặc khác nhau giữa nhiều đối tượng liên quan. Nó thành lập " mối quan hệ IS A ".
Trang web của Oracle cung cấp sự khác biệt chính giữa interface
và abstract
lớp.
Cân nhắc sử dụng các lớp trừu tượng nếu:
Cân nhắc sử dụng các giao diện nếu:
Serializable
giao diện.Thí dụ:
Lớp trừu tượng ( quan hệ IS )
Reader là một lớp trừu tượng.
BufferedReader là mộtReader
FileReader là mộtReader
FileReader
và BufferedReader
được sử dụng cho mục đích chung: Đọc dữ liệu và chúng có liên quan thông qua Reader
lớp học.
Interface ( HAS Một khả năng)
Nối tiếp là một giao diện.
Giả sử rằng bạn có hai lớp trong ứng dụng của mình, đang triển khai Serializable
giao diện
Employee implements Serializable
Game implements Serializable
Ở đây bạn không thể thiết lập bất kỳ mối quan hệ nào thông qua Serializable
giao diện giữa Employee
và Game
, có nghĩa là cho các mục đích khác nhau. Cả hai đều có khả năng nối tiếp trạng thái và sự so sánh kết thúc ở đó.
Hãy xem những bài viết này:
Làm thế nào tôi có thể giải thích sự khác biệt giữa một lớp Giao diện và lớp Trừu tượng?
Được rồi, tôi đã tự "mò mẫm" điều này - đây là điều khoản của giáo dân (cứ tự nhiên sửa tôi nếu tôi sai) - Tôi biết chủ đề này là oooooold, nhưng một ngày nào đó người khác có thể vấp phải nó ...
Các lớp trừu tượng cho phép bạn tạo một kế hoạch chi tiết và cho phép bạn bổ sung các thuộc tính và phương thức XÂY DỰNG (thực hiện) mà bạn muốn TẤT CẢ con cháu của nó sở hữu.
Mặt khác, một giao diện chỉ cho phép bạn khai báo rằng bạn muốn các thuộc tính và / hoặc các phương thức có tên đã tồn tại trong tất cả các lớp thực hiện nó - nhưng không chỉ định cách bạn nên triển khai nó. Ngoài ra, một lớp có thể thực hiện NHIỀU giao diện, nhưng chỉ có thể mở rộng MỘT lớp Trừu tượng. Giao diện là một công cụ kiến trúc cấp cao (sẽ trở nên rõ ràng hơn nếu bạn bắt đầu nắm bắt các mẫu thiết kế) - một Tóm tắt có một chân trong cả hai trại và cũng có thể thực hiện một số công việc bẩn thỉu.
Tại sao sử dụng cái này hơn cái kia? Cái trước cho phép định nghĩa cụ thể hơn về con cháu - cái sau cho phép đa hình lớn hơn . Điểm cuối cùng này rất quan trọng đối với người dùng cuối / người viết mã, người có thể sử dụng thông tin này để triển khai AP I (nterface) trong nhiều kết hợp / hình dạng phù hợp với nhu cầu của họ.
Tôi nghĩ rằng đây là khoảnh khắc "bóng đèn" đối với tôi - nghĩ về các giao diện ít hơn từ nhận thức của tác giả và nhiều hơn từ bất kỳ lập trình viên nào đến sau trong chuỗi, người đang thêm triển khai vào dự án hoặc mở rộng API.
Theo quan điểm của tôi:
Một giao diện về cơ bản xác định một hợp đồng, mà bất kỳ lớp thực hiện nào cũng phải tuân thủ (thực hiện các thành viên giao diện). Nó không chứa bất kỳ mã nào.
Mặt khác, một lớp trừu tượng có thể chứa mã và có thể có một số phương thức được đánh dấu là trừu tượng mà một lớp kế thừa phải thực hiện.
Các tình huống hiếm gặp mà tôi đã sử dụng các lớp trừu tượng là khi tôi có một số chức năng mặc định mà lớp kế thừa có thể không thú vị khi ghi đè, nói một lớp cơ sở trừu tượng, mà một số lớp chuyên biệt kế thừa.
Ví dụ (một trong rất thô sơ!): Xem xét một lớp cơ sở gọi là khách hàng trong đó có phương pháp trừu tượng như CalculatePayment()
, CalculateRewardPoints()
và một số phương pháp phi trừu tượng như GetName()
, SavePaymentDetails()
.
Các lớp chuyên biệt thích RegularCustomer
và GoldCustomer
sẽ kế thừa từ Customer
lớp cơ sở và thực hiện logic riêng CalculatePayment()
và CalculateRewardPoints()
phương thức của chúng, nhưng sử dụng lại các phương thức GetName()
và SavePaymentDetails()
.
Bạn có thể thêm nhiều chức năng hơn vào một lớp trừu tượng (phương thức không trừu tượng) mà không ảnh hưởng đến các lớp con đang sử dụng phiên bản cũ hơn. Trong khi đó việc thêm các phương thức vào một giao diện sẽ ảnh hưởng đến tất cả các lớp thực hiện nó vì bây giờ chúng sẽ cần phải thực hiện các thành viên giao diện mới được thêm vào.
Một lớp trừu tượng với tất cả các thành viên trừu tượng sẽ tương tự như một giao diện.
Khi nào nên làm những gì là một điều rất đơn giản nếu bạn có khái niệm rõ ràng trong tâm trí của bạn.
Các lớp trừu tượng có thể được tạo ra trong khi Giao diện có thể được triển khai. Có một số khác biệt giữa hai. Khi bạn lấy được một lớp Trừu tượng, mối quan hệ giữa lớp dẫn xuất và lớp cơ sở là 'là một mối quan hệ'. ví dụ: Chó là Động vật, Cừu là Động vật có nghĩa là lớp Derogen đang thừa hưởng một số thuộc tính từ lớp cơ sở.
Trong khi thực hiện các giao diện, mối quan hệ là "có thể". ví dụ, một con chó có thể là một con chó gián điệp. Một con chó có thể là một con chó xiếc. Một con chó có thể là một con chó đua. Điều đó có nghĩa là bạn thực hiện các phương pháp nhất định để có được một cái gì đó.
Tôi hy vọng tôi rõ ràng.
1.Nếu bạn đang tạo một cái gì đó cung cấp chức năng chung cho các lớp không liên quan, hãy sử dụng một giao diện.
2.Nếu bạn đang tạo một cái gì đó cho các đối tượng có liên quan chặt chẽ trong hệ thống phân cấp, hãy sử dụng một lớp trừu tượng.
Tôi đã viết một bài báo về khi nào nên sử dụng một lớp trừu tượng và khi nào nên sử dụng một giao diện. Có rất nhiều sự khác biệt giữa chúng ngoài "một IS-A ... và một CAN-DO ...". Đối với tôi, đó là những câu trả lời đóng hộp. Tôi đề cập đến một vài lý do khi sử dụng một trong hai. Hy vọng nó giúp.
Tôi nghĩ rằng cách đặt cô đọng nhất là như sau:
Thuộc tính được chia sẻ => lớp trừu tượng.
Chức năng dùng chung => giao diện.
Và để nó bớt ngắn gọn ...
Ví dụ lớp trừu tượng:
public abstract class BaseAnimal
{
public int NumberOfLegs { get; set; }
protected BaseAnimal(int numberOfLegs)
{
NumberOfLegs = numberOfLegs;
}
}
public class Dog : BaseAnimal
{
public Dog() : base(4) { }
}
public class Human : BaseAnimal
{
public Human() : base(2) { }
}
Vì động vật có một tài sản chung - số chân trong trường hợp này - nên tạo ra một lớp trừu tượng có chứa tài sản chung này. Điều này cũng cho phép chúng tôi viết mã phổ biến hoạt động trên tài sản đó. Ví dụ:
public static int CountAllLegs(List<BaseAnimal> animals)
{
int legCount = 0;
foreach (BaseAnimal animal in animals)
{
legCount += animal.NumberOfLegs;
}
return legCount;
}
Ví dụ về giao diện:
public interface IMakeSound
{
void MakeSound();
}
public class Car : IMakeSound
{
public void MakeSound() => Console.WriteLine("Vroom!");
}
public class Vuvuzela : IMakeSound
{
public void MakeSound() => Console.WriteLine("VZZZZZZZZZZZZZ!");
}
Lưu ý ở đây rằng Vuvuzelas và Ô tô là những thứ hoàn toàn khác nhau, nhưng chúng có chung chức năng: tạo ra âm thanh. Do đó, một giao diện có ý nghĩa ở đây. Hơn nữa, nó sẽ cho phép các lập trình viên nhóm các thứ tạo ra âm thanh với nhau theo một giao diện chung - IMakeSound
trong trường hợp này. Với thiết kế này, bạn có thể viết mã sau đây:
List<IMakeSound> soundMakers = new List<ImakeSound>();
soundMakers.Add(new Car());
soundMakers.Add(new Vuvuzela());
soundMakers.Add(new Car());
soundMakers.Add(new Vuvuzela());
soundMakers.Add(new Vuvuzela());
foreach (IMakeSound soundMaker in soundMakers)
{
soundMaker.MakeSound();
}
Bạn có thể cho biết những gì sẽ đầu ra?
Cuối cùng, bạn có thể kết hợp cả hai.
Ví dụ kết hợp:
public interface IMakeSound
{
void MakeSound();
}
public abstract class BaseAnimal : IMakeSound
{
public int NumberOfLegs { get; set; }
protected BaseAnimal(int numberOfLegs)
{
NumberOfLegs = numberOfLegs;
}
public abstract void MakeSound();
}
public class Cat : BaseAnimal
{
public Cat() : base(4) { }
public override void MakeSound() => Console.WriteLine("Meow!");
}
public class Human : BaseAnimal
{
public Human() : base(2) { }
public override void MakeSound() => Console.WriteLine("Hello, world!");
}
Ở đây, chúng tôi yêu cầu tất cả mọi người BaseAnimal
tạo ra âm thanh, nhưng chúng tôi chưa biết việc thực hiện nó. Trong trường hợp như vậy, chúng ta có thể trừu tượng hóa việc thực hiện giao diện và ủy thác việc thực hiện nó cho các lớp con của nó.
Một điểm cuối cùng, hãy nhớ làm thế nào trong ví dụ lớp trừu tượng chúng ta có thể hoạt động trên các thuộc tính được chia sẻ của các đối tượng khác nhau và trong ví dụ giao diện, chúng ta có thể gọi chức năng chia sẻ của các đối tượng khác nhau? Trong ví dụ cuối cùng này, chúng ta có thể làm cả hai.
Khi nào thích một lớp trừu tượng hơn giao diện?
Khi nào thích một giao diện hơn lớp trừu tượng?
Các lớp chỉ có thể kế thừa từ một lớp cơ sở, vì vậy nếu bạn muốn sử dụng các lớp trừu tượng để cung cấp đa hình cho một nhóm các lớp, tất cả chúng phải kế thừa từ lớp đó. Các lớp trừu tượng cũng có thể cung cấp các thành viên đã được thực hiện. Do đó, bạn có thể đảm bảo một số lượng chức năng giống hệt nhau với một lớp trừu tượng, nhưng không thể có giao diện.
Dưới đây là một số đề xuất để giúp bạn quyết định nên sử dụng giao diện hay lớp trừu tượng để cung cấp tính đa hình cho các thành phần của bạn.
Sao chép từ: http://msdn.microsoft.com/en-us/l Library / scsyfw1d% 28v = vs.71% 29.aspx
Cân nhắc sử dụng các lớp trừu tượng nếu bất kỳ câu lệnh nào trong số này áp dụng cho tình huống của bạn:
Cân nhắc sử dụng các giao diện nếu bất kỳ câu lệnh nào trong số này áp dụng cho tình huống của bạn:
Các câu trả lời khác nhau giữa các ngôn ngữ. Ví dụ, trong Java, một lớp có thể thực hiện (kế thừa từ) nhiều giao diện nhưng chỉ kế thừa từ một lớp trừu tượng. Vì vậy, giao diện cho phép bạn linh hoạt hơn. Nhưng điều này không đúng trong C ++.
Đối với tôi, tôi sẽ đi với giao diện trong nhiều trường hợp. Nhưng tôi thích các lớp trừu tượng trong một số trường hợp.
Các lớp trong OO tướng đề cập đến việc thực hiện. Tôi sử dụng các lớp trừu tượng khi tôi muốn buộc một số chi tiết triển khai cho các đứa trẻ khác mà tôi đi với các giao diện.
Tất nhiên, các lớp trừu tượng không chỉ hữu ích trong việc ép buộc thực hiện mà còn trong việc chia sẻ một số chi tiết cụ thể giữa nhiều lớp liên quan.
Sử dụng một lớp trừu tượng nếu bạn muốn cung cấp một số triển khai cơ bản.
trong java, bạn có thể kế thừa từ một lớp (trừu tượng) để "cung cấp" chức năng và bạn có thể thực hiện nhiều giao diện để "đảm bảo" chức năng
Hoàn toàn dựa trên cơ sở thừa kế, bạn sẽ sử dụng Tóm tắt trong đó bạn xác định rõ ràng mối quan hệ con cháu, trừu tượng (ví dụ: động vật-> mèo) và / hoặc yêu cầu thừa kế các thuộc tính ảo hoặc không công khai, đặc biệt là trạng thái chia sẻ (mà Giao diện không thể hỗ trợ ).
Mặc dù vậy, bạn nên thử và ưu tiên thành phần (thông qua tiêm phụ thuộc) so với thừa kế ở nơi bạn có thể và lưu ý rằng Giao diện là hợp đồng hỗ trợ kiểm tra đơn vị, tách mối quan tâm và (thay đổi ngôn ngữ) theo cách thừa kế theo cách không thể tóm tắt.
Một vị trí thú vị nơi giao diện có giá tốt hơn các lớp trừu tượng là khi bạn cần thêm chức năng bổ sung vào một nhóm các đối tượng (liên quan hoặc không liên quan). Nếu bạn không thể cung cấp cho họ một lớp trừu tượng cơ bản (ví dụ: họ sealed
đã hoặc đã có cha mẹ), bạn có thể cung cấp cho họ một giao diện giả (trống), và sau đó chỉ cần viết các phương thức mở rộng cho giao diện đó.
Đây có thể là một cuộc gọi rất khó thực hiện ...
Một con trỏ tôi có thể đưa ra: Một đối tượng có thể thực hiện nhiều giao diện, trong khi một đối tượng chỉ có thể kế thừa một lớp cơ sở (trong ngôn ngữ OO hiện đại như c #, tôi biết C ++ có nhiều sự kế thừa - nhưng không phải vì thế mà sao?)
Một lớp trừu tượng có thể có triển khai.
Một giao diện không có triển khai, nó chỉ đơn giản là xác định một loại hợp đồng.
Cũng có thể có một số khác biệt phụ thuộc vào ngôn ngữ: ví dụ C # không có nhiều kế thừa, nhưng nhiều giao diện có thể được thực hiện trong một lớp.
Quy tắc ngón tay cái cơ bản là: Đối với "Danh từ", hãy sử dụng lớp Tóm tắt và cho "Động từ" sử dụng giao diện
Ví dụ: car
là một lớp trừu tượng và drive
, chúng ta có thể biến nó thành một giao diện.
drive
trong xe - đó là một lớp trừu tượng.