Giao diện trên một lớp trừu tượng


30

Đồng nghiệp của tôi và tôi có ý kiến ​​khác nhau về mối quan hệ giữa các lớp cơ sở và giao diện. Tôi tin rằng một lớp không nên thực hiện giao diện trừ khi lớp đó có thể được sử dụng khi cần thực hiện giao diện. Nói cách khác, tôi thích xem mã như thế này:

interface IFooWorker { void Work(); }

abstract class BaseWorker {
    ... base class behaviors ...
    public abstract void Work() { }
    protected string CleanData(string data) { ... }
}

class DbWorker : BaseWorker, IFooWorker {
    public void Work() {
        Repository.AddCleanData(base.CleanData(UI.GetDirtyData()));
    }
}

DbWorker là những gì có giao diện IFooWorker, bởi vì đây là một triển khai giao diện có thể thực hiện được ngay lập tức. Nó hoàn toàn đáp ứng hợp đồng. Đồng nghiệp của tôi thích gần giống nhau:

interface IFooWorker { void Work(); }

abstract class BaseWorker : IFooWorker {
    ... base class behaviors ...
    public abstract void Work() { }
    protected string CleanData(string data) { ... }
}

class DbWorker : BaseWorker {
    public void Work() {
        Repository.AddCleanData(base.CleanData(UI.GetDirtyData()));
    }
}

Trường hợp lớp cơ sở có giao diện và nhờ vào điều này, tất cả những người thừa kế của lớp cơ sở cũng là giao diện đó. Điều này làm tôi bực mình nhưng tôi không thể đưa ra lý do cụ thể tại sao, bên ngoài "lớp cơ sở không thể tự mình đứng ra như một triển khai của giao diện".

Những ưu và nhược điểm của phương pháp của anh ấy so với của tôi và tại sao nên sử dụng phương pháp này?


Đề xuất của bạn rất giống với kế thừa kim cương , có thể gây ra nhiều nhầm lẫn hơn nữa.
Spoike

@Spoike: Làm thế nào để thấy điều đó?
Niing

@Niing liên kết tôi cung cấp trong nhận xét có sơ đồ lớp đơn giản và giải thích đơn giản về lớp lót cho những gì đó là. nó được gọi là một vấn đề "kim cương" bởi vì cấu trúc thừa kế về cơ bản được vẽ giống như hình dạng kim cương (tức là ♦).
Spoike

@Spoike: tại sao câu hỏi này sẽ gây ra sự kế thừa kim cương?
Niing

1
@Niing: Kế thừa BaseWorker và IFooWorker với cùng một phương thức được gọi Worklà vấn đề thừa kế kim cương (trong trường hợp đó cả hai đều thực hiện hợp đồng qua Workphương thức). Trong Java, bạn bắt buộc phải thực hiện các phương thức của giao diện, do đó, vấn đề về Workphương thức mà chương trình nên sử dụng được tránh theo cách đó. Các ngôn ngữ như C ++ tuy nhiên không phân biệt điều này cho bạn.
Spoike

Câu trả lời:


24

Tôi tin rằng một lớp không nên thực hiện giao diện trừ khi lớp đó có thể được sử dụng khi cần thực hiện giao diện.

BaseWorkerđáp ứng yêu cầu đó Chỉ vì bạn không thể khởi tạo trực tiếp một BaseWorkerđối tượng không có nghĩa là bạn không thể có một BaseWorker con trỏ hoàn thành hợp đồng. Thật vậy, đó là khá nhiều điểm của các lớp trừu tượng.

Ngoài ra, thật khó để nói từ ví dụ đơn giản mà bạn đã đăng, nhưng một phần của vấn đề có thể là giao diện và lớp trừu tượng là dư thừa. Trừ khi bạn có các lớp khác thực hiện IFooWorkerkhông xuất phát từ BaseWorker, bạn không cần phải giao diện ở tất cả. Các giao diện chỉ là các lớp trừu tượng với một số hạn chế bổ sung giúp cho việc thừa kế dễ dàng hơn.

