Три формы внедрения зависимостей: конструктор, сеттер, интерфейс [и четвертая, контекстная]. Я никогда не слышал о Getter Injection, поэтому не буду упоминать его. Внедрение — это просто как объект или компонент связан с другим объектом или компонентом, обычно с классом. Без внедрения зависимостей компоненты считаются «сильно связанными», если они имеют какую-либо форму связи. Например, рассмотрим этот класс (написанный на Java), который не использует внедрение зависимостей:
public class Salesman implements Worker {
private Car itemForSale;
public Salesman() {
itemForSale = new Car("BMW");
}
public void printItemSold() {
System.out.println("The salesman sold a " + itemForSale.name()
+ " at price $"+itemForSale.price());
}
}
Здесь класс Salesman тесно связан с классом Car. Учтите, что продавец может фактически также продавать детали, инструменты и услуги. Однако поле itemForSale является объектом Car, и конструктор по умолчанию здесь устанавливает для itemForSale объект Car. У нас была бы более слабая связь, если бы мы изменили конструктор на public Salesman(String carName, double carPrice)
, но, тем не менее, это не разделило бы Salesman и Cars — объект Saleman по-прежнему «зависит» от объекта Car в своей функциональности.
А теперь пример внедрения зависимостей конструктора:
public class Salesman implements Worker {
private Item itemForSale;
public Salesman(Item item){
itemForSale = item;
}
public void printItemSold(){
System.out.println("salesman sold a " + itemForSale");
}
}
Теперь мы можем написать в другом классе:
Item car = new Car("Jeep", 8900); // the Car class implements the Item item class/interface
Salesman salesman = new Salesman(car);
salesman.printItemSold();
Item part = new Part("tires", 67.99); // the Part class also implements the Item class/interface
Salesman salesman = new Salesman(part);
salesman.printItemSold();
... and so on.
Ключевым преимуществом здесь, которое может быть трудно увидеть в таком простом случае, является то, что объект Salesman никоим образом не зависит от класса Car. Чтобы охватить все различные типы продаваемых объектов, нам не нужны поля «Автомобиль», «Запчасти», «Сервис» и т. д., а также нам не нужно реализовывать такую логику, как
if (car != null)
printItemSold(car)
else if (part != null)
printItemSold(part)
...
Скорее, все, что нам нужно сделать, это просто предоставить (или внедрить) Item, который продавец продал объекту Salesman.
Внедрение сеттера работает примерно так же, за исключением того, что вместо конструктора используется сеттер. Это может быть полезно, если мы хотим изменить продаваемый объект, не создавая каждый раз новый объект.
public class Salesman implements Worker {
private Item itemForSale;
public Salesman() { }
public void setItem(Item item){
this.itemForSale = item;
}
...
}
Теперь мы можем сделать:
Salesman salesman = new Salesman();
salesman.setItem(new Car("Toyota", 23938.23);
salesman.print();
salesman.setItem(new Service("oil change", 17.78);
salesman.print();
Я думаю, можно с уверенностью сказать, что внедрение конструктора обычно реализуется вместе с внедрением сеттера, поскольку, как только вы реализуете первую форму, очень легко реализовать вторую.
Я не буду вдаваться в инъекцию интерфейса, так как я не использовал ее раньше и могу дать вам неверную информацию. Если вы используете Java Spring, знайте, что Spring использует только внедрение конструктора и сеттера, а не интерфейс.
Наконец, контекстная инъекция зависимостей — это не совсем то, что имеет в виду большинство людей, когда используют термин «внедрение зависимостей». Но для меня это то, о чем я больше всего заботился, когда пытался узнать, что такое внедрение зависимостей (извините, если следующее объяснение не так полезно - мне трудно дать объяснение в каких-либо других терминах, кроме Java Spring). Допустим, у вас есть сложный объект, такой как соединение с базой данных, которое имеет множество полей и жизненный цикл, которым необходимо тщательно управлять.
Вы можете установить это как поле в своем классе, настроить его, установить или открыть, начать и завершить транзакции и убедиться, что оно закрыто к моменту завершения вашей программы, и все это самостоятельно. Однако было бы неплохо, если бы все, что вам нужно было сделать, это просто объявить, что вам нужен объект подключения, а затем управлять этим подключением за вас. Соединение открывается только тогда, когда оно необходимо, когда вы вызываете connection.save(data)
, транзакция автоматически начинается и фиксируется, а когда вы завершаете свою программу, соединение автоматически закрывается несмотря ни на что, даже если у вас есть исключение или ошибка. В Java Spring, например, это будет просто включать аннотацию @Component
или @Bean
в ваш класс Connection, а затем объявить @Autowired Connection connection // the Connection will even be constructed for you
в любом классе, использующем соединение.
Это стало большой проблемой в мире программирования. Чтобы понять почему, предположим, что у вас есть соединение, которое принимает объект Driver, такой как MySQLDriver, MSSQLDriver или PostgresDriver. Конечно, каждый из этих драйверов реализуется по-разному и требует уникального набора конфигураций. Но с объектом управляемого соединения, который вы запрашиваете в объявляющем классе, все, что вам нужно сделать при переходе от одной базы данных к другой, — это использовать форму внедрения конструктора или установщика: Connection conn = new Connection(MSSQLDriver);
или @Autowired Connection conn; conn.setDriver(PostgresDriver);
Это может сэкономить часы времени и устранить необходимо изучить сложный «шаблонный» код, поскольку все, что вам нужно сделать, это внести одно изменение в наше поле, и ваш объект будет управляться соответствующим образом.
И, к вашему сведению, это пост «вы не узнаете этого, пока не объясните», так что не стесняйтесь исправлять меня, если я допустил ошибку.
person
Christian Meyer
schedule
22.04.2019