Khi nào nên sử dụng các lớp trừu tượng thay vì giao diện với các phương thức mở rộng trong C #?


70

"Lớp trừu tượng" và "giao diện" là các khái niệm tương tự nhau, với giao diện là trừu tượng hơn của cả hai. Một yếu tố khác biệt là các lớp trừu tượng cung cấp các triển khai phương thức cho các lớp dẫn xuất khi cần. Tuy nhiên, trong C #, yếu tố khác biệt này đã bị giảm đi do sự ra đời gần đây của các phương thức mở rộng, cho phép triển khai được cung cấp cho các phương thức giao diện. Một yếu tố khác biệt là một lớp chỉ có thể kế thừa một lớp trừu tượng (nghĩa là không có nhiều kế thừa), nhưng nó có thể thực hiện nhiều giao diện. Điều này làm cho giao diện ít hạn chế và linh hoạt hơn. Vậy, trong C #, khi nào chúng ta nên sử dụng các lớp trừu tượng thay vì giao diện với các phương thức mở rộng?

Một ví dụ đáng chú ý của mô hình phương thức giao diện + mở rộng là LINQ, trong đó chức năng truy vấn được cung cấp cho bất kỳ loại nào thực hiện IEnumerablethông qua vô số phương thức mở rộng.


2
Và chúng ta cũng có thể sử dụng 'Thuộc tính' trong các giao diện.
Gul Sơn

1
Âm thanh giống như một C # cụ thể chứ không phải là một câu hỏi chung chung. (Hầu hết các ngôn ngữ OO khác không có phương thức mở rộng cũng như thuộc tính.)
Péter Török


4
Các phương thức mở rộng không giải quyết được hoàn toàn vấn đề mà các lớp trừu tượng giải quyết, vì vậy lý do không khác gì so với trong Java hoặc một số ngôn ngữ kế thừa đơn tương tự khác.
Công việc

3
Nhu cầu thực hiện phương thức lớp trừu tượng không bị giảm bởi các phương thức mở rộng. Các phương thức mở rộng không thể truy cập các trường thành viên riêng, cũng không thể được khai báo ảo và bị ghi đè trong các lớp dẫn xuất.
zooone9243

Câu trả lời:


63

Khi bạn cần một phần của lớp sẽ được thực hiện. Ví dụ tốt nhất tôi đã sử dụng là mẫu phương thức mẫu.

public abstract class SomethingDoer
{
    public void Do()
    {
        this.DoThis();
        this.DoThat();
    }

    protected abstract void DoThis();
    protected abstract void DoThat();
}

Do đó, bạn có thể xác định các bước sẽ được thực hiện khi Do () được gọi, mà không cần biết chi tiết cụ thể về cách chúng sẽ được thực hiện. Các lớp dẫn xuất phải thực hiện các phương thức trừu tượng, nhưng không phải phương thức Do ().

Các phương thức mở rộng không nhất thiết phải thỏa mãn phần "phải là một phần của lớp" trong phương trình. Ngoài ra, iirc, các phương thức mở rộng không thể (dường như) là bất cứ điều gì ngoài phạm vi công khai.

biên tập

Câu hỏi thú vị hơn so với ban đầu tôi đã công nhận. Sau khi kiểm tra thêm, Jon Skeet đã trả lời một câu hỏi như thế này trên SO để sử dụng các giao diện + phương thức mở rộng. Ngoài ra, một nhược điểm tiềm năng là sử dụng sự phản chiếu đối với hệ thống phân cấp đối tượng được thiết kế theo cách này.

Cá nhân, tôi gặp khó khăn khi thấy lợi ích của việc thay đổi một thực tiễn phổ biến hiện nay, nhưng cũng thấy ít hoặc không có nhược điểm trong việc thực hiện nó.

Cần lưu ý rằng có thể lập trình theo cách này bằng nhiều ngôn ngữ thông qua các lớp Utility. Phần mở rộng chỉ cung cấp đường cú pháp để làm cho các phương thức trông giống như chúng thuộc về lớp.


Đánh bại tôi đi ..
Giorgi

1
Nhưng điều tương tự có thể được thực hiện với Giao diện và phương thức mở rộng. Ở đó các phương thức bạn đang định nghĩa trong lớp cơ sở trừu tượng như thế nào Do()sẽ được định nghĩa là một phương thức mở rộng. Và các phương thức còn lại cho định nghĩa của các lớp dẫn xuất DoThis()sẽ là thành viên của Giao diện chính. Và với các giao diện, bạn có thể hỗ trợ nhiều triển khai / kế thừa. Và xem this- odetocode.com/blogs/scott/archive/2009/10/05/...
Gulshan

@Gulshan: Bởi vì một điều có thể được thực hiện theo nhiều cách không làm cho cách được chọn không hợp lệ. Không có lợi thế trong việc sử dụng giao diện. Bản thân lớp trừu tượng giao diện. Bạn không cần giao diện để hỗ trợ nhiều triển khai.
ThomasX