Một lần nữa rất khó để nói từ một ví dụ đơn giản hóa, việc sử dụng một phương thức được bảo vệ, tham chiếu rõ ràng đến cơ sở từ một lớp dẫn xuất và việc thiếu một vị trí rõ ràng để khai báo giao diện là những dấu hiệu cảnh báo rằng bạn đang sử dụng kế thừa một cách không phù hợp thành phần. Không có sự kế thừa đó, toàn bộ câu hỏi của bạn sẽ trở thành tranh luận.


1
+1 để chỉ ra rằng chỉ vì BaseWorkerkhông thể thực hiện trực tiếp, không có nghĩa là BaseWorker myWorkerkhông thực hiện IFooWorker.
Avner Shahar-Kashtan

2
Tôi không đồng ý rằng các giao diện chỉ là các lớp trừu tượng. Các lớp trừu tượng thường định nghĩa một số chi tiết triển khai (nếu không thì một giao diện sẽ được sử dụng). Nếu những chi tiết đó không theo ý thích của bạn, thì bây giờ bạn phải thay đổi mọi cách sử dụng của lớp cơ sở sang thứ khác. Giao diện nông cạn; họ định nghĩa mối quan hệ "plug-and-socket" giữa người phụ thuộc và người phụ thuộc. Như vậy, khớp nối chặt chẽ với bất kỳ chi tiết thực hiện không còn là một mối quan tâm. Nếu một lớp kế thừa giao diện không theo ý thích của bạn, hãy loại bỏ nó và đưa vào một thứ hoàn toàn khác; mã của bạn có thể quan tâm ít hơn.
KeithS

5
Cả hai đều có lợi thế, nhưng thông thường giao diện phải luôn được sử dụng cho sự phụ thuộc, cho dù có hay không có một lớp trừu tượng xác định các triển khai cơ sở. Sự kết hợp của giao diện và lớp trừu tượng mang lại điều tốt nhất cho cả hai thế giới; giao diện duy trì mối quan hệ plug-and-socket bề mặt nông, trong khi lớp trừu tượng cung cấp mã chung hữu ích. Bạn có thể loại bỏ và thay thế bất kỳ cấp độ nào bên dưới giao diện này theo ý muốn.
KeithS

Theo "con trỏ", bạn có nghĩa là một thể hiện của một đối tượng (mà một con trỏ sẽ tham chiếu)? Các giao diện không phải là các lớp, chúng là các kiểu và có thể hoạt động như một sự thay thế không hoàn chỉnh cho nhiều kế thừa, không phải là sự thay thế - nghĩa là chúng "bao gồm hành vi từ nhiều nguồn trong một lớp".
samis

46

Tôi phải đồng ý với đồng nghiệp của bạn.

Trong cả hai ví dụ bạn đưa ra, BaseWorker định nghĩa phương thức trừu tượng Work (), có nghĩa là tất cả các lớp con có khả năng đáp ứng hợp đồng của IFooWorker. Trong trường hợp này, tôi nghĩ BaseWorker nên thực hiện giao diện và việc triển khai đó sẽ được kế thừa bởi các lớp con của nó. Điều này sẽ giúp bạn không phải chỉ ra rõ ràng rằng mỗi lớp con thực sự là một IFooWorker (nguyên tắc DRY).

Nếu bạn không định nghĩa Work () là một phương thức của BaseWorker hoặc nếu IFooWorker có các phương thức khác mà các lớp con của BaseWorker sẽ không muốn hoặc không cần, thì (rõ ràng) bạn phải chỉ ra các lớp con nào thực sự triển khai IFooWorker.


