2010-04-06 31 views
8

Tôi có một vài lớp học như thể hiện ở đâycó được khối khởi tạo tĩnh để chạy trong một java mà không cần nạp lớp

public class TrueFalseQuestion implements Question{ 
    static{ 
     QuestionFactory.registerType("TrueFalse", "Question"); 
    } 
    public TrueFalseQuestion(){} 
} 

...

public class QuestionFactory { 

static final HashMap<String, String > map = new HashMap<String,String>(); 

public static void registerType(String questionName, String ques) { 
    map.put(questionName, ques); 
    } 
} 



public class FactoryTester { 
    public static void main(String[] args) { 
     System.out.println(QuestionFactory.map.size()); 
     // This prints 0. I want it to print 1 
    } 
} 

Làm thế nào tôi có thể thay đổi TrueFalseQuestion lớp để các phương pháp tĩnh luôn luôn chạy để tôi nhận được 1 thay vì 0 khi tôi chạy phương pháp chính của tôi? Tôi không muốn có bất kỳ thay đổi nào trong phương pháp chính.

Tôi đang thực sự cố gắng triển khai các mẫu nhà máy nơi các lớp con đăng ký với nhà máy nhưng tôi đã đơn giản hóa mã cho câu hỏi này.

Trả lời

5

Để đăng ký lớp TrueFalseQuestion với nhà máy, trình khởi chạy tĩnh của nó cần phải được gọi. Để thực thi trình khởi tạo tĩnh của lớp TrueFalseQuestion, lớp cần được tham chiếu hoặc cần phải được nạp bởi sự phản chiếu trước khi gọi QuestionFactory.map.size(). Nếu bạn muốn rời khỏi phương thức main, bạn phải tham chiếu hoặc tải phương thức đó bằng cách phản chiếu trong bộ khởi tạo tĩnh QuestionFactory. Tôi không nghĩ rằng đây là một ý tưởng tốt, nhưng tôi sẽ chỉ trả lời câu hỏi của bạn :) Nếu bạn không nhớ QuestionFactory biết về tất cả các lớp thực hiện Question để xây dựng chúng, bạn chỉ có thể tham khảo trực tiếp hoặc tải chúng qua phản ánh. Một cái gì đó như:

public class QuestionFactory { 

static final HashMap<String, String > map = new HashMap<String,String>(); 

static { 
    this.getClassLoader().loadClass("TrueFalseQuestion"); 
    this.getClassLoader().loadClass("AnotherTypeOfQuestion"); // etc. 
} 

public static void registerType(String questionName, String ques) { 
    map.put(questionName, ques); 
    } 
} 

Khai chắn map 's và xây dựng là trước khi khối static. Nếu bạn không muốn QuestionFactory có bất kỳ kiến ​​thức nào về việc triển khai Question, bạn sẽ phải liệt kê chúng trong tệp cấu hình được tải bởi QuestionFactory. Cách duy nhất khác (có thể điên rồ) mà tôi có thể nghĩ đến, sẽ xem xét toàn bộ classpath cho các lớp thực hiện Question :) Điều đó có thể hoạt động tốt hơn nếu tất cả các lớp thực hiện Question được yêu cầu phải thuộc cùng một gói - LƯU Ý: tôi không ủng hộ giải pháp này;)

lý do tôi không nghĩ làm bất kỳ điều này trong initializer tĩnh QuestionFactory là bởi vì các lớp học như TrueFalseQuestion có initializer tĩnh của mình mà các cuộc gọi vào QuestionFactory, mà tại thời điểm đó là một đối tượng được xây dựng không đầy đủ, chỉ là yêu cầu rắc rối. Có một tệp cấu hình chỉ đơn giản liệt kê các lớp mà bạn muốn QuestionFactory để biết cách xây dựng, sau đó đăng ký chúng trong hàm tạo của nó là một giải pháp tốt, nhưng điều đó có nghĩa là thay đổi phương thức main của bạn.

+0

