- Java EE 8 and Angular
- Prashant Padmanabhan
- 919字
- 2021-07-02 19:22:34
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.
- Spring Boot 2實戰之旅
- JSP網絡編程(學習筆記)
- 大學計算機應用基礎實踐教程
- 軟件界面交互設計基礎
- .NET 4.0面向對象編程漫談:基礎篇
- 軟件測試項目實戰之性能測試篇
- Python Deep Learning
- Linux網絡程序設計:基于龍芯平臺
- Spring Boot企業級項目開發實戰
- 軟件測試實用教程
- Building Machine Learning Systems with Python(Second Edition)
- HTML5+CSS3+jQuery Mobile APP與移動網站設計從入門到精通
- Hadoop大數據分析技術
- Scrapy網絡爬蟲實戰
- Building a Media Center with Raspberry Pi