11
+1 sẽ đăng điều tương tự. Xin lỗi insta, nhưng tôi nghĩ đồng nghiệp của bạn cũng đúng trong trường hợp này, nếu có gì đó đảm bảo thực hiện hợp đồng, thì nên kế thừa hợp đồng đó, cho dù bảo đảm được thực hiện bởi giao diện, lớp trừu tượng hay lớp cụ thể. Nói một cách đơn giản: Người bảo lãnh nên kế thừa hợp đồng mà nó bảo lãnh.
Jimmy Hoffa

2
Tôi thường đồng ý, nhưng muốn chỉ ra rằng các từ như "cơ sở", "tiêu chuẩn", "mặc định", "chung chung", v.v ... là mùi mã trong một tên lớp. Nếu một lớp cơ sở trừu tượng có cùng tên với một giao diện nhưng với một trong những từ chồn được bao gồm, thì đó thường là dấu hiệu của sự trừu tượng không chính xác; giao diện xác định những gì một cái gì đó làm , định nghĩa kiểu thừa kế nó những gì . Nếu bạn có một IFooBaseFoosau đó nó ngụ ý rằng đó IFookhông phải là một giao diện chi tiết phù hợp, hoặc đó BaseFoolà sử dụng tính kế thừa trong đó thành phần có thể là một lựa chọn tốt hơn.
Aaronaught

@Aaronaught Điểm hay, mặc dù có thể thực sự có một cái gì đó mà tất cả hoặc hầu hết các triển khai của IFoo cần phải kế thừa (chẳng hạn như xử lý một dịch vụ hoặc triển khai DAO của bạn).
Matthew Flynn

2
Sau đó, nên không phải là lớp cơ sở được gọi là MyDaoFoohay SqlFoohay HibernateFoohay bất cứ điều gì, để cho biết rằng nó chỉ là một khả năng cây của các lớp thực hiện IFoo? Nó thậm chí còn có mùi hơn đối với tôi nếu một lớp cơ sở được ghép nối với một thư viện hoặc nền tảng cụ thể và không có đề cập nào về cái tên này ...
Aaronaught

@Aaronaught - Vâng. Tôi hoàn toàn đồng ý.
Matthew Flynn

11

Tôi thường đồng ý với đồng nghiệp của bạn.

Hãy lấy mô hình của bạn: giao diện chỉ được các lớp con triển khai, mặc dù lớp cơ sở cũng thực thi việc hiển thị các phương thức IFooWorker. Trước hết, nó là dư thừa; cho dù lớp con có thực hiện giao diện hay không, chúng được yêu cầu ghi đè các phương thức tiếp xúc của BaseWorker, và tương tự, bất kỳ triển khai IFooWorker nào cũng phải hiển thị chức năng cho dù chúng có nhận được sự trợ giúp nào từ BaseWorker hay không.

Điều này cũng làm cho hệ thống phân cấp của bạn mơ hồ; "Tất cả IFooWorkers là BaseWorkers" không nhất thiết phải là một tuyên bố đúng và cũng không phải là "Tất cả các BaseWorkers đều là IFooWorkers". Vì vậy, nếu bạn muốn xác định một biến đối tượng, tham số hoặc kiểu trả về có thể là bất kỳ triển khai nào của IFooWorker hoặc BaseWorker (lợi dụng chức năng tiếp xúc phổ biến là một trong những lý do để kế thừa ở vị trí đầu tiên), thì không phải những điều này được đảm bảo là toàn diện; một số BaseWorker sẽ không được gán cho một biến loại IFooWorker và ngược lại.

Mô hình đồng nghiệp của bạn dễ sử dụng và thay thế hơn nhiều. "Tất cả BaseWorkers là IFooWorkers" giờ đây là một tuyên bố đúng; bạn có thể cung cấp bất kỳ phiên bản BaseWorker nào cho bất kỳ phụ thuộc IFooWorker nào, không có vấn đề gì. Câu nói ngược lại "Tất cả IFooWorkers là BaseWorkers" là không đúng sự thật; cho phép bạn thay thế BaseWorker bằng BetterBaseWorker và mã tiêu thụ của bạn (phụ thuộc vào IFooWorker) sẽ không phải nói sự khác biệt.


