Là một phương thức Nếu một phương thức được sử dụng lại mà không thay đổi, hãy đặt phương thức đó vào một lớp cơ sở, nếu không thì tạo một giao diện, một quy tắc tốt?


10

Một đồng nghiệp của tôi đã đưa ra một quy tắc ngón tay cái để lựa chọn giữa việc tạo một lớp cơ sở hoặc một giao diện.

Anh ta nói:

Hãy tưởng tượng mọi phương pháp mới mà bạn sắp thực hiện. Đối với mỗi người trong số họ, hãy xem xét điều này: phương thức này sẽ được thực hiện bởi nhiều hơn một lớp trong chính xác mẫu này, mà không có bất kỳ thay đổi nào? Nếu câu trả lời là "có", hãy tạo một lớp cơ sở. Trong mọi tình huống khác, tạo một giao diện.

Ví dụ:

Xem xét các lớp catdog, mở rộng lớp mammalvà có một phương thức duy nhất pet(). Sau đó chúng tôi thêm lớp alligator, không mở rộng bất cứ thứ gì và có một phương thức duy nhất slither().

Bây giờ, chúng tôi muốn thêm một eat()phương thức cho tất cả chúng.

Nếu việc thực hiện eat()phương thức sẽ hoàn toàn giống nhau cat, dogalligator, chúng ta nên tạo một lớp cơ sở (giả sử, animal), thực hiện phương thức này.

Tuy nhiên, nếu nó được thực hiện theo cách alligatorkhác nhau, chúng ta nên tạo một IEatgiao diện và thực hiện mammalalligatorthực hiện nó.

Ông nhấn mạnh rằng phương pháp này bao gồm tất cả các trường hợp, nhưng có vẻ như tôi đơn giản hóa quá mức.

Có đáng theo quy tắc này không?


27
alligatoreatTất nhiên, việc thực hiện khác nhau ở chỗ nó chấp nhận catdoglàm tham số.
sbichenko

1
Trường hợp bạn thực sự thích một lớp cơ sở trừu tượng để chia sẻ triển khai, nhưng nên sử dụng các giao diện để có khả năng mở rộng chính xác, thay vào đó bạn thường có thể sử dụng các đặc điểm . Đó là, nếu ngôn ngữ của bạn hỗ trợ này.
amon

2
Khi bạn vẽ mình vào một góc, tốt nhất là ở trong một cửa gần nhất.
JeffO

10
Sai lầm lớn nhất mà mọi người mắc phải là tin rằng các giao diện chỉ đơn giản là các lớp trừu tượng trống rỗng. Giao diện là cách để một lập trình viên nói, "Tôi không quan tâm những gì bạn đưa cho tôi, miễn là nó tuân theo quy ước này." Tái sử dụng mã sau đó nên được thực hiện (lý tưởng) thông qua thành phần. Đồng nghiệp của bạn là sai.
riwalk

2
Một hồ sơ CS của tôi đã dạy rằng các siêu lớp nên is avà các giao diện là acts likehoặc is. Vì vậy, một con chó is ađộng vật có vú và acts likemột người ăn. Điều này sẽ cho chúng ta biết rằng động vật có vú nên là một lớp và người ăn nên là một giao diện. Nó luôn luôn là một hướng dẫn rất hữu ích. Sidenote: Một ví dụ về issẽ là The cake is eatablehoặc The book is writable.
MirroredFate

Câu trả lời:


13

Tôi không nghĩ rằng đây là một quy tắc tốt. Nếu bạn lo ngại về việc tái sử dụng mã, bạn có thể thực hiện PetEatingBehaviorvai trò là xác định chức năng ăn uống cho mèo và chó. Sau đó, bạn có thể có IEatvà mã tái sử dụng với nhau.

Những ngày này tôi thấy ngày càng ít lý do để sử dụng thừa kế. Một lợi thế lớn là dễ sử dụng. Lấy một khung GUI. Một cách phổ biến để thiết kế API cho việc này là hiển thị một lớp cơ sở khổng lồ và ghi lại các phương thức mà người dùng có thể ghi đè. Vì vậy, chúng ta có thể bỏ qua tất cả những điều phức tạp khác mà ứng dụng của chúng ta cần quản lý, do việc triển khai "mặc định" được đưa ra bởi lớp cơ sở. Nhưng chúng ta vẫn có thể tùy chỉnh mọi thứ bằng cách thực hiện lại phương thức chính xác khi chúng ta cần.

