Инъекция зависимости на основе условия - PullRequest
9 голосов
/ 06 августа 2011

Я использую Google Guice для внедрения зависимостей. Предположим, у меня есть следующее:

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

Исходя из этого, очень простой модуль для привязки, например:

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

Как видите, экземпляр Payment внедряется в конструктор Order. Это сделано в классе MyModule, и в целом это действительно круто.

Моя главная выглядит так:

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

Однако я не могу понять, как я могу включить какой-либо способ условного связывания либо PaymentCardImpl , либо a PaymentCashImpl с конструктором Order ,

Скажем, например, что заказ был «онлайн». Мне тогда нужно было бы это:

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

Какой лучший способ сделать это? Я новичок в внедрении зависимостей.

Ответы [ 4 ]

11 голосов
/ 07 августа 2011

Внедрение зависимостей полезно для создания объектов стиля service.Они имеют следующие характеристики: -

  • возможно несколько реализаций,
  • тяжелое поведение,
  • внутреннее состояние ограничено их зависимостями и, как правило, они не изменяемы
  • будет отображаться на актера в реальном мире (например, кассира), а не на вещь

Исходя из этого, Payment является сервисным объектом.Я бы переименовал его в PaymentService, чтобы отличить его от записи в бухгалтерской книге, которую вы можете хранить о платеже (который был бы объектом значения).

В вашем примере мало что показывает класс Order., но я предполагаю, что он будет содержать такую ​​информацию, как некоторые позиции, адрес доставки и общую сумму.Это value объект.Он представляет собой предмет в бизнес-сфере.

Значимые объекты тяжелы по состоянию и легче по поведению.Возможно несколько реализаций, но вы вряд ли захотите заменить одну реализацию другой.

Объекты-значения не создаются вашей структурой внедрения зависимостей.Они созданы вашим кодом бизнес-логики.В вашем примере вы создаете все объекты, используя Guice.Я ожидаю, что в действительности вам нужно будет создать Order во время выполнения на основе пользовательского ввода.

Сервисные объекты могут зависеть от объектов-значений, но никак не наоборот.Я думаю, вам следует реализовать:

checkoutService.payfor( order, method );

, а не order.finishOrder( method )

В классе CheckoutService вы можете выбрать значение PaymentServiceи передайте order ему.CheckoutService будет принимать в качестве аргументов конструктора PaymentCardPaymentService (эквивалентно вашему PaymentCardImpl) и CashPaymentService (эквивалентно вашему PaymentCashImpl).

9 голосов
/ 06 августа 2011

Я знаю, почему вы хотите это сделать. Но я бы не стал смешивать конструкторский код (который является конфигурацией внедрения зависимостей) с бизнес-логикой. Если вы сделаете это, ваша бизнес-логика может быть не понятна больше. И мне кажется, что ваша условная инъекция зависит от ситуации, то есть ввода от пользовательского интерфейса.

Так почему бы просто не ввести оба и сделать условие явным? Я бы предпочел это. Пример приложения:

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

Тем не менее для вашей собственной практики: Оплата. Вам не нужен код Guice там. Остальное делает Guice автоматически. Если вы начнете использовать интерфейсы, вы начнете использовать привязки, как описано здесь: http://code.google.com/p/google-guice/wiki/GettingStarted. Но если у вас нет никаких интерфейсов, построение выполняется без какой-либо конфигурации. Достаточно аннотаций @Inject.

Конечно, это всего лишь пример дизайна. Но это показывает, как легко настроить хорошо отсоединенное Java-приложение с Guice.

5 голосов
/ 12 августа 2011

Вы можете комментировать, какой вы хотите ввести.Если вы сделаете именованную привязку, это решит проблему.

См. Ниже:

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

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

Тогда, где вы хотите внедрить, вы делаете:

@Named("Cash") Payment payment 

или:

@Named("Card") Payment payment
4 голосов
/ 07 августа 2011

Используя расширение MapBinder , вы можете получить несколько привязок, содержащихся в Map.Тогда вопрос реализации зависит от того, какой из них использовать.

Вам понадобится ключ, в вашем случае это может быть String или Enumeration и привязать все реализации Payment к соответствующим ключам:

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

Затем в Order вы получаете инъекцию всей карты и решаете, какую реализацию использовать:

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

Примечание: Поставщики инъекционных услуг не работаютс javax.inject.Provider вам нужно использовать com.google.inject.Provider.

Путем введения провайдеров вместо экземпляров вы можете добиться ленивых реализаций и разных политик реализации для каждой реализации.Как и в приведенном выше примере, PaymentCardImpl является одиночным, в то время как другой создается каждый раз.Вы можете использовать guice scopes для контроля продолжительности жизни или даже использовать своего собственного провайдера для выполнения чего-то еще.

...