Tôi thích âm thanh của câu trả lời này nhưng không hoàn toàn làm theo phần cuối. Bạn đang gợi ý rằng BetterBaseWorker sẽ là một lớp trừu tượng độc lập thực hiện IFooWorker, để plugin giả định của tôi có thể nói đơn giản là "oh boy! Công nhân cơ sở tốt hơn cuối cùng đã ra mắt!" và thay đổi bất kỳ tài liệu tham khảo BaseWorker nào thành BetterBaseWorker và gọi nó là một ngày (có thể chơi với các giá trị được trả về mới tuyệt vời cho phép tôi loại bỏ các khối lớn mã dự phòng của mình, v.v.)?
Anthony

Nhiều hay ít, vâng. Ưu điểm lớn là bạn sẽ giảm số lượng tài liệu tham khảo đến BaseWorker sẽ phải thay đổi thành BetterBaseWorkers; hầu hết mã của bạn sẽ tham chiếu trực tiếp lớp cụ thể, thay vào đó sử dụng IFooWorker, vì vậy khi bạn thay đổi những gì được gán cho các phụ thuộc IFooWorker đó (thuộc tính của lớp, tham số của hàm tạo hoặc phương thức), mã sử dụng IFooWorker sẽ không thấy bất kỳ sự khác biệt nào trong cách sử dụng.
KeithS

6

Tôi cần thêm một cái gì đó cảnh báo cho những câu trả lời này. Ghép lớp cơ sở với giao diện tạo ra một lực trong cấu trúc của lớp đó. Trong ví dụ cơ bản của bạn, không có gì phải suy nghĩ rằng cả hai nên được ghép nối, nhưng điều đó có thể không đúng trong mọi trường hợp.

Lấy các lớp khung bộ sưu tập của Java:

abstract class AbstractList
class LinkedList extends AbstractList implements Queue

Việc hợp đồng xếp hàng được thực hiện bởi LinkedList đã không đẩy mối quan tâm vào Tóm tắt .

Sự khác biệt giữa hai trường hợp là gì? Mục đích của BaseWorker luôn luôn (như được truyền đạt bằng tên và giao diện của nó) để thực hiện các hoạt động trong IWorker . Mục đích của AbstractList và của Queue là khác nhau, nhưng phần giải thích của cái trước vẫn có thể thực hiện hợp đồng sau.


Điều này xảy ra một nửa thời gian. Anh ấy luôn thích triển khai giao diện trên lớp cơ sở và tôi luôn thích triển khai nó trên bê tông cuối cùng. Kịch bản bạn trình bày xảy ra thường xuyên và là một phần của các giao diện lý do trên cơ sở làm phiền tôi.
Bryan Boettcher

1
Phải, insta và tôi coi việc sử dụng các giao diện theo cách này như là một khía cạnh của sự lạm dụng thừa kế mà chúng ta thấy trong nhiều môi trường phát triển. Giống như GoF đã nói trong cuốn sách mẫu thiết kế của họ, thích sáng tác hơn kế thừa và giữ giao diện ngoài lớp cơ sở là một trong những cách thúc đẩy chính nguyên tắc đó.
Mihai Danila

1
Tôi nhận được cảnh báo, nhưng bạn sẽ lưu ý rằng lớp trừu tượng của OP bao gồm các phương thức trừu tượng khớp với giao diện: BaseWorker hoàn toàn là IFooWorker. Làm cho nó rõ ràng làm cho thực tế này có thể sử dụng nhiều hơn. Tuy nhiên, có rất nhiều phương thức được bao gồm trong Queue không được bao gồm trong AbstractList, và như vậy, AbstractList không phải là Queue, ngay cả khi các phần tử con của nó có thể. AbstractList và Queue là trực giao.
Matthew Flynn

