Control Reversal Container & Dependency Injection mode
Source Document: Https://martinfowler.com/articles/injection.html
Lightweight containers have recently been surging in the Java community, and these containers have been able to build a cohesive application from different projects. These containers are based on a common pattern, which determines how the container completes the assembly of components, which people call the "control inversion" "inversion of". This article will delve into the working mechanism of this model and give it a specific name: "Dependency Injection" Dependency injection, and compare it with the service locator (services Locator, which will continue using this translation) mode. The choice between the two is not important, the key is a principle that both follow: the application and configuration should be separated.
There is an interesting phenomenon in enterprise applications in the Java world: Many people are trying to be a substitute for mainstream EE technology, which appears in the open-source community. This aspect largely reflects the heavy and complex of mainstream EE technology, and on the other hand, there are many ways to be creative. A common problem is how to assemble the various elements: the Web control layer and the data interface are developed by different teams, and the team knows little about each other, how do you mediate them to work together. Many frameworks have tried this, and some have branched out in this direction, working to provide a common set of components for each layer of assembly solutions. These frameworks are often referred to as lightweight containers, such as: Picocontainer Spring.
Behind these containers are some interesting design principles that are not rigidly tied to specific containers and Java platforms. Here I will explore these principles, examples are Java but I believe the same with most of my articles these principles also apply to other object-oriented environments, especially. NET. Components & Services
Assembling elements together, this topic started me into a tricky term question: "Service" "component" for the definition of these two concepts, you can easily find voluminous and the opposite of the article. So I will first clarify the use intention of the two.
My "component" component here refers to a software unit that can be used by the application but cannot be changed, and the component author has no control over the application. Cannot be modified I mean the source code of the component cannot be modified, but we can extend the behavior of the component by the way the author allows it. (Translator Note: For example, nlog component extension layoutrenderer).
The concept and components of service services are similar to those that are called by external applications. The main difference between components and services is that I think that components can be called locally (either a JAR package assembly DLL or a source code introduction), and the service is either synchronous or asynchronous via a remote interface (such as Web service, message system, Rpc,socket).
I will use the word "service" to cover that most of the logic in this article also applies to local components. In fact, you tend to xu some local frameworks to better access remote services. But the use of "components or services" is too wordy, hard to write, and the word "service" is now more popular. Simple Example
In order to make the problem more specific, I will discuss this topic through an example. Like all my super-simple examples, this example is simply untrue, but hopefully it will allow you to see exactly what is going on and not dwell on the details of the real problem.
In this example I wrote a component that provides a list of movies directed by a director. There is only one way to achieve this wonderful feature:
Class Movielister
... Public movie[] Moviesdirectedby (String Arg) {
List allmovies = Finder.findall ();
for (Iterator it = Allmovies.iterator (); It.hasnext ();) {
Movie movie = (movie) it.next ();
if (!movie.getdirector (). Equals (ARG)) It.remove ();
}
Return (movie[]) Allmovies.toarray (new Movie[allmovies.size ());
}
The implementation of this feature is really simple, and it goes back to the finder to find all the movie listings, and then iterate through the list and return the work of a particular director through the Finder object (which is mentioned later). It's simple, I'm not going to improve it, it's just a matter of explaining the problem in this article, that's all.
What this article really cares about is the Finder object, or how to relate the Lister object to the Finder object. The interesting reason for this problem is that I want to expect that beautiful moviedirectedby to be independent of the way the film is stored. So all the methods will refer to a Finder,finder object to complete the Findeall function. We can pull this out and make the interface.
Public interface Moviefinder {
List findAll ();
}
Now that we have done a good object decoupling, but I need to use an entity class to complete the work of the movie search involves a specific class; Here I put the code in the Lister constructor.
Class Movielister
... Private Moviefinder finder;
Public Movielister () {
finder = new Colondelimitedmoviefinder ("Movies1.txt");
}
The name of this entity class can express the fact that we need to get a list of movies from a comma-delimited list of text files. Specific implementation details I omitted, as long as I know that this is a way to achieve it.
If this class is not as long as I have a problem with myself. But what if one of my friends is amazed at this wonderful feature and wants to use it? If they also keep the movie list in a text file and use commas to separate it, and change the file name to "Movies1.txt" then there is no problem. If only the name of the movie list is different then there is no problem, I can read from the configuration file. But what if they use a completely different storage medium? such as SQL database, XML file, Web Service, even if it is just a text file stored in another format rule. So we need a new class to get the data. Since a moviefinder interface has been extracted, I can not modify the Moviesdirectedby method, and I would like to get an instance of the appropriate Moviefinder implementation class by other means.
Figure 1: Dependency on simple creation of Moivefinder instances in the Moivelister class
The diagram above shows the dependencies in this case: the Movielister class relies on the Moivefinder interface and its implementation. We certainly expect Moivelister to rely only on interfaces, but how do we get an instance of getting a moivefinder?
In my "Enterprise Application Model" book, we refer to this as the plugin Plugin:moivefinder is not added to the program at compile time, because I don't know what kind of finder my friends will use. I want my moivelister class to work with any moivefinder implementation, and allow it to load at run time without my control at all. The question now is how to design this connection so that the Moivelister class can collaborate with them without knowing the specifics of the implementation class.
To extend this to the real system, we may have dozens of services and components. In each case, we can abstract the use of the component, extract the interface and interact with the component through the interface (if the component does not provide an interface so that it can be adapted to the obedient while look), but we want to deploy the system in different ways, we need to use plug-in way to handle the interaction between the services. This makes it possible for us to use different implementations in different deployment scenarios.
So the central question now is how we can assemble these plugins in one application. This is precisely the main obstacle faced by the new generation of lightweight containers, and they have no exception to the control reversal. Control Reversal
When the designers of these containers talk, they say that these containers are so useful because they achieve "inversion of control". And I'm confused, control reversal is a common feature of the framework, and if a framework is implemented to control the inverse to feature the equivalent of saying that my car has wheels.
The question is what they reverse. My first contact with control inversion it is concerned with the control of the user interface. The early user interface was completely controlled by the program. You will design a series of commands: Similar to "Please enter the name" "Please enter the address" your program will display the prompt message and get the user response. In a graphical (or even touch-based) user interface, the user interface framework maintains a main loop, and your application only needs to design events and processing functions for different areas. The main control of the program here is reversed: from the application to the framework.
So for the new generation of containers, they want to reverse is how to locate the specific implementation of plug-ins. In my simple example, the Movielister class is responsible for the positioning of the Moviefinder: it instantiates a subclass directly. This dependency, Moivefinder is not a plugin, because it is not loaded at runtime. The methods of these containers are: as long as the plugin complies with certain conversion rules, a separate assembly module can be injected into the Lister.
The result is that I need to give this pattern a more specific name. Control reversals are so broad that they are often confusing. After discussing with some IOC enthusiasts, we named Dependency injection.
I'm going to start talking about a variety of dependency injections, but the first thing to point out is that the dependency injection is never only way to get the application's dependency on the plug-in to be dispelled. You can also do this by using the service locator mode, and after discussing dependency injection we'll talk about service locator services location mode. the form of dependency injection
The basic idea of dependency injection is that there is a separate object – an assembler, which obtains a field that implements the appropriate implementation class for the Finder interface and assigns it to the Movielister class. The current dependency situation is shown in the following figure:
Figure 2 Dependency of the dependency injector
There are three main forms of dependency injection: constructor injection, attribute injection, and interface injection. If you are concerned about recent discussions about dependency injection, you will find that this is the type mentioned in 1IoC type 2IoC type 3IoC. Numbers are often difficult to remember, so I use names here. using Picocontainer for constructor injection
First I implement the constructor injection by using a lightweight container picocontainer. The reason to start here is because my colleagues at the ThoughtWorks company are active in the development community of Picocontainer, yes, but it's a preference.
Picocontainer uses constructors to determine how to inject a finder's implementation class into the Lister class. To achieve this, the Moivelister class must be a constructor of life and contain enough injected information.
Class Movielister
... Public movielister (Moviefinder Finder) {
this.finder = finder;
}
The finder itself is also managed by Picocontainer, so the name of the text file can also be implemented through container injection.
Class Colonmoviefinder
... Public Colonmoviefinder (String filename) {
this.filename = filename;
}
The next thing to do is tell Picocontainer which interfaces and which classes implement associations, and which strings are injected into the Finder component.
Private Mutablepicocontainer Configurecontainer () {
Mutablepicocontainer pico = new Defaultpicocontainer ();
parameter[] Finderparams = {new Constantparameter ("Movies1.txt")};
Pico.registercomponentimplementation (Moviefinder.class, Colonmoviefinder.class, finderParams);
Pico.registercomponentimplementation (movielister.class);
return pico;
}
This configuration code is usually located in a different class. For our example, every use of my Lister class is just to write the appropriate configuration code in my own configuration class. Of course, the usual practice is to put these configuration information in a separate configuration file. You can write a class to read the configuration file to set up the container, although Picocontainer does not include this functionality in another closely related project Nanocontainer provides some wrappers that allow you to use XML configuration files. The nanocontainer resolves and configures the picocontainer. The philosophical philosophy of this project is to separate the configuration form from the underlying configuration mechanism.
Using this container your code will roughly look like this:
public void Testwithpico () {
Mutablepicocontainer pico = Configurecontainer ();
Movielister lister = (movielister) pico.getcomponentinstance (movielister.class);
movie[] Movies = Lister.moviesdirectedby ("Sergio Leone");
Assertequals ("Once upon a time in the West", Movies[0].gettitle ());
}
Although I used constructor injection here, Picocontainer also supports attribute injection, but its developers recommend constructor injection. attribute injection using the Spring framework
The spring framework is an application-wide, enterprise-class Java development framework. It contains functional abstractions for transactions, persistence frameworks, Web application development, and JDBC. Like Picocontainer, it also supports constructor injection and attribute injection, but its developer recommendation is to use attribute injection – which is appropriate for this example.
To get my moive lister to receive injections, I'm going to add an assignment method to it:
Class Movielister
... Private Moviefinder finder;
public void Setfinder (Moviefinder finder) {
this.finder = finder;
}
Similarly we have added a method for filename
Class Colonmoviefinder
... public void Setfilename (String filename) {
this.filename = filename;
}
The third step is to set up the configuration file. Spring supports a variety of configuration methods, and you can configure XML files via XML or through code, which is an ideal way to configure files.
<beans>
<bean id= "Movielister" class= "spring. Movielister ">
<property name=" finder ">
<ref local=" Moviefinder "/>
</property>
</bean>
<bean id= "Moviefinder" class= "spring. Colonmoviefinder ">
<property name=" filename ">
<value>movies1.txt</value>
</property>
</bean>
</beans>
The test code is roughly like this:
public void Testwithspring () throws Exception {
ApplicationContext ctx = new Filesystemxmlapplicationcontext (" Spring.xml ");
Movielister lister = (movielister) ctx.getbean ("Movielister");
movie[] Movies = Lister.moviesdirectedby ("Sergio Leone");
Assertequals ("Once upon a time in the West", Movies[0].gettitle ());
}
Interface Injection
The third injection technique is to define the information required for injection in the interface and complete the injection through the interface. Avalon is a typical example of a framework that uses this technology. I'll discuss more about it later, and now this example is implemented with simple code.
Using this technique I will first define an interface and inject it with this end interface. The purpose of this interface is to inject a finder instance into the object that implements the interface.
Public interface Injectfinder {
void Injectfinder (Moviefinder finder);
}
This interface is provided by the class that provides the Moviefinder. Any entity class that wants to use Moviefinder must implement this interface, such as Lister.
Class Movielister implements Injectfinder public
void Injectfinder (Moviefinder finder) {
This.finder = finder;< c10/>}
I use a similar method to inject the file name into the Finder implementation:
Public interface Injectfinderfilename {
void injectfilename (String filename);
Class Colonmoviefinder implements Moviefinder, Injectfinderfilename ...
public void Injectfilename (String filename) {
this.filename = filename;
}
Next i need some configuration code to wrap up the implementation of these components and I'll do it in the code for simplicity:
Class Tester
... Private Container Container;
private void Configurecontainer () {
container = new container ();
Registercomponents ();
Registerinjectors ();
Container.start ();
}
The configuration is divided into two stages, registering the component by locating the keyword, as in other examples:
Class Tester
... private void Registercomponents () {
container.registercomponent ("Movielister", movielister.class);
Container.registercomponent ("Moviefinder", Colonmoviefinder.class);
}
The next step is to register the injector that depends on the component, and each injection interface requires some code to inject into the dependent object. Here I use the container to complete the registration of the injector. Each injector object implements an injection interface.
Class Tester
... private void Registerinjectors () {
container.registerinjector (Injectfinder.class, Container.lookup (" Moviefinder "));
Container.registerinjector (Injectfinderfilename.class, New Finderfilenameinjector ());
}
Public interface Injector {public
void inject (Object target);
}
When a dependency is designed to be a class written for a container then it makes sense to inject the interface into the component itself, just as I did with the Moive Finder. For normal classes, such as String, I use an internal class to complete the configuration code.
Class Colonmoviefinder implements Injector
... public void inject (Object target) {
(Injectfinder) target]. Injectfinder (this);
}
Class Tester
... public static class Finderfilenameinjector implements Injector {public
void inject (Object target) {
( Injectfinderfilename) target). Injectfilename ("Movies1.txt");
}
}
The test code can use this container directly.
Class Tester
... public void Testiface () {
configurecontainer ();
Movielister lister = (movielister) container.lookup ("Movielister");
movie[] Movies = Lister.moviesdirectedby ("Sergio Leone");
Assertequals ("Once upon a time in the West", Movies[0].gettitle ());
}
The container uses an interface that declares the injector to indicate the dependency between the injector and the entity class. (specifically with what containers do not matter, I will not do the show it will only make you laugh) using the service locator
The key advantage of dependency injection is that it eliminates the dependency of the Lister class on a specific Finder class implementation. This allows my friends to conveniently insert a specific implementation into their application environment. Injection is by no means the only way out, and another solution is to use service Locator: Services Locator mode.
The basic idea of a service locator is that an object knows how to get all the services that the application needs. In our example, the service locator should have a way to return the desired finder instance. It's just a little bit less of a burden, and from the dependencies in the graph below you can see that we still need to get the locator in the Lister.
Figure 3 Dependency relationship of the service locator
Here I make a single-piece registry of the service locator. Lister can obtain an instance of a finder from the service locator when it is instantiated.
Class Movielister
... Moviefinder finder = Servicelocator.moviefinder ();
Class Servicelocator
... public static Moviefinder Moviefinder () {
return soleinstance.moviefinder;
}
private static Servicelocator soleinstance;
Private Moviefinder Moviefinder;
As with injection, we must also configure the service locator. I still do this in code, and it's not difficult to configure by reading the configuration file.
Class Tester
... private void Configure () {
servicelocator.load (new Servicelocator (New Colonmoviefinder ("Movies1.txt"));
}
Class Servicelocator
... public static void Load (Servicelocator arg) {
soleinstance = arg;
}
Public Servicelocator (Moviefinder moviefinder) {
this.moviefinder = Moviefinder;
}
This is the test code.
Class Tester
... public void TestSimple () {
configure ();
Movielister lister = new Movielister ();
movie[] Movies = Lister.moviesdirectedby ("Sergio Leone");
Assertequals ("Once upon a time in the West", Movies[0].gettitle ());
}
I've always heard such complaints. The service locator is not a good thing because you cannot replace the instance returned by the service locator and you cannot test it. Of course, if you have a bad design, it's inevitable to meet these problems. In this example, the service locator is a simple data container that can be easily modified to create a service for testing.
For more complex situations I can derive subclasses from the service locator and pass subclasses to the registry class variables. In addition, I can let the service locator expose a static method instead of directly accessing the variables of the class instance. I can also use a specific thread store to provide a service locator for a particular thread. None of these changes will modify the client using the service locator.
An improved approach to service locators is not designed as a single-piece form. A single piece is really a simple way to implement a registry, but this is just the implementation of the decision to easily change it. using separate interfaces for locators
One problem with the simple scenario above is that the Lister class relies on the entire service locator even if it uses only one service. We can change this dependency on the service locator by using a separate interface, Lister using the part of the interface it wants to use.
The provider of the corresponding Lister class should also provide such a locator interface, where the user can obtain the Finder through this interface.
Public interface Moviefinderlocator {public
moviefinder moviefinder ();
The service locator needs to implement the above interfaces, providing the ability to access Moviefinder instances.
Moviefinderlocator locator = Servicelocator.locator ();
Moviefinder finder = Locator.moviefinder ();
public static Servicelocator Locator () {
return soleinstance;
}
Public Moviefinder Moviefinder () {
return moviefinder;
}
private static Servicelocator soleinstance;
Private Moviefinder Moviefinder;
Note that because of the interface we are no longer able to access the service directly using the static method, we must obtain the locator instance in the class and use the various services required. Dynamic Service Locator
The above example is static, and the service locator has all the services you want. This is not the only way, you can use the dynamic service locator, which allows you to register any of the services you need and get the services on demand at run time.
In the following example, the service locator uses a map to hold the service information, not in a field, and provides a common way to get the service.
Class Servicelocator
... private static Servicelocator soleinstance;
public static void Load (Servicelocator arg) {
soleinstance = arg;
}
Private MAP Services = new HashMap ();
public static Object GetService (String key) {
return soleInstance.services.get (key);
}
public void Loadservice (String key, Object service) {
services.put (key, service);
}
The function of configuration is to load the service with specific keywords.
Class Tester
... private void Configure () {
Servicelocator locator = new Servicelocator ();
Locator.loadservice ("Moviefinder", New Colonmoviefinder ("Movies1.txt"));
Servicelocator.load (locator);
}
Use the service with the same keywords.
Class Movielister
... Moviefinder finder = (moviefinder) servicelocator.getservice ("Moviefinder");
In general I don't like this method, although it is flexible but it is not straightforward. I can only locate a service by using the string keyword, and I tend to be more clear about how to get a particular service by looking at the interface definition. using service locator and Dependency injection via Avalon
Dependency Injection and service locator are not concepts that are incompatible with fire and fire. The Avalon Framework is a great example of the harmonious use of the two. Avalon uses the service locator but also uses injection to tell the component where to find the locator.
Berin Loritsch sent me a simple example that can be run, which is implemented with Avalon.
public class Mymovielister implements Movielister, serviceable {
private moviefinder finder;
public void Service (ServiceManager manager) throws serviceexception {
finder = (moviefinder) manager.lookup ("Finder ");
The service method is an example of method injection, which can be a container that injects a ServiceManager object into a Lister object. And Servicemananger is an example of a service locator. In this example, Mymoivelister does not want to save servicemanager in the field, but instead uses it to obtain an instance of the Finder and save the Finder. Make a choice
So far I have focused on my views on these two patterns and their forms of change. Here's how to count their pros and cons, which will help you decide when to use them. Service Locator vs Dependency Injection
The first is the choice of service locator and dependency injection. The 1th is to note that although the above two simplistic examples do not show the fact that both models provide basic decoupling capabilities. In both cases, the application code does not depend on the implementation of the specific service interface. The difference between the two is how to provide specific implementations to the application class. Using a service locator is the implementation of a service that sends messages through an application that explicitly wants. With dependency injection ambiguous requests, the implementation of the service will naturally appear in the application's class, which is control inversion.
Control reversal is a common feature of all frameworks, but you have to pay a price. It can be difficult to understand and cause problems when debugging. So I'm trying to avoid using it as a whole. That's not to say it's bad, but I'm more inclined to have an intuitive approach.
The key difference is that the consumer of each service using a service locator relies on the service locator. The locator hides the consumer's reliance on the implementation of other services, and must rely on the service locator itself. So, the choice of injection or locator is whether you care about the dependency on the locator.
With dependency injection you can clearly see the dependencies between components. You only need to observe what dependency injection mechanisms are used, such as constructor injection, to see the dependencies.
In the service locator you have to search the source code for calls to the service locator, and the IDE with the referencing analysis can simplify this, but it's not as straightforward as simply observing the constructors and properties.
The choice between the two is largely dependent on the nature of our service users. If you have different classes in your application that use the same service, then it is not a big problem for your application to use the service builder. Examples of the above movie list using the service locator is good. My friends just need to do a bit of configuration (configuration code or configuration file) to get the proper service implementation. In this application scenario, the inversion of the injector is not a noticeable area.
It's different if you think of Lister as a component that is available to other applications. In this case I don't know what service Locator API the user will use, and each user may have their own service locator, which is incompatible with each other. I can use the detach interface method to solve this problem, each user can develop their own service locator adapter. But even so I want to look for a specific interface in the first service locator. And once the adapter appears the locator's simple and straightforward, it starts to weaken.
Using an injector does not create a dependency between the component and the injector, but once the injector is configured, the component is not able to get more services.
People tend to choose dependency injection because it is easier to test. The key point is that you can use a real service or a test service created using stubs piles or mocks methods for testing purposes. But there is no difference in actually using dependency injection and service locators: both are well-supported "piles". I guess the reason why so many people are relying on injection is easier to test because they are not trying to replace the locator. That's the point of continuous testing, if you can't easily use piles for testing, which means you're having a serious problem with your design.
Of course, if the component environment is highly intrusive, then the problem of testing is more serious, such as the Java EJB Framework. My point is that these frameworks should minimize the impact of the framework itself on application code, especially those that might make the edit-execute process slow. Replacing heavyweight components with plug-ins can be a great aid to the testing process. This is also the key to tdd– test-driven development practices.
The main problem, therefore, is whether the author wants the component to be removed from its own control and applied to other programs. If the author thinks so, then it cannot make any assumptions about the service locator – even the smallest assumptions can cause trouble. constructor Injection vs attribute injection
When services are integrated, you have to follow certain rules to integrate everything together. A bit of dependency injection is: As long as a very simple convention-at least for constructor injection and attribute injection. You don't have to do anything special in your component, just do the configuration directly.
Interface injection is much more intrusive because you have to write as many interfaces as you need. It's a good idea to use a container to provide a small set of interfaces, and that's what Avalon does.
There is still much work to be done on consolidation and dependency, which is why many lightweight containers are still focused on the cause of attribute injection and constructor injection.
Property injection and constructor injection reflect an interesting question that all object-oriented systems will encounter: the object field should be populated there: the constructor or the property.
All along, my practice has been to try to put object creation in the construction phase, that is, the constructor fills the object field. This advice can be traced back to Kent Beck's Smalltalk Best Practice Patterns:constructor method and Constructor Parameter method. With the parameter constructor you can explicitly create the intent, If you do not know a way to construct, you should provide multiple constructors.
Another benefit of the constructor is that it allows you to hide some invariant fields as long as there is no setter. I think this is important. If a field is considered not to be changed, then the attribute assignment lock is a good indication of this. If you use property initialization that's painful, actually I tend to avoid using the Setup method, but instead use a method like Initfoo to emphasize that this method should be called at the beginning of the object creation.
But there's an exception to everything, and if the constructor has too many arguments, it's a bit confusing, especially for languages that don't support keyword parameters. A lengthy argument list indicates that the class is too busy to be split, except in cases where a lot of parameters are really needed.
If you construct a valid object in multiple ways, it is difficult to describe the construction intent by the number and type of constructors. This is where the factory method pattern comes in, and the factory approach can do its job with multiple private constructors and setter method combinations. The problem with Classic factory method mode is that it is often implemented in the form of static methods, and you cannot declare them on the interface. You can create a factory class, but that becomes another service entity class. Factory service is a good technique, but you need some way to instance the chemical plant object, the problem is still there.
Constructor injection is also problematic if you want to pass in simple types such as strings: using attribute injection you can describe the purpose of the parameter in the name of the property method, and the constructor injection you can only depend on the position of the parameter to determine the role of each parameter, which is much harder to practice.
If your object has many constructors and there are inheritance relationships between objects, things can get worse. In order to be able to initialize correctly you must move the constructor call of the subclass to the parent class constructor and then process your own arguments. This can lead to explosive expansion of the constructor.
Despite the many flaws I was the constructor injection, but we are ready to turn to attribute injection Once this problem is encountered.
There is a debate over the topic of attribute injection or constructor injection in the context of several development teams with dependency injection as the core of the framework. But most people have realised that even if they are biased, there are two ways to inject it. using code to configure or use a configuration file
Another, relatively independent, topic is the use of configuration files or the use of code to complete Configuration service assembly. The configuration file is more appropriate for most applications that you want to deploy in multiple places. The configuration file is xml,xml and really competent. But there are some situations where it's easier to use code for configuration. One scenario is that you want to do a simple application without the deployment of a change requirement, when using code configuration is simpler and clearer than the individual XML.
The opposite is the case with complex application assembly, which involves a lot of conditional steps. Once the XML configuration logic in a programming language becomes complex, you should use the appropriate language to describe the configuration information so that the program logic becomes clear. You can write a builder to complete the assembly, and if you use builder more than one scene you can provide multiple builder classes and detach from the configuration file.
I often find people eager to define configuration files. Programming languages typically provide a simple way to configure management and can be plugged into large systems. If the compilation process is a hassle, the scripting language can provide a good help.
The common view is that profiles should not be written in programming languages because they can be edited by non-technical people. But how much is this going to happen? Do we really want a non-technician to change the transaction isolation level of a complex server-side application? A non-programming language configuration file works best only in a simple scenario. If the configuration file becomes complex, you should consider switching to a suitable programming language.
In the Java world there is a case of disharmony in the configuration file: Each component has its own configuration file, and the format is different. You want to use a bunch of components to maintain a bunch of configuration files, and quickly synchronize the configuration file problems will let you patience.
My advice: All profiles are available through a unified programming interface, so separating the configuration files is an optional option. With this interface you can easily manage the configuration files. If you write a component, it is up to the user to decide whether to use your programming interface, configure the format of the file, or define their own configuration file format and tightly integrate with your programming interface. detach the configuration from the app
The focus of this topic is to ensure that services are configured and applied separate. In fact, this is a basic design principle: separating interfaces and concrete implementations. In an object-oriented program, we use conditional logic in one place to decide that conditional logic after initializing that class is implemented by polymorphism rather than repeating the preceding conditional logic.
If it is useful for a piece of code interface and implementation separation, it is critical when you use external elements such as components and services. The first question is whether you want to defer the implementation-specific choices to the deployment phase. If so, you'll need to implement the Insert method. With insert, the assembly of the plug-in is separated from the rest of the application, so you can easily adjust the configuration for different deployments. How to do this is secondary, using the service locator or injection technology can be. More Topics
I focus on the core topic of this article is the use of dependency injection and service locator for service configuration. There are also some topics related to these containers to be aware that I have no time to continue digging further down. Particular attention is given to life cycle issues: Some components have specific life cycle events, such as stopping the start. Another topic is that people have a strong interest in how to apply aspect-oriented ideas to these containers. I do want to write a little more on these topics, whether it's an extension or a start-up.
You can find more information on the lightweight container's website: in Picocontainer and spring you can find a lot of discussion content to do further research. Conclusion
At the bottom of today's popular lightweight containers, the same patterns are used to integrate service-dependency injection patterns. There is also an effective alternative to the mode of service locator. While the application is being developed in the same way, I think the service locator pattern has a slight upper hand because it behaves more intuitively. If you are developing a component that needs to be used by multiple programs, then dependency injection is a better choice.
If you use Dependency injection mode There are some implementations to choose from. I recommend that you use constructor injection, and then turn to attribute injection when you encounter some specific problems. If you need to select a container to find a support for both the injection method.
The choice between service locator mode and Dependency injection mode is not so important, it is important that the service configuration should be detached from within the application.