Tôi nên sử dụng phương pháp trừu tượng hay ảo?


11

Nếu chúng ta giả sử rằng lớp cơ sở không phải là một lớp giao diện thuần túy và sử dụng 2 ví dụ từ bên dưới, thì đây là cách tiếp cận tốt hơn, sử dụng định nghĩa lớp phương thức trừu tượng hay ảo?

  • Ưu điểm của phiên bản "trừu tượng" là nó có thể trông gọn gàng hơn và buộc lớp dẫn xuất phải đưa ra một triển khai có ý nghĩa hy vọng.

  • Ưu điểm của phiên bản "ảo" là nó có thể dễ dàng được kéo bởi các mô-đun khác và được sử dụng để thử nghiệm mà không cần thêm một loạt các khung cơ bản như phiên bản trừu tượng yêu cầu.

Phiên bản trừu tượng:

public abstract class AbstractVersion
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

Phiên bản ảo:

public class VirtualVersion
{
    public virtual ReturnType Method1()
    {
        return ReturnType.NotImplemented;
    }

    public virtual ReturnType Method2()
    {
        return ReturnType.NotImplemented;
    }
             .
             .
    public virtual ReturnType MethodN()
    {
        return ReturnType.NotImplemented;
    }

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

Tại sao chúng ta giả sử một giao diện là không mong muốn?
Anthony Pegram

Không có vấn đề một phần, thật khó để nói cái này tốt hơn cái kia.
Codism

@Anthony: Giao diện không được mong muốn vì có chức năng hữu ích cũng sẽ đi vào lớp này.
Dunk

4
return ReturnType.NotImplemented? Nghiêm túc? Nếu bạn không thể từ chối loại chưa thực hiện tại thời gian biên dịch (bạn có thể; sử dụng các phương thức trừu tượng) ít nhất là ném một ngoại lệ.
Jan Hudec

3
@Dunk: Ở đây họ cần thiết. Giá trị trả về sẽ không được kiểm tra.
Jan Hudec

Câu trả lời:


15

Phiếu bầu của tôi, nếu tôi tiêu thụ công cụ của bạn, sẽ dành cho các phương pháp trừu tượng. Điều đó đi cùng với "thất bại sớm." Có thể là một nỗi đau tại thời điểm khai báo để thêm tất cả các phương thức (mặc dù bất kỳ công cụ tái cấu trúc tốt nào cũng sẽ thực hiện việc này một cách nhanh chóng), nhưng ít nhất tôi biết vấn đề là gì ngay lập tức và khắc phục nó. Tôi muốn thay vì gỡ lỗi 6 tháng và 12 lần thay đổi sau này để xem tại sao chúng ta đột nhiên nhận được một ngoại lệ không được triển khai.


Điểm hay về việc không nhận được lỗi Không thực hiện được. Đó là một điểm cộng về mặt trừu tượng, bởi vì bạn sẽ nhận được thời gian biên dịch thay vì lỗi thời gian chạy.
Dunk

3
+1 - Ngoài việc thất bại sớm, những người thừa kế có thể thấy ngay những phương thức họ cần thực hiện thông qua "Thực hiện lớp trừu tượng" thay vì làm những gì họ nghĩ là đủ và sau đó thất bại khi chạy.
Telastyn

Tôi đã chấp nhận câu trả lời này vì thất bại trong thời gian biên dịch là một điểm cộng mà tôi không thể liệt kê và vì tham chiếu đến việc sử dụng IDE để tự động thực hiện các phương thức một cách nhanh chóng.
Dunk

25

Phiên bản ảo dễ bị lỗi và không chính xác về mặt ngữ nghĩa.

Tóm tắt là "phương pháp này không được triển khai ở đây. Bạn phải thực hiện nó để làm cho lớp này hoạt động"

Virtual đang nói "Tôi có một triển khai mặc định nhưng bạn có thể thay đổi tôi nếu bạn cần"

Nếu mục tiêu cuối cùng của bạn là khả năng kiểm tra thì giao diện thường là lựa chọn tốt nhất. (lớp này không x chứ không phải lớp này là ax). Bạn có thể cần phải chia các lớp của mình thành các thành phần nhỏ hơn để điều này hoạt động tốt.


3

Điều này phụ thuộc vào việc sử dụng lớp học của bạn.

Nếu các phương thức có một số cách triển khai trống trống hợp lý, bạn có rất nhiều phương thức và bạn thường ghi đè chỉ một vài trong số chúng, sau đó sử dụng virtualcác phương thức có ý nghĩa. Ví dụ ExpressionVisitorđược thực hiện theo cách này.

Nếu không, tôi nghĩ bạn nên sử dụng abstractphương pháp.

Lý tưởng nhất là bạn không nên có các phương pháp không được thực hiện, nhưng trong một số trường hợp, đó là cách tiếp cận tốt nhất. Nhưng nếu bạn quyết định làm điều đó, các phương thức như vậy nên ném NotImplementedException, không trả lại một số giá trị đặc biệt.


Tôi sẽ lưu ý rằng "NotImcellenceedException" thường chỉ ra lỗi thiếu sót, trong khi "NotSupportedException" chỉ ra sự lựa chọn công khai. Ngoài ra, tôi đồng ý.
Anthony Pegram

Đáng lưu ý rằng nhiều phương pháp được định nghĩa theo nghĩa "Làm bất cứ điều gì cần thiết để đáp ứng bất kỳ nghĩa vụ nào liên quan đến X". Gọi một phương thức như vậy trên một đối tượng không có mục nào liên quan đến X có thể là vô nghĩa, nhưng vẫn sẽ được xác định rõ. Hơn nữa, nếu một đối tượng có thể có hoặc không có bất kỳ nghĩa vụ nào liên quan đến X, thì nói chung là sạch sẽ và hiệu quả hơn khi nói "Thỏa mãn tất cả các nghĩa vụ của bạn liên quan đến X", trước tiên là hỏi liệu nó có bất kỳ nghĩa vụ nào không và có điều kiện yêu cầu nó đáp ứng chúng .
supercat

1

Tôi sẽ đề nghị bạn xem xét lại việc có một giao diện riêng được xác định mà lớp cơ sở của bạn thực hiện, và sau đó bạn làm theo cách tiếp cận trừu tượng.

Mã hình ảnh như thế này:

public interface IVersion
{
    ReturnType Method1();        
    ReturnType Method2();
             .
             .
    ReturnType MethodN();
}

public abstract class AbstractVersion : IVersion
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

Làm điều này giải quyết những vấn đề này:

