2011-08-06 41 views
9

Tôi đang sử dụng Google Guice để tiêm phụ thuộc. Giả sử tôi có như sau:Tiêm phụ thuộc dựa trên điều kiện

public interface Payment { 
    public void pay(); 
} 

public class PaymentCardImpl implements Payment { 
    public void pay() { 
     System.out.println("I pay with a card"); 
    } 
} 

public class PaymentCashImpl implements Payment { 
    public void pay() { 
     System.out.println("I pay cash"); 
    } 
} 

public class Order { 

    private Payment payment; 

    @Inject 
    public Order(Payment payment){ 
     this.payment=payment; 
    } 

    public void finishOrder(){ 
     this.payment.pay(); 
    } 
} 

Sau ngày từ này, là một module rất đơn giản để ràng buộc, như vậy:

public class MyModule extends AbstractModule { 
    @Override 
    protected void configure() { 
     bind(Payment.class).to(PaymentCashImpl.class); 
    } 
} 

Như bạn có thể thấy, một trường hợp Payment được tiêm vào các nhà xây dựng theo thứ tự . Điều này được thực hiện trong lớp học MyModule và tổng thể là thực sự mát mẻ.

vẻ chính của tôi như:

public static void main(String[] args) { 
    MyModule module = new MyModule(); 
    Injector injector = Guice.createInjector(module); 
    Order order = injector.getInstance(Order.class); 
    order.finishOrder(); 
} 

Những gì tôi không thể nhìn thấy tuy nhiên, là làm thế nào tôi có thể kết hợp một số cách để có điều kiện ràng buộc hoặc là một PaymentCardImplhoặc một trường hợp PaymentCashImpl để các nhà xây dựng Order.

Ví dụ: thứ tự là đơn hàng 'trực tuyến'. Sau đó tôi sẽ cần điều này:

bind(Payment.class).to(PaymentCardImpl.class); 

Cách tốt nhất để làm điều này là gì? Tôi mới vào tiêm phụ thuộc.

+0

Khi nào bạn biết _which_ thực hiện bạn cần? –

Trả lời

5

Bạn có thể chú thích cái nào bạn muốn tiêm. Nếu bạn làm một ràng buộc có tên, nó sẽ giải quyết vấn đề.

Xem dưới đây:

bind(Payment.class).annotatedWith(Names.named("Card")).to(PaymentCardImpl.class); 

bind(Payment.class).annotatedWith(Names.named("Cash")).to(PaymentCashImpl.class); 

Sau đó, nơi bạn muốn tiêm bạn làm:

@Named("Cash") Payment payment 

hay:

@Named("Card") Payment payment 
+0

+1 Tôi sẽ đánh dấu đây là câu trả lời hay nhất vì đây là cách tiếp cận mà tôi đã sử dụng cuối cùng. Nó mang đến cho tôi sự linh hoạt lớn nhất. – Joeblackdev

8

Tôi biết lý do bạn muốn thực hiện việc này. Nhưng tôi sẽ không trộn lẫn mã xây dựng (đó là cấu hình tiêm phụ thuộc) với logic nghiệp vụ. Nếu bạn làm điều đó, logic kinh doanh của bạn có thể không hiểu được nữa. Và với tôi có vẻ như, việc tiêm điều kiện của bạn phụ thuộc vào tình huống, tức là đầu vào từ giao diện người dùng.

Vậy tại sao không chỉ tiêm cả hai và làm cho điều kiện rõ ràng? Tôi thích điều đó. Ứng dụng ví dụ:

public class MyModule extends AbstractModule { 
    @Override 
    protected void configure() { 
    } 

    public static void main(String[] args) { 
    MyModule module = new MyModule(); 
    Injector injector = Guice.createInjector(module); 
    Order order = injector.getInstance(Order.class); 
    order.finishOrder(PaymentMethod.CARD); 
    } 
} 

public class PaymentProvider { 
    private final Payment cashPayment, cardPayment; 

