官术网_书友最值得收藏!

Introduction to context and dependency injection

CDI caters to a very fundamental design idea, as that of dependency injection. When writing any application, you would typically define multiple classes and some relationship between them. Thus, to work with one object you would need to hand it its dependencies during its creation. When using CDI, these dependencies are provided to the object, rather than the owning object fetching them for itself. Dependency Injection (DI) leads to the owning object becoming independent of the dependency creation and lookup code, which makes it loosely coupled, and the code is testable. Both qualities, while difficult to pull off, are worth striving for.

Now, before we get into the nuts and bolts of CDI and how it helps to structure the code, let's explore what dependency injection is and what problems it helps solve with an example. Consider a business scenario where we are building an issue management system. It will facilitate collaboration between all members to look at various issue tickets that have been created in the system and work on them as tasks. To begin with, we will have a Task class which holds the current ticket number. To obtain this ticket number, we need to somehow reach into the database and fetch the latest ticket that was assigned to a user. Here’s what the Task class looks like:

public class Task {
private String ticketId;
private String status;

public Task(User member) {
DataSource ds = DatabaseResource.getDS();
TicketPersistence ticketPersistence =
new TicketPersistence(ds);
this.ticketId = ticketPersistence
.getLatestAssignedTicket(member);
}
}

Now, this code is fairly simple to read as it takes a User instance during the Task construction and fetches the ticketId value from the database. But in order to get the ticketId, which is what the Task class really needs, it has to obtain an instance of the TicketPersistence class, which requires a DataSource for the lookup of the latest assigned ticket. There are quite a few things which aren't quite right about this code. Spend a minute thinking about what you think is not right, then have a look at the points listed here:

  • The instantiation of a Task has multiple dependencies, such as DataSource and TicketPersistence class, making its construction complex.
  • The testing of a Task class would require setting up all the dependencies as well, such as passing a User as a dummy object and setting up a test database for the DataSource. But we are still left with using the real TicketPersistence, which can't be swapped.
  • The Task class knows too much about TicketPersistence and its internal dependency on a DataSource, while all it really wanted was the ticketId.
  • There may be other code that requires a Task object to be passed, such as a TaskStatusReport class, which simply checks the status of a Task class for reporting. Here, TaskStatusReport doesn't care about the ticketId, yet it has to deal with the complexity of the Task creation, which occurs due to the ticketId property.

Whenever the instantiation of an object becomes complex due to either a constructor or static block initialisers, it hinders the testability of such objects. As a first step, we could certainly move the construction of dependencies to some kind of factory class and then pass the objects required to Task. Here's a refactored constructor of the Task class:

public Task(User member, TicketPersistence ticketPersistence) {
this.ticketId = ticketPersistence
.getLatestAssignedTicket(member);
}

While this is better than the earlier code, this would still leave us with the option of passing both the User and TicketPersistence dependencies as mocks in order to test a Task. Knowing that neither User nor TicketPersistence is actually being saved as local field members of the Task class for referencing later, we can do away with these as well. A cleaner and testable Task code looks like the following:

public class Task {
private String ticketId;
private String status;

public Task(String latestTicket) {
this.ticketId = latestTicket;
}
}

With the preceding code, it's much easier to work with a Task object. Instead of concerning itself with how to look up the ticketId value from the database, this code is asking for what it needs, which is the ticketId value. The responsibility of looking up the ticketId value from some repository can be moved to a factory or builder, or even a DI. The two key points to take away from the preceding example are:

  • Ask for a dependency rather than getting it yourself
  • Don't use an intermediate object to get to the object that you really want

Going back to our Task class, which did have dependency on TaskPersistence, the same could have been injected into the Task class or a even a Factory, with the following code as a part of applying DI:

@Inject TaskPersistence persistence; 
// Field injection

The following code could also be used:

@Inject Task(TaskPersistence persistence) { ... } 
//Constructor injection

Here, DI (CDI) would create an instance of TaskPersistence and inject it into the calling object, in this case Task would be provided as its dependency. Notice the type of object injected is TaskPersistence, as that's the simplest form of injection.

When talking about dependency injection, another term that comes up is Inversion of Control (IoC). It simply means to delegate the responsibility of finding the dependency to another piece of code or container, which injects the object being asked for. Having given you an understanding of dependency injection, let's explore CDI and how it helps to write code when you do have dependencies to work with.

主站蜘蛛池模板: 辽阳市| 江源县| 东光县| 独山县| 甘谷县| 平顺县| 安庆市| 屯昌县| 同仁县| 临夏市| 正蓝旗| 武安市| 铁力市| 朔州市| 英吉沙县| 朔州市| 乌鲁木齐县| 辽宁省| 萝北县| 灵宝市| 桦甸市| 临漳县| 清丰县| 寻甸| 广宁县| 建湖县| 永胜县| 新余市| 达州市| 连城县| 托里县| 景德镇市| 甘孜| 中方县| 中山市| 三江| 祁东县| 报价| 安丘市| 成都市| 申扎县|