Khi bạn sử dụng tính kế thừa để sử dụng lại mã, bạn có thấy quá khó khăn khi nó nuốt phải lợi ích của việc tái sử dụng không?


8

Tôi đã viết mã được khoảng 8 năm, tuy nhiên tôi vẫn thấy tính kế thừa quá linh hoạt và đôi khi nó khiến bạn hoàn toàn bối rối với mã bạn đã viết. Một ví dụ đơn giản nhất sẽ là:

abstract class AClass {
    protected void method1() {
        if(check()) {
            do1();
        } else {
            do2();
        }
    }
    protected abstract void do1();
    protected abstract void do2();
}

Mục đích của lớp là mọi người có thể triển khai do1 () và do2 () để có thể thực hiện một số logic hơn nữa, tuy nhiên đôi khi mọi người quyết định quá tải phương thức1 (), và sau đó mọi thứ trở nên phức tạp ngay lập tức.

Tôi chỉ tìm thấy trong mẫu chiến lược, mã được sử dụng lại thông qua kế thừa, trong hầu hết trường hợp, người thiết kế lớp cơ sở biết rất rõ các lớp con của nó và sự kế thừa đó là hoàn toàn tùy chọn.

Tôi có một lớp được kế thừa bởi 4 lớp - một IoHandler và đó là lớp con cho phía máy chủ, phía máy khách, máy chủ biên, máy chủ gốc và nó bắt đầu khiến tôi phát điên. Tôi luôn luôn trong việc tái cấu trúc mã, tôi luôn đưa ra những ý tưởng mà tôi nghĩ sẽ hiệu quả và sau đó được chứng minh là không. Người ta nói rằng bộ não con người chỉ có thể chứa 7 mẩu thông tin một lần, tôi có mang quá nhiều không?


1
Bạn biết bạn có thể ngăn chặn quá tải các phương pháp cụ thể? Đánh dấu là final[nếu bạn đang sử dụng java]. Điều này thực sự làm cho nó không ảo, và nó không thể bị quá tải. Hoàn hảo để sử dụng trong trường hợp này [mẫu]
Farrell

@Farrell Trên thực tế tôi đánh dấu nó 'được bảo vệ chuyên sâu để nó có thể bị quá tải'. Tôi nghĩ rằng khi tôi làm điều này, tôi đang sử dụng một số ý tưởng từ MFC, được thiết kế tồi.
chiến thuật

2
Phương thức nạp chồng không liên quan gì đến thừa kế. Thật là kỳ lạ khi một số ý kiến ​​và thậm chí câu trả lời được chấp nhận lặp lại nguyên văn. Chắc chắn bạn đang nói về việc ghi đè ?
Aaronaught