  1. Bằng cách có tất cả các mã sử dụng các đối tượng có nguồn gốc từ AbstractVersion giờ đây có thể được triển khai để nhận giao diện IVersion, Điều này có nghĩa là chúng có thể được kiểm tra đơn vị dễ dàng hơn.

  2. Phát hành 2 sản phẩm của bạn sau đó có thể triển khai giao diện IVersion2 để cung cấp chức năng bổ sung mà không vi phạm mã khách hàng hiện tại của bạn.

ví dụ.

public interface IVersion
{
    ReturnType Method1();        
    ReturnType Method2();
             .
             .
    ReturnType MethodN();
}

public interface IVersion2
{
    ReturnType Method2_1();
}

public abstract class AbstractVersion : IVersion, IVersion2
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();
    public abstract ReturnType Method2_1();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

Cũng đáng đọc về nghịch đảo phụ thuộc, để ngăn chặn lớp này chứa các phụ thuộc được mã hóa cứng ngăn chặn thử nghiệm đơn vị hiệu quả.


Tôi ủng hộ việc cung cấp khả năng xử lý các phiên bản khác nhau. Tuy nhiên, tôi đã thử và thử lại để sử dụng hiệu quả các lớp giao diện, như một phần bình thường của thiết kế và luôn nhận ra rằng các lớp giao diện không cung cấp giá trị không / ít và thực sự làm rối mã thay vì làm cho cuộc sống dễ dàng hơn. Rất hiếm khi tôi có nhiều lớp kế thừa từ lớp khác, như lớp giao diện và không có nhiều điểm chung không thể chia sẻ được. Các lớp trừu tượng có xu hướng hoạt động tốt hơn khi tôi có được khía cạnh có thể chia sẻ mà các giao diện không cung cấp.
Dunk

Và việc sử dụng tính kế thừa với các tên lớp tốt cung cấp một cách trực quan hơn nhiều để dễ dàng hiểu các lớp (và do đó là hệ thống) so với một loạt các tên lớp giao diện chức năng khó có thể liên kết với nhau về mặt tinh thần. Ngoài ra, sử dụng các lớp giao diện có xu hướng tạo ra một tấn các lớp bổ sung, làm cho hệ thống khó hiểu hơn theo một cách khác.
Dunk

-2

Phụ thuộc tiêm phụ thuộc vào giao diện. Đây là một ví dụ ngắn. Class Student có một hàm gọi là CreatStudent yêu cầu một tham số thực hiện giao diện "IReporting" (với phương thức ReportAction). Sau khi nó tạo ra một sinh viên, nó gọi ReportAction trên tham số lớp cụ thể. Nếu hệ thống được thiết lập để gửi email sau khi tạo một sinh viên, chúng tôi sẽ gửi một lớp cụ thể gửi email trong triển khai ReportAction của mình hoặc chúng tôi có thể gửi một lớp cụ thể khác gửi đầu ra tới máy in trong quá trình thực hiện Báo cáo. Tuyệt vời cho việc sử dụng lại mã.


1
điều này dường như không cung cấp bất cứ điều gì đáng kể qua các điểm được thực hiện và giải thích trong 5 câu trả lời trước
gnat
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.