Understanding Software Design Patterns

To understand what dependency injection is and how you can apply it to MVC applications, we'll need to talk about software design patterns. A software design pattern is used to formalize the description of a problem and a solution to that problem, so that developers can use the pattern to simplify the identification and communication of common problems and solutions.

The design pattern isn't necessarily to claim the invention of something new or novel, but rather exists to give a formal name and definition from common practices in the industry. When you read about a design pattern, you may recognize it from solutions you've used in particular problems in the past.

Design Patterns

The concept of patterns and a pattern language is generally credited to Christopher Alexander, Sara Ishikawa, and Murray Silverstein in their book A Pattern Language: Towns, Buildings, and Construction published in 1977. The book presents a view of architecture and urban planning in terms of patterns, which they used to describe problems (and solutions to those problems).

In the software development world, Kent Beck and Ward Cunningham were among the first to adopt the idea of a pattern language, and presented their experiment at the 1987 OOPSLA conference. Perhaps the first and best known comprehensive treatment on core software development patterns was the book Design Patterns: Elements of Reusable Object-Oriented Software, published in 1994. The book is often called the “Gang of Four” (or “GoF”) book, named so because of the four authors: Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides.

Since that time, the use of software patterns has exploded, and several volumes of work have been devoted to the subject by such luminaries as Martin Fowler, Alan Shalloway, and James R. Trott.

Design Pattern: Inversion of Control

Everybody has probably seen (or written) code like this before:

public class EmailService
{
       public void SendMessage() { ... }
}

public class NotificationSystem
{
       private EmailService svc;

       public NotificationSystem()
       {
             svc = new EmailService();
       }

       public void InterestingEventHappened()
       {
             svc.SendMessage();
       }
}

Reading the code, you can see that NoticicationSystem has a dependency on EmailService. When a component has a dependency on something else, we call that coupling. In this case, the notification system creates an instance of the e-mail service directly inside of the notification system's constructor; in other words, the notification system knows exactly what kind of service class it's creating and consuming. This coupling is an indication of how inter-connected your code is. A class that knows a lot about the other classes it interacts with (as in the preceding example) is said to be tightly coupled.

In software design, tight coupling is often considered to be a liability in your design. When one class knows explicitly about the design and implementation of another class, you raise the risk that changes to one class will break the other class.

Also consider another potential problem with the design above: What if the notification system wants to start sending other kinds of messages when the interesting event happens? For example, maybe the administrator of the system wants to start getting text messages instead of e-mails, or also wants to start logging every notification into a database so they can be reviewed at a later time. To enable this behavior, we have to dive back into the implementation of the notification system.

To reduce coupling, you generally take two separate but related steps:

1. Introduce an abstraction layer between two pieces of code: To perform this step in .NET, you often use interfaces (or abstract classes) to represent the abstractions between two classes. Using the previous example, you introduce an interface to represent your abstraction, and ensure that your code only calls methods or properties on that interface. Your private copy becomes an instance of that interface rather than the concrete type, and we limit the knowledge of the actual type to the constructor, as shown below:

public interface IMessagingService
{
       void SendMessage();
}

public class EmailService : IMessagingService
{
       public void SendMessage() { ... }
}

public class NotificationSystem
{
       private IMessagingService svc;

       public NotificationSystem()
       {
              svc = new EmailService();
       }

       public void InterestingEventHappened()
       {
              svc.SendMessage();
       }
}

2. Move the responsibility of choosing the implementation of the abstraction to outside of the consuming class: You need to move the creation of the EmailService class outside of NotificationSystem.

note

Moving the creation of dependencies outside of the class that consumes those dependencies is called the inversion of control pattern, named so because what you're inverting here is the creation of dependencies (and as such, removing the control of dependency creation from the consumer of the class).

The inversion of control (IoC) pattern is abstract; it says that one should move dependency creation out of the consumer class, but it doesn't talk about exactly how to achieve that. In the following sections, we'll explore two popular ways to apply the inversion of control pattern to achieve this responsibility shift: service locator and dependency injection.

Design Pattern: Service Locator

