it-swarm-vi.com

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 #?

"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 IEnumerable thông qua vô số phương thức mở rộng.

70
Gulshan

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ẫ .

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.

chỉnh sửa

Câu hỏi thú vị hơn so với ban đầu tôi đã công nhận. Khi kiểm tra thêm, Jon Skeet đã trả lời một câu hỏi như thế này trên SO ủng ​​hộ sử dụng giao diện + phương thức mở rộng. Ngoài ra, một nhược điểm tiềm năng đang sử dụng sự phản chiếu chống lại một 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.

63
Steven Evers

Đó là tất cả về what mà bạn muốn lập mô hình:

Các lớp trừu tượng hoạt động bằng kế thừa. Chỉ là các lớp cơ sở đặc biệt, chúng mô hình hóa một số is-a - mối quan hệ.

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

class Dog : Animal { ... }

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

Mặt khác mặt khác là một câu chuyện khác. Họ không sử dụng tính kế thừa nhưng cung cấp đa hình (mà có thể cũng đượ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ệ is-a , nhưng nhiều hơn một nó hỗ trợ .

Lấy ví dụ IComparable - một đối tượng hỗ trợ 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ó triển khai, 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ể Dispose của a Graphics hoặc a FileStream mà không cần chúng thực hiện IDisposable. Giao diện chỉ liên quan các phương thức 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!

25
Dario

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.

3
Maglob

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ẽ đến kịp 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

2

Á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 hành vi chung 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.

1
Ed James

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.

0
Beginner