Là các lớp / phương thức trừu tượng đã lỗi thời?


37

Tôi đã sử dụng để tạo ra rất nhiều lớp / phương thức trừu tượng. Sau đó, tôi bắt đầu sử dụng giao diện.

Bây giờ tôi không chắc chắn nếu các giao diện không làm cho các lớp trừu tượng trở nên lỗi thời.

Bạn cần một lớp hoàn toàn trừu tượng? Tạo một giao diện thay thế. Bạn cần một lớp trừu tượng với một số thực hiện trong đó? Tạo một giao diện, tạo một lớp. Kế thừa lớp, thực hiện giao diện. Một lợi ích bổ sung là một số lớp có thể không cần lớp cha, nhưng sẽ chỉ thực hiện giao diện.

Vì vậy, các lớp / phương thức trừu tượng đã lỗi thời?


Sẽ thế nào nếu ngôn ngữ lập trình bạn chọn không hỗ trợ giao diện? Tôi dường như nhớ lại đây là trường hợp của C ++.
Bernard

7
@Bernard: trong C ++, một lớp trừu tượng một giao diện hoàn toàn trừ tên. Rằng họ cũng có thể làm nhiều hơn giao diện 'thuần túy' không phải là bất lợi.
gbjbaanb

@gbjbaanb: Tôi cho là vậy. Tôi không nhớ sử dụng chúng làm giao diện, mà là để cung cấp các cài đặt mặc định.
Bernard

Các giao diện là "tiền tệ" của các tham chiếu đối tượng. Nói chung, họ là nền tảng của hành vi đa hình. Các lớp trừu tượng phục vụ một mục đích khác nhau mà deadalnix giải thích hoàn hảo.
nhảy lên

4
Đây không phải là nói "các phương thức vận tải đã lỗi thời bây giờ chúng ta có xe hơi?" Vâng, hầu hết thời gian, bạn sử dụng một chiếc xe hơi. Nhưng cho dù bạn có cần bất cứ thứ gì ngoài xe hơi hay không, thì thực sự không đúng khi nói "Tôi không cần sử dụng các phương thức vận tải". Một giao diện giống như một lớp trừu tượng mà không có bất kỳ triển khai nào, và với một tên đặc biệt, không?
Jack V.

Câu trả lời:


111

Không.

Các giao diện không thể cung cấp triển khai mặc định, các lớp trừu tượng và phương thức có thể. Điều này đặc biệt hữu ích để tránh trùng lặp mã trong nhiều trường hợp.

Đây cũng là một cách thực sự tốt đẹp để giảm khớp nối tiếp. Không có phương thức / lớp trừu tượng, bạn không thể thực hiện mẫu phương thức mẫu. Tôi đề nghị bạn xem bài viết trên wikipedia này: http://en.wikipedia.org/wiki/Template_method_potype


3
@deadalnix Mẫu phương thức mẫu có thể khá nguy hiểm. Bạn có thể dễ dàng kết thúc với mã được ghép nối cao và bắt đầu hack mã để mở rộng lớp trừu tượng templated để xử lý "chỉ một trường hợp nữa".
quant_dev

9
Điều này có vẻ giống như một mô hình chống. Bạn có thể nhận được chính xác điều tương tự với bố cục đối với một giao diện. Điều tương tự áp dụng cho các lớp một phần với một số phương thức trừu tượng. Thay vào đó, sau đó buộc mã máy khách phải phân lớp và ghi đè lên chúng, bạn nên thực hiện chèn . Xem: vi.wikipedia.org/wiki/Cysis_over_inherribution#Benefits
back2dos

4
Điều này không giải thích AT ALL làm thế nào để giảm khớp nối tiếp. Tôi đồng ý rằng một số cách tồn tại để đạt được mục tiêu này, nhưng xin vui lòng, trả lời vấn đề bạn đang nói thay vì mù quáng viện dẫn một số nguyên tắc. Bạn sẽ kết thúc việc lập trình sùng bái hàng hóa nếu bạn không thể giải thích tại sao điều này lại liên quan đến vấn đề thực tế.
deadalnix