The service locator pattern says that inversion of control is achieved by having components get their dependencies through an external component known as the service locator. Sometimes a service locator will be a very specific interface, with strongly typed requests for specific services, and sometimes it may show up as a very generic way to request services of any arbitrary type.

Strongly-Typed Service Locator

A strongly-typed service locator for the sample application might have an interface like this:

public interface IServiceLocator
{
       IMessagingService GetMessagingService();
}

In this case, when you need an implementation of IMessagingService, you know to call GetMessagingService. The method returns exactly IMessagingService, so you won't need to cast the result.

You'll notice that I'm showing the service locator as an interface here rather than as a concrete type. Remember that one of your goals is to reduce the tight coupling between components; this includes the coupling between the consumer code and the service locator itself. If the consumer code is coded against IServiceLocator that means you can substitute alternative implementations at run time as appropriate. This can have tremendous value in unit testing, as is discussed in the next chapter.

Now if you re-write NotificationSystem in terms of the strongly-typed service locator, it might look like this:

public class NotificationSystem
{
       private IMessagingService svc;

       public NotificationSystem(IServiceLocator locator)
       {
              svc = locator.GetMessagingService();
       }

       public void InterestingEventHappened()
       {
              svc.SendMessage();
       }
}

We're assuming that anybody who creates an instance of NotificationSystem will have access to a service locator. What's convenient is that if your application creates instances of NotificationSystem through the service locator, then the locator can pass itself to the NotificationSystem constructor; if you create instances of NotificationSystem outside of the service locator, you'll need to provide an implementation of the service locator to NotificationSystem so that it can find its dependencies.

Why might you choose a strongly-typed service locator? It's fairly easy to understand and consume: you know exactly what kinds of things you can get from this service locator (and, perhaps just as importantly, what kinds of services you cannot get). Additionally, if you needed some parameters to create the implementation of IMessagingService, you can request them directly as parameters to the call to GetMessagingService.

Why might you not choose a strongly-typed service locator? First, this service locator is limited to creating objects of types that have been predetermined at the time that IServiceLocator was designed. It's not capable of creating any other types. Second, it could become a maintenance burden having to constantly expand the definition of IServiceLocator as you find need for more services in your application.

Weakly-Typed Service Locator

If the downsides of a strongly-typed service locator seem to outweigh the upsides, you could consider using a weakly-typed service locator instead. That might look something like this:

public interface IServiceLocator
{
       object GetService(Type serviceType);
}

This variant of the service locator pattern is much more flexible, because it allows you to ask for any arbitrary service type. It's called a weakly-typed service locator because it takes a Type, and returns an un-typed instance (that is, an object of type Object). You need to cast the result of the call to GetService to get the correctly typed object back.

What would NotificationSystem look like now with this version of the service locator? It might look something like this:

public class NotificationSystem
{
       private IMessagingService svc;

       public NotificationSystem(IServiceLocator locator)
       {
              svc = (IMessagingService)locator.GetService(typeof(IMessagingService));
       }

       public void InterestingEventHappened()
       {
              svc.SendMessage();
       }
}

This code is a little less pretty than the previous version, owing primarily to the required casting to IMessagingService. With the introduction of generics in .NET 2.0, you could have also included a generic version of the GetService method:

public interface IServiceLocator
{
       object GetService(Type serviceType);
       TService GetService<TService>();
}

The contract for such a method implies that it will return an object already cast to the correct type (notice that its return type is TService now instead of Object). That makes the consuming code quite a bit cleaner:

public class NotificationSystem
{
       private IMessagingService svc;

       public NotificationSystem(IServiceLocator locator)
       {
              svc = locator.GetService<IMessagingService>();
       }

       public void InterestingEventHappened()
       {
              svc.SendMessage();
       }
}

Why bother with the object version?

You might be asking yourself why we even bother having the object version of GetService, rather than just having our API consist of only the generic version. Because it saves us a cast, we will be calling the generic version pretty much everywhere, right?

In practice, you find that not every consumer who calls an API will know the exact type they'll be calling it with at compile time. An example you'll see later is the case where the MVC framework is trying to create controller types. MVC knows what type the controller is, but it only discovers that at run time, not at compile time (for example, mapping a request for /Home into HomeController). Because the type parameter of the generic version is not only for casting but also for specifying the service type, you would not be able to call the service locator without resorting to reflection.

