From 7496c1d2dd73e7467df3f814c2376a7b90d6d476 Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Sun, 15 Feb 2015 17:21:36 +0100 Subject: [PATCH] Describe some antipatterns --- docs/practices.rst | 88 ++++++++++++++++++++++++++++++++++++++++++++++ injector.py | 26 ++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/docs/practices.rst b/docs/practices.rst index 4353156..0f13578 100644 --- a/docs/practices.rst +++ b/docs/practices.rst @@ -164,3 +164,91 @@ Here's the output of the application:: Sleeping... Sleeping... (...) + + +Injecting Injector and abusing Injector.get +``````````````````````````````````````````` + +Sometimes code like this is written: + +.. code-block:: python + + class A(object): + pass + + class B(object): + pass + + class C(object): + @inject(injector=Injector) + def __init__(self, injector): + self.a = injector.get(A) + self.b = injector.get(B) + + +It is advised to use the following pattern instead: + +.. code-block:: python + + class A(object): + pass + + class B(object): + pass + + class C(object): + @inject(a=A, b=B) + def __init__(self, a, b) + self.a = a + self.b = b + + +The second form has the benefits of: + +* expressing clearly what the dependencies of ``C`` are +* making testing of the ``C`` class easier - you can provide the dependencies + (whether they are mocks or not) directly, instead of having to mock + :class:`Injector` and make the mock handle :meth:`Injector.get` calls +* following the common practice and being easier to understand + + +Injecting dependencies only to pass them somewhere else +``````````````````````````````````````````````````````` + +A pattern similar to the one below can emerge: + +.. code-block:: python + + class A(object): + pass + + class B(object): + def __init__(self, a): + self.a = a + + class C(object): + @inject(a=A) + def __init__(self, a): + self.b = B(a) + +Class ``C`` in this example has the responsibility of gathering dependencies of +class ``B`` and constructing an object of type ``B``, there may be a valid reason +for it but in general it defeats the purpose of using ``Injector`` and should +be avoided. + +The appropriate pattern is: + +.. code-block:: python + + class A(object): + pass + + class B(object): + @inject(a=A) + def __init__(self, a): + self.a = a + + class C(object): + @inject(b=B) + def __init__(self, b): + self.b = b diff --git a/injector.py b/injector.py index 00a9158..90fd488 100644 --- a/injector.py +++ b/injector.py @@ -653,6 +653,32 @@ class Injector(object): def get(self, interface, scope=None): """Get an instance of the given interface. + .. note:: + + Although this method is part of :class:`Injector`'s public interface + it's meant to be used in limited set of circumstances. + + For example, to create some kind of root object (application object) + of your application (note that only one `get` call is needed, + inside the `Application` class and any of its dependencies + :func:`inject` can and should be used): + + .. code-block:: python + + class Application(object): + + @inject(dep1=Dep1, dep2=dep2) + def __init__(self, dep1, dep2): + self.dep1 = dep1 + self.dep2 = dep2 + + def run(self): + self.dep1.something() + + injector = Injector(configuration) + application = injector.get(Application) + application.run() + :param interface: Interface whose implementation we want. :param scope: Class of the Scope in which to resolve. :returns: An implementation of interface.