3
@deadalnix: Nói rằng khớp nối tuần tự được giảm tốt nhất với mẫu mẫu lập trình sùng bái hàng hóa (sử dụng mẫu trạng thái / chiến lược / nhà máy có thể cũng hoạt động tốt), cũng như giả định rằng nó phải luôn luôn giảm. Khớp nối tiếp thường là một hệ quả đơn thuần của việc phơi bày sự kiểm soát chi tiết rất tốt đối với một cái gì đó, có nghĩa là không có gì ngoài sự đánh đổi. Điều đó vẫn không có nghĩa là tôi không thể viết một trình bao bọc không có khớp nối tiếp theo bằng cách sử dụng bố cục. Trong thực tế, nó làm cho nó dễ dàng hơn rất nhiều.
back2dos

4
Java hiện có các cài đặt mặc định cho các giao diện. Điều đó có thay đổi câu trả lời không?
raptortech97

15

Một phương thức trừu tượng tồn tại để bạn có thể gọi nó từ bên trong lớp cơ sở của mình nhưng thực hiện nó trong một lớp dẫn xuất. Vì vậy, lớp cơ sở của bạn biết:

public void DoTask()
{
    doSetup();
    DoWork();
    doCleanup();
}

protected abstract void DoWork();

Đó là một cách khá hợp lý để thực hiện một lỗ hổng trong mẫu ở giữa mà không có lớp dẫn xuất biết về các hoạt động thiết lập và dọn dẹp. Nếu không có các phương thức trừu tượng, bạn sẽ phải dựa vào lớp dẫn xuất thực hiện DoTaskvà ghi nhớ để gọi base.DoSetup()base.DoCleanup()mọi lúc.

Chỉnh sửa

Ngoài ra, cảm ơn deadalnix đã đăng một liên kết đến Mẫu Phương thức Mẫu , đó là những gì tôi đã mô tả ở trên mà không thực sự biết tên. :)


2
+1, tôi thích câu trả lời của bạn cho deadalnix '. Lưu ý rằng bạn có thể triển khai phương thức mẫu theo cách đơn giản hơn bằng cách sử dụng các đại biểu (trong C #):public void DoTask(Action doWork)
Joh

1
@Joh - đúng, nhưng điều đó không hẳn là tốt, tùy thuộc vào API của bạn. Nếu lớp cơ sở của bạn là Fruitvà lớp dẫn xuất của bạn là Apple, bạn muốn gọi myApple.Eat(), không myApple.Eat((a) => howToEatApple(a)). Ngoài ra, bạn không muốn Applephải gọi base.Eat(() => this.howToEatMe()). Tôi nghĩ rằng nó sạch hơn chỉ để ghi đè một phương thức trừu tượng.
Scott Whitlock

1
@Scott Whitlock: Ví dụ của bạn sử dụng táo và trái cây hơi quá tách rời khỏi thực tế để đánh giá xem việc đi thừa kế có thích hợp hơn với các đại biểu nói chung hay không. Rõ ràng, câu trả lời là "nó phụ thuộc", vì vậy nó còn nhiều chỗ cho các cuộc tranh luận ... Dù sao, tôi thấy rằng các phương thức mẫu xảy ra rất nhiều trong quá trình tái cấu trúc, ví dụ như khi loại bỏ mã trùng lặp. Trong những trường hợp như vậy, tôi thường không muốn gây rối với hệ thống phân cấp kiểu và tôi muốn tránh xa sự kế thừa. Loại phẫu thuật này dễ thực hiện hơn với lambdas.
Joh

Câu hỏi này minh họa nhiều hơn một ứng dụng đơn giản của mẫu Phương thức mẫu - khía cạnh công khai / không công khai được biết đến trong cộng đồng C ++ là mẫu Giao diện không ảo . Bằng cách có một chức năng phi ảo công khai bao bọc chức năng ảo - ngay cả khi ban đầu, chức năng này không gọi được cái sau - bạn để lại một điểm tùy chỉnh để thiết lập / dọn dẹp, ghi nhật ký, lược tả, kiểm tra bảo mật, v.v. có thể cần thiết sau này.
Tony

10

Không, chúng không lỗi thời.

Trong thực tế, có một sự khác biệt mơ hồ nhưng cơ bản giữa các lớp / phương thức trừu tượng và các giao diện.