The downside to this approach is it forces implementers of IServiceLocator to create two nearly identical methods instead of one. This unfortunate duplication of effort can be eliminated with a feature introduced into .NET 3.5: extension methods.

Extension methods are written as static methods on a static class, and utilize the special this keyword on their first parameter to indicate what type this extension method is attached to. Separating the generic GetService method into an extension method yields the following:

public interface IServiceLocator
{
       object GetService(Type serviceType);
}

public static class ServiceLocatorExtensions
{
       public static TService GetService<TService>(this IServiceLocator locator)
       {
              return (TService)locator.GetService(typeof(TService));
       }
}

Now we've eliminated the duplication and extra effort associated with the generic version of the method. We write it once and everybody can take advantage of our implementation.

Extension Methods in ASP.NET MVC

The MVC framework makes heavy use of extension methods. Most of the HTML helpers that you use to generate forms inside of your views are actually extension methods on the HtmlHelper, AjaxHelper, or UrlHelper class (which are the types of objects you get when you access the Html, Ajax, and Url objects in a view, respectively).

Extension methods in MVC are in their own separate namespace (usually System.Web.Mvc.Html or System.Web.Mvc.Ajax). The MVC team did this because they understood that the HTML generators may not exactly match those that you want for your application. You could write your own HTML generator extension methods, customized to your needs. If you remove MVC's namespace(s) from the Web.config file, none of the built-in extension methods will show up, allowing you to have your own and eliminate MVC's. Or, you may choose to include both. Writing the HTML generators as extension methods gives you the flexibility to decide what's right for your app.

Why might you choose a weakly-typed locator? It allows you to fix many of the downsides of the strongly-typed locator; that is, you get an interface that can create arbitrary types without knowing about them ahead of time, and it reduces your maintenance burden because the interface is not constantly evolving.

On the other hand, a weakly-typed locator interface doesn't really communicate anything about the kinds of services that might be requested, and it doesn't offer a simple way to customize the creation of the service. You could add an arbitrary optional array of objects as “creation parameters” for the service, but the only way you know services would require parameters is by way of external documentation.

The Pros and Cons of Service Locators

Using a service locator is relatively straightforward: You get the service locator from somewhere and ask it for your dependencies. You might find the service locator in a known (global) location, or you might get the service locator provided to you by whoever is creating you. As your dependencies change, your signature stays the same, because the only thing you require to find your dependencies is the locator.

The benefit of the constant signature is at least as much a downside as it is an upside. It creates opacity of requirements for your component: The developers who consume your component can't tell just by looking at the signature to your constructor what your service requirements are going to be. They are forced to consult documentation, which may be out of date, or simply to pass in an empty service locator and see what kinds of things you request.

This opacity of requirements is a strong driver behind choosing your next IoC pattern: dependency injection.

Design Pattern: Dependency Injection

The dependency injection (DI) pattern is another form of the inversion of control pattern, wherein there is no intermediary object like the service locator. Instead, components are written in a way that allows their dependencies to be stated explicitly, usually by way of constructor parameters or property setters.

Developers who choose dependency injection over service location are often making a conscious decision to choose transparency of requirements over opacity. Choosing the transparency of dependency injection also has significant advantages during unit testing, as we will discuss in the next chapter.

Constructor Injection

The most common form of dependency injection is called constructor injection. This technique involves creating a constructor for your class that expresses all of its dependencies explicitly (as opposed to the previous service location examples, where your constructor took the service locator as its only constructor parameter).

Now let's look at what NotificationSystem would look like if designed to support constructor injection:

public class NotificationSystem
{
       private IMessagingService svc;

       public NotificationSystem(IMessagingService service)
       {
              this.svc = service;
       }

       public void InterestingEventHappened()
       {
               svc.SendMessage();
       }
}

In this code, the first benefit is that the implementation of the constructor is dramatically simplified. The component is always expecting whoever creates it to pass the required dependencies. It only needs to store the instance of IMessagingService for later use.