    @Inject 
    public PaymentProvider(CardPayment cardPayment, CashPayment cashPayment) { 
    this.cardPayment = cardPayment; 
    this.cashPayment = cashPayment; 
    } 

    public Payment getPaymentByMethod(PaymentMethod method) { 
    switch (method) { 
     case CARD: 
     return cardPayment; 
     case CASH: 
     return cashPayment; 
     default: 
     throw new IllegalArgumentException("Unkown payment method: " + method); 
    } 
    } 
} 

public enum PaymentMethod { CASH, CARD } 

public class Order { 
    private final PaymentProvider paymentProvider; 

    @Inject 
    public Order(PaymentProvider paymentProvider) { 
    this.paymentProvider = paymentProvider; 
    } 

    public void finishOrder(PaymentMethod method) { 
    paymentProvider.getPaymentByMethod(method).pay(); 
    } 
} 

Vẫn còn thực hành của riêng bạn: Công cụ thanh toán. Bạn không cần bất kỳ mã Guice nào trong đó. Phần còn lại được thực hiện bởi Guice automagically. Nếu bạn bắt đầu sử dụng các giao diện, bạn sẽ bắt đầu sử dụng các ràng buộc như mô tả ở đây: http://code.google.com/p/google-guice/wiki/GettingStarted. Nhưng nếu bạn không có bất kỳ giao diện nào, việc xây dựng được thực hiện mà không cần bất kỳ cấu hình nào. Chú thích @Inject là đủ.

Tất nhiên đây chỉ là một thiết kế mẫu. Nhưng nó cho thấy cách dễ dàng để thiết lập một ứng dụng Java được tách riêng độc đáo với Guice.

+0

Bạn không thể tiêm cả hai. Nếu tôi thử điều này, tôi nhận được một ngoại lệ từ Guice. Tôi chỉ có thể làm một ràng buộc. Bạn có thể cung cấp một ví dụ về ý của bạn? Tôi nghĩ rằng tôi có thể thiếu điểm Dependency Injection ở đây. Tôi đang nghĩ đến thời gian chạy. – Joeblackdev

+0

Nó không phải là một trừu tượng tốt nhưng cách ngây thơ nhất sẽ được điều này (bài chỉnh sửa) –

+0

Cảm ơn ví dụ. Khi bạn nói "tiêm tất cả các phương thức thanh toán có thể vào nó", bạn có ý nghĩa gì ở đây? Cảm ơn – Joeblackdev

10

Chèn phụ thuộc rất hữu ích khi tạo đối tượng kiểu service. Những có các đặc điểm sau: -

  • nhiều triển khai có nhiều khả năng,
  • nặng trên hành vi,
  • trạng thái nội bộ được giới hạn phụ thuộc của họ và họ nói chung là không thể thay đổi
  • sẽ ánh xạ một diễn viên trong thế giới thực (ví dụ như thủ quỹ), thay vì một điều

Dựa trên điều này, Payment là một đối tượng dịch vụ. Tôi sẽ đổi tên nó PaymentService để phân biệt nó với mục sổ kế toán bạn có thể lưu trữ về một khoản thanh toán (mà sẽ là một đối tượng giá trị).

Ví dụ của bạn không hiển thị nhiều về lớp Order, nhưng tôi cho rằng nó sẽ giữ thông tin như một số chi tiết đơn hàng, địa chỉ giao hàng và tổng số tiền. Đây là đối tượng value. Nó đại diện cho một thứ trong lĩnh vực kinh doanh.

Đối tượng giá trị nặng trên trạng thái và bật hành vi. Có thể triển khai nhiều lần, nhưng bạn ít có khả năng muốn thay thế một triển khai với một triển khai khác.

Đối tượng giá trị không được tạo bởi khung tiêm phụ thuộc của bạn. Chúng được tạo bởi mã logic nghiệp vụ của bạn. Trong ví dụ của bạn, bạn đang tạo tất cả các đối tượng sử dụng Guice. Tôi hy vọng trong thực tế bạn sẽ cần phải tạo ra Order tại thời gian chạy dựa trên đầu vào của người dùng.