nếu tập hợp các lớp trong đó một trong số chúng phải được sử dụng có một hành vi chung mà chúng chia sẻ (các lớp liên quan, ý tôi là vậy), thì hãy tìm các lớp / phương thức Trừu tượng.

Ví dụ: clerk, Cán bộ, Giám đốc - tất cả các lớp này đều có tính toán chung là CompateSalary (), sử dụng các lớp cơ sở trừu tượng.CalculateSalary () có thể được triển khai khác nhau nhưng có một số điều khác như GetAttendance () có định nghĩa chung trong lớp cơ sở.

Nếu các lớp của bạn không có gì chung (Các lớp không liên quan, trong bối cảnh được chọn) ở giữa chúng nhưng có một hành động khác biệt rất nhiều trong việc triển khai, thì hãy đi đến Giao diện.

Ví dụ: bò, ghế dài, xe hơi, các lớp không liên quan đến kính thiên văn nhưng Isortable có thể ở đó để sắp xếp chúng thành một mảng.

Sự khác biệt này thường bị bỏ qua khi tiếp cận từ góc độ đa hình. Nhưng cá nhân tôi cảm thấy rằng có những tình huống mà một người là một apt hơn những người khác vì lý do đã giải thích ở trên.


6

Ngoài các câu trả lời hay khác, có một sự khác biệt cơ bản giữa các giao diện và các lớp trừu tượng mà không ai đề cập cụ thể, đó là các giao diện ít đáng tin cậy hơn và do đó đặt ra gánh nặng kiểm tra lớn hơn nhiều so với các lớp trừu tượng. Ví dụ: xem xét mã C # này:

public abstract class Frobber
{
    private Frobber() {}
    public abstract void Frob(Frotz frotz);
    private class GreenFrobber : Frobber
    { ... }
    private class RedFrobber : Frobber
    { ... }
    public static Frobber GetFrobber(bool b) { ... } // return a green or red frobber
}

public sealed class Frotz
{
    public void Frobbit(Frobber frobber)
    {
         ...
         frobber.Frob(this);
         ...
    }
}

Tôi được đảm bảo rằng chỉ có hai đường dẫn mã tôi cần kiểm tra. Tác giả của Frobbit có thể dựa vào thực tế là frobber có màu đỏ hoặc xanh lá cây.

Nếu thay vào đó chúng ta nói:

public interface IFrobber
{
    void Frob(Frotz frotz);
}
public class GreenFrobber : IFrobber
{ ... }
public class RedFrobber : Frobber
{ ... }

public sealed class Frotz
{
    public void Frobbit(IFrobber frobber)
    {
         ...
         frobber.Frob(this);
         ...
    }
}

Bây giờ tôi hoàn toàn không biết về ảnh hưởng của cuộc gọi đó đến Frob ở đó. Tôi cần chắc chắn rằng tất cả các mã trong Frobbit đều mạnh mẽ chống lại mọi triển khai IFrobber có thể có , thậm chí cả những người không đủ năng lực (xấu) hoặc chủ động thù địch với tôi hoặc người dùng của tôi (tệ hơn nhiều).

Các lớp trừu tượng cho phép bạn tránh tất cả những vấn đề này; sử dụng chúng!


1
Vấn đề bạn nói đến nên được giải quyết bằng cách sử dụng các kiểu dữ liệu đại số, thay vì uốn các lớp đến một điểm mà chúng vi phạm nguyên tắc mở / đóng.
back2dos

1
Thứ nhất, tôi không bao giờ nói nó là tốt hay đạo đức . Bạn đặt nó trong miệng của tôi. Thứ hai (là quan điểm thực tế của tôi): Không có gì ngoài lý do nghèo nàn cho một tính năng ngôn ngữ bị thiếu. Cuối cùng, có một giải pháp mà không cần các lớp trừu tượng: pastebin.com/DxEh8Qfz . Ngược lại, cách tiếp cận của bạn sử dụng các lớp lồng nhau và trừu tượng, ném tất cả trừ mã yêu cầu sự an toàn lại với nhau thành một quả bóng lớn. Không có lý do chính đáng tại sao RedFrobber hoặc GreenFrobber nên được gắn với các ràng buộc bạn muốn thực thi. Nó làm tăng khớp nối và khóa trong nhiều quyết định mà không có lợi ích gì.
back2dos