Ngoài ra, có thể có những bất lợi cho giao diện + mô hình mở rộng. Lấy thư viện Linq chẳng hạn. Thật tuyệt, nhưng nếu bạn chỉ cần sử dụng một lần trong tệp mã đó, thư viện Linq đầy đủ sẽ có sẵn trong IntelliSense trên MỌI IE có thể đếm được trong tệp mã của bạn. Điều này có thể làm chậm hiệu suất IDE đáng kể.
KeithS

-1 bởi vì bạn chưa thể hiện bất kỳ cách nào trong đó các lớp trừu tượng phù hợp hơn với Phương thức mẫu so với các giao diện
Benjamin Hodgson

25

Đó là tất cả về những gì bạn muốn mô hình:

Các lớp trừu tượng làm việc bằng kế thừa . Là lớp cơ sở chỉ đặc biệt, họ mô hình một số là-một -relationship.

Ví dụ, một con chó một động vật, do đó chúng ta có

class Dog : Animal { ... }

Vì thực sự không có ý nghĩa gì khi thực sự tạo ra một loài động vật chung chung (trông nó sẽ như thế nào?), Chúng tôi tạo ra Animallớp cơ sở này abstract- nhưng nó vẫn là một lớp cơ sở. Và Doglớp học không có ý nghĩa mà không phải là một Animal.

Mặt khác giao diện là một câu chuyện khác nhau. Họ không sử dụng tính kế thừa nhưng cung cấp tính đa hình ( cũng có thể được thực hiện với tính kế thừa). Họ không mô hình hóa một mối quan hệ - nhưng nhiều hơn là nó hỗ trợ .

Lấy ví dụ IComparable- một đối tượng hỗ trợ được so sánh với đối tượng khác .

Chức năng của một lớp không phụ thuộc vào các giao diện mà nó thực hiện, giao diện chỉ cung cấp một cách chung để truy cập chức năng. Chúng ta vẫn có thể Disposecủa một Graphicshoặc FileStreamkhông có chúng thực hiện IDisposable. Các giao diện chỉ liên hệ các phương pháp với nhau.

Về nguyên tắc, người ta có thể thêm và xóa các giao diện giống như các phần mở rộng mà không thay đổi hành vi của một lớp, chỉ cần làm phong phú thêm quyền truy cập vào nó. Mặc dù vậy, bạn không thể lấy đi một lớp cơ sở vì đối tượng sẽ trở nên vô nghĩa!


Khi nào bạn sẽ chọn giữ một lớp cơ sở trừu tượng?
Gul Sơn

1
@Gulshan: Nếu có - vì một số lý do - không có ý nghĩa thực sự tạo ra một thể hiện của lớp cơ sở. Bạn có thể "tạo ra" Chihuahua hoặc cá mập trắng, nhưng bạn không thể tạo ra một con vật mà không cần chuyên môn hóa thêm - do đó bạn sẽ làm cho Animaltrừu tượng. Điều này cho phép bạn viết các abstracthàm được chuyên biệt bởi các lớp dẫn xuất. Hoặc nhiều hơn về các thuật ngữ lập trình - có thể không có ý nghĩa gì khi tạo một UserControl, nhưng vì Button nó là một UserControl, vì vậy chúng ta có cùng một loại mối quan hệ lớp / lớp cơ sở.
Dario

nếu bạn có các phương thức trừu tượng, hơn bạn có lớp trừu tượng. Và các phương pháp trừu tượng bạn có khi không có giá trị để thực hiện chúng (như "đi" cho động vật). Và tất nhiên đôi khi bạn không muốn cho phép các đối tượng của một số lớp chung, hơn là bạn làm cho nó trừu tượng.
Dainius

Không có quy tắc nào nói rằng nếu nó có ý nghĩa về mặt ngữ pháp và ngữ nghĩa để nói "B là A" thì A phải là một lớp trừu tượng. Bạn nên thích giao diện cho đến khi bạn thực sự không thể tránh lớp. Chia sẻ mã có thể được thực hiện thông qua thành phần của các giao diện, v.v ... Điều đó giúp dễ dàng tránh việc vẽ chính mình vào một góc với những cây thừa kế kỳ lạ.
sara

3

Tôi mới nhận ra rằng tôi đã không sử dụng các lớp trừu tượng (ít nhất là không được tạo ra) trong nhiều năm.

Lớp trừu tượng là một con thú hơi kỳ lạ giữa giao diện và thực hiện. Có một cái gì đó trong các lớp trừu tượng làm nhói cảm giác nhện của tôi. Tôi sẽ lập luận rằng, người ta không nên "phơi bày" việc triển khai đằng sau giao diện theo bất kỳ cách nào (hoặc thực hiện bất kỳ ràng buộc nào).