Nếu bạn sử dụng giao diện cho api của mình, người dùng của bạn thường phải làm nhiều việc hơn để làm cho các ví dụ đơn giản hoạt động. Nhưng API với các giao diện thường ít được kết nối và dễ bảo trì hơn vì lý do đơn giản IEatchứa ít thông tin hơn Mammallớp cơ sở. Vì vậy, người tiêu dùng sẽ phụ thuộc vào một hợp đồng yếu hơn, bạn có nhiều cơ hội tái cấu trúc hơn, v.v.

Để trích dẫn Rich Hickey: Đơn giản! = Dễ dàng


Bạn có thể cung cấp một ví dụ cơ bản về PetEatingBehaviorviệc thực hiện?
sbichenko

1
@exizt Đầu tiên, tôi rất tệ trong việc chọn tên. Vì vậy, PetEatingBehaviorcó lẽ là sai. Tôi không thể cung cấp cho bạn một triển khai vì đây chỉ là một ví dụ đồ chơi. Nhưng tôi có thể mô tả các bước tái cấu trúc: nó có một hàm tạo lấy tất cả thông tin được sử dụng bởi mèo / chó để xác định eatphương thức (ví dụ về răng, ví dụ về dạ dày, v.v.). Nó chỉ chứa mã phổ biến cho mèo và chó được sử dụng trong eatphương pháp của họ . Bạn chỉ cần tạo một PetEatingBehaviorthành viên và chuyển tiếp nó gọi tới dog.eat/cat.eat.
Simon Bergot

Tôi không thể tin rằng đây là câu trả lời duy nhất của nhiều người để giúp OP tránh xa các khoản thừa kế :( Kế thừa không dành cho việc sử dụng lại mã!
Danny Tuppeny

1
@DannyTuppeny Tại sao không?
sbichenko

4
@exizt Kế thừa từ một lớp học là một vấn đề lớn; bạn đang có một sự phụ thuộc (có thể là vĩnh viễn) vào một thứ gì đó, trong nhiều ngôn ngữ bạn chỉ có thể có một ngôn ngữ. Sử dụng lại mã là một điều khá nhỏ để sử dụng lớp cơ sở thường chỉ này trên. Điều gì xảy ra nếu bạn kết thúc với các phương thức mà bạn muốn chia sẻ chúng với một lớp khác, nhưng nó đã có một lớp cơ sở vì sử dụng lại các phương thức khác (hoặc thậm chí, đó là một phần của hệ thống phân cấp "thực" được sử dụng đa hình (?)). Sử dụng tính kế thừa khi ClassA là một loại ClassB (ví dụ: Xe thừa hưởng từ Xe), không phải khi nó chia sẻ một số chi tiết triển khai nội bộ :-)
Danny Tuppeny

11

Có đáng theo quy tắc này không?

Đó là một quy tắc tốt, nhưng tôi biết khá nhiều nơi tôi vi phạm.

Để công bằng, tôi sử dụng các lớp cơ sở (trừu tượng) nhiều hơn so với các đồng nghiệp của tôi. Tôi làm điều này như lập trình phòng thủ.

Đối với tôi (trong nhiều ngôn ngữ), một lớp cơ sở trừu tượng thêm ràng buộc chính là chỉ có thể có một lớp cơ sở. Bằng cách chọn sử dụng lớp cơ sở thay vì giao diện, bạn sẽ nói "đây là chức năng cốt lõi của lớp (và bất kỳ người thừa kế nào) và không phù hợp để trộn willy-nilly với chức năng khác". Các giao diện thì ngược lại: "Đây là một số đặc điểm của một đối tượng, không nhất thiết là trách nhiệm cốt lõi của nó".