1
Nói rằng các phương thức trừu tượng đã lỗi thời là không có nghi ngờ, nhưng mặt khác, tuyên bố rằng chúng nên được ưu tiên hơn các giao diện là sai lầm. Chúng chỉ đơn giản là một công cụ để giải quyết các vấn đề khác nhau hơn các giao diện.
Groo

2
"Có một sự khác biệt cơ bản [...] giao diện ít đáng tin cậy hơn [...] so với các lớp trừu tượng". Tôi không đồng ý, sự khác biệt mà bạn đã minh họa trong mã của mình phụ thuộc vào các hạn chế truy cập, mà tôi không nghĩ có bất kỳ lý do nào để khác biệt đáng kể giữa các giao diện và các lớp trừu tượng. Nó có thể là như vậy trong C #, nhưng câu hỏi là bất khả tri ngôn ngữ.
Joh

1
@ back2dos: Trên thực tế, tôi muốn chỉ ra rằng giải pháp của Eric thực sự sử dụng kiểu dữ liệu đại số: một lớp trừu tượng là một kiểu tổng. Gọi một phương thức trừu tượng giống như khớp mẫu trên một tập hợp các biến thể.
Rodrick Chapman

4

Bạn tự nói điều đó:

Bạn cần một lớp trừu tượng với một số thực hiện trong đó? Tạo một giao diện, tạo một lớp. Kế thừa lớp, thực hiện giao diện

nghe có vẻ nhiều việc so với 'kế thừa lớp trừu tượng'. Bạn có thể tự làm việc bằng cách tiếp cận mã từ quan điểm 'thuần túy', nhưng tôi thấy tôi đã có đủ việc để làm mà không cần thêm vào khối lượng công việc của mình mà không có lợi ích thiết thực.


Chưa kể rằng nếu lớp không kế thừa giao diện (không được đề cập đến), bạn phải viết các hàm chuyển tiếp bằng một số ngôn ngữ.
Sjoerd

Umm, "Rất nhiều công việc" mà bạn đã đề cập là bạn đã tạo một giao diện và có các lớp thực hiện nó - điều đó có thực sự giống như rất nhiều công việc không? Trên hết, tất nhiên, giao diện cung cấp cho bạn khả năng triển khai giao diện từ một hệ thống phân cấp mới sẽ không hoạt động với một lớp cơ sở trừu tượng.
Bill K

4

Như tôi đã nhận xét trên bài đăng @deadnix: Việc triển khai một phần là một mô hình chống, mặc dù thực tế là mẫu mẫu chính thức hóa chúng.

Một giải pháp sạch cho ví dụ wikipedia này của mẫu mẫu :

interface Game {
    void initialize(int playersCount);
    void makePlay(int player);
    boolean done();
    void finished();
    void printWinner();
}
class GameRunner {
    public void playOneGame(int playersCount, Game game) {
        game.initialize(playersCount);
        int j = 0;
        for (int i = 0; !game.finished(); i++)
             game.makePlay(i % playersCount);
        game.printWinner();
    }
} 
class Monopoly implements Game {
     //... implementation
}

Giải pháp này tốt hơn, bởi vì nó sử dụng thành phần thay vì kế thừa . Mẫu mẫu giới thiệu một sự phụ thuộc giữa việc thực hiện các quy tắc Độc quyền và việc triển khai cách các trò chơi được chạy. Tuy nhiên đây là hai trách nhiệm hoàn toàn khác nhau và không có lý do chính đáng để kết đôi chúng.


+1. Như một lưu ý phụ: "Việc triển khai từng phần là một mô hình chống, mặc dù thực tế là mẫu khuôn mẫu chính thức hóa chúng.". Mô tả trên wikipedia định nghĩa mẫu rõ ràng, chỉ có ví dụ mã là "sai" (theo nghĩa là nó sử dụng tính kế thừa khi không cần thiết và tồn tại một cách thay thế đơn giản hơn, như minh họa ở trên). Nói cách khác, tôi không nghĩ rằng chính mô hình đó là đáng trách, chỉ có cách mọi người có xu hướng thực hiện nó.
Joh