Cảm ơn bạn cho cái nhìn sâu sắc này; Tôi đồng ý rằng trường hợp của OP tình cờ rơi vào loại đó, nhưng tôi cảm thấy có một cuộc thảo luận lớn hơn để tìm kiếm một quy tắc sâu sắc hơn và tôi muốn chia sẻ những quan sát của tôi về một xu hướng mà tôi đã đọc về (và thậm chí là chính tôi trong một thời gian ngắn) lạm dụng thừa kế, có lẽ vì đó là một trong những công cụ trong hộp công cụ của OOP.
Mihai Danila

Dang, đã sáu năm rồi. :)
Mihai Danila

2

Tôi sẽ hỏi câu hỏi, điều gì xảy ra khi bạn thay đổi IFooWorker, chẳng hạn như thêm một phương thức mới?

Nếu BaseWorkerthực hiện giao diện, theo mặc định, nó sẽ phải khai báo phương thức mới, ngay cả khi nó trừu tượng. Mặt khác, nếu nó không thực hiện giao diện, bạn sẽ chỉ nhận được các lỗi biên dịch trên các lớp dẫn xuất.

Vì lý do đó, tôi sẽ làm cho lớp cơ sở triển khai giao diện , vì tôi thể thực hiện tất cả các chức năng cho phương thức mới trong lớp cơ sở mà không cần chạm vào các lớp dẫn xuất.


1

Đầu tiên hãy nghĩ về một lớp cơ sở trừu tượng là gì và giao diện là gì. Xem xét khi nào bạn sẽ sử dụng cái này hay cái kia và khi nào thì không.

Mọi người thường nghĩ rằng cả hai đều là những khái niệm rất giống nhau, thực tế đó là một câu hỏi phỏng vấn chung (sự khác biệt giữa hai là ??)

