dependency injection

Post on 23-Dec-2014

378 Views

Category:

Technology

3 Downloads

Preview:

Click to see full reader

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?

top related