@Aaronaught Vâng, sự điều chỉnh của bạn thực sự hữu ích :(
Tipoth

Câu trả lời:


3

Khi bạn tiếp tục lặp lại các mẫu như vậy, chúng dường như không còn khó khăn nữa.

Ngoài ra, bạn phải biết về các lớp cơ sở trước khi kế thừa chúng. Và kế thừa sẽ không phải là tùy chọn miễn là mã chung được sử dụng lại. Lấy bất kỳ mô hình nào khác như nhà máy trừu tượng / hỗn hợp - sự kế thừa đó thực sự phục vụ một mục đích.

Quy tắc của ngón tay cái: Đừng sử dụng tính kế thừa chỉ để giữ một nhóm các lớp với nhau. Sử dụng nó bất cứ nơi nào nó có ý nghĩa. Trong ví dụ giả định, người dùng của lớp con đang làm quá tải phương thức1 phải có một số lý do nghiêm trọng để làm như vậy. (OTOH, tôi cũng muốn có một cách để ngăn quá tải một số chức năng nhất định - để tránh điều này). Và trước khi sử dụng lớp con đó, bạn cũng nên biết về vấn đề này. Nhưng một mô hình chiến lược thuần túy sẽ không làm điều đó - nếu có, tài liệu rất tốt.

Nếu bạn đưa ra ví dụ mã thực tế của mình, có thể mọi người ở đây có thể xem lại nó cho bạn.


Cảm ơn câu trả lời, tôi vẫn nghĩ rằng kế thừa chủ yếu được sử dụng để chia sẻ logic, tôi đã luôn áp dụng nguyên tắc khi mã có thể được chia sẻ, chúng nên được chia sẻ và khi bạn quyết định không chia sẻ chúng, bạn sẽ thấy nó trở nên rõ ràng hơn .
chiến thuật

Ví dụ mã quá lớn, tôi có hơn 30 lớp cần được sắp xếp.
chiến thuật

18

Vâng, nó có thể rất khó khăn ... và bạn đang ở trong một công ty tốt.

Joshua Bloch trong Java hiệu quả nắm bắt một số vấn đề về thừa kế và khuyến nghị nên ưu tiên thành phần thay vì kế thừa.

Kế thừa là một cách mạnh mẽ để đạt được việc sử dụng lại mã, nhưng nó không phải luôn là công cụ tốt nhất cho công việc. Được sử dụng không phù hợp, nó dẫn đến phần mềm dễ vỡ. Việc sử dụng tính kế thừa trong một gói là an toàn, trong đó lớp con và lớp triển khai siêu lớp nằm dưới sự kiểm soát của cùng các lập trình viên. Cũng an toàn khi sử dụng tính kế thừa khi mở rộng các lớp được thiết kế và ghi lại cụ thể cho phần mở rộng (Mục 15). Kế thừa từ các lớp bê tông thông thường qua các ranh giới gói, tuy nhiên, là nguy hiểm. Xin nhắc lại, cuốn sách này sử dụng từ "thừa kế" có nghĩa là kế thừa thực hiện (khi một lớp mở rộng một lớp khác). Các vấn đề được thảo luận trong mục này không áp dụng cho kế thừa giao diện (khi một lớp thực hiện một giao diện hoặc khi một giao diện mở rộng giao diện khác).

Không giống như gọi phương thức, kế thừa phá vỡ đóng gói. Nói cách khác, một lớp con phụ thuộc vào các chi tiết triển khai của siêu lớp của nó cho chức năng phù hợp của nó. Việc triển khai của lớp cha có thể thay đổi từ phát hành sang phát hành và nếu có, lớp con có thể bị phá vỡ, mặc dù mã của nó chưa bị chạm. Kết quả là, một lớp con phải phát triển song song với siêu lớp của nó, trừ khi các tác giả của siêu lớp đã thiết kế và ghi lại nó một cách cụ thể cho mục đích mở rộng.


+1. Và lưu ý rằng ý tưởng đã tồn tại lâu hơn rất nhiều và phổ biến rộng rãi hơn nhiều so với chỉ Khối và Java hiệu quả ; thấy ở đây .
Josh Kelley

1

Tôi nghĩ rằng câu hỏi của bạn quá mơ hồ và chung chung. Những gì bạn mô tả nghe có vẻ như là một vấn đề phức tạp, có hoặc không có sự kế thừa. Vì vậy, IMHO hoàn toàn bình thường khi bạn đang vật lộn với nó.

Tất nhiên trong một số trường hợp, thừa kế không phải là công cụ tốt nhất cho công việc (tham chiếu bắt buộc để "ưu tiên thành phần hơn thừa kế" ;-). Nhưng khi sử dụng cẩn thận, tôi thấy nó hữu ích. Từ mô tả của bạn, thật khó để quyết định liệu thừa kế có phải là công cụ phù hợp cho trường hợp của bạn hay không.


Đó là một câu hỏi mơ hồ và ý định là thu thập một số ý tưởng để dễ dàng đưa ra quyết định thiết kế. Tôi đã chuyển đổi giữa thành phần và kế thừa qua lại.
chiến thuật

1

nếu các phương thức của bạn quá phức tạp, nó làm cho các lớp con khóc

làm cho phương pháp của bạn làm một điều cụ thể


1

Kế thừa thành phần thu hẹp các vectơ thay đổi.

Hãy tưởng tượng giải pháp của bạn được triển khai và bạn sử dụng AClass ở 300 vị trí khác nhau của cơ sở mã và bây giờ có yêu cầu thay đổi 45 lệnh gọi phương thức1 () để phương thức do3 () được gọi thay vì do2 ().

Bạn có thể triển khai do3 () trên IAnInterface (hy vọng tất cả các cài đặt của giao diện có thể xử lý phương thức do3 () một cách dễ dàng) và tạo BClass gọi do1 () hoặc do3 () trong lệnh gọi phương thức1 () và thay đổi 45 tham chiếu đến cả AClass và phụ thuộc được tiêm.

bây giờ hình ảnh thực hiện thay đổi tương tự cho do4 (), do5 (), ...

Hoặc bạn có thể làm như sau ...

interface IFactoryCriteria {
    protected boolean check();
}

public final class DoerFactory() {
    protected final IAnInterfaceThatDoes createDoer( final IFactoryCriteria criteria ) {
        return criteria.check() ? new Doer1() : new Doer2();
    }
}

interface IAnInterfaceThatDoes {
    abstract void do();
}

public final class AClass implements IFactoryCriteria {
    private DoerFactory factory;
    public AClass(DoerFactory factory)
    {
        this.factory = factory;
    }

    protected void method1() {
        this.factory.createDoer( this ).do();
    }

    protected boolean check() {
        return true;//or false
    }
}

Bây giờ bạn chỉ cần thực hiện một nhà máy thứ hai trả về Doer1 hoặc Doer2 và thay đổi tiêm tại nhà máy ở 45 nơi. AClass không thay đổi, IAnInterface không thay đổi và bạn có thể dễ dàng xử lý nhiều thay đổi hơn như do4 (), do5 ().


1
+1 Tôi thích ý nghĩa này của vấn đề đối với đề xuất của Ed. Tôi nghĩ rằng điều này làm cho vấn đề SRP nhiều hơn cũng như hạn chế các phụ thuộc mà AClass có. Tôi đoán câu hỏi chính của tôi sẽ là ưu và nhược điểm của việc chuyển IAnInterfaceThatDoes vào AClass thay vì lớp nhà máy. Ví dụ: điều gì xảy ra nếu logic xác định đúng IAnInterfaceThatDoes nằm ngoài phạm vi của nhà máy?
dreza

Tôi đã tạo cho nhà máy sự phụ thuộc chỉ để giảm thiểu phơi nhiễm của IAnInterfaceThatDoes. Nếu mã cuộc gọi cần quyền truy cập vào giao diện đó thì bạn chắc chắn có thể di chuyển nó ra. Tôi không biết về tình huống mà tôi muốn chuyển giao việc tạo giao diện ra khỏi nhà máy. Tôi sẽ nói rằng nếu điều đó phải thay đổi thì việc thực hiện giao diện DoerFactoryI với phương thức createDoer được đưa ra sẽ là một cách khác. Sau đó, bạn có thể triển khai một nhà máy bằng cách sử dụng khung DI hoặc một cái gì đó.
Heath Lilley

0

Tại sao không làm:

interface IAnInterface {
    abstract void do1();
    abstract void do2();
}

public final class AClass {
    private IAnInterface Dependent;
    public AClass(IAnInterface dependent)
    {
        this.Dependent = dependent;
    }

    protected void method1() {
        if(check()) {
            this.Dependent.do1();
        } else {
            this.Dependent.do2();
        }
    }
}

(Tôi nghĩ rằng trận chung kết tương đương với "niêm phong" trong C #, ai đó vui lòng sửa cho tôi nếu cú ​​pháp đó không chính xác).

Sự phụ thuộc tiêm / IoC có nghĩa là bạn chỉ có thể kế thừa các phương pháp mà bạn thực sự muốn mọi người có thể thay đổi, mà tôi nghĩ rằng giải quyết vấn đề của bạn. Thêm vào đó, nó không có vẻ như lớp chính thực sự làm bất cứ điều gì do1 và do2 là gì, hơn nữa nó ủy thác các cuộc gọi vào các lớp con của nó dựa trên một điều kiện: bạn đang phá vỡ mối quan tâm!

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.