Статьи

Проводка приложения на автопилоте

Мы говорили о том, как важно отделить новые операторы от логики приложения . Это разделение заставляет ваш код иметь фабрики, которые отвечают за соединение вашего приложения . Разделяя эту ответственность, тесты всегда могут соединить подмножество приложения с заменой ключевых компонентов на дружественные, что делает тестирование проще и более сфокусированным.

Но давайте посмотрим на образец фабрики

class CarFactory {
Car create() {
return new Car(
new EngineCompartment(
new Engine(),
new ManualTransmission(),
new PowerSteering(),
new Battery()
),
new Cabin(
new Door(new PowerWindow()),
new Door(new PowerWindow()),
new PowerSeat(), new PowerSeat()
),
Arrays.asList(
new Wheel(new Tire(), new Rim()),
new Wheel(new Tire(), new Rim()),
new Wheel(new Tire(), new Rim()),
new Wheel(new Tire(), new Rim())
)
);
}
}

Этот завод строит автомобиль. Первое, на что нужно обратить внимание, это то, что все операторы new находятся здесь (если бы вы заглянули внутрь каждого из классов, он был бы лишен «новых»). Второе — это полное отсутствие логики (без циклов и условий). И, наконец, третье, поведение вашего приложения контролируется тем, как классы связаны друг с другом. Если бы я хотел автомобиль с автоматической коробкой передач, все, что нам нужно сделать, это подключить классы по-другому. Ответственность за проводку в заводском классе не связана с логикой приложения

Однако почему мы должны сказать JVM, как соединить эти классы вместе? Разве это не самоочевидно? В конце концов посмотрите на конструкторы этих классов:

Car(EngineCompartment ec, Cabin c, List<Wheel> ws);
EngineCompartment(Engine e, Transmission t,
Steering s, Battery b);
Cabin(Door driverDoor, Door pasangerDoor,
Seat driverSear, Seat passangerSear);
Engine(float dissplacement, int pistonCount);
Battery(float voltage);
Door(Window window);
PowerWindow() implements Window;
PowerSeat() implements Seat;
Wheel(Tire tire, Rim rim);
...

Представьте, что вы можете просто попросить вещи. Давайте начнем с простого и посмотрим на Колесо. Конструктор Колеса нуждается в Шине и Ободе. Поэтому, когда мы просим Колесо, должно быть само собой разумеющимся, что мы хотим новое Колесо (новый Шин (), новый Обод ()). Почему мы должны сделать это явно на нашем заводе? Давайте создадим фреймворк, из которого мы можем запросить класс, и он вернет экземпляр этого класса. Таким образом, в нашем случае, если мы запрашиваем getInstance (Wheel.class), он возвращает экземпляр нового колеса (new Tire (), new Rim ()). Теперь такую ​​структуру легко создать, поскольку все, что нам нужно сделать, — это посмотреть на конструктор и рекурсивно попытаться создать экземпляры объектов, пока все рекурсивные конструкторы не будут выполнены.

Но все немного сложнее, чем это. Что если мы попросим Cabing как в getInstance (Cabin.class). Каюта нуждается в двух дверях и двух сиденьях, но сиденье — это интерфейс, и поэтому мы должны принять решение. Какой подкласс Seat мы должны создать? Чтобы помочь нашей платформе принять это решение, мы добавим метод связывания, такой как связывание (Seat.class, PowerSeat.class). Отлично, теперь мы вызываем get (Seat.class), фреймворк возвращает новый PowerSeat (). Точно так же нам придется вызывать bind (Window.class, PowerWindow.class). Теперь мы можем вызвать get (Cabin.class), и фреймворк вернет новую Cabin (new Door (new PowerWindow ()), new Door (new PowerWindow ()), new PowerSeat (), new PowerSeat ()).

Обратите внимание, что чем ближе к корню класс, тот класс, который вы запрашиваете, тем больше работы нам сделает фреймворк. Так что в идеале мы просто хотим запросить корневой объект. В нашем случае Авто. Вызов get (Car.class) приведет к тому, что фреймворк выполнит всю работу, изначально выполненную на нашем заводе.

Как видите, структура, которая будет вызывать новых операторов от вашего имени, очень полезна. Это потому, что вам нужно только запросить корневой объект (в нашем случае это Car), и фреймворк построит весь граф объектов от вашего имени. Эти виды фреймворков называются фреймворками с автоматическим внедрением зависимостей, и их там немного. А именно GUICE , PicoContainer и Spring .

Поскольку я больше всего знаю о GUICE , приведенный выше пример можно переписать в GUICE следующим образом:

class CarModule extends AbstractModule() {
public void bind() {
bind(Seat.class, PowerSeat.class);
bind(Seat.class, PowerSeat.class);
bind(Transmission.class, ManualTransmission.class);
bind(new TypeLitteral<List<Wheel>>(){})
.toProvider(new Provider<List<Wheel>>(){
@Inject Provider<Wheel> wp;
List<Wheel> get() {
return Array.asList(wp.get(), wp.get(),
wp.get(), wp.get());
}
});
}
}
Injector injector;
injector = GUICE.createInjector(new CarModule());
Car car = injector.getInstance(Car.class);

Как вы можете видеть, фреймворки Automatic Dependency Injection могут многое сделать для вас. А именно, вам не нужно беспокоиться о написании фабрик. Вы просто пишете логику своего приложения и запрашиваете свои зависимости в конструкторе и позволяете фреймворку решать их все за вас. Вы перекладываете ответственность за вызов нового оператора на платформу. Или, иначе говоря, DI-framework — это ваш новый «новый». Теперь DI-каркасы могут сделать много других вещей, которые выходят за рамки данной статьи, такие как управление временем жизни объекта, соблюдение одиночек (в хорошем смысле, см: Основная причина Одиночки и Одиночки являются Патологические лжецы ) и управлять различными конфигурациями вашего приложения, такие как производство против сервера разработки.

Для более реального примера модуля GUICE см .: CalculatorServerModule.java

Первоначально опубликовано на Miško Hevery — Блог исследователя тестируемости