Các loại lựa chọn thiết kế này tạo ra các hướng dẫn ngầm cho người triển khai của bạn về cách họ nên sử dụng mã của bạn và bằng cách mở rộng viết mã của họ. Do tác động lớn hơn của chúng đến codebase, tôi có xu hướng cân nhắc những điều này mạnh mẽ hơn khi đưa ra quyết định thiết kế.


1
Đọc lại câu trả lời này 3 năm sau, tôi thấy đây là một điểm tuyệt vời: Bằng cách chọn sử dụng lớp cơ sở thay vì giao diện, bạn đang nói "đây là chức năng cốt lõi của lớp (và bất kỳ người thừa kế nào), và đó là không phù hợp để trộn willy-nilly với các chức năng khác "
sbichenko

9

Vấn đề với sự đơn giản hóa của bạn bè của bạn là việc xác định liệu một phương pháp có cần thay đổi hay không là cực kỳ khó khăn. Tốt hơn là sử dụng một quy tắc nói lên tâm lý đằng sau các siêu lớp và giao diện.

Thay vì tìm ra những gì bạn nên làm bằng cách tìm đến những gì bạn nghĩ chức năng là, hãy nhìn vào hệ thống phân cấp bạn đang cố gắng tạo ra.

Đây là cách một giáo sư của tôi đã dạy sự khác biệt:

Các siêu lớp nên is avà giao diện là acts likehoặc is. Vì vậy, một con chó is ađộng vật có vú và acts likemột người ăn. Điều này sẽ cho chúng ta biết rằng động vật có vú nên là một lớp và người ăn nên là một giao diện. Một ví dụ về issẽ The cake is eatablehay The book is writable(làm cho cả hai eatablewritablegiao diện).

Sử dụng một phương pháp như thế này khá đơn giản và dễ dàng, và khiến bạn viết mã theo cấu trúc và các khái niệm hơn là những gì bạn nghĩ rằng mã sẽ làm. Điều này làm cho việc bảo trì dễ dàng hơn, mã dễ đọc hơn và thiết kế đơn giản hơn để tạo.

Bất kể mã thực sự có thể nói gì, nếu bạn sử dụng một phương thức như thế này, bạn có thể quay lại năm năm kể từ bây giờ và sử dụng cùng một phương thức để truy xuất các bước của bạn và dễ dàng tìm ra cách chương trình của bạn được thiết kế và cách nó tương tác.

Chỉ hai xu của tôi.


6

Theo yêu cầu của exizt, tôi đang mở rộng bình luận của mình thành một câu trả lời dài hơn.

Sai lầm lớn nhất mà mọi người mắc phải là tin rằng các giao diện chỉ đơn giản là các lớp trừu tượng trống rỗng. Giao diện là cách để một lập trình viên nói, "Tôi không quan tâm những gì bạn đưa cho tôi, miễn là nó tuân theo quy ước này."

Thư viện .NET phục vụ như một ví dụ tuyệt vời. Ví dụ: khi bạn viết một hàm chấp nhận một IEnumerable<T>, điều bạn đang nói là: "Tôi không quan tâm đến việc bạn đang lưu trữ dữ liệu của mình như thế nào . Tôi chỉ muốn biết rằng tôi có thể sử dụng một foreachvòng lặp.

Điều này dẫn đến một số mã rất linh hoạt. Đột nhiên để được tích hợp, tất cả những gì bạn cần làm là chơi theo các quy tắc của các giao diện hiện có. Nếu việc thực hiện giao diện khó khăn hoặc khó hiểu, thì có lẽ đó là một gợi ý rằng bạn đang cố gắng đẩy một chốt vuông vào một lỗ tròn.

Nhưng sau đó, câu hỏi được đặt ra: "Thế còn việc sử dụng lại mã? Các giáo sư CS của tôi nói với tôi rằng thừa kế là giải pháp cho tất cả các vấn đề tái sử dụng mã và sự thừa kế đó cho phép bạn viết một lần và sử dụng ở mọi nơi và nó sẽ chữa khỏi bệnh viêm màng não trong quá trình giải cứu trẻ mồ côi. từ những vùng biển đang trỗi dậy và sẽ không còn nước mắt nữa và v.v. v.v. "

