IOC container and Dependency Injection mode Author / Martin Fowler Compile / Transparent Java community recently set off a boom of lightweight containers, which help developers assemble components from different projects into a consolidated application. Behind their back has the same mode, this mode determines how these containers are assembled. This mode is called this mode with a big name: "Inversion OFCONTROL, IOC). In this article, I will explore the working principle of this mode, give it a name that is more describing its characteristics - "Dependency Injection, and" Service Locator "mode A comparison. However, the difference between the two is not very important, more importantly, the configuration of the component should be separated from the use of the two modes. There is an interesting phenomenon in the world of enterprise Java: there are many people to invest many alternatives for the mainstream J2EE technology - natural, which happens in the Open Source community. To a large extent, this can be seen as a response to the growing and complex development of mainstream J2EE technology, but there are indeed a lot of creative ideas, which have some options available. One problem that J2EE developers often encounter is how to assemble different program elements: if the web controller architecture and database interface is developed by different teams, how do you make them work? Many frameworks have tried to solve this problem, with several frameworks develop in this direction, providing more common solutions for "assembling each layer of components". Such frames are often referred to as "lightweight containers", PicoContainer and Spring are in this column. Behind these containers, some interesting design principles play a role. These principles have exceeded the category of specific containers, and even exceeded the Java platform. In this article, I will initially reveal these principles. The example I use is Java code, but as most of my articles, these principles are equally applicable to other OO environments, especially .NET. Components and services "Assembly Elements", such topics Immediately drag me into a tricky term: How to distinguish "service" and "component"? You can find out more about the long arms of these two words defined, and all contradictory definitions will let you feel the dilemma I am. In view of this, I will first explain the usage in this article for these two vocabulary that have been severely abused. The so-called "component" refers to such a software unit: it will be used by other applications that the author cannot be controlled, but the latter cannot modify the components. That is, the application using one component cannot modify the source code of the component, but can be extended by the author reserved for some way to change the behavior of the component. Services and components have some similarities: they all use them outside applications. In my opinion, the biggest difference between the two is that the component is used locally (such as JAR files, assemblies, DLL, or source import); and service is to pass - synchronization or asynchronous - remote interface To remotely use (eg Web Service, Message System, RPC, or Socket). In this article, I will mainly use the word "service", but most of the logic in the text also applies to local components. In fact, in order to facilitate access to remote services, you often need a local component framework.
However, "Components or Services" such a phrase is too much trouble, and "service" is also very popular, so this article will use "service" to refer to both. A simple example in order to better illustrate the problem, I want to introduce an example. Like all examples I used, this is a super simple example: it is very small, it is a bit not really enough, but it is enough to help you see the truth, and not in the quagmire that is in real examples. In this example, I have written a component for providing a movie list, and the movies listed on the list are directed by a specific director. Implementing this great feature requires only one way: class movieLister ... public movie [] moviesdirected = 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 .Size ()]);} You can see that this feature is extremely simple: MoviesDirectedby method first requests Finder (we will talk about this object later) Return to all the films you know, then Traverse the list of Finder objects returned and returned to the movie directed by a particular director. Very simple, but don't worry, this is just a scaffolding of the entire example. We really want to examine the Finder object, or how to connect the Movielister object with a specific Finder object. Why are we very interested in this problem? Because I hope that this beautiful MoviesDirectedBy method does not rely on the actual storage of the film. So, this method can only reference a Finder object, and the Finder object must know how to respond to the FindAll method. In order to help the reader understand more clearly, I define an interface to find: public interface moviefinder {list findall ();} There is no coupling between the two objects. However, when I want to find a movie, I must involve a specific subclass of MoviefInder. Here, I put the code "involving the specific subclass" in the structure of the Movielister class. Class Movielister ... Private Moviefinder Finder; PUBLIC MOVIELISTER () {FINDER = New ColondelimitedMoviefIefinder ("Movies1.txt");} This implementation class's name is explained: I will have a film list from a comma-separated text file. You don't have to worry about specific implementation details, as long as you want to achieve a class. If this class is only used by myself, everything is fine. However, if my friend sighs to take this wonderful function, I want to use my program, what will it? If they also save the film list in a comma-separated text file, and also name this file "movie1.txt", then everything is no problem. If they just change this file, I can also get a file name from a configuration file, which is easy.
However, if they use a completely different way - such as SQL database, XML file, web service, or another format text file - to store a video list? In this case, we need to use another class to get data. Since the MoviefInder interface has been defined, I can modify the MoviesDirectedBY method. However, I still need to get an example of the appropriate MoviefInder implementation class through some way. Figure 1: Depending on the dependency when creating a Moviefer instance in the MovieLister class, Figure 1 shows the dependencies in this case: The MovieLister class relies on the MoviefInder interface and relying on the specific implementation class. We certainly hope that the MovieLister class relies on interfaces, but how do we get an instance of a MoviefInder subclass? In Patterns of Enterprise Application Architecture book, we call this situation as "plugin": Moviefinder implementation class is not in the compile period, because I don't know which one I will use. Implement the class. We hope that the Movielister class works with any class of Moviefinder, and allows for insertion of specific implementations in the running period, insertion action is completely detached from me (original author). The problem here is: How to design this connection process, so that the MovieLister class works with its examples without knowing the details of the details. Push this example, in a real system, we may have dozens of services and components. At any time, we can always be abstract with the situation of the components, communicate with specific components (if the components do not design an interface, can also be communicated by the adapter). However, if we want to deploy this system in a different way, we need to use the plug-in mechanism to handle the interaction process between services so that we may use different implementations in different deployments. So, the current problem is: How to combine these plugins into an application? This is the main problem facing the freshman lightweight container, and they solve this problem with the means of controlling the Inversion of Control mode. The author of controlling several lightweight containers has proudly said to me: these containers are very useful because they have implemented "control reversal". Such a speech makes me deeply confused: Control reversal is the characteristics of the framework. If only because the control is used, it is considered that these lightweight containers are not unique, it is like saying "My car is Different because it has four wheels. " The key to the problem is: what is the control there in? The control reversal of my first contact is directed to the main control of the user interface. Early user interface is controlled by the application, you pre-design a series of commands, such as "Enter Name", "Input Address", etc., the application outputs the prompt information, and retrieves the user's response. In the graphical user interface environment, the UI framework will be responsible for performing a primary loop, and your application only provides event handler function for each area of the screen. Here, the main control of the program has been reversed: moved from the application to the frame. For these new containers, they reverse "how to locate the specific implementation of the plugin". In the simple example of the previous example, the MovieLister class is responsible for localizing the specific implementation of Moviefinder - it directly instantiates a subclass of the latter. In this way, Moviefinder is not a plugin because it is not inserted into the application in the running period.
These lightweight containers use a more flexible approach, as long as the plugins follow certain rules, a separate assembly module can implement the specific implementation of the plugin to the application. Therefore, I think we need to give this model a name that measures its characteristics - "Control reversal" this name is too panic, often make people confuse. After discussing with multiple IOC enthusiasts, we decided to call this mode "Dependency Injection". Below, I will begin to introduce several different forms of the Dependency Injection mode. However, before this, I should first point out: To eliminate the application's dependence on the plugin, the dependency injection is not the only choice, you can also get the same effect with the ServiceLocator mode. After describing the Dependency Injection mode, I also talk about the ServiceLocator mode. The basic idea of relying on several forms of Injection is: use a separate object (assembler) to get a suitable implementation of Moviefinder, and exfoliate to a field of the MovieLister class. In this way, we have obtained the dependent graph shown in Figure 2: Figure 2: There are three in the form of dependency relying on the relying on the injector, I am called constructor inJection, setting value, respectively. Method Injection and Interface Injection. If you read some discussion materials on IOC, you are not difficult to see: These three implantations are Type 1 IOC (interface injection), TYPE 2 IOC (value value method injection), and TYPE 3 IOC (constructor injection). I found that the number numbers were often more difficult, so I used the name of the name here. Structure of the PicoContainer is injected first, I want to show the reader how to use a lightweight container named PicoContainer to complete dependency injection. The reason why starting from here is because I am very active in the development of PicoContainer's development community in ThoughtWorks. It can also be said to be some kind. PicoContainer judges "How to Inject the Moviefinder Instance into the Movielister class" by constructing. Therefore, the Movielister class must declare a structure and contain all elements that need to be injected: Class Movielister ... public movielister (moviefinder finder) {this.finder = finder;} The Moviefinder instance itself will also be managed by PicoContainer, so text The name of the file can also be injected by the container: Class ColonMoviefInder ... Public ColonMoviefInder (String filename) {this.filename = filename;} Subsequent, you need to tell PicoContainer: Which interface is related to which implement class association, which string is injected into the MoviefIinder component .
private MutablePicoContainer configureContainer () {MutablePicoContainer pico = new DefaultPicoContainer (); Parameter [] finderParams = {newConstantParameter ( "movies1.txt")}; pico.registerComponentImplementation (MovieFinder.class, ColonMovieFinder.class, finderParams); pico.registerComponentImplementation (MovieLister .CLASS); RETURN PICO;} This configuration code is usually located in another class. For our example, friends who use my Movielister class need to write appropriate configuration code in their settles. Of course, these configuration information can also be placed in a separate profile, which is also a common practice. You can write a class to read the configuration file and then make the container for proper settings. Although the PicoContainer itself does not contain this feature, another item Nanocontainer with its relationship is provided with some packages, allowing developers to save configuration information using XML configuration files. NanoContainer can parse the XML file and configure the PicoContainer under the bottom. The philosophical concept of this project is: separating the format of the configuration file with the configuration mechanism under the bottom. With this container, probably your write code 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 use the constructive sub-injection, I actually picoContainer also supports the implanarization method. However, the developer of the project is more recommended to use the structure of the structure. Using Spring Filing Spring Frames is a wide range of enterprise Java development frameworks, including abstraction of common functions such as transactions, persistence frames, web application development and JDBC. As with PicoContainer, it also supports structural sub-injection and setting method injection, but developers of the project are more recommended to use value-in-setting method-just suitable for this example. In order to let the MovieLister class are injected, I need to define a set value method, the method accepts the type of Moviefinder: Class Movielister ... private moviefinder Finder; public void setfinder {this.finder = finder;} Similarly, in the implementation class of the Moviefinder, I also define a value-setting method, accept the type of string: class colonmoviefinder ... public void setfilename (string filename) {this.FileName = filename;} The third step is Set the configuration file. Spring supports a variety of configurations, you can configure through an XML file or configure them directly in your code.
However, the XML file is a relatively ideal way.
Simple, I will complete the configuration directly in the code and save the configured MovieLister object in the field named lister: Class ifacetester ... Private Movielister Lister; Private Void ConfigureLister () {ColonMovieFinder Finder = New ColonMoviefInder (); Finder.injectFileName ("Movies1.txt"); list = new movielister (); list .injectfinder (Finder);} Test code can be used directly: class ifcetster ... public void Testiface () {configureLister (); Movie [] Movies = lister.moviesdirectedby ("Sergio Leone"); Assertequals ("Once Upon A Time In the West", Movies [0] .gettitle ());} The biggest benefit of using Service Locator depends on: it eliminates The MovieLister class is dependent on the specific Moviefinder. In this way, I can give the MovieLister class to a friend, let them insert a suitable MoviefInder implementation according to their own environment. However, the Dependency Injection mode is not the only means of breaking this depends on the relationship, and another method is to use the Service Locator mode. The basic idea behind the Service Locator mode is that there is an object (ie the service locator) knows how to get all the services you need to apply. That is, in our example, the service locator should have a method for obtaining a MoviefInder instance. Of course, this is just that the trouble is changed. We still have to get the service locator in Movielister, and finally the dependence is shown in Figure 3: Figure 3: Relying on the dependence after using the Service Locator mode, I put The ServiceLocator class is implemented as a Singleton's registry, so MovieLister can get a MOVIEFINDER instance through ServiceLocator when instantiation. class MovieLister ... MovieFinder finder = ServiceLocator.movieFinder (); class ServiceLocator ... public static MovieFinder movieFinder () {return soleInstance.movieFinder;} private static ServiceLocator soleInstance; private MovieFinder movieFinder; and injecting the same way, we have Configure the service locator. Here, I am directly configured in the code, but designing a mechanism for obtaining data through profiles is not difficult.
class Tester ... private void configure () {ServiceLocator.load (new ServiceLocator (newColonMovieFinder ( "movies1.txt")));} class ServiceLocator ... public static void load (ServiceLocator arg) {soleInstance = arg;} public ServiceLocator (MovieFinder movieFinder) {this.movieFinder = movieFinder;} The following are 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 often hear such an argument: such a service locator is not a good thing, because you can't Replace the service implementation it returns, resulting in testing of them. Of course, if your design is very bad, you will have this trouble; but you can also choose a good design. In this example, the ServiceLocator instance is just a simple data container, just do some simple modifications to it, allowing it to return the service implementation for the test. For more complex situations, I can derive multiple subclasses from ServiceLocator and pass instances of subtypes to the class variables of the registry. In addition, I can modify the static method of ServiceLocator to call the method of the ServiceLocator instance instead of accessing instance variables. I can also use thread-specific storage mechanisms to provide threaded service locator. All all this improvements do not need to modify the user of ServiceLocator. An improved ideology is that the service locator is still a registry, but not Singleton. Singleton is indeed a simple way to implement the registry, but this is just a decision when an implementation can easily change it. This simple implementation method for the locator provides the separated interface There is a problem: The MovieLister class will depend on the entire ServiceLocator class, but it is just a service provided by the latter. We can provide a separate interface for this service, reducing the dependence of MovieLister's dependence on ServiceLocator. In this way, Movielister does not have to use the entire serviceLocator interface, just declare the part of the interface it wants to use. At this point, the provider of the Movielister class should also provide a locator interface, and the user can obtain the MoviefInder instance through this interface.
public interface MovieFinderLocator {public MovieFinder movieFinder (); locator service transactions needed to accomplish the above interfaces, provide the ability to access MovieFinder instance: 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; you should have noticed: Because you want to use the interface, we can not directly access the services through static methods - we must First get the locator instance through the ServiceLocator class, then use the locator instance to get the service we want. The dynamic service locator is an example of a static positioner - the ServiceLocator class has a corresponding method for each service you need. This is not the only way to achieve the service locator, you can also create a dynamic service locator, you can register any of the services you need and which service is determined in the running period. In this example, ServiceLocator uses a map to save service information, and no longer saved this information in the field. In addition, ServiceLocator also provides a general method for obtaining and loading service objects. 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);} There is also a need to configure the service locator to load the service object with the appropriate keyword into the locator: Class Tester. ..private void configure () {ServiceLocator locator = new ServiceLocator (); locator.loadService ( "MovieFinder", newColonMovieFinder ( "movies1.txt")); ServiceLocator.load (locator);} I use the same service object class name String string as a service object key: class movielister ... moviefinder finder = (moviefinder) ServiceLocator.getService ("Moviefinder"); Overall, I don't like this way. Undoubtedly, such a service locator has stronger flexibility, but it is not intuitive enough. I only find a service object with a keyword in the form of text. In contrast, I appreciate the way "through a way to get the service object", because this allows the user to clearly know how to get a service from the interface definition.
Two modes with Avalon balance service locator and dependency injection Dependency Injection and Service Locator are not mutually exclusive, you can use them at the same time, the Avalon framework is an example. Avalon uses a service locator, but the information "How to Get Locator" is informing the component by injecting. For the previous example have been used, Berin Loritsch sent to me a simple Avalon implementation version: public class MyMovieLister implements MovieLister, Serviceable {private MovieFinder finder; public void service (ServiceManager manager) throws ServiceException {finder = (MovieFinder) manager.lookup ("Finder");} Service method is an example of interface implantation, which allows the container to inject a ServiceManager object into the MyMovielister object. ServiceManager is a service locator. In this example, MyMovieLister does not save the ServiceManager object in the field, but immediately find the MoviefInder instance, and save the latter. Make a choice until now, I have been elaborating your own views on these two modes (Dependency Injection mode and ServiceLocator mode) and their changes. Now, I am going to discuss their advantages and disadvantages in order to point out their respective scenes. Service Locator vs. Dependency Injection First, we face the choice between Service Locator and Dependency INJECTION. It should be noted that although the simple example we have insufficient, it is actually the basic decoupling capability - no matter which mode is used, the application code does not rely on the specific implementation of the service interface. The most important difference between the two is that this "concrete implementation" is provided to the application code. When using Service Locator mode, the application code sends a message directly to the service locator, explicitly requires the implementation of the service; when using the Dependency Injection mode, the application code does not issue an explicit request, and the implementation of the service will naturally appear in the application code. In this, this is the so-called "control reversal" control reversal is a common feature of the framework, but it also requires you to pay a certain price: it will increase the difficulty of understanding, and bring a certain difficult to debug. So, overall, unless necessary, I will try to avoid using it. This doesn't mean that the control is not easy, but I think it will be appropriate to use a more intuitive solution (such as Service Locator mode) in many cases. A key difference is that when using the Service Locator mode, the user must rely on the service locator. The locator can hide the user's dependence on the specific implementation of the service, but you must first see the locator itself. Therefore, the answer to the question is very clear: Selecting Service Locator or dependency inJection, depending on whether "dependency on the locator" will bring you trouble. Dependency Injection Mode can help you see the dependencies between components: You can only see the entire dependency of the mechanism (such as constructors), you can master the entire dependency.
When using Service Locator mode, you must search for the call to the service locator everywhere in the source code. IDE with full-text search capabilities can be slightly simplified, but it is better to directly observe the structure of the structure or the setting method. This choice mainly depends on the nature of the service user. If there are many different classes in your application to use a service, then the application code is not a big problem with the dependency of the service locator. In the previous example, I have to give the Movielister class to a friend. In this case, it is very good to use the service locator: My friends only need to configure the locator (by configuration file or some configuration The code) allows it to provide the right service implementation. In this case, I can't see what the control of the Dependency Injection mode is in turn to the attractive place. However, if you think of MovieLister as a component, you have to use the application written by others to use, and the situation is different. At this time, I can't predict what kind of service locator API will use, and each user may have its own service locator and cannot be compatible with each other. One solution is to provide a separate interface for each service, and the user can write an adapter that makes my interface cooperate with their service locator. But even so, I still need to find the interface I specified in the first service locator. And once the adapter is used, the simplicity provided by the service locator is greatly weakened. On the other hand, if you use the Dependency Injection mode, there will be no dependencies between the component and the injector, so the component cannot obtain more services from the injector, and can only get those provided in configuration information. This is also one of the limitations of the Dependency Injection mode. One common reason that people tend to use the Dependency Injection mode is: it simplifies test work. The key here is: For testing, you must easily switch between "real service implementation" and "The 'Pseudo' Components for Test". However, if you think from this perspective, the Dependency Injection mode and Service Locator model do not differ much: both can support the insertion of the "pseudo" component well. The reason why many people have "Dependency Injection Mode more beneficial to test", I guess because they did not work hard to ensure the replaceability of the service locator. This is where you continue to test the role: If you can't easily use some "pseudo" components to test it, this means that your design has a serious problem. Of course, if the component environment has very strong aggressive (like the EJB framework), the test problem will be more serious. My point is: I should try to reduce this type of framework impact on application code, especially if you don't do anything that can slow "edit-execution". Subjecting the heavyweight component with a plugin mechanism helps the test process, which is the key to the practice of testing drive development (TDD). Therefore, the main problem is whether the author of the code wants to write itself from its own control and is used in another application. If the answer is affirmative, then he can't do any assumption on the service locator - even the smallest hypothesis will bring trouble to the user. Configuration sub-injects VS. Settings When injecting a combined service, you have to follow a certain agreement before you may assemble all things. The advantage of relying on injection is mainly: it only requires a very simple agreement - at least for the implantation of the constructive sub-injection and value method. Compared to both, the aggression of interface injection is much stronger, and it is not so obvious compared to the advantages of Service Locator mode.
So, if you want to provide a component to give multiple users, construct sub-injection and value method injection looks very attractive: You don't have to join what the curved things in the component, the injector can be quite easy to put all Things are configured. Selection of value function injection and configuration sub-injection is quite interesting because it reflects some more common problems that are programmed to object programming: Where should I fill the field, constructor or value method? All along, my preferred practice is to try to create a complete, legal object in the construction phase - that is, fill the object field in the construct. The benefits of doing this can be traced back to Kent Beck two modes introduced in the SMALLTALK BEST PractICE PATTERNS: Constructor Method and Constructor Parameter Method. Constructions with parameters can clearly tell you how to create a legitimate object. If you create a legal object, you can also provide multiple constructs to illustrate different combined methods. Another benefit of constructing sub-initialization is: You can hide any unality of field - as long as it does not provide value to it. I think this is very important: if a field should not be changed, "There is no method for this field" clearly explains this. If you complete the initialization by setting the value method, the exposed method method is likely to be your heart forever. (In fact, in this time I am more willing to avoid the usual value-setting method, but use the method names such as initfoo to indicate that the method should only call at the beginning of the object.) However, the world has a total exception . If the parameters are too many, the structure will be messy, especially for languages that do not support keyword parameters. Indeed, if the structure of the sub-parameter list is too long, it is usually marked that the object is too busy, and it should be split into several objects, but sometimes there are so many parameters. If there is more than one way to construct a legal object, it is also difficult to describe this information through a structure, since the construct is only differentiated by the number and type of the parameters. This is where the Factory Method mode is applicable, and the factory method can accomplish your own task by means of a combination of multiple private constructors and settings. The problem with classic Factory Method mode is that they tend to appear in the form of a static method, you can't declare them in the interface. You can create a factory class, but it turns into another service entity. "Factory Services" is a good trick, but you still need to instantiate this factory object in some way, and the problem is still not resolved. If the parameters to be incoming are simple types like a string, constructor injection will also bring some trouble. When using the value method to inject, you can explain the use of the parameters in the name of each setting method; and when using the constructive sub-injection, you can only determine the role of each parameter by the location of the parameters, and remember the parameters. The correct position is obviously more difficult. If the object has multiple constructors, there is a inheritance relationship between the objects, and things will become particularly annoying. In order to let all things are initialized correctly, you must forward the call to the subclass structure to the superclass constructor, and then process your own parameters. This may result in further expansion of the structure of the structure. Despite these defects, I still recommend that you first consider the structure of the sub-injection. However, once the problems mentioned earlier are really a problem, you should be ready to use the value method to inject. Between several teams of the Dependecy Injection mode as the core part of the framework, "constructive sub-injection or value method injection" triggered a lot of debate. However, now, most people who develop these frameworks have been aware that no matter which implantation mechanism, it is necessary to support the two.
The code is configured vs. Profile Another problem is relatively independent, but it is often involved with other issues: How to configure the assembly of the service, is also directly encoded by the configuration file or directly encoded? A separate profile will be more appropriate for most applications that need to be deployed multiple. The profile is almost all XML files, and XML is also very suitable for this purpose. However, sometimes it is more simple to implement assembly directly in the program code. For example, a simple application, there is no change in deployment, and use a few code to configure much more clear than the XML file. In contrast, sometimes the assembly of the application is very complex, involving a large number of conditions. Once the configuration logic in the programming language begins to become complex, you should use a suitable language to describe configuration information, making program logic clearer. Then you can write a builder class to complete the assembly. If you use a constructor, you can provide multiple constructors, and then choose between them through a simple profile. I often find that people are eager to define profiles. The programming language usually provides a simple and powerful configuration management mechanism. Modern programming languages can also compile the program into small modules and insert them into large systems. If the compilation process will cost, the scripting language can also be helpful in this regard. It is usually considered that the configuration file should not be written in a programming language because they need to be edited by system managers who do not understand programming. However, how big is this chance? Do we really hope that system managers who don't understand programming will change the transaction isolation level of a complex server-side application? Only when it is very simple, the non-programming configuration file has the best results. If the configuration information begins to become complex, you should consider selecting a suitable programming language to write a configuration file. In the Java world, we have heard that there is no harmony from the configuration file - each component has its own profile, and the format is different. If you want to use a hit like this, you have to maintain a hit profile, which will soon let you die. Here, my suggestion is: Always provide a standard configuration, so that the programmer can easily complete the configuration work through the same programming interface. As for other configuration files, only them as an optional feature. With this programming interface, developers can easily manage profiles. If you have written a component, you can choose how to manage configuration information by the user: Use your programming interface, directly manipulate the configuration file format, or define their own configuration file format, and put it with your programming interface Combine. The key to separating configuration and using all this is that the configuration of the service should be separated from the use. In fact, this is a basic design principle - separation interface and implementation. In the object-oriented program, we decide which class is used in a local conditional logic to decide which class of specific instantiation, and the subsequent conditional branch is implemented by the polymorphism, rather than continuing to repeat the previous conditional logic, which is "separation interface and implementation" the rules. If the interface is only "useful" for a piece of code, it is a big event that you need to use external elements (such as components and services). The first question here is: Do you want to postpone the "Select Specific Class" decision to the deployment phase. If yes, then you need to use insert technology. After using the insertion technology, the assembly of the plug-in is in principle separated from the rest of the app, so you can easily replace different configurations for different deployments. This configuration mechanism can be implemented by a service locator (Service Locator mode), or directly complete the dependency injection (Dependency INJECTION mode).