2009-03-19 29 views
19

Có quy tắc CheckstyleDesignForExtension. Nó nói: nếu bạn có một phương pháp công cộng/được bảo vệ mà không phải là trừu tượng cũng không cuối cùng cũng không rỗng nó không phải là "được thiết kế cho phần mở rộng". Đọc số description for this rule on the Checkstyle page để biết lý do.Thiết kế cách mở rộng

Hãy tưởng tượng trường hợp này. Tôi có một lớp trừu tượng trong đó xác định một số lĩnh vực và một phương thức validate cho những lĩnh vực:

public abstract class Plant { 
    private String roots; 
    private String trunk; 

    // setters go here 

    protected void validate() { 
     if (roots == null) throw new IllegalArgumentException("No roots!"); 
     if (trunk == null) throw new IllegalArgumentException("No trunk!"); 
    } 

    public abstract void grow(); 
} 

Tôi cũng có một lớp con của thực vật:

public class Tree extends Plant { 
    private List<String> leaves; 

    // setters go here 

    @Overrides 
    protected void validate() { 
     super.validate(); 
     if (leaves == null) throw new IllegalArgumentException("No leaves!"); 
    } 

    public void grow() { 
     validate(); 
     // grow process 
    } 
} 

Tiếp theo Checkstyle cai trị Plant.validate (phương pháp) không được thiết kế để mở rộng. Nhưng làm thế nào để thiết kế cho phần mở rộng trong trường hợp này?

+5

Bạn không nên ném một IllegalArgumentException trong một phương pháp mà không có đối số. .. – markt

+4

@markt Hãy giả vờ nó là IllegalStateException vì lợi ích của "đối số" :) –

Trả lời

17

Quy tắc là khiếu nại bởi vì có thể cho một lớp phát sinh (mở rộng) thay thế hoàn toàn chức năng bạn đã cung cấp mà không nói cho bạn biết về điều đó. Đó là dấu hiệu mạnh mẽ cho thấy bạn chưa xem xét đầy đủ cách loại có thể được mở rộng. Những gì nó muốn bạn làm thay vào đó là một cái gì đó như thế này:

public abstract class Plant { 
    private String roots; 
    private String trunk; 

    // setters go here 

    private void validate() { 
     if (roots == null) throw new IllegalArgumentException("No roots!"); 
     if (trunk == null) throw new IllegalArgumentException("No trunk!"); 
     validateEx(); 
    } 

    protected void validateEx() { } 

    public abstract void grow(); 
} 

Lưu ý rằng bây giờ ai đó vẫn có thể cung cấp mã xác nhận riêng của họ, nhưng họ không thể thay thế mã trước bằng văn bản của bạn. Tùy thuộc vào cách bạn có ý định sử dụng phương pháp validate, bạn cũng có thể đặt nó ở chế độ công khai thay vào đó.

+1

Tôi thấy điểm, nhưng tôi vẫn còn thiếu một cách để thực hiện phương thức validate() "tốt" và "thực tế" cùng một lúc. Nếu Tree thực hiện một validateEx() và các cuộc gọi validate(), một lớp mở rộng Tree (cho phép nói Forest) sẽ không thể ghi đè validateEx(). Solution? Implement validateExEx()? –

+0

Sử dụng tên tốt hơn, sau đó. Có thể ValidateTreeEx(). –

+2

Chắc chắn - họ không thể thay thế mã của bạn .. nhưng làm cách nào để xác thực() có thể được gọi? – markt

1

Mặc dù câu trả lời của Joel Coehoorn giải thích cách khắc phục sự cố cụ thể do OP đưa ra, tôi muốn đề xuất một cách tiếp cận có cái nhìn rộng hơn về 'cách thiết kế cho phần mở rộng?' Như OP chỉ ra trong một trong những nhận xét của ông, giải pháp đã cho không mở rộng tốt với độ sâu thừa kế (lớp) đang phát triển. Ngoài ra, dự đoán trong lớp cơ sở cần phải xác nhận các lớp con có thể (validateTreeEx()) là vấn đề vì lý do hiển nhiên.

Đề xuất: Kiểm tra thuộc tính của nhà máy tại thời điểm xây dựng và xóa validate() hoàn toàn (cùng với những người đặt chỗ có thể; xem thêm http://www.javaworld.com/article/2073723/core-java/why-getter-and-setter-methods-are-evil.html). Mã ban đầu cho thấy rằng validate() là một bất biến, điều này phải đúng trước mỗi hoạt động grow(). Tôi nghi ngờ rằng thiết kế này là cố ý. Nếu không có hoạt động, mà có thể 'phá vỡ' một nhà máy sau khi xây dựng, không cần phải kiểm tra lại hiệu lực hơn và hơn nữa.

Đi xa hơn nữa, tôi sẽ đặt câu hỏi về tính đúng đắn của thiết kế kế thừa ban đầu. Nếu không có các hoạt động bổ sung (có thể đa hình), Tree chỉ cần sử dụng lại một số thuộc tính của Nhà máy. Tôi giữ quan điểm mạnh mẽ, sự kế thừa lớp đó không nên được sử dụng để tái sử dụng mã. Josh Bloch có điều này để nói (từ hiệu quả Java, 2nd Edition, chương 4):

Nếu bạn sử dụng thừa kế khi bố cục thích hợp, bạn không cần thiết vạch trần chi tiết triển khai. API kết quả liên kết bạn với triển khai ban đầu, mãi mãi hạn chế hiệu suất của lớp học của bạn. Nghiêm trọng hơn, bằng cách phơi bày nội bộ bạn để cho ứng dụng truy cập trực tiếp vào chúng.

Ngoài ra kiểm tra 'Mục 17: Thiết kế và tài liệu cho các thừa kế hoặc nếu không cấm nó' (còn chương 4 của cuốn sách tương tự)

+1

Cảm ơn Horst, mặc dù câu trả lời của bạn thảo luận về thiết kế mã mẫu chứ không phải câu hỏi ban đầu: cách thiết kế để mở rộng. Nó thực sự chỉ là mẫu mã, không hoàn hảo có thể. –

Các vấn đề liên quan