Designing For Change
Inversion of Control via Dependency Injection - originally presented at Cleveland Day of .NET, May 17, 2008
1866 views | comments | 4 favorites | 80 downloads | 1 embeds (Stats)
More Info
This slideshow is Public
Total Views: 1866 on Slideshare: 1723 from embeds: 143
Most viewed embeds (Top 5):
More
Slideshow Transcript
- Slide 1: Designing for Change
Inversion of Control via Dependency Injection
Nate Kohari
- Slide 2: About Me
Software Architect at CTI in Akron, OH
Creator of Ninject, a .NET dependency
injection framework
- Slide 3: Disclaimer
There are no
silver bullets
Use critical
thinking when
evaluating any
new idea
- Slide 4: Goals
What is inversion of control (IoC)?
How can it make our software more
flexible?
What is dependency injection (DI)?
How can we use DI to achieve IoC?
Why should we use a DI framework?
- Slide 5: Change Happens
Software seems like it should be easy to
change
As a result, people always find reasons to
change it!
Simple changes can be very difficult to
implement, and have widespread effects
- Slide 6: Agile Software
This has given rise to Agile software
development methodologies
Project management strategies aren’t
enough
You need to build the flexibility into your
software itself
- Slide 7: Divide and Conquer
When a problem is too complex, break it
into smaller, more easily-solvable
problems
Each problem is a concern
Separation of Concerns (SoC)
- Slide 8: Divide and Conquer (cont.)
Think of your application like a jigsaw
puzzle
How should we carve it up?
How can we recombine the pieces?
Two measures to consider: cohesion and
coupling
- Slide 9: Cohesion
How similar is the code within a given
class?
Highly-cohesive components = more
flexible software
Single Responsibility Principle (SRP)
- Slide 10: Cohesion (BAD)
class Samurai {
public void Attack(string target) {
Console.WriteLine(“Chopped {0} in half!”,target);
}
}
- Slide 11: Cohesion (better)
class Samurai {
public Sword Weapon { get; set; }
public void Attack(string target) {
Weapon.Hit(target);
}
}
class Sword {
public void Hit(string target) {
Console.WriteLine(“Chopped {0} in half!”,target);
}
}
- Slide 12: Coupling
How much does a given concrete class
require other concrete classes in order to
operate?
Loosely-coupled components = more
flexible software
Interface Segregation Principle (ISP)
- Slide 13: Coupling (BAD)
class Samurai {
public Sword Weapon { get; set; }
public void Attack(string target) {
Weapon.Hit(target);
}
}
class Sword {
public void Hit(string target) {
Console.WriteLine(“Chopped {0} in half!”,target);
}
}
- Slide 14: Coupling (better)
class Samurai {
public IWeapon Weapon { get; set; }
public void Attack(string target) {
Weapon.Hit(target);
}
}
interface IWeapon {
void Hit(string target);
}
- Slide 15: Coupling (better, cont.)
class Sword : IWeapon {
public void Hit(string target) {
Console.WriteLine(“Chopped {0} in half!”,target);
}
}
class Crossbow : IWeapon {
public void Hit(string target) {
Console.WriteLine(“Pierced {0}’s armor.”,target);
}
}
- Slide 16: Cohesion = Good
Coupling = Bad
- Slide 17: It’s All About Dependencies
Each piece of your app has other pieces
that it needs to operate (e.g. a Samurai
needs an IWeapon)
Once you break your app into pieces, you
have to glue the pieces back together
These are called dependencies
- Slide 18: The Dependency Trap
class Samurai {
public IWeapon Weapon { get; }
public Samurai() {
Weapon = new Sword();
}
}
What’s wrong with this code?
- Slide 19: The Dependency Trap (cont.)
class Samurai {
public IWeapon Weapon { get; }
public Samurai() {
Weapon = new Sword();
}
}
Samurai is still strongly coupled to Sword!
- Slide 20: The Problem of Control
By creating the Sword within the Samurai
type, they’re still tightly coupled!
Objects should not be responsible for
creating their own dependencies
This is the idea behind inversion of control
- Slide 21: Dependency Injection
One way of achieving inversion of control
Create the dependencies somewhere
outside the object, and inject them into it
Really just the Strategy pattern used en
masse
- Slide 22: Dependency Injection
class Samurai {
public IWeapon Weapon { get; }
public Samurai(IWeapon weapon) {
Weapon = weapon;
}
public void Attack(string target) {
Weapon.Hit(target);
}
}
- Slide 23: Dependency Injection (cont.)
class Program {
public static void Main() {
Samurai sam = new Samurai(new Sword());
sam.Attack(“the evildoers”);
}
}
This is dependency injection by hand.
- Slide 24: Problems with DI by Hand
Wiring up objects by hand is cumbersome
What if your dependencies have
dependencies of their own, etc?
DI frameworks to the rescue!
- Slide 25: DI Frameworks
Also called inversion of control containers
DI frameworks are like super-factories
Rather than wiring up dependencies
manually, just tell the framework how the
parts fit together
- Slide 26: DI Frameworks (cont.)
Using a DI framework lowers the “cost” of
resolving dependencies to almost zero
This means you’re more likely to make the
appropriate separations
- Slide 27: DI Frameworks (cont.)
Frameworks galore!
In Java: Guice, Spring, Pico, Nano
In .NET: Castle Windsor, StructureMap,
Spring.NET, ObjectBuilder/Unity
And of course my personal favorite...
- Slide 29: DI with Ninject
class Samurai {
[Inject] public IWeapon Weapon { get; set; }
public void Attack(string target) {
Weapon.Hit(target);
}
}
The [Inject] attribute marks the dependency.
- Slide 30: DI with Ninject (cont.)
class Program {
public static void Main() {
IKernel kernel = new StandardKernel(...);
Samurai sam = kernel.Get<Samurai>();
sam.Attack(“the evildoers”);
}
}
When the Samurai is returned from the call to
Get(), it will already be “armed” with a Sword.
- Slide 31: The Kernel
The kernel is Ninject’s super-factory
Your application should only have one
kernel (except in very complex cases)
All services should be created via the
kernel, either via Get() or an [Inject]
decoration
- Slide 32: DI with Ninject (cont.)
class Samurai {
[Inject] public IWeapon Weapon { get; set; }
public void Attack(string target) {
Weapon.Hit(target);
}
}
But wait, IWeapon is just an interface!
How does Ninject know what to inject?
- Slide 33: Bindings
Bindings are hints that you give to Ninject
Creates a map between a service type and
an implementation type
Lets you build future flexibility into your
code, and then adjust without changing
the code itself
Defined via Ninject’s fluent interface
- Slide 34: Bindings (example)
When IWeapon is requested, create a Sword:
Bind<IWeapon>().To<Sword>();
When Samurai is requested, create a Samurai:
Bind<Samurai>().ToSelf();
- Slide 35: DSL > XML
Other DI frameworks are geared around
defining bindings in XML
XML is verbose, ugly, and not much better
than plain text
Defining bindings via code lets you take
advantage of IDE features (auto-complete,
type-safety)
- Slide 36: Modules
In Ninject, bindings are collected into
modules
Modules are just class definitions
In other containers, you end up with a
monolithic XML file
Your app can have any number of modules
- Slide 37: Modules (example)
class WarriorModule : StandardModule {
public override void Load() {
Bind<IWeapon>().To<Sword>();
Bind<Samurai>().ToSelf();
}
}
Modules are just code, so you can build in as
much intelligence as you want.
- Slide 38: Behaviors
Ninject can also help re-use instances
Singleton, one-per-thread, one-per-
request (web)
Separates instantiation behavior from
code, making it easier to change
- Slide 39: Singleton (typical)
class Shogun {
private static Shogun _instance = new Shogun();
public static Instance {
get { return _instance; }
}
private Shogun() { }
public void RuleWithIronFist() {
//...
}
}
- Slide 40: Singleton (Ninject)
[Singleton]
class Shogun {
public Shogun() { }
public void RuleWithIronFist() {
//...
}
}
The first time a Shogun is requested, it will be
activated, then on subsequent requests the
same instance will be returned.
- Slide 41: Advanced Features
Contextual binding: returns different
implementations depending on situation
Loose-coupled events via event broker
Method interception (aspect-oriented
programming)
- Slide 42: Contextual Binding
Bind<IWeapon>().To<Sword>(
When.Context.Target.Tag == “melee”
);
Bind<IWeapon>().To<Crossbow>(
When.Context.Target.Tag == “range”
);
These conditional bindings examine the
activation context of the current object.
- Slide 43: Contextual Binding
class Swordsman {
[Inject, Tag(“melee”)]
public IWeapon Weapon { get; set; }
}
class Archer {
[Inject, Tag(“range”)]
public IWeapon Weapon { get; set; }
}
Contextual binding is useful for matching
identical type hierarchies.
- Slide 44: Loose-Coupled Events
class Publisher {
[Publish(“SomeEvent”)]
public event EventHandler SomeEvent;
}
class Subscriber {
[Subscribe(“SomeEvent”)]
public void OnSomeEvent(object obj, EventArgs e)
{ ... }
}
Objects can subscribe to event channels
without knowing about actual instances.
- Slide 45: Method Interception
class ComplexObjectFactory {
[Cache] public void Create() {
// Do something difficult to create object
}
}
Results in a chain of interceptors being called
before the method itself is executed.
- Slide 46: Summary
Make your software more flexible by
maximizing cohesion and minimizing
coupling
Carve your app into a jigsaw puzzle
Use a DI framework like Ninject to glue
together the pieces
- Slide 47: For More Information
http://ninject.org/
http://kohari.org/
http://twitter.com/nkohari
Questions?