Sử dụng tính kế thừa chỉ vì bạn thích âm thanh của từ "tái sử dụng mã" là một ý tưởng khá tồi. Các hướng dẫn đang styling bởi Google làm cho thời điểm này khá ngắn gọn:

Thành phần thường thích hợp hơn thừa kế. ... [B] vì mã thực hiện một lớp con được trải rộng giữa lớp cơ sở và lớp con, việc hiểu một triển khai có thể khó khăn hơn. Lớp con không thể ghi đè các hàm không ảo, vì vậy lớp con không thể thay đổi việc thực hiện.

Để minh họa tại sao sự kế thừa không phải lúc nào cũng là câu trả lời, tôi sẽ sử dụng một lớp có tên MyecialFileWriter. Một người mù quáng tin rằng thừa kế là giải pháp cho tất cả các vấn đề sẽ lập luận rằng bạn nên cố gắng kế thừa từ đó FileStream, kẻo bạn sẽ sao chép FileStreammã của mình. Người thông minh nhận ra rằng điều này là ngu ngốc. Bạn chỉ nên có một FileStreamđối tượng trong lớp của mình (dưới dạng biến cục bộ hoặc biến thành viên) và sử dụng chức năng của nó.

Các FileStreamví dụ có vẻ giả tạo, nhưng nó không phải. Nếu bạn có hai lớp mà cả hai thực hiện cùng một giao diện theo cùng một cách chính xác, thì bạn nên có một lớp thứ ba gói gọn mọi hoạt động được sao chép. Mục tiêu của bạn là viết các lớp là các khối có thể tái sử dụng khép kín, có thể ghép lại với nhau như các bản legos.

Điều này không có nghĩa là nên tránh việc thừa kế bằng mọi giá. Có nhiều điểm để xem xét, và hầu hết sẽ được đề cập bằng cách nghiên cứu câu hỏi, "Thành phần so với Kế thừa". Stack Overflow rất riêng của chúng tôi có một vài câu trả lời hay về chủ đề này.

Vào cuối ngày, tình cảm đồng nghiệp của bạn thiếu chiều sâu hoặc sự hiểu biết cần thiết để đưa ra quyết định sáng suốt. Nghiên cứu chủ đề và tìm ra nó cho chính mình.

Khi minh họa sự kế thừa, mọi người đều sử dụng động vật. Điều đó là vô ích. Trong 11 năm phát triển, tôi chưa bao giờ viết một lớp có tên Cat, vì vậy tôi sẽ không sử dụng nó làm ví dụ.


Vì vậy, nếu tôi hiểu bạn một cách chính xác, tiêm phụ thuộc cung cấp các khả năng tái sử dụng mã tương tự như thừa kế. Nhưng bạn sẽ sử dụng thừa kế để làm gì? Bạn sẽ cung cấp một trường hợp sử dụng? (Tôi thực sự đánh giá cao người có FileStream)
sbichenko

1
@exizt, không, nó không cung cấp khả năng tái sử dụng mã tương tự. Nó cung cấp khả năng tái sử dụng mã tốt hơn (trong nhiều trường hợp) vì nó giữ cho việc triển khai được thực hiện tốt. Các lớp được tương tác rõ ràng thông qua các đối tượng và API công khai của chúng, thay vì hoàn toàn đạt được các khả năng và thay đổi hoàn toàn một nửa chức năng bằng cách làm quá tải các chức năng hiện có.
riwalk

4

Các ví dụ hiếm khi đưa ra quan điểm, đặc biệt là khi họ nói về ô tô hoặc động vật. Cách ăn (hoặc chế biến thức ăn) của động vật không thực sự gắn liền với sự kế thừa của nó, không có cách ăn duy nhất nào có thể áp dụng cho tất cả động vật. Điều này phụ thuộc nhiều hơn vào khả năng vật lý của nó (cách thức nhập, xử lý và đầu ra có thể nói). Động vật có vú có thể là động vật ăn thịt, động vật ăn cỏ hoặc động vật ăn tạp chẳng hạn, trong khi chim, động vật có vú và cá có thể ăn côn trùng. Hành vi này có thể được nắm bắt với các mẫu nhất định.

