Recently, I have always been exposed to programming principles or patterns such as IoC (Inversion of Control) and DI (Dependency Injection), which are the core of the famous Java frameworks Spring, Struts, etc. In response to this, I checked various items in Wikipedia and borrowed relevant books from the library. After reading them, I have some understanding. Now I will combine the explanations in the book and my own processing and sort them out as follows:
eg1
Problem description:
Develop a system that can generate reports in Excel or PDF format according to different requirements, such as daily reports, monthly reports, etc.
Solution:
According to the principle of "interface-oriented programming", the interface and implementation should be separated, that is, the function of generating reports is extracted into a general interface ReportGenerator, and two implementations that generate Excel and PDF reports in Excel and PDF formats are provided. The client then obtains the corresponding report printing function through the service provider ReportService.
Implementation method:
According to the above, the following class diagram is obtained:
Code implementation:
interface ReportGenerator { public void generate(Table table); } class ExcelGenerator implements ReportGenerator { public void generate(Table table) { System.out.println("generate an Excel report ..."); } } class PDFGenerator implements ReportGenerator { public void generate(Table table) { System.out.println("generate an PDF report ..."); } } class ReportService { // Responsible for creating the report generator for specific needs private ReportGenerator generator = new PDFGenerator(); // private static ReportGenerator generator = new ExcelGenerator(); public void getDailyReport(Date date) { table.setDate(date); // ... generator.generate(table); } public void getMonthlyReport(Month month) { table.setMonth(month); // ... generator.generate(table); } } public class Client { public static void main(String[] args) { ReportService reportService = new ReportService(); reportService.getDailyReport(new Date()); //reportService.getMonthlyReport(new Date()); } }
eg2
Problem description:
As shown in the comments in the above code, the specific report generator is created by hard-coded internally in the ReportService class, so that ReportService has directly relied on PDFGenerator or ExcelGenerator, and this obvious tight coupling must be eliminated.
Solution: Introduce containers to introduce an intermediate manager, that is, the container (Container), which uniformly manages the objects involved in the reporting system (here is a component, we call it a Bean), including ReportService and various XXGenerators. Here you use a HashMap instance in the form of a key-value pair to save these beans.
Implementation method:
The class diagram is obtained as follows:
Code implementation:
class Container { // Save various required components in key-value pairs Bean private static Map<String, Object> beans; public Container() { beans = new HashMap<String, Object>(); // Create and save specific report generator ReportGenerator reportGenerator = new PDFGenerator(); beans.put("reportGenerator", reportGenerator); // Get and manage the reference of ReportService ReportService ReportService = new ReportService(); beans.put("reportService", reportService); } public static Object getBean(String id) { return beans.get(id); } } class ReportService { // Eliminate the tight coupling relationship and replace it with the container// private static ReportGenerator generator = new PDFGenerator(); private ReportGenerator generator generator = (ReportGenerator) Container.getBean("reportGenerator"); public void getDailyReport(Date date) { table.setDate(date); generator.generate(table); } public void getMonthlyReport(Month month) { table.setMonth(month); generator.generate(table); } } public class Client { public static void main(String[] args) { Container container = new Container(); ReportService reportService = (ReportService)Container.getBean("reportService"); reportService.getDailyReport(new Date()); //reportService.getMonthlyReport(new Date()); } }
The timing chart is roughly as follows:
Effect:
As shown above, ReportService is no longer directly associated with the specific ReportGenerator. It has used containers to isolate the interface and implementation, improving the reusability of system component beans. At this time, you can also use configuration files to obtain the definition of specific components in the Container in real time.
eg3
Problem description:
However, if you look at the above class diagram, it is easy to find that there is a two-way relationship between ReportService and Container, and they have mutual dependencies. And, if you want to reuse ReportService, it also directly depends on the specific search logic of a single Container. If other containers have different component search mechanisms (such as JNDI), reusing ReportService at this time means that the internal search logic of the Container needs to be modified.
Solution: Introducing Service Locator
Re-introducing an indirect layer Service Locator is used to provide an interface for component search logic. Please see the description in Wikipedia or the description of it by Java EE 1 and 2. This will enable the possible isolation of possible changes.
Implementation method:
The class diagram is as follows:
Code implementation:
// In actual application, you can use interface to provide a unified interface class ServiceLocator { private static Container container = new Container(); public static ReportGenerator getReportGenerator() { return (ReportGenerator)container.getBean("reportGeneraator"); } } class ReportService { private ReportGenerator reportGenerator = ServiceLocator.getReportGenerator(); // ... }eg4
Problem description:
However, whether it is introducing Container or using Service Locator, ReportService is 'active' in searching and creating specific components, which means that as a customer, the ReportService must be clear about what it needs, where it can be obtained, and how it can be obtained. All of a sudden, specific logical details have to be added because of What, Where, and How.
For example, in the previous implementation method of 'introducing Container', there is the following code:
class ReportService { // Eliminate the tight coupling relationship and replace it with the container // private static ReportGenerator generator = new PDFGenerator(); // Find private ReportGenerator generator = (ReportGenerator) Container .getBean("reportGenerator");
In the implementation method of 'introducing Service Locator', there is the following code:
class ServiceLocator { privatestatic Container container = new Container(); publicstatic ReportGenerator getReportGenerator() { // It is still container.getBean(), just a delegate is used return (ReportGenerator) container.getBean("reportGeneraator"); }} class ReportService { // ReportService In the end, it still 'actively' search and delegates to ServiceLocator private ReportGenerator reportGenerator = ServiceLocator.getReportGenerator(); }
Solution:
In this case, changing 'active' to 'passive' will undoubtedly reduce the internal knowledge of ReportService (i.e. the logic of finding components). According to the principle of inversion of control (IoC), this pull (Pull, active) is converted into a push (Push, passive) mode.
For example, the RSS subscription we usually use is a Push application, which saves us the hassle of logging into our favorite site several times a day to actively obtain article updates.
Dependency injection (DI) achieves this kind of passive reception and reduces the problems of customers (here, ReportService) itself containing complex logic and knowing too much.
Implementation method:
Because we want to be a 'passive' reception, we should go back to the Container example without using the Service Locator mode. The modified class diagram is obtained from this as follows:
The original class diagram is as follows. You can check it out and pay attention to the comment prompts:
Code implementation:
In order to enable the example to be compiled and run, and to use the running results of the tracking code to explicitly instantiate the entire class diagram and cooperate with each other in sequence, many numbered print statements and two insignificant classes were added to the constructor of each class, which is a bit insulting, as follows:
import java.util.Date;import java.util.HashMap;import java.util.Map; // In order to be able to compile and run, there are two more insignificant classes. Class Month { }class Table { publicvoid setDate(Date date) { } publicvoid setMonth(Month month) { }} // ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- System.out.println("2...start initialization of ExcelGenerator..."); } publicvoid generate(Table table) { System.out.println("generate an Excel report ..."); }} class PDFGenerator implements ReportGenerator { public PDFGenerator() { System.out.println("2...start initialization of PDFGenerator..."); } publicvoid generate(Table table) { System.out.println("generate an PDF report ..."); }}//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Get and manage the reference of ReportService ReportService reportService = new ReportService(); // Inject the specific ReportGenerator instance created above reportService.setReportGenerator(reportGenerator); beans.put("reportService", reportService); System.out.println("5...end initialization Container..."); } publicstatic Object getBean(String id) { System.out.println("Last get service component...getBean() --> " + id + " ..."); returnbeans.get(id); }} class ReportService { // private static ReportGenerator generator = new PDFGenerator(); // Eliminate the tight coupling relationship above and replace it with the container// private ReportGenerator generator = (ReportGenerator) Container // .getBean("reportGenerator"); // Remove the "active" lookup above and provide private fields to save the externally injected object private ReportGenerator generator; // Inject publicvoid from the outside in setter form setReportGenerator(ReportGenerator generator) { System.out.println("4...start inject ReportGenerator..."); this.generator = generator; } private Table table = new Table(); public ReportService() { System.out.println("3...start initialization ReportService..."); } publicvoid getDailyReport(Date date) { table.setDate(date); generator.generate(table); } publicvoid getMonthlyReport(Month month) { table.setMonth(month); generator.generate(table); }} publicclass Client { publicstaticvoid main(String[] args) { // Initialize the container new Container(); ReportService reportService = (ReportService) Container .getBean("reportService"); reportService.getDailyReport(new Date()); // reportService.getMonthlyReport(new Date()); }}
Running results:
1...Start initialization Container...2...Start initialization PDFGenerator...3...Start initialization ReportService...4...Start inject ReportGenerator...5...End initialization Container... Finally get the service component...getBean() --> reportService...generate an PDF report...
Notice:
1. According to the printing order of the above run results, it can be seen that the specific numbers added to the code are reasonable, which simulates the process of program execution, so no longer draws sequence diagrams.
2. Note that the use of IoC and DI in this example is based on ReportService as the client (i.e. component demander), and the test code in the client class main() in the code is the end user of the service component, but what it needs is not the component, but the services that the component has.
3. In fact, in the Spring box clip, initializing the Container is obviously not what the end user client should do, it should be started in advance by the service provider.
4. In the end user client, we still use Container.getBean("reportService") to obtain service components that have been instantiated in the Container constructor in advance. In specific applications, the available service components are usually deployed to the server using XML and other configuration files, and then the configuration files are read by the Container and combined with reflection technology to create and inject specific service components.
analyze:
Previously, the ReportService actively requested the service component from the Container, but now it passively waited for the Container injection (Inject, that is, Push) service component. Control is obviously transferred from the underlying module (ReportService is the component demander) to the high-level module (Container is the component provider), that is, control is reversed.