John Gobble 0For years now Dependency Injection (DI) has been at the forefront of discussion within the software architecture and development communities. Even though the concept of DI is not earth-shattering in its basic form, the act of NOT practicing DI has yielded mountains of legacy systems and code that become increasingly difficult to support over time. In this 3-part series, I’d like to tackle an explanation the design principles that govern DI and prove that becoming a DI practitioner is a requirement for you to succeed in your software design and coding endeavors.
Part 1 – Live Free, DI Hard<<< You are Here
Part 3 – DI/IoC Tools
The Basics of NOT Injection
At times I understand a concept best when it is contrasted with another, maybe more familiar, maybe less than best practice concept. So let’s start with the question: What isn’t DI? To the sage I call embedded dependencies and “Static” (often global) lookup services. One of the most common embedded dependencies that I’ve seen throughout the years is the model and connection resource. This dependency may include integration concerns such as a connection string or data access logic, such as is available through an Object Relation Model (ORM) technology or through hard-coded/embedded queries and object hydration methods.
Such embedded dependencies restrict your code to something concrete (e.g., Hibernate or Linq-to-SQL), which in turn, forces your application code to use that >>>fill in the blank<<< technology or access your data store in rigid manner forever. Likewise, using a static, often global model / data service ties your code to that service and its concrete types at compile time and forces you to use the service for all >>>fill in the action<<< within your code or system going forward.
A Better Approach
By now you should get the point: avoid embedded, concrete versions of every dependency at all costs! But before you can avoid them, you need to understand what your alternatives are. However, the way you answer this question will depend on where you code is in its lifecycle: design or support.
If you have the benefit of starting an application from scratch, use the following guideline to establish DI excellence:
1. Identify your future dependency, which may include file system, database, or service access, and stand up a private member for each dependency within your code that depends on an Interface representation of the dependency.
2. Determine when these dependencies should be received and set within your code. The most common forms of receiving dependencies are through your class constructor and through properties or public setters on your class. Most of the time I opt for receiving dependencies on the constructor, since it guarantees that the dependency will be available when the class is created. However, the consumer of your class may not always have access a dependency when it is created (e.g., your logging framework hasn’t yet been initialized), at which point you may need to use property / public setter injection.
3. Write your class code so that it interacts with the dependency’s Interface rather than a concrete version of the dependency (e.g., IEmployeeDao rather than AdoEmployeeDao, which implements IEmployeeDao).
If the code you’re dealing with is in a support phase, then follow these steps to factor out dependencies:
1. Identify your existing dependency, which may include file system, database, or service access, and stand up a private member for each dependency within your code that depends on an Interface representation of the dependency.
2. Systematically work through your dependencies do determine if they should be received through the constructor or through property injection.
3. Create private members within your class that depend on the Interface of each dependency rather than on the current concrete.
4. Substitute each reference to the dependency with your local, Interface member of that dependency. Refactor the consumer of your class, when possible, and force the consumer to instantiate each dependency. Later we’ll discuss how to handle this “automagically” using an Inversion of Control (IoC) container.
Once you begin the practice of good DI hygiene, you’ll begin to realize the amazing benefits of NOT relying on concrete and static dependencies. The results are that your code can receive other implementations of the same class (known as loose coupling), and it, in turn, becomes more useful to its consumers.