2010-05-25 2 views
19

Hãy nói rằng tôi có một lớp JavaChạy một phương pháp sau các nhà xây dựng của bất kỳ lớp được thừa kế

abstract class Base { 
    abstract void init(); 
    ... 
} 

và tôi biết mỗi lớp được thừa kế sẽ phải gọi init() sau khi nó được xây dựng. Tôi có thể, tất nhiên, chỉ cần gọi nó trong nhà xây dựng các lớp học có nguồn gốc:

class Derived1 extends Base { 
    Derived1() { 
     ... 
     init(); 
    } 
} 

class Derived2 extends Base { 
    Derived2() { 
     ... 
     init(); 
    } 
} 

nhưng điều này phá vỡ 'không lặp lại chính mình' nguyên tắc chứ không phải xấu (và có sẽ có nhiều lớp con của Base). Tất nhiên, các cuộc gọi init() không thể đi vào các nhà xây dựng Base(), vì nó sẽ được thực hiện quá sớm.

Bất kỳ ý tưởng nào về cách vượt qua vấn đề này? Tôi cũng rất vui khi thấy giải pháp Scala.

UPDATE: Đây là một phiên bản generic của cách tiếp cận Factory Method:

interface Maker<T extends Base> { 
    T make(); 
} 

class Base { 
    ... 
    static <T extends Base> T makeAndInit(Maker<T> maker) { 
     T result = maker.make(); 
     result.init(); 
     return result; 
    } 
} 

UPDATE 2: Câu hỏi này về cơ bản là "làm thế nào để bạn sử dụng Template Method cho nhà xây dựng"? Và câu trả lời có vẻ là, "Bạn có thể, nhưng đó là một ý tưởng tồi". Vì vậy, tôi có thể làm một nhà máy mẫu (Template Method + Abstract Factory) để thay thế.

+2

Nếu init() là trừu tượng trong lớp cơ sở, thì tại sao mọi lớp dẫn xuất phải gọi nó? Điều gì sẽ xảy ra nếu họ không? –

+1

Tại sao bạn không gọi init() trong hàm tạo của lớp 'Base'? – aioobe

+0

@Skip Head: chúng sẽ không được khởi tạo đúng cách. –

Trả lời

10

Điều gì sẽ xảy ra trong init()? Nó có khả năng là một thiết kế tốt hơn có thể loại bỏ hoàn toàn phương pháp, hoặc ít nhất là thư giãn yêu cầu mà nó thực thi sau khi hàm tạo của lớp con. Hãy chắc chắn rằng init() không làm cho đối tượng đang được xây dựng hiển thị với bất kỳ chủ đề nào khác trước khi hàm tạo hoàn tất, vì điều đó tạo ra các lỗi đồng thời.

Là một (xấu xí) thay thế, một phương pháp trừu tượng có thể được thực hiện bởi phụ lớp học như một pseudo-constructor:

abstract class Base { 
    Base() { 
    ctor(); 
    init(); 
    } 
    abstract void ctor(); 
    abstract void init(); 
} 
+0

Thực ra, điều này có vẻ tốt hơn so với các giải pháp khác cho tôi (xấu xí, nhưng ít xấu xí hơn các lựa chọn thay thế). –

10

Tránh điều này. Nếu bạn làm điều đó, bất kỳ lớp học nào mở rộng lớp học DerivedX của bạn cũng có thể quyết định cũng gọi init() do đó để đối tượng ở trạng thái không nhất quán.

Một cách tiếp cận là để cho phương thức init() được khách hàng lớp của bạn gọi thủ công. Có một trường initialized và ném IllegalStateExcepion nếu bất kỳ phương thức nào yêu cầu khởi tạo được gọi mà không có nó.

Một cách tiếp cận tốt hơn sẽ được sử dụng một phương pháp nhà máy tĩnh thay vì cấu trúc:

public Derived2 extends Base { 
    public static Derived2 create() { 
     Derived2 instance = new Dervied2(); 
     instance.init(); 
     return instance; 
    } 
} 

Cập nhật: Khi bạn đề nghị trong bản cập nhật, bạn có thể vượt qua Builder đến một phương pháp nhà máy tĩnh, mà sẽ gọi init() trên ví dụ. Nếu các lớp con của bạn là rất ít, tôi nghĩ rằng đây là một overcomplication, mặc dù.

+1

Điều này vẫn còn lại cho tôi với việc gọi 'init()' một cách riêng biệt trong phương thức factory cho 'Derived1',' Derived2', vv –

+0

có, phiên bản builder của bạn là một bổ sung tốt. – Bozho

+0

Trả lời câu cuối cùng của bạn, từ câu hỏi: "(và sẽ có nhiều lớp con của Base)". –

6

Ngoài số recommendation của Bozho, một thùng chứa ứng dụng rất tuyệt vời cho tác vụ.

Đánh dấu phương thức init() với chú thích javax.annotation.PostConstruct và EJB hoặc Spring container được cấu hình đúng sẽ thực thi phương pháp sau khi tiêm phụ thuộc xong, nhưng trước khi ứng dụng có thể sử dụng ứng dụng.

Một phương pháp dụ:

@PostConstruct 
public void init() { 
    // logic.. 
} 

Trong một ứng dụng doanh nghiệp bạn có thể mở nguồn lực để ví dụ như hệ thống tập tin trong phương pháp init(). Việc khởi tạo này có thể ném các ngoại lệ và không nên được gọi từ một hàm tạo.

+0

Hết sức tò mò. Tại sao một người không nên ném từ các nhà xây dựng nhưng từ 'init()' giống như các phương thức? – Tarrasch

1

Nếu bạn là bất lợi cho việc sử dụng các nhà máy đối với một số lý do, bạn có thể sử dụng các thủ thuật sau đây:

trait RunInit { 
    def init():Unit 
    init() 
} 

class Derived1 extends Base with RunInit { 
    def init() = println("INIT'ing!") 
} 

này sẽ chạy init() trước khi Derived1 constructor/body.

+1

Vấn đề chính là tôi muốn 'init' chạy _after_ hàm tạo/cơ thể' Derived1'. –

2

nếu Java có, chúng tôi sẽ không thấy tất cả các cuộc gọi phương thức init() này trong tự nhiên.

"trình tạo con xung quanh với thứ gì đó" - điều đó không thể được thực hiện bằng java thuần túy. Quá xấu, bởi vì có thể có các ứng dụng rất thú vị, đặc biệt là với lớp ẩn dụ + khối khởi tạo thể hiện.

nhà máy và vùng chứa - chúng có thể hữu ích khi người bản xứ new không thực hiện công việc; nhưng đó là tầm thường và nhàm chán, và sẽ không làm việc với các lớp học vô danh.