2011-08-06 24 views
9

Używam Google Guice do wstrzyknięcia zależności. Załóżmy, że mam następujące:Injection Injection na podstawie warunku

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(); 
    } 
} 

Opierając się na tym, jest bardzo prosty moduł do wiązania, tak jak poniżej:

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

Jak widać, instancja Payment jest wstrzykiwany do konstruktora zamówienia . Odbywa się to w klasie MyModule i ogólnie jest naprawdę fajne.

Moje główne wygląda następująco:

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

Co nie widzę jednak to, w jaki sposób mogę włączyć jakiś sposób warunkowo wiązania albo PaymentCardImpllub się PaymentCashImpl instancji konstruktora Order.

Załóżmy na przykład, że zamówienie było zamówieniem "online". Wtedy potrzebowałbym tego:

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

Jaki jest najlepszy sposób na zrobienie tego? Jestem nowy w zastrzyku uzależnienia.

+0

Kiedy wiesz _Który_ realizację potrzeba? –

Odpowiedz

5

Możesz opisać, który z nich chcesz wstrzyknąć. Jeśli zrobisz nazwane powiązanie, rozwiąże to problem.

Patrz poniżej:

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

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

Więc gdzie chcesz wstrzyknąć zrobić:

@Named("Cash") Payment payment 

lub:

@Named("Card") Payment payment 
+0

+1 Zaznaczę to jako najlepszą odpowiedź, ponieważ jest to podejście, którego użyłem na końcu. Dało mi to największą elastyczność. – Joeblackdev

8

Wiem, dlaczego chcesz to zrobić. Ale nie zamieniłbym kodu konstrukcyjnego (który jest konfiguracją wtrysku zależności) z logiką biznesową. Jeśli to zrobisz, twoja logika biznesowa może już nie być zrozumiała. Wydaje mi się, że twój zastrzyk warunkowy zależy od sytuacji, tj. Od danych wejściowych z interfejsu użytkownika.

Dlaczego więc po prostu nie wstrzyknąć obydwu i nie dopuścić, aby stan był jednoznaczny? Wolałbym to. Przykładowa aplikacja:

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(); 
    } 
} 

Nadal za swoją własną praktykę: Płatności. Nie potrzebujesz tam żadnego kodu Guice. Reszta jest wykonywana przez Guice automagicznie. Jeśli zaczniesz korzystać z interfejsów, zaczniesz używać wiązań zgodnie z opisem tutaj: http://code.google.com/p/google-guice/wiki/GettingStarted. Ale jeśli nie masz żadnych interfejsów, konstrukcja jest wykonywana bez żadnej konfiguracji. Adnotacje @Inject są wystarczające.

Oczywiście to tylko przykładowy projekt. Ale pokazuje, jak łatwo jest ustawić ładnie odłączoną aplikację Java z Guice.

+0

Nie można wstrzykiwać obu. Jeśli spróbuję tego, otrzymam wyjątek od Guice. Mogę zrobić tylko jedno wiązanie. Czy możesz podać przykład tego, co masz na myśli? Myślę, że może mi brakować punktu Dependency Injection tutaj. Myślę o związaniu w czasie wykonywania. – Joeblackdev

+0

To nie jest dobra abstrakcja, ale najbardziej naiwny byłby to (edytowany po wpisie) –

+0

Dzięki za przykład. Kiedy mówisz "wstrzyknij wszystkie możliwe metody płatności", co dokładnie masz na myśli? Dzięki – Joeblackdev

10

Wstrzyknięcie zależności przydaje się do tworzenia obiektów stylu service. Mają one następujące cechy: -

  • wielokrotne implementacje są prawdopodobne,
  • ciężki na zachowanie,
  • stan wewnętrzny jest ograniczona do ich zależności i są one na ogół nie zmienny
  • będzie mapować do uczestnika rzeczywisty świat (np. kasjer), a nie rzecz:

Na tej podstawie Payment jest obiektem usługowym. Zmieniłbym nazwę na PaymentService, aby odróżnić ją od pozycji księgi, którą możesz przechowywać na temat płatności (która byłaby obiektem wartościowym).

Twój przykład nie pokazuje zbyt wiele informacji na temat klasy Order, ale zakładam, że zawiera informacje, takie jak niektóre elementy zamówienia, adres dostawy i łączna kwota. To jest obiekt value. Reprezentuje rzecz w domenie biznesowej.

Obiekty wartościowe są obciążone i lżejsze pod względem zachowania. Możliwe są różne implementacje, ale mniej prawdopodobne jest, że chcesz zastąpić jedną implementację drugą.

Obiekty wartości nie są tworzone przez strukturę wtrysku zależności. Są tworzone przez Twój logiczny kod biznesowy. W twoim przykładzie tworzysz wszystkie obiekty używając Guice. Oczekuję, że w rzeczywistości będziesz musiał stworzyć Order w czasie wykonywania na podstawie danych wprowadzonych przez użytkownika.

Obiekty usługowe mogą zależeć od obiektów wartości, ale nigdy odwrotnie. Myślę, że należy szukać do wdrożenia:

checkoutService.payfor(order, method);

i nie order.finishOrder(method)

W klasie CheckoutService, można wybrać approriate PaymentService i przekazać order do niego. CheckoutService może przyjąć jako argumenty konstruktora: PaymentCardPaymentService (odpowiednik Twojej PaymentCardImpl) i CashPaymentService (odpowiednik twojego PaymentCashImpl).

+0

Świetna odpowiedź, dziękuję za to. W moim prawdziwym scenariuszu używam usługi SOAP, która nie ma żadnego stanu wewnętrznego, więc zarysowane przez ciebie cechy współgrają z tym, czego potrzebuję. Niemniej jednak dałeś mi kilka fajnych wskazówek! Jeśli mógłbyś podać przykład pracy tego, co zarysujesz, byłoby to również bardzo pomocne. Dzięki – Joeblackdev

+0

Czym właściwie jest usługa SOAP? Czy to są implementacje płatności? Czy nie mają jakiegoś stanu, takiego jak adres URL, konfiguracja proxy itp.? Nie znam Guice, więc nie mogę podać przykładu, ale myślę, że najpierw musisz uporządkować swoje zajęcia, a następnie odpowiedzi innych respondentów są użyteczne. –

3

Przy użyciu przedłużki można uzyskać kilka wiązań zawartych w Map. Następnie jest to kwestia wdrożenia, której należy użyć.

Będziesz potrzebował klucza, w Twoim przypadku to może być String lub Enumeration i wiążą wszystkie Payment implementacje do odpowiednich klawiszy:

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); 
    } 
} 

Następnie w Order Ci wstrzyknięto całej mapie i zdecydować, które realizacja używać:

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(); 
    } 
} 

Uwaga: Wstrzykiwanie dostawcy nie działa z javax.inject.Provider, trzeba używać com.google.inject.Provider.

Wstrzykując dostawców zamiast instancji, można osiągnąć leniwą instancję i różne zasady tworzenia instancji na wdrożenie. Podobnie jak w powyższym przykładzie, PaymentCardImpl jest singleton, podczas gdy drugi jest tworzony za każdym razem. Możesz użyć guice scopes, aby kontrolować długość życia, a nawet wdrożyć własnego dostawcę, aby osiągnąć coś innego.