Ngay cả khi có nhu cầu về hành vi chung giữa các lớp thực hiện giao diện, tôi sẽ sử dụng một lớp trợ giúp độc lập, thay vì một lớp trừu tượng.

Tôi sẽ đi cho giao diện.


2
-1: Tôi không chắc bạn bao nhiêu tuổi, nhưng bạn có thể muốn tìm hiểu các mẫu thiết kế, đặc biệt là mẫu phương thức mẫu. Các mẫu thiết kế sẽ giúp bạn trở thành một lập trình viên tốt hơn và họ sẽ chấm dứt sự thiếu hiểu biết của bạn về các lớp trừu tượng.
Jim G.

Tương tự về cảm giác ngứa ran. Các lớp, tính năng đầu tiên và quan trọng nhất của C # và Java, là một tính năng để tạo một loại và ngay lập tức xâu chuỗi nó mãi mãi với một triển khai.
Jesse Millikan

2

IMHO, tôi nghĩ rằng có một sự pha trộn các khái niệm ở đây.

Các lớp sử dụng một kiểu thừa kế nhất định, trong khi các giao diện sử dụng một kiểu thừa kế khác.

Cả hai loại thừa kế đều quan trọng và hữu ích, và mỗi loại đều có ưu và nhược điểm.

Hãy cùng bắt đầu lại từ đầu.

Câu chuyện với các giao diện bắt đầu một thời gian dài trở lại với C ++. Vào giữa những năm 1980, khi C ++ được phát triển, ý tưởng về giao diện như một loại hình khác vẫn chưa được hiện thực hóa (nó sẽ xuất hiện đúng lúc).

C ++ chỉ có một loại thừa kế, loại thừa kế được sử dụng bởi các lớp, được gọi là thừa kế thực hiện.

Để có thể áp dụng đề xuất Sách GoF: "Lập trình cho giao diện chứ không phải triển khai", C ++ đã hỗ trợ các lớp trừu tượng và kế thừa nhiều triển khai để có thể thực hiện những giao diện nào trong C # có khả năng (và hữu ích!) .

C # giới thiệu một loại kiểu mới, giao diện và một kiểu thừa kế mới, kế thừa giao diện, để cung cấp ít nhất hai cách khác nhau để hỗ trợ "Để lập trình cho giao diện chứ không phải cho việc triển khai", chỉ với một cảnh báo quan trọng: C # hỗ trợ nhiều kế thừa với kế thừa giao diện (và không phải kế thừa thực hiện).

Vì vậy, lý do kiến ​​trúc đằng sau các giao diện trong C # là khuyến nghị của GoF.

Tôi hy vọng điều này có thể giúp đỡ.

Trân trọng, Gastón


1

Áp dụng các phương thức mở rộng cho một giao diện rất hữu ích để áp dụng các hành vi phổ biến trên các lớp chỉ có thể chia sẻ một giao diện chung. Các lớp như vậy có thể đã tồn tại và được đóng vào các phần mở rộng thông qua các phương tiện khác.

Đối với các thiết kế mới, tôi sẽ ưu tiên sử dụng các phương thức mở rộng trên các giao diện. Đối với hệ thống phân cấp các loại, các phương thức mở rộng cung cấp một phương tiện để mở rộng hành vi mà không phải mở toàn bộ cấu trúc phân cấp để sửa đổi.

Tuy nhiên, các phương thức mở rộng không thể truy cập triển khai riêng tư, do đó, ở đầu phân cấp, nếu tôi cần gói gọn việc thực hiện riêng tư đằng sau giao diện chung thì một lớp trừu tượng sẽ là cách duy nhất.


Không, đó không phải là cách an toàn duy nhất. Có một cách khác an toàn hơn. Đó là phương pháp mở rộng. Khách hàng sẽ không bị nhiễm bệnh gì cả. Đó là lý do tại sao tôi đã đề cập đến giao diện + phương thức mở rộng.
Gul Sơn

@Ed James Tôi nghĩ "kém mạnh mẽ hơn" = "linh hoạt hơn" Khi nào bạn sẽ chọn một lớp trừu tượng mạnh hơn thay vì giao diện linh hoạt hơn?
Gul Sơn

@Ed James Trong các phương thức mở rộng asp.mvc đã được sử dụng ngay từ đầu. Bạn nghĩ gì về điều này?
Gul Sơn

0

Tôi nghĩ rằng trong C #, nhiều kế thừa không được hỗ trợ và đó là lý do tại sao khái niệm giao diện được giới thiệu trong C #.

Trong trường hợp các lớp trừu tượng, nhiều kế thừa cũng không được hỗ trợ. Vì vậy, thật dễ dàng để quyết định thời tiết sử dụng các lớp hoặc giao diện trừu tượng.


Nói chính xác, đó là các lớp trừu tượng thuần túy được gọi là "giao diện" trong C #.
Johan Boulé

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.