Đối tượng dịch vụ có thể phụ thuộc vào đối tượng giá trị, nhưng không bao giờ theo cách khác. Tôi nghĩ bạn nên tìm cách để thực hiện:

checkoutService.payfor(order, method);

và không order.finishOrder(method)

Trong lớp CheckoutService, bạn có thể chọn một approriate PaymentService và vượt qua order với nó. Số CheckoutService sẽ lấy làm đối số hàm tạo PaymentCardPaymentService (tương đương với PaymentCardImpl của bạn) và CashPaymentService (tương đương với PaymentCashImpl của bạn).

+0

Câu trả lời hay, cảm ơn vì điều này. Trong kịch bản thực tế của tôi, tôi đang sử dụng dịch vụ SOAP không có bất kỳ trạng thái nội bộ nào, do đó các đặc tính mà bạn phác thảo cộng hưởng với những gì tôi cần. Tuy nhiên, bạn đã cho tôi một số mẹo hay! Nếu bạn có thể cung cấp một ví dụ làm việc về những gì bạn phác thảo, điều này cũng sẽ rất hữu ích. Cảm ơn – Joeblackdev

+0

Dịch vụ SOAP thực sự là gì? Đó có phải là việc triển khai thanh toán không? Họ sẽ không có một số nhà nước như URL, cấu hình proxy, vv? Tôi không biết Guice nên tôi không thể đưa ra một ví dụ nhưng tôi nghĩ bạn cần phải lên lớp ngay trước và sau đó các câu trả lời khác là hữu ích. –

3

Sử dụng tiện ích mở rộng MapBinder bạn có thể được tiêm một số liên kết có trong số Map. Sau đó, nó là một vấn đề thực hiện mà một trong những sử dụng.

Bạn sẽ cần một chìa khóa, trong trường hợp của bạn có thể là một String hoặc Enumeration và ràng buộc tất cả Payment triển khai cho các phím thích hợp:

public class MyModule extends AbstractModule { 
    @Override 
    protected void configure() { 
    // this one aggregates all bindings and could be further annotated if you 
    // need several maps with same key/value types 
    MapBinder<String, Payment> mapBinder = MapBinder.newMapBinder(binder(), 
     String.class, Payment.class); 

    // simple binding of PaymentCashImpl to 'cash' key 
    mapBinder.addBinding("cash").to(PaymentCashImpl.class); 

    // you can scope during binding or using @Singleton annotation on implementations 
    mapBinder.addBinding("card").to(PaymentCardImpl.class).in(Singleton.class); 
    } 
} 

Sau đó, trong Order bạn được tiêm với toàn bộ bản đồ và quyết định thực hiện để sử dụng:

public class Order { 
    @Inject 
    Map<String, Provider<Payment>> paymentProcessors; 

    public void finishOrder(String paymentMethod) { 
    if (!paymentProcessors.containsKey(paymentMethod)) { 
     throw new IllegalArgumentException("Unknown payment method " + paymentMethod); 
    } 
    Payment paymentProcessor = paymentProcessors.get(paymentMethod).get(); 

    // do your stuff... 
    paymentProcessor.pay(); 
    } 
} 

Lưu ý: nhà cung cấp chích không làm việc với javax.inject.Provider, bạn cần để sử dụng com.google.inject.Provider.

Bằng cách tiêm các nhà cung cấp thay vì trường hợp bạn có thể đạt instantiation lười biếng và các chính sách khác nhau cho mỗi instantiation thực hiện. Giống như trong ví dụ trên, PaymentCardImpl là singleton, còn số kia được tạo mỗi lần. Bạn có thể sử dụng guice scopes để kiểm soát tuổi thọ, hoặc thậm chí thực hiện nhà cung cấp của riêng bạn để thực hiện điều gì đó khác.

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