Bạn trong trường hợp này tốt hơn bằng cách trừu tượng hóa hành vi, như thế này:

public interface IFoodEater
{
    void Eat(IFood food);
}

public class Animal : IFoodEater
{
    private IFoodProcessor _foodProcessor;
    public Animal(IFoodProcessor foodProcessor)
    {
        _foodProcessor = foodProcessor;
    }

    public void Eat(IFood food)
    {
        _foodProcessor.Process(food);
    }
}

Hoặc triển khai mẫu khách truy cập trong đó FoodProcessorcố gắng cho động vật tương thích ( ICarnivore : IFoodEater, MeatProcessingVisitor). Ý nghĩ về động vật sống có thể cản trở bạn trong việc suy nghĩ về việc xé toạc đường tiêu hóa của chúng và thay thế nó bằng một con chung, nhưng bạn thực sự đang giúp chúng.

Điều này sẽ làm cho động vật của bạn trở thành một lớp cơ sở, và thậm chí tốt hơn, bạn sẽ không bao giờ phải thay đổi nữa khi thêm một loại thức ăn mới và một bộ xử lý, vì vậy bạn có thể tập trung vào những thứ tạo nên lớp động vật cụ thể này làm việc trên rất độc đáo

Vì vậy, có, các lớp cơ sở có vị trí của chúng, quy tắc ngón tay cái được áp dụng (thường, không phải luôn luôn, vì đó là quy tắc của ngón tay cái) nhưng tiếp tục tìm kiếm hành vi bạn có thể cô lập.


1
IFoodEater là loại dư thừa. Bạn còn mong đợi gì nữa để có thể ăn thức ăn? Vâng, động vật là ví dụ là điều khủng khiếp.
Euphoric

1
Còn cây ăn thịt thì sao? :) Nghiêm túc mà nói, một vấn đề lớn khi không sử dụng giao diện trong trường hợp này là bạn đã gắn bó chặt chẽ khái niệm ăn với Động vật và việc giới thiệu trừu tượng mới mà lớp cơ sở Động vật không phù hợp là rất nhiều.
Julia Hayward

1
Tôi tin rằng "ăn" là ý tưởng sai ở đây: đi trừu tượng hơn. Làm thế nào về một IConsumergiao diện. Động vật ăn thịt có thể tiêu thụ thịt, động vật ăn cỏ tiêu thụ thực vật và thực vật tiêu thụ ánh sáng mặt trời và nước.

2

Một giao diện thực sự là một hợp đồng. Không có chức năng. Nó là tùy thuộc vào người thực hiện để thực hiện. Không có sự lựa chọn vì người ta phải thực hiện hợp đồng.

Các lớp trừu tượng cho người thực hiện lựa chọn. Người ta có thể chọn có một phương thức mà mọi người phải thực hiện, cung cấp một phương thức với chức năng cơ bản mà người ta có thể ghi đè hoặc cung cấp một số chức năng cơ bản mà tất cả những người thừa kế có thể sử dụng.

Ví dụ:

public abstract class Person
    {
        /// <summary>
        /// Inheritors must implement a hello
        /// </summary>
        /// <returns>Hello</returns>
        public abstract string SayHello();
        /// <summary>
        /// Inheritors can use base functionality, or override
        /// </summary>
        /// <returns></returns>
        public virtual string SayGoodBye()
        {
            return "Good Bye";
        }
        /// <summary>
        /// Base Functionality that is inherited 
        /// </summary>
        public void DoSomething()
        {
            //Do Something Here
        }
    }

Tôi muốn nói rằng quy tắc ngón tay cái của bạn cũng có thể được xử lý bởi một lớp cơ sở. Đặc biệt, vì nhiều động vật có vú "ăn" giống nhau sau đó sử dụng lớp cơ sở có thể tốt hơn giao diện, vì người ta có thể thực hiện một phương pháp ăn ảo mà hầu hết những người thừa kế có thể sử dụng và sau đó các trường hợp ngoại lệ có thể ghi đè.

Bây giờ nếu định nghĩa "ăn" thay đổi từ động vật sang động vật thì có lẽ và giao diện sẽ tốt hơn. Đây đôi khi là một khu vực màu xám và phụ thuộc vào tình huống cá nhân.


