Dependencies
A dependency is anything your code knows about. That could be another class, method, or interface. It could also be knowledge of how an object does something. We say that code with a lot of dependencies is tightly coupled, while code with minimal dependencies is loosely coupled.
In addition, code that is polymorphic and exhibits good encapsulation is loosely coupled.
What does that mean?
Consider the method below from BankingApp
. What are its dependencies?
void printAccountBalances() {
ICollection<Account> accounts = new List<Account>();
accounts.Add(new CheckingAccount());
accounts.Add(new SavingsAccount());
accounts.Add(new CreditAccount());
foreach (Account a in accounts) {
Console.WriteLine("Balance is " + a.balance());
}
}
Let's take them in order.
Method parameters
Our method is dependent its Console.WriteLine(String)
method:
Console.WriteLine("Balance is " + a.balance());
Local collection variables
Our method is dependent on ICollection
and List
(and its parameterless constructor):
ICollection<Account> accounts = new List<Account>();
It is also assumes that ICollection
implements IEnumerable
, to allow usage of the enhanced foreach
loop:
foreach (Account a in accounts) {
Account
Our method depends on the existence of an Account
type:
Collection<Account> accounts = new List<Account>();
Also, that it has a balance
method that returns a value whose toString()
method returns a String
appropriate for display purposes:
Console.WriteLine("Balance is " + a.balance());
Types of Accounts
Our method depends on their being three types of accounts, each of which isA Account
: CheckingAccount
, SavingsAccount
, and CreditAccount
. Also, that each has a default (parameterless) constructor:
Collection<Account> accounts = new List<>();
accounts.Add(new CheckingAccount());
accounts.Add(new SavingsAccount());
accounts.Add(new CreditAccount());
The (mostly) entire list
To sum up, it is dependent on the following:
Account
and itsbalance()
methodCheckingAccount
and its default (parameterless) constructorSavingsAccount
and its default (parameterless) constructorCreditAccount
and its default (parameterless) constructorSystem.Collections.Generic.IEnumerable<T>
System.Collections.Generic.ICollection<T>
System.Collections.Generic.List<T>
System.Console.WriteLine()
and itsConsole.WriteLine(String)
method
Dependencies are things that could change and break our code. Tightly coupled code (a lot of dependencies) is fragile.
The collections classes and Console.WriteLine()
are unlikely to change, so we're a bit less worried about those dependencies
Dependency Injection
A family of techniques used to implement Inversion of Control. The simplest of these are constructor injection and direct instance variable injection (aka field injection). Setter injection is also oft-used, but usually a code smell.
Constructor-injection
Let's return to BankingApp
. It has a lot of dependencies. We could use constructor injection to pass control of Account
creation outside of our app. If we were using a framework (C# .NET), we would allow the framework to inject our dependencies.
First, we would give our app a constructor that accepts Account
s and an instance variable to store them:
private ICollection<Account> accounts;
public BankingApp(ICollection<Account> accounts) {
this.accounts = accounts;
}
Note that we have already eliminated the dependecy on List
! We only know that we're dealing with a ICollection
.
Our new improved method
void printAccountBalances() {
foreach (Account a in accounts) {
Console.WriteLine("Balance is " + a.balance());
}
}
Now we have eliminated the dependecy on specific Account
types as well as how they are constructed. Our method no longer controls how accounts are created or which accounts are created.
Our leaner and meaner list of dependencies
We still have several of our stable dependencies on core C# classes. Our acceptance of dependencies should vary inversely with how likely they are to change:
System.Collections.Generic.IEnumerable<T>
System.Collections.Generic.ICollection<T>
System.Console.WriteLine()
and itsConsole.WriteLine(String)
method
But our dependencies on application-specific classes have now been reduced to Account
and its balance()
method.
In our simple example, we still rely on balance()
returning something with a toString()
method that renders a suitable String
for display. In the real world, we would create another class responsible for converting balance to a String
, so we would eliminate this dependency as well.
Inversion of Control
Often expressed as the Hollywood Principle: "Don't call us, we'll call you." (That links to Ward Cunningham's WikiWikiWeb. Not only is it the first wiki on the web, but he invented wikis!)
When using a framework (such as C# .NET), we tell a framework about our classes, then give control of how our classes are assembled and invoked to that framework. We don't explicitly call the framework anymore; the framework calls us.
See Martin Fowler's Inversion of Control Containers and the Dependency Injection pattern.
Dependency inversion principle
The dependency inversion principle is the culmination of these tools and techniques. From the lips of our beloved Uncle Bob:
A. High level modules should not depend upon low level modules. Both should depend upon abstractions.
B. Abstractions should not depend upon details. Details should depend upon abstractions.
Easier to say than do, to be sure!
Program to interfaces, not implementations
SOLID
SOLID is an acronym introduced by Michael Feathers and promoted by Uncle Bob.
Dependency inversion is the D in SOLID.
SOLID is a mnemonic for a set of principles that represent an agile, clean object oriented development philosophy:
- Single responsibility principle
- Open/closed principle
- Liskov substitution principle
- Interface segregation principle
- Dependency inversion principle
Remember, be agile or be fragile.