Để tham khảo, thiết kế này đến để tránh sự cần thiết của nhà máy biết về các lớp câu hỏi (ở đây: http : //stackoverflow.com/questions/2582357/augment-the-factory-pattern-in-java). –

+1

Tôi không thấy câu hỏi đó. Một cái gì đó cần phải biết về việc thực hiện giao diện Câu hỏi, cho dù đó là nhà máy trực tiếp, hoặc thông qua một số loại tập tin cấu hình. Cách khác duy nhất là, như tôi đã nói, đi qua tất cả các lớp trên classpath và xem họ có thực hiện Câu hỏi hay không. Cũng xin lưu ý rằng hãy báo trước về việc có một nhà máy được xây dựng không đầy đủ trong câu trả lời của tôi ở trên. Nó có thể hoạt động ngay bây giờ, nhưng không có sự bảo đảm về trạng thái của đối tượng trong tương lai (hoặc thậm chí trong hiện tại, trên các nền tảng). –

6

Bạn có thể gọi:

Class.forName("yourpackage.TrueFalseQuestion"); 

này sẽ được tải lớp mà không cần bạn thực sự chạm vào nó, và sẽ thực hiện khối initializer tĩnh.

+0

nơi tôi gọi phương thức này? Mỗi lớp là một tập tin khác nhau. – randomThought

+0

trước khi bạn thực sự cần bộ khởi tạo 'TrueFalseQuestion' để chạy. Trong ví dụ của bạn - ở đầu phương thức chính – Bozho

+0

Có cách nào tôi có thể đạt được điều này mà không thay đổi bất cứ thứ gì trong phương thức chính vì điều này sẽ tạo ra một loại phụ thuộc của lớp này trong phương thức chính mà tôi muốn tránh. – randomThought

3

Trình khởi tạo tĩnh cho lớp không thể thực thi nếu lớp không bao giờ được nạp.

Vì vậy, bạn cần phải tải tất cả các lớp chính xác (sẽ khó, vì bạn không biết tất cả chúng ở thời gian biên dịch) hoặc loại bỏ yêu cầu cho bộ khởi tạo tĩnh.

Một cách để thực hiện việc sau là sử dụng ServiceLoader.

Với ServiceLoader bạn chỉ cần đặt một tệp vào META-INF/services/package.Question và liệt kê tất cả các triển khai. Bạn có thể có nhiều tệp như vậy, một tệp cho mỗi tệp .jar. Bằng cách này, bạn có thể dễ dàng gửi thêm các triển khai Question riêng biệt từ chương trình chính của mình.

Trong QuestionFactory bạn có thể sau đó chỉ cần sử dụng ServiceLodaer.load(Question.class) để có được một ServiceLoader, mà thực hiện Iterable<Question> và có thể được sử dụng như thế này:

for (Question q : ServiceLoader.load(Question.class)) { 
    System.out.println(q); 
} 
2

Để chạy initializers tĩnh, các lớp học cần phải được nạp. Để điều này xảy ra, lớp "chính" của bạn phải phụ thuộc (trực tiếp hoặc gián tiếp) vào các lớp, hoặc phải trực tiếp hoặc gián tiếp khiến chúng được nạp động; ví dụ. sử dụng Class.forName(...).

Tôi nghĩ bạn đang cố gắng tránh các phụ thuộc được nhúng trong mã nguồn của bạn. Vì vậy, các phụ thuộc tĩnh là không thể chấp nhận được và các cuộc gọi tới Class.forName(...) với các tên lớp được mã hóa cứng cũng không thể chấp nhận được.

này để lại cho bạn hai lựa chọn:

  • Viết một số mã lộn xộn để lặp qua các tên tài nguyên trong một số gói, và sau đó sử dụng Class.forName(...) để tải những nguồn lực mà trông giống như lớp học của bạn. Cách tiếp cận này là khó khăn nếu bạn có một classpath phức tạp, và không thể nếu classpath hiệu quả của bạn bao gồm một URLClassLoader với một URL từ xa (ví dụ).

  • Tạo tệp (ví dụ: tài nguyên bộ nạp lớp) chứa danh sách tên lớp bạn muốn tải và viết một số mã đơn giản để đọc tệp và sử dụng Class.forName(...) để tải từng tệp.

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