1

Không.

Các giao diện là về cách các phương thức được thực hiện. Nếu bạn đang căn cứ vào quyết định của mình bất cứ khi nào sử dụng giao diện về cách các phương thức sẽ được thực hiện, thì bạn đang làm sai.

Đối với tôi, sự khác biệt giữa giao diện và lớp cơ sở là về nơi yêu cầu đứng. Bạn tạo giao diện, khi một số đoạn mã yêu cầu lớp với API cụ thể và hành vi ngụ ý xuất hiện từ API này. Bạn không thể tạo một giao diện mà không có mã sẽ thực sự gọi nó.

Mặt khác, các lớp cơ sở thường có nghĩa là cách sử dụng lại mã, vì vậy bạn hiếm khi bận tâm với mã sẽ gọi lớp cơ sở này và chỉ tính đến việc tìm kiếm các đối tượng mà lớp cơ sở này là một phần của.


Chờ đã, tại sao lại thực hiện eat()mọi động vật? Chúng ta có thể thực mammalhiện nó, phải không? Tôi hiểu quan điểm của bạn về các yêu cầu, mặc dù.
sbichenko

@exizt: Xin lỗi, tôi đọc câu hỏi của bạn không chính xác.
Euphoric

1

Đó thực sự không phải là một quy tắc tốt bởi vì nếu bạn muốn triển khai khác nhau, bạn luôn có thể có một lớp cơ sở trừu tượng với một phương thức trừu tượng. Mỗi người thừa kế được đảm bảo có phương pháp đó, nhưng mỗi người xác định việc thực hiện riêng của họ. Về mặt kỹ thuật, bạn có thể có một lớp trừu tượng không có gì ngoài một tập các phương thức trừu tượng và về cơ bản nó giống như một giao diện.

Các lớp cơ sở rất hữu ích khi bạn muốn triển khai thực tế một số trạng thái hoặc hành vi có thể được sử dụng trên một số lớp. Nếu bạn thấy mình có một vài lớp có các trường và phương thức giống hệt nhau với các triển khai giống hệt nhau, có lẽ bạn có thể cấu trúc lại nó thành một lớp cơ sở. Bạn chỉ cần cẩn thận trong cách bạn thừa kế. Mỗi trường hoặc phương thức hiện có trong lớp cơ sở phải có ý nghĩa trong người thừa kế, đặc biệt khi nó được chọn làm lớp cơ sở.

Các giao diện rất hữu ích khi bạn cần tương tác với một nhóm các lớp theo cùng một cách, nhưng bạn không thể dự đoán hoặc không quan tâm đến việc triển khai thực tế của chúng. Lấy ví dụ một cái gì đó như giao diện Bộ sưu tập. Chúa chỉ biết tất cả vô số cách mà ai đó có thể quyết định thực hiện một bộ sưu tập vật phẩm. Danh sách liên kết? Lập danh sách? Cây rơm? Xếp hàng? Phương thức SearchAndReplace này không quan tâm, nó chỉ cần được thông qua một cái gì đó mà nó có thể gọi Thêm, Xóa và GetEnumerator trên.


1

Bản thân tôi đang xem câu trả lời cho câu hỏi này, để xem người khác nói gì, nhưng tôi sẽ nói ba điều mà tôi có xu hướng nghĩ về điều này:

  1. Mọi người đặt quá nhiều niềm tin vào các giao diện và quá ít niềm tin vào các lớp học. Các giao diện về cơ bản là rất nhiều lớp cơ sở, và đôi khi việc sử dụng một thứ gì đó nhẹ có thể dẫn đến những thứ như mã nồi hơi quá mức.

  2. Không có gì sai cả khi ghi đè một phương thức, gọi super.method()bên trong nó và để phần còn lại của cơ thể chỉ làm những việc không can thiệp vào lớp cơ sở. Điều này không vi phạm Nguyên tắc thay thế Liskov .

  3. Theo nguyên tắc thông thường, việc cứng nhắc và giáo điều này trong công nghệ phần mềm là một ý tưởng tồi, ngay cả khi một cái gì đó nói chung là một thực tiễn tốt nhất.