Vì vậy, một lớp cơ sở trừu tượng cung cấp cho bạn một cái gì đó giao diện không thể là triển khai mặc định của các phương thức. (Có những thứ khác, trong C #, bạn có thể có các phương thức tĩnh trên một lớp trừu tượng, không phải trên một giao diện chẳng hạn).

Ví dụ, một cách sử dụng phổ biến này là với IDis Dùng. Lớp trừu tượng triển khai phương thức Vứt bỏ của IDis, có nghĩa là mọi phiên bản dẫn xuất của lớp cơ sở sẽ tự động được dùng một lần. Sau đó bạn có thể chơi với một số tùy chọn. Hãy loại bỏ trừu tượng, buộc các lớp dẫn xuất phải thực hiện nó. Cung cấp một triển khai mặc định ảo, để họ không hoặc biến nó thành ảo hoặc trừu tượng và gọi nó là các phương thức ảo được gọi là những thứ như BeforeDispose, AfterDispose, OnDispose hoặc Disposeing chẳng hạn.

Vì vậy, bất cứ lúc nào tất cả các lớp dẫn xuất cần hỗ trợ giao diện, nó sẽ đi vào lớp cơ sở. Nếu chỉ có một hoặc một số cần giao diện đó, nó sẽ đi vào lớp dẫn xuất.

Đó thực sự là một tổng số đơn giản hóa. Một cách khác là có các lớp dẫn xuất thậm chí không thực hiện các giao diện, nhưng cung cấp chúng thông qua một mẫu bộ điều hợp. Một ví dụ về điều này tôi thấy gần đây là trong IObjectContextAd CHƯƠNG.


1

Tôi vẫn còn nhiều năm nữa để nắm bắt hoàn toàn sự khác biệt giữa một lớp trừu tượng và một giao diện. Mỗi khi tôi nghĩ rằng tôi nắm được các khái niệm cơ bản, tôi lại tìm kiếm trên stackexchange và tôi lùi lại hai bước. Nhưng một vài suy nghĩ về chủ đề và câu hỏi của OP:

Đầu tiên:

Có hai cách giải thích chung về giao diện:

  1. Giao diện là danh sách các phương thức và thuộc tính mà bất kỳ lớp nào cũng có thể thực hiện và bằng cách triển khai giao diện, một lớp đảm bảo các phương thức đó (và chữ ký của chúng) và các thuộc tính đó (và các kiểu của chúng) sẽ có sẵn khi "giao tiếp" với lớp đó hoặc một đối tượng của lớp đó. Một giao diện là một hợp đồng.

  2. Giao diện là các lớp trừu tượng không / không thể làm gì. Chúng rất hữu ích vì bạn có thể triển khai nhiều hơn một, không giống như các lớp cha có nghĩa. Giống như, tôi có thể là một đối tượng của lớp BananaBread và kế thừa từ BaseBread, nhưng điều đó không có nghĩa là tôi cũng không thể thực hiện cả giao diện IWithNuts và ITastesYummy. Tôi thậm chí có thể thực hiện giao diện IDoesTheDishes, vì tôi không chỉ là bánh mì, phải không?

Có hai cách giải thích chung về một lớp trừu tượng:

  1. Một lớp trừu tượng là, bạn biết, điều không phải là điều có thể làm. Nó giống như, bản chất, không thực sự là một điều thực sự. Đợi một chút, điều này sẽ giúp. Một chiếc thuyền là một lớp trừu tượng, nhưng một Du thuyền Playboy gợi cảm sẽ là một lớp con của BaseBoat.

  2. Tôi đã đọc một cuốn sách về các lớp trừu tượng và có lẽ bạn nên đọc cuốn sách đó, bởi vì bạn có thể không hiểu nó và sẽ làm sai nếu bạn không đọc cuốn sách đó.

Nhân tiện, cuốn sách Citers luôn có vẻ ấn tượng, ngay cả khi tôi vẫn bước đi bối rối.

Thứ hai:

Trên SO, ai đó đã hỏi một phiên bản đơn giản hơn của câu hỏi này, câu hỏi kinh điển, "tại sao lại sử dụng giao diện? Sự khác biệt là gì? Tôi còn thiếu gì?" Và một câu trả lời đã sử dụng một phi công không quân làm ví dụ đơn giản. Nó không hoàn toàn hạ cánh, nhưng nó đã gây ra một số ý kiến ​​tuyệt vời, một trong số đó đã đề cập đến giao diện IFlyable có các phương thức như Take Offer, PilotEject, v.v. Và điều này thực sự đã nhấp vào tôi như một ví dụ thực tế về lý do tại sao giao diện không chỉ hữu ích, nhưng rất quan trọng Một giao diện làm cho một đối tượng / lớp trực quan, hoặc ít nhất mang lại ý nghĩa của nó. Một giao diện không vì lợi ích của đối tượng hoặc dữ liệu, mà là thứ cần tương tác với đối tượng đó. Trái cây cổ điển-> Apple-> Fuji hoặc Shape-> Tam giác-> Các ví dụ tương đương về thừa kế là một mô hình tuyệt vời để hiểu về mặt phân loại một đối tượng nhất định dựa trên hậu duệ của nó. Nó thông báo cho người tiêu dùng và bộ xử lý về các phẩm chất, hành vi chung của nó, cho dù đối tượng là một nhóm vật, sẽ vòi hệ thống của bạn nếu bạn đặt sai vị trí hoặc mô tả dữ liệu cảm giác, đầu nối đến một kho lưu trữ dữ liệu cụ thể hoặc dữ liệu tài chính cần thiết để làm bảng lương.

Một đối tượng Máy bay cụ thể có thể có hoặc không có phương pháp hạ cánh khẩn cấp, nhưng tôi sẽ tức giận nếu tôi giả sử nó khẩn cấp như mọi đối tượng có thể bay được khác chỉ để thức dậy trong DumbPlane và biết rằng các nhà phát triển đã đi với quickLand bởi vì họ nghĩ rằng tất cả đều giống nhau Cũng giống như cách tôi sẽ nản lòng nếu mọi nhà sản xuất ốc vít có cách giải thích riêng về độ kín đúng hoặc nếu TV của tôi không có nút tăng âm lượng trên nút giảm âm lượng.

Các lớp trừu tượng là mô hình thiết lập những gì mà bất kỳ đối tượng hậu duệ nào cũng phải có để định tính là lớp đó. Nếu bạn không có tất cả các phẩm chất của một con vịt, thì việc bạn có giao diện IQuack được thực hiện không thành vấn đề, bạn chỉ là một con chim cánh cụt kỳ lạ. Giao diện là những thứ có ý nghĩa ngay cả khi bạn không thể chắc chắn bất cứ điều gì khác. Jeff Goldblum và Starbuck đều có thể lái tàu vũ trụ của người ngoài hành tinh vì giao diện tương tự nhau.

Thứ ba:

Tôi đồng ý với đồng nghiệp của bạn, bởi vì đôi khi bạn cần thực thi một số phương pháp ngay từ đầu. Nếu bạn đang tạo ORM Bản ghi hoạt động, nó cần một phương thức lưu. Đây không phải là lớp con có thể được khởi tạo. Và nếu giao diện ICRUD đủ di động để không được ghép riêng với một lớp trừu tượng, thì các lớp khác có thể được triển khai để làm cho chúng đáng tin cậy và trực quan cho bất kỳ ai đã quen thuộc với bất kỳ lớp con cháu nào của lớp trừu tượng đó.

Mặt khác, đã có một ví dụ tuyệt vời trước đó khi không nhảy vào việc buộc một giao diện vào lớp trừu tượng, bởi vì không phải tất cả các loại danh sách sẽ (hoặc nên) thực hiện giao diện hàng đợi. Bạn nói kịch bản này xảy ra một nửa thời gian, điều đó có nghĩa là bạn và đồng nghiệp của bạn đều sai một nửa thời gian, và do đó, điều tốt nhất để làm là tranh luận, tranh luận, cân nhắc và nếu họ tỏ ra đúng, hãy thừa nhận và chấp nhận khớp nối . Nhưng đừng trở thành một nhà phát triển tuân theo triết lý ngay cả khi đó không phải là nhà phát triển tốt nhất cho công việc.


0

Có một khía cạnh khác về lý do tại sao DbWorkernên thực hiện IFooWorker, như bạn đề xuất.

Trong trường hợp theo kiểu đồng nghiệp của bạn, nếu vì một lý do nào đó, việc tái cấu trúc sau đó xảy ra và DbWorkerđược coi là không mở rộng BaseWorkerlớp trừu tượng , DbWorkersẽ mất IFooWorkergiao diện. Điều này có thể, nhưng không nhất thiết, có tác động đến khách hàng tiêu thụ nó, nếu họ mong đợi IFooWorkergiao diện.


-1

Để bắt đầu, tôi muốn định nghĩa các giao diện và các lớp trừu tượng khác nhau:

Interface: a contract that each class must fulfill if they are to implement that interface
Abstract class: a general class (i.e vehicle is an abstract class, while porche911 is not)

Trong trường hợp của bạn, tất cả các công nhân thực hiện work()bởi vì đó là những gì hợp đồng yêu cầu họ làm. Do đó, lớp cơ sở của bạn Baseworkernên thực hiện phương thức đó một cách rõ ràng hoặc như là một hàm ảo. Tôi sẽ đề nghị bạn đưa phương thức này vào lớp cơ sở của bạn vì thực tế đơn giản là tất cả các công nhân cần phải làm work(). Do đó, rất hợp lý khi lớp cơ sở của bạn (mặc dù bạn không thể tạo một con trỏ kiểu đó) bao gồm nó như là một hàm.

Bây giờ, DbWorkerkhông thỏa mãn IFooWorkergiao diện, nhưng nếu có một cách cụ thể mà lớp này thực hiện work(), thì nó thực sự cần phải quá tải định nghĩa được kế thừa từ đó BaseWorker. Lý do nó không nên thực hiện giao diện IFooWorkertrực tiếp là vì đây không phải là điều gì đặc biệt DbWorker. Nếu bạn đã làm điều đó mỗi khi bạn triển khai các lớp tương tự DbWorkerthì bạn sẽ vi phạm DRY .

Nếu hai lớp thực hiện cùng một chức năng theo những cách khác nhau, bạn có thể bắt đầu tìm kiếm siêu lớp phổ biến nhất. Hầu hết thời gian bạn sẽ tìm thấy một. Nếu không, hãy tiếp tục tìm kiếm hoặc từ bỏ và chấp nhận rằng họ không có đủ điểm chung để tạo ra một lớp cơ sở.


-3

Wow, tất cả những câu trả lời này và không ai chỉ ra rằng giao diện trong ví dụ không phục vụ mục đích gì. Bạn không sử dụng tính kế thừa và giao diện cho cùng một phương thức, điều đó là vô nghĩa. Bạn sử dụng cái này hay cái khác Có rất nhiều câu hỏi trên diễn đàn này với các câu trả lời giải thích lợi ích của mỗi và các kịch bản kêu gọi cái này hay cái khác.


3
Đó là một sự giả dối bằng sáng chế. Giao diện là một hợp đồng mà hệ thống dự kiến ​​sẽ hành xử, mà không liên quan đến việc thực hiện. Lớp cơ sở được kế thừa là một trong nhiều triển khai có thể có cho giao diện này. Một hệ thống quy mô đủ lớn sẽ có nhiều lớp cơ sở đáp ứng các giao diện khác nhau (thường được đóng bằng generic), đó chính xác là những gì thiết lập này đã làm và tại sao nó hữu ích để phân chia chúng.
Bryan Boettcher

Đó là một ví dụ thực sự tồi tệ từ góc độ OOP. Như bạn nói "lớp cơ sở chỉ là một triển khai" (nên có hoặc sẽ không có điểm nào cho giao diện), bạn đang nói rằng sẽ có các lớp không công nhân thực hiện giao diện IFooWorker. Sau đó, bạn sẽ có một lớp cơ sở là một fooworker chuyên biệt (chúng ta nên cho rằng cũng có thể có barworkers) và bạn sẽ có những người làm việc khác thậm chí không phải là công nhân cơ bản! Đó là lạc hậu. Trong nhiều hơn một cách.
Martin Maat

1
Có lẽ bạn bị kẹt với từ "Công nhân" trong giao diện. Thế còn "Nguồn dữ liệu"? Nếu chúng ta có IDatasource <T>, chúng ta có thể có SqlDatasource <T>, RestDatasource <T>, MongoDatasource <T> một cách tầm thường. Doanh nghiệp quyết định việc đóng giao diện generic nào đi vào việc triển khai đóng các nguồn dữ liệu trừu tượng.
Bryan Boettcher

Có thể có ý nghĩa khi sử dụng tính kế thừa chỉ vì lợi ích của DRY và đặt một số triển khai chung trong một lớp cơ sở. Nhưng bạn sẽ không căn chỉnh bất kỳ phương thức ghi đè nào với bất kỳ phương thức giao diện nào, lớp cơ sở sẽ chứa logic hỗ trợ chung. Nếu họ xếp hàng, điều đó sẽ cho thấy sự kế thừa sẽ giải quyết vấn đề của bạn tốt và giao diện sẽ không phục vụ mục đích nào. Bạn sử dụng một giao diện trong trường hợp thừa kế không giải quyết được vấn đề của bạn vì các đặc điểm chung bạn muốn mô hình hóa không phù hợp với cây lớp singke. Và vâng, từ ngữ trong ví dụ làm cho nó càng sai.
Martin Maat
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.