2

Không. Ngay cả đề xuất thay thế của bạn bao gồm việc sử dụng các lớp trừu tượng. Ngoài ra, vì bạn không chỉ định ngôn ngữ, nên tôi sẽ tiếp tục và nói rằng mã chung là lựa chọn tốt hơn so với kế thừa dễ vỡ. Các lớp trừu tượng có lợi thế đáng kể trên các giao diện.


-1. Tôi không hiểu "mã chung so với thừa kế" có liên quan gì đến câu hỏi. Bạn nên minh họa hoặc biện minh tại sao "các lớp trừu tượng có lợi thế đáng kể so với giao diện".
Joh

1

Các lớp trừu tượng không phải là giao diện. Chúng là những lớp không thể khởi tạo được.

Bạn cần một lớp hoàn toàn trừu tượng? Tạo một giao diện thay thế. Bạn cần một lớp trừu tượng với một số thực hiện trong đó? Tạo một giao diện, tạo một lớp. Kế thừa lớp, thực hiện giao diện. Một lợi ích bổ sung là một số lớp có thể không cần lớp cha, nhưng sẽ chỉ thực hiện giao diện.

Nhưng sau đó bạn sẽ có một lớp vô dụng không trừu tượng. Các phương thức trừu tượng được yêu cầu để điền vào lỗ chức năng trong lớp cơ sở.

Ví dụ, cho lớp này

public abstract class Frobber {
    public abstract void Frob();

    public abstract boolean IsFrobbingNeeded { get; }

    public void FrobUntilFinished() {
        while (IsFrobbingNeeded) {
            Frob();
        }
    }
}

Làm thế nào bạn sẽ thực hiện chức năng cơ bản này trong một lớp học có không Frob()hay IsFrobbingNeeded?


1
Một giao diện cũng không thể được khởi tạo.
Sjoerd

@Sjoerd: Nhưng bạn cần một lớp cơ sở với việc triển khai được chia sẻ; đó không thể là một giao diện.
cấu hình

1

Tôi là người tạo ra khuôn khổ servlet nơi các lớp trừu tượng đóng vai trò thiết yếu. Tôi muốn nói thêm, tôi cần các phương thức bán trừu tượng, khi một phương thức cần được ghi đè trong 50% trường hợp và tôi muốn thấy cảnh báo từ trình biên dịch về phương thức đó không bị ghi đè. Tôi giải quyết vấn đề thêm chú thích. Quay trở lại câu hỏi của bạn, có hai trường hợp sử dụng khác nhau của các lớp và giao diện trừu tượng và cho đến nay không có ai bị lỗi thời.


0

Tôi không nghĩ rằng chúng bị lỗi thời bởi các giao diện, nhưng chúng có thể bị lỗi thời bởi mẫu chiến lược.

Việc sử dụng chính của một lớp trừu tượng là trì hoãn một phần của việc thực hiện; để nói, "phần thực hiện này của lớp có thể được làm khác đi".

Thật không may, một lớp trừu tượng buộc khách hàng thực hiện điều này thông qua kế thừa . Trong khi đó mẫu chiến lược sẽ cho phép bạn đạt được kết quả tương tự mà không cần bất kỳ sự kế thừa nào. Máy khách có thể tạo các thể hiện của lớp của bạn thay vì luôn xác định riêng của chúng và "cách thực hiện" (hành vi) của lớp có thể thay đổi linh hoạt. Mẫu chiến lược có thêm điểm cộng là hành vi có thể được thay đổi trong thời gian chạy, không chỉ ở thời điểm thiết kế và còn có sự kết hợp yếu hơn đáng kể giữa các loại liên quan.


0

Nhìn chung, tôi đã phải đối mặt với rất nhiều vấn đề bảo trì liên quan đến giao diện thuần túy hơn so với ABC, thậm chí ABC được sử dụng với nhiều kế thừa. YMMV - dunno, có lẽ nhóm chúng tôi chỉ sử dụng chúng không đầy đủ.