Vì vậy, tôi sẽ lấy những gì đã nói với một hạt muối.


5
People put way too much faith into interfaces, and too little faith into classes.Buồn cười. Tôi có xu hướng nghĩ rằng điều ngược lại là đúng.
Simon Bergot

1
"Điều này không vi phạm Nguyên tắc thay thế Liskov." Nhưng nó cực kỳ gần để trở thành một vi phạm LSP. Tốt hơn là an toàn hơn xin lỗi.
Euphoric

1

Tôi muốn có một cách tiếp cận ngôn ngữ và ngữ nghĩa đối với điều này. Một giao diện không có cho DRY. Mặc dù nó có thể được sử dụng như một cơ chế để tập trung hóa bên cạnh việc có một lớp trừu tượng thực hiện nó, nhưng nó không có nghĩa là dành cho DRY.

Một giao diện là một hợp đồng.

IAnimalgiao diện là một hợp đồng tất cả hoặc không có gì giữa cá sấu, mèo, chó và khái niệm của một con vật. Những gì nó nói về cơ bản là "nếu bạn muốn trở thành một con vật, hoặc bạn phải có tất cả các thuộc tính và đặc điểm này, hoặc bạn không còn là một con vật nữa".

Kế thừa ở phía bên kia là một cơ chế để tái sử dụng. Trong trường hợp này, tôi nghĩ rằng có một lý luận ngữ nghĩa tốt có thể giúp bạn quyết định phương pháp nào nên đi đâu. Ví dụ, trong khi tất cả các loài động vật có thể ngủ và ngủ giống nhau, tôi sẽ không sử dụng lớp cơ sở cho nó. Trước tiên tôi đặt Sleep()phương thức này vào IAnimalgiao diện như một điểm nhấn (ngữ nghĩa) cho những gì nó cần là một con vật, sau đó tôi sử dụng một lớp trừu tượng cơ bản cho mèo, chó và cá sấu như một kỹ thuật lập trình để không lặp lại chính mình.


0

Tôi không chắc đây là quy tắc tốt nhất ngoài kia. Bạn có thể đặt một abstractphương thức trong lớp cơ sở và yêu cầu mỗi lớp thực hiện nó. Vì mỗi người mammalphải ăn nên sẽ có ý nghĩa để làm điều đó. Sau đó, mỗi người mammalcó thể sử dụng một eatphương pháp của mình. Tôi thường chỉ sử dụng các giao diện để liên lạc giữa các đối tượng hoặc làm điểm đánh dấu để đánh dấu một lớp là một cái gì đó. Tôi nghĩ rằng việc lạm dụng các giao diện làm cho mã cẩu thả.

Tôi cũng đồng ý với @Panzercrisis rằng quy tắc ngón tay cái chỉ nên là một hướng dẫn chung. Không phải cái gì đó nên viết bằng đá. Chắc chắn sẽ có những lúc nó không hoạt động.


-1

Giao diện là các loại. Họ cung cấp không thực hiện. Họ đơn giản xác định hợp đồng để xử lý loại đó. Hợp đồng này phải được đáp ứng bởi bất kỳ lớp nào thực hiện giao diện.

Việc sử dụng các giao diện và các lớp trừu tượng / cơ sở nên được xác định bởi các yêu cầu thiết kế.

Một lớp duy nhất không yêu cầu giao diện.

Nếu hai lớp có thể hoán đổi cho nhau trong một hàm thì chúng sẽ thực hiện một giao diện chung. Bất kỳ chức năng nào được triển khai giống hệt nhau sau đó có thể được tái cấu trúc thành một lớp trừu tượng thực hiện giao diện. Giao diện có thể được coi là dư thừa tại thời điểm đó nếu không có chức năng phân biệt. Nhưng sau đó, sự khác biệt giữa hai lớp là gì?

Sử dụng các lớp trừu tượng như các kiểu nói chung là lộn xộn khi nhiều chức năng được thêm vào và khi một hệ thống phân cấp mở rộng.

Thành phần ủng hộ hơn thừa kế - tất cả điều này biến mất.

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.