Another benefit is that you've reduced the number of things NotificationSystem needs to know about. Previously, it needed to understand service locators in addition to its own dependencies; now, it is focused solely on its own dependencies.

The third benefit, as alluded to previously, is this new transparency of requirements. Any code that wants to create an instance of NotificationSystem can look at the constructor and know exactly what kinds of things are necessary to make NotificationSystem function. There is no guess work, and no indirection through the service locator.

Property Injection

A less common form of dependency injection is called property injection. As the name implies, dependencies for a class are injected by setting public properties on the object rather than through the use of constructor parameters.

A version of NotificationSystem that uses property injection would look like this:

public class NotificationSystem
{
       public IMessagingService MessagingService
       {
             get;
             set;
       }

       public void InterestingEventHappened()
       {
             MessagingService.SendMessage();
       }
}

This code removes the constructor arguments (in fact, it removes the constructor entirely), and replaces it with a property. This class expects any consumers to provide you with your dependencies via properties rather than the constructor.

The InterestingEventHappened method is now slightly dangerous. It presumes that the service dependency has already been provided; if it hasn't then it will throw a NullReferenceException. You should update the InterestingEventHappened method to ensure that it has been provided with its dependency before using the service:

public void InterestingEventHappened()
{
       if (MessagingService == null)
       {
               throw new InvalidOperationException(
                     "Please set MessagingService before calling " +
                     "InterestingEventHappened()."
               );
       }

       MessagingService.SendMessage();
}

It should be obvious that you've slightly reduced your transparency of requirements here; it's not quite as opaque as using the service locator, but it's definitely more error prone than constructor injection.

With this reduced transparency, you're probably wondering why a developer would choose property injection over constructor injection. Two situations might warrant that choice:

  • If your dependencies are truly optional in the sense that you have some fallback when the consumer doesn't provide you with one, property injection is probably a good choice.
  • Instances of your class might be created in such a way that you don't have control over the constructor that's being called. This is a less obvious reason. You'll see a couple examples of this later in the chapter when we discuss how dependency injection is applied to view pages.

In general, developers tend to favor using constructor injection whenever possible, falling back to property injection only when one of the preceding reasons dictates. Obviously, you can mix both techniques in a single object: Put your mandatory dependencies in as constructor parameters, and your optional dependencies in as properties.

Dependency Injection Containers

One big piece of the puzzle that's missing in both examples of dependency injection is exactly how it takes place. It's one thing to say, “Write your dependencies as constructor arguments,” but it's another to understand how they might be fulfilled. The consumer of your class could manually provide you with all those dependencies, but that can become a pretty significant burden over time. If your entire system is designed to support dependency injection, creating any component means you have to understand how to fulfill everybody's requirements.

Using a dependency injection container is one way to make the resolution of these dependencies simpler. A dependency injection container is a software library that acts as a factory for components, automatically inspecting and fulfilling their dependency requirements. The consumption portion of the API for a dependency injection container looks a lot like a service locator because the primary action you ask it to perform is to provide you with some component, usually based on its type.

The difference is in the details, of course. The implementation of a service locator is typically very simple: You tell the service locator, “If anybody asks for this type, you give them this object.” Service locators are rarely involved in the process of actually creating the object in question. A dependency injection container, on the other hand, is often configured with logic like, “If anybody asks for this type, you create an object of this concrete type and give them that.” The implication is that creating the object of that concrete type will, in turn, often require the creation of other types to fulfill its dependency requirements. This difference, while subtle, makes a fairly large difference in the actual usage of service locators versus dependency injection containers.

More or less, all containers have configuration APIs that allow you to map types (which is the equivalent of saying, “When someone asks for type T1, build an object of type T2 for them”). Many also allow configuration by name (“when someone asks for the type T1 named N1, build an object of type T2”). Some will even attempt to build arbitrary types, even if they have not been preconfigured, so long as the requested type is concrete and not abstract. A few containers even support a feature called interception wherein you can set the equivalent of event handlers for when types get created, and/or when methods or properties get called on those objects.

For the purposes of this book, the discussion of the use of these advanced features is beyond our scope. When you have decided on a dependency injection container, you will typically find documentation online that will discuss how to do advanced configuration operations.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.3.167