dependency injection
DESCRIPTION
Dependency injection pattern, explained with a simple and common real life example.TRANSCRIPT
dependency injectionThe pattern
@BeFactory
by Marc Morera
class Carpenter { public function work() { $hammer = new Hammer(); $hammer->chop(); } } !$carpenter = new Carpenter(); $carpenter->work();
Problems?
Let’s talk about responsibility
• Any carpenter should ignore how to build a hammer, but how to use it.
• The carpenter goes to a hardware store to get some nice brown and big hammer.
• Not testable
• High coupling
class Carpenter { public function work(Hammer $hammer) { $hammer->chop(); } } !$hammer = new Hammer(); $carpenter = new Carpenter(); $carpenter->work($hammer);
• So can we just have a carpenter without a hammer?
• No way!
• A carpenter ALWAYS have a hammer.
• No hammer, no carpenter.
class Carpenter { private $hammer; ! public function __construct(Hammer $hammer) { $this->hammer = $hammer; } ! public function work() { $this->hammer->chop(); } } !$hammer = new Hammer(); $carpenter = new Carpenter($hammer); $carpenter->work();
2 years later…
• All carpenters are upgraded.
• No hammers anymore.
• All carpenters will use a simple small red mallet
• We want to make sure all objects that every carpenter will use to chop, will be suitable for that.
• How can ensure this?
Interfaces
interface ToolInterface { public function chop(); } !class Hammer implements ToolInterface { public function chop() { } } !class Mallet implements ToolInterface { public function chop() { } }
class Carpenter { private $tool; ! public function __construct(ToolInterface $tool) { $this->tool = $tool; } ! public function work() { $this->tool->chop(); } } !$carpenter = new Carpenter(new Hammer); $carpenter = new Carpenter(new Mallet); $carpenter->work();
• Very testable and easily mockable
• Low coupling
• Seems to be the best solution ever
But…is this implementation a good one?
NO.
• This is purely what means dependency injection.
• Every dependency of an object is injected
• … into the constructor if is needed for the class to be built
• … in a method if is just needed for this method
Think about a full project with this code
$iron = new Iron; $wood = new Wood; $plastic = new Plastic(new Blue); $hammer = new Hammer($iron, $wood, $plastic); $paintCan = new Paint(new Red); $carpenter = new Carpenter($hammer, $glasses, $paintCan); !$nail = new Nail($iron); $carpenter->work($iron);
• Not maintainable
• Not scalable
• Responsibility of building a carpenter is now distributed by all controllers.
Container
What container should provide?
• Definition
• Customization
• Instantiation
• Our container is the provider of all instances.
• It is therefore responsable of building them
Services
• A service is just an instance of an object
• Each service must define the path of the object ( given by the namespace )
• Also can define how to be built ( arguments )
• An argument can be another service ( using @ )
services: iron: class: Iron wood: class: Wood plastic: class: Plastic hammer: class: Hammer arguments: iron: @iron wood: @wood plastic: @plastic carpenter : class: Carpenter arguments: hammer: @hammer
$carpenter = $container->get(‘carpenter’);
Customization
• In this example, every time a Carpenter is requested, a new Hammer will be used.
• If we want to switch to a Mallet, we must change definition.
• We should be able to configure it outside the definition of the container.
services: iron: class: Iron wood: class: Wood plastic: class: Plastic hammer: class: Hammer arguments: iron: @iron wood: @wood plastic: @plastic carpenter : class: Carpenter arguments: hammer: @hammer
Parameters
• Any parameter should be able to be defined in the project
• A parameter is used using “%” keyword.
parameters: tool_class: Hammer services: iron: class: Iron wood: class: Wood plastic: class: Plastic tool: class: %tool_class% arguments: iron: @iron wood: @wood plastic: @plastic carpenter : class: Carpenter arguments: tool: @tool
• Prototype.
• Container. Some objects can be just reused in every request ( an infinite paint can ) - In the real world, a Logger or a simple Text library.
Scopes
Prototype
• One instance per container request
• Container just build an instance and return it
• It’s defined by setting scope: prototype
• For example, a nail
Container
• First time, object is built and stored.
• From next requests, same instance is reused and returned
• Saving memory ( storing objects ) and cpu ( building them )
• It’s defined by setting scope: container. Default behavior
services: paint_can: class: PaintCan scope: container carpenter : class: Painter scope: prototype arguments: paint_can: @paint_can
• Each time a painter is requested, we’ll reuse same paint can object since they all can share it.
Implementations
• Symfony2 - DependencyInjection Component
• Android - Dagger
• Objective C - Typhoon
DI Saves your lifeUse it, dude!
QA?