Package: mediapills.dependency-injection
OverviewDependency Injectionis a technique in which an object receives other
objects that it depends on, called dependencies. Typically, the receiving
object is called a client and the passed-in (‘injected’) object is called a
service. The code that passes the service to the client is called the injector.
Instead of the client specifying which service it will use, the injector tells
the client what service to use. The ‘injection’ refers to the passing of a
dependency (a service) into the client that uses it.Dependency injection solves the following problems:How can a class be independent of how the objects on which it depends are created?How can the way objects are created be specified in separate configuration files?How can an application support different configurations?Creating objects directly within the class commits the class to particular
implementations. This makes it difficult to change the instantiation at
runtime, especially in compiled languages where changing the underlying objects
can require re-compiling the source code.Dependency injection separates the creation of a client’s dependencies from the
client’s behavior, which promotes loosely coupled programs and the dependency
inversion and single responsibility principles. Fundamentally, dependency
injection is based on passing parameters to a method.Dependency injection is an example of the more general concept of inversion of
control.RolesDependency injection involves four roles:the service objects to be usedthe client object, whose behavior depends on the services it usesthe interfaces that define how the client may use the servicesthe injector, which constructs the services and injects them into the clientAny object that may be used can be considered aservice. Any object that
uses other objects can be considered aclient. The names relate only to the
role the objects play in an injection.Theinterfacesare the types the client expects its dependencies to be. The
client should not know the specific implementation of its dependencies, only
know the interface’s name and API. As a result, the client will not need to
change even if what is behind the interface changes. Dependency injection can
work with true interfaces or abstract classes, but also concrete services,
though this would violate the dependency inversion principle and sacrifice the
dynamic decoupling that enables testing. It is only required that the client
never treats its interfaces as concrete by constructing or extending them. If
the interface is refactored from a class to an interface type (or vice versa)
the client will need to be recompiled. This is significant if the client and
services are published separately.Theinjectorintroduces services to the client. Often, it also constructs
the client. An injector may connect a complex object graph by treating the same
object as both a client at one point and as a service at another. The injector
itself may actually be many objects working together, but may not be the client
(as this would create a circular dependency). The injector may be referred to
as an assembler, provider, container, factory, builder, spring, or construction
code.ProsA basic benefit of dependency injection is decreased coupling between classes
and their dependencies. By removing a client’s knowledge of how its
dependencies are implemented, programs become more reusable, testable and
maintainable.This also results in increased flexibility: a client may act on anything that
supports the intrinsic interface the client expects.Many of dependency injection’s benefits are particularly relevant to
unit-testing.For example, dependency injection can be used to externalize a system’s
configuration details into configuration files, allowing the system to be
reconfigured without recompilation. Separate configurations can be written for
different situations that require different implementations of components. This
includes testing. Similarly, because dependency injection does not require any
change in code behavior it can be applied to legacy code as a refactoring. The
result is clients that are more independent and that are easier to unit test in
isolation using stubs or mock objects that simulate other objects not under
test. This ease of testing is often the first benefit noticed when using
dependency injection.More generally, dependency injection reduces boilerplate code, since all
dependency creation is handled by a singular component.Finally, dependency injection allows concurrent development. Two developers can
independently develop classes that use each other, while only needing to know
the interface the classes will communicate through. Plugins are often developed
by third party shops that never even talk to the developers who created the
product that uses the plugins.ConsCreates clients that demand configuration details, which can be onerous when
obvious defaults are available.Make code difficult to trace because it separates behavior from construction.Is typically implemented with reflection or dynamic programming. This canhinder
IDE automation.Typically requires more upfront development effort.Forces complexity out of classes and into the links between classes which might
be harder to manage.Encourage dependence on a framework.InstallationBefore using mediapills.dependency_injection in your project, add it to yourrequirements.txtfile:$echo"-e git+ssh://git@github.com/mediapills/dependency_injection.git@0.0.2#egg=mediapills.dependency_injection">>requirements.txtor$echo"mediapills.dependency_injection==0.0.2">>requirements.txtUsageCreating a injector is a matter of creating aInjectorinstance:frommediapills.dependency_injectionimportInjectorinjector=Injector()As many other dependency injection containers, mediapills.dependency_injection manages two
different kind of data:servicesandparameters.Defining ServicesA service is an object that does something as part of a larger system. Examples
of services: a database connection, a templating engine, or a mailer. Almost
any object can be a service.Services are defined byanonymous functionsthat return an instance of an
object:# define some servicesinjector['session_storage']=lambdadi:(SessionStorage('SESSION_ID'))injector['session']=lambdadi:(Session(di['session_storage']))Notice that the anonymous function has access to the current injector
instance, allowing references to other services or parameters.As objects are only created when you get them, the order of the definitions
does not matter.Using the defined services is also very easy:# get the session objectsession=injector['session']# the above call is roughly equivalent to the following code:# storage = SessionStorage('SESSION_ID')# session = Session(storage)Defining Factory ServicesBy default, each time you get a service, Injector returns thesame instanceof it. If you want a different instance to be returned for all calls, wrap your
anonymous function with thefactory()methodinjector['session']=injector.factory(lambdadi:(Session(di['session_storage'])))Now, each call toinjector['session']returns a new instance of the
session.Defining ParametersDefining a parameter allows to ease the configuration of your container from
the outside and to store global values:# define some parametersinjector['cookie_name']='SESSION_ID'injector['session_storage_cls']=SessionStorageIf you change thesession_storageservice definition like below:injector['session_storage']=lambdadi:(di['session_storage_cls'](di['cookie_name']))You can now easily change the cookie name by overriding thecookie_nameparameter instead of redefining the service
definition.Protecting ParametersBecause Pimple sees anonymous functions as service definitions, you need to
wrap anonymous functions with theprotect()method to store them as
parameters:injector['random_func'] = lambda i: rand()
injector.protect('random_func')Modifying Services after DefinitionIn some cases you may want to modify a service definition after it has been
defined. You can use theextend()method to define additional code to be
run on your service just after it is created:injector['session_storage']=lambdadi:(di['session_storage_class'](di['cookie_name']))defsession_storage_ext(storage:Callable,di:Injector):# Do something with base storage using direturnstorageinjector.extend('session_storage',session_storage_ext)The first argument is the name of the service to extend, the second a function
that gets access to the object instance and the container.Fetching the Service Creation FunctionWhen you access an object, Injector automatically calls the anonymous function
that you defined, which creates the service object for you. If you want to get
raw access to this function, you can use theraw()method:injector['session']=lambdadi:(Session(di['session_storage']))sessionFunction=container.raw('session')