Điều đó nói rằng, nếu chúng ta sử dụng một sự tương tự trong thế giới thực, thì có bao nhiêu sử dụng cho các giao diện thuần túy hoàn toàn không có chức năng và trạng thái? Nếu tôi sử dụng USB làm ví dụ, đó là một giao diện khá ổn định (tôi nghĩ bây giờ chúng tôi đang ở USB 3.2, nhưng nó cũng duy trì khả năng tương thích ngược).

Tuy nhiên, đây không phải là một giao diện phi trạng thái. Nó không có chức năng. Nó giống như một lớp cơ sở trừu tượng hơn là một giao diện thuần túy. Nó thực sự gần hơn với một lớp cụ thể với các yêu cầu chức năng và trạng thái rất cụ thể, với sự trừu tượng duy nhất là những gì cắm vào cổng là phần duy nhất có thể thay thế.

Nếu không, nó sẽ chỉ là một "lỗ hổng" trong máy tính của bạn với một yếu tố hình thức được tiêu chuẩn hóa và các yêu cầu chức năng lỏng lẻo hơn sẽ không tự làm gì cho đến khi mọi nhà sản xuất đưa ra phần cứng của riêng họ để làm cho lỗ hổng đó làm gì đó, tại thời điểm đó nó trở thành một tiêu chuẩn yếu hơn nhiều và không có gì khác hơn là một "lỗ hổng" và một đặc điểm kỹ thuật về những gì nó nên làm, nhưng không có quy định trung tâm nào về cách thực hiện nó. Trong khi đó, chúng tôi có thể kết thúc với 200 cách khác nhau để làm điều đó sau khi tất cả các nhà sản xuất phần cứng cố gắng đưa ra những cách riêng để gắn chức năng và trạng thái vào "lỗ hổng" đó.

Và tại thời điểm đó, chúng tôi có thể có một số nhà sản xuất giới thiệu các vấn đề khác nhau so với các nhà sản xuất khác. Nếu chúng ta cần cập nhật thông số kỹ thuật, chúng ta có thể có 200 triển khai cổng USB cụ thể khác nhau với các cách hoàn toàn khác nhau để giải quyết thông số kỹ thuật phải được cập nhật và kiểm tra. Một số nhà sản xuất có thể phát triển các triển khai tiêu chuẩn thực tế mà họ chia sẻ với nhau (lớp cơ sở tương tự của bạn triển khai giao diện đó), nhưng không phải tất cả. Một số phiên bản có thể chậm hơn những phiên bản khác. Một số có thể có thông lượng tốt hơn nhưng độ trễ kém hơn hoặc ngược lại. Một số có thể sử dụng nhiều năng lượng pin hơn những người khác. Một số có thể bị bong ra và không hoạt động với tất cả các phần cứng được cho là hoạt động với các cổng USB. Một số có thể yêu cầu một lò phản ứng hạt nhân được gắn vào để hoạt động có xu hướng khiến người dùng bị nhiễm độc phóng xạ.

Và đó là những gì tôi đã tìm thấy, cá nhân, với các giao diện thuần túy. Có thể có một số trường hợp chúng có ý nghĩa, giống như chỉ mô hình hóa yếu tố hình thức của bo mạch chủ dựa trên vỏ CPU. Sự tương tự của yếu tố hình thức, thực sự, là khá nhiều trạng thái không trạng thái và không có chức năng, như với "lỗ hổng" tương tự. Nhưng tôi thường coi đó là một sai lầm rất lớn đối với các đội khi coi điều đó là vượt trội trong mọi trường hợp, thậm chí không gần gũi.

Ngược lại, tôi nghĩ rằng nhiều trường hợp sẽ được ABC giải quyết tốt hơn so với giao diện nếu đó là hai lựa chọn trừ khi nhóm của bạn rất khổng lồ đến mức thực sự mong muốn có tương tự trên 200 triển khai USB cạnh tranh thay vì một tiêu chuẩn trung tâm duy trì. Trong một đội cũ tôi đã tham gia, tôi thực sự đã phải chiến đấu hết mình chỉ để nới lỏng tiêu chuẩn mã hóa để cho phép ABC và nhiều kế thừa, và chủ yếu là để đối phó với các vấn đề bảo trì được mô tả ở trên.

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.