4. Fundamentals

4.1 Overview

Plug-in architectures do not have to be designed and implemented from scratch since already some sophisticated frameworks exist. The architecture of a plug-in framework shall work with all applications of the intended domain. The Test Suite product is in the domain of Windows-based applications. It is very difficult to design a framework architecture that is applicable for a variety of applications. This means that flexibility and extensibility are essential for a framework design [GHJV95, p. 27]. Additionally, low coupling between the framework and the application is important. Modifications on the framework should not bring much migration work for the application. Gamma et al. write the following about these issues:

A framework that addresses them using design patterns is far more likely to achieve high levels of design and code reuse than one that doesn't. Mature frameworks usually incorporate several design patterns. The patterns help make the framework's architecture suitable to many different applications without redesign [GHJV95, p. 27].

Basic knowledge of the most important design patterns and concepts in the field of plug-in architectures help to evaluate different plug-in frameworks. Particularly, it is easier to estimate how a framework will affect the whole application design. The previous chapter gives a short introduction into the Plugin pattern. Whereas this chapter addresses some further important design patterns and concepts used in the field of plug-in architectures.

4.2 Dependency Injection

The Dependency Injection (DI) pattern arose from the Java community when they tried to find alternatives to the high complex enterprise Java world. This pattern helps to wire components of different layers together. The components are often developed by different teams with minor knowledge of each other. A well-known task for an architect is to compose the components into a coherent overall application. A number of design patterns, such as Factory Method, Abstract Factory, Builder, etc. [GHJV95], are already devoted to deal with this issue. An alternative for implementing these design patterns is to use a reliable framework. Some frameworks, which deal with the wiring of components, are known as Inversion of Control (IoC) container. They are also referred as lightweight containers because of the minor performance impact and the lower application complexity compared to other container technologies (e.g. Microsoft .NET Framework Enterprise Services) [Caprio05].

Inversion of Control is a general principle which is often used to characterize frameworks [Fowler05]. It is also known as Hollywood principle "Don't call us, we'll call you". It means that the framework takes control over the program and calls the code of the client. For example, a GUI framework calls a method of the client if a button is pressed. Fowler writes that this term is too general and does not suite as a description for the pattern used by IoC containers [Fowler04a]. Thus the name Dependency Injection is used for this particular pattern.

In Dependency Injection a client object (Birthday printer) declares its dependencies (Address book). Dependencies are objects (Address book Implementation) which are required by the client to fulfill its tasks. The client is not responsible to get the dependent objects. This is done by an external mechanism which is known as Assembler. The specific characteristic of this pattern is that the client does not have any dependencies to the Assembler or any other object for locating the dependent objects. The resulting dependencies between the classes can be seen in Figure 2.

Figure 2: UML class diagram for dependency injection [Fowler04]
Figure 2: UML class diagram for dependency injection [Fowler04].

The tasks of the Assembler are:

This process is shown in Figure 3, except of reading the dependency information.

Figure 3: UML sequence diagram for dependency injection [Fowler04].
Figure 3: UML sequence diagram for dependency injection [Fowler04].

The Dependency Injection pattern does not define in which way the dependencies have to be declared. A popular approach is to write the dependencies in an external file, particular in an XML file. Another possible solution seen in DI frameworks is the using of associated meta-data direct in the programming language like Attributes in .NET or Annotations in Java.

How the Assembler locates the dependent object is not specified. The Plugin pattern can be used for this task. A common way for the configuration of the Plugin factory is the use of an XML file. XML files can be easily changed for different deployment scenarios. Nevertheless, other approaches can be useful too like retrieving the configuration dynamically from a server.

The injection can be done in various ways. Fowler writes that there are three main styles of dependency injection [Fowler04a]:

An example for Setter Injection can be seen in Figure 3. The Assembler calls the setter method setAddressBook of the object Birthday printer to inject an implementation of the Address book interface.

An alternative to the previous approach is that the Birthday printer class does not provide the setter method. Instead, it requires the Address book implementation already in the constructor (Listing 1). The Assembler passes the implementation of the Address book interface to the Birthday printer constructor. This procedure is called Constructor Injection.

 1  public class BirthdayPrinter
 2  {
 3      private AddressBook _book;
 4  
 5      public BirthdayPrinter(AddressBook book)
 6      {
 7          _book = book;
 8      }
 9  
10      ...

Listing 1: Extract of the client class which is configured by constructor injection.

Interface Injection is not relevant for this diploma thesis because the investigated solutions do not support this type of injection. Most of the lightweight containers do not promote this approach. According to Fowler, the reason is the more invasive nature of Interface Injection since many interfaces are required to get it working [Fowler04a].

4.3 Service Locator

An alternative to Dependency Injection is the Service Locator pattern [Sun02]. Basically, it uses a central object (Service locator) that knows how to locate the dependent objects (Address book Implementation). The dependent objects are referred as services in this context. The client (Birthday printer) requests the concrete implementation of the Address book interface from the Service locator. In contrast to the Dependency Injection pattern the client takes an active role in retrieving the concrete implementation. Thus, it has a dependency to the Service locator (Figure 4).

Figure 4: UML class diagram for a service locator [Fowler04].
Figure 4: UML class diagram for a service locator [Fowler04].

This time the tasks of the Assembler are (Figure 5):

Figure 5: UML sequence diagram for a service locator [Fowler04].
Figure 5: UML sequence diagram for a service locator [Fowler04].

How the Assembler is going to find the right implementation is neither specified by this pattern nor specified by the Dependency Injection pattern. In this case the Plug-In pattern is a possible solution too.

The Service locator class can be realized as a Singleton [GHJV95, p. 127]. If the Service locator should provide an implementation depending on the application context, the Registry pattern [Fowler03, p. 480] is a good alternative. For example, the Registry is able to provide a separate database connection service for every thread which simplifies the development of multi-threaded applications.

The class diagram (Figure 4) shows the Service locator class with the service specific methods initAddressBook and getAddressBook. These methods can be written in a more general way, so that different services can be registered and retrieved. The example code (Listing 2) shows how generics can be used to write a general ServiceLocator class.

 1  public class ServiceLocator
 2  {
 3    public static T get<T>() { ... }   // Instead of getAddressBook
 4
 5    public static void register<T>(T service) { ... }  // Instead of
 6                                                       // initAddressBook
 7    public static void deregister<T>() { ... }
 8  }

Listing 2: A generic service locator implementation.

In Listing 2 the type T is used to identify the service. Alternatively, a string or integer value could be used as an identifier. Using the types has the advantage that the refactoring and error checking capabilities of the IDE still works. The disadvantage is that only one service of the same type can be registered. Thus, this approach is not as flexible as using string or integer values as an identifier [Nilsson06, p. 373].

4.4 Attributes vs. Configuration Files

The .NET Framework provides Attributes for adding meta-data to an assembly, a type, a type member or other targets. The Attributes can rather be used to declare information in the code than creating external configuration files. This is also known as declarative programming. The meta-data can be read by an application through the reflection API of the .NET Framework.

Declarative programming is an interesting alternative for configuring frameworks to the classic configuration files. The main advantage is that the Attributes are associated directly with a target. This can save a lot amount of configuration as it is shown in Listing 3 and Listing 4.

 1  [ServiceDependency]
 2  public IMovieFinder MovieFinder
 3  {
 4      set { _movieFinder = value; }
 5  }

Listing 3: Configuration of setter injection with an Attribute.

 1  <objects>
 2    <object id="MyMovieLister" type="MovieLister.MovieLister,
 3        MovieLister">
 4      <property name="MovieFinder" ref="MyMovieFinder" />
 5    </object>
 6    ...
 7  </objects>

Listing 4: Configuration of setter injection with an external XML file.

These both code examples list a setter injection configuration for the same component. Listing 3 uses a ServiceDependency attribute for the configuration. The configuration is minimal as it consists only of the Attribute type name. The Attribute is directly above the MovieFinder property and thus, the meta-data is attached to this property. Listing 4 configures a setter injection for another Dependency Injection implementation in an external XML file. Here the configuration consists of the lines 2, 3 and 4. In this case, more information is necessary to configure the injection. Most of this information is necessary to address the MovieFinder property. If the configuration has to refer to the code, the approach with Attributes needs less amount of information. Furthermore, the maintenance is simplified because the code and configuration is at the same place. This makes in many cases of component refactoring, modifications to the configuration unnecessary. For example, the renaming of the MovieFinder property does not require a change to the information declared by the Attribute.

Attributes also have a few drawbacks. The main weakness is that they do not physically separate the configuration from the code. If the Attributes are overused, the source code can become messy [Sosnoski05]. Additionally the code requires a reference to the assembly that provides the Attributes. This reference can be a problem if the code should be independent of the framework or the library (Framework dependencies, p. 38). The use of a configuration file does not have these drawbacks.

Sosnoski [Sosnoski05] writes in more detail about the differences of using meta-data inlined with the code and configuration files. He uses the term Annotations instead of Attributes as it is the Java keyword for the same concept. Declarative programming and external configuration files are widespread for framework configuration. Understanding the impact of these concepts on the application design helps to evaluate the frameworks.

4.5 Summary

The Dependency Injection and the Service Locator patterns are two possible ways to wire different components together. Wit the Service Locator pattern the client retrieves its dependent objects by requesting a central object. In this case, the client has an active role to get hold of the needed objects. In contrast, the client in the Dependency Injection pattern has a passive role. An external mechanism is responsible that the client gets the dependent objects. This mechanism is known as injection.

These patterns are often seen in plug-in architectures. Understanding them can help to evaluate the different architectures and frameworks. Dependency Injection has a minor impact for the application design whereas Service Locator is easier to understand and to debug. In the Service Locator pattern it is also possible to provide different objects depending on the application context. Which pattern should be preferred is dependent on the requirements.

A Dependency Injection implementation requires some kind of configuration. The most popular ways for configuring are the use of Attributes and the use of external configuration files. Both concepts have different advantages and drawbacks. Which of them should be preferred depends on the requirements defined for the application.