|
1 | | -What is dependency injection and inversion of control? |
2 | | ------------------------------------------------------- |
| 1 | +What is dependency injection? |
| 2 | +----------------------------- |
3 | 3 |
|
4 | 4 | .. meta:: |
5 | | - :keywords: Python,DI,Dependency injection,IoC,Inversion of Control |
6 | | - :description: This article provides definition of dependency injection, |
7 | | - inversion of control and dependency inversion. It contains |
8 | | - example code in Python that is refactored to be following |
9 | | - inversion of control principle. |
10 | | - |
11 | | -Definition |
12 | | -~~~~~~~~~~ |
13 | | - |
14 | | -Wikipedia provides quite good definitions of dependency injection pattern |
15 | | -and related principles: |
16 | | - |
17 | | -.. glossary:: |
18 | | - |
19 | | - `Dependency injection`_ |
20 | | - In software engineering, dependency injection is a software design |
21 | | - pattern that implements inversion of control for resolving |
22 | | - dependencies. A dependency is an object that can be used (a service). |
23 | | - An injection is the passing of a dependency to a dependent object (a |
24 | | - client) that would use it. The service is made part of the client's |
25 | | - state. Passing the service to the client, rather than allowing a |
26 | | - client to build or find the service, is the fundamental requirement of |
27 | | - the pattern. |
28 | | - |
29 | | - Dependency injection allows a program design to follow the dependency |
30 | | - inversion principle. The client delegates to external code (the |
31 | | - injector) the responsibility of providing its dependencies. The client |
32 | | - is not allowed to call the injector code. It is the injecting code |
33 | | - that constructs the services and calls the client to inject them. This |
34 | | - means the client code does not need to know about the injecting code. |
35 | | - The client does not need to know how to construct the services. The |
36 | | - client does not need to know which actual services it is using. The |
37 | | - client only needs to know about the intrinsic interfaces of the |
38 | | - services because these define how the client may use the services. |
39 | | - This separates the responsibilities of use and construction. |
40 | | - |
41 | | - `Inversion of control`_ |
42 | | - In software engineering, inversion of control (IoC) describes a design |
43 | | - in which custom-written portions of a computer program receive the |
44 | | - flow of control from a generic, reusable library. A software |
45 | | - architecture with this design inverts control as compared to |
46 | | - traditional procedural programming: in traditional programming, the |
47 | | - custom code that expresses the purpose of the program calls into |
48 | | - reusable libraries to take care of generic tasks, but with inversion |
49 | | - of control, it is the reusable code that calls into the custom, or |
50 | | - task-specific, code. |
51 | | - |
52 | | - Inversion of control is used to increase modularity of the program and |
53 | | - make it extensible, and has applications in object-oriented |
54 | | - programming and other programming paradigms. The term was popularized |
55 | | - by Robert C. Martin and Martin Fowler. |
56 | | - |
57 | | - The term is related to, but different from, the dependency inversion |
58 | | - principle, which concerns itself with decoupling dependencies between |
59 | | - high-level and low-level layers through shared abstractions. |
60 | | - |
61 | | - `Dependency inversion`_ |
62 | | - In object-oriented programming, the dependency inversion principle |
63 | | - refers to a specific form of decoupling software modules. When |
64 | | - following this principle, the conventional dependency relationships |
65 | | - established from high-level, policy-setting modules to low-level, |
66 | | - dependency modules are reversed, thus rendering high-level modules |
67 | | - independent of the low-level module implementation details. The |
68 | | - principle states: |
69 | | - |
70 | | - + High-level modules should not depend on low-level modules. |
71 | | - Both should depend on abstractions. |
72 | | - + Abstractions should not depend on details. |
73 | | - Details should depend on abstractions. |
74 | | - |
75 | | - The principle inverts the way some people may think about |
76 | | - object-oriented design, dictating that both high- and low-level |
77 | | - objects must depend on the same abstraction. |
78 | | - |
79 | | -Example |
80 | | -~~~~~~~ |
81 | | - |
82 | | -Let's go through the code of ``example.py``: |
83 | | - |
84 | | -.. literalinclude:: ../../examples/di_demo/example.py |
85 | | - :language: python |
86 | | - |
87 | | -At some point, things defined above mean, that the code from ``example.py``, |
88 | | -could look different, like in ``example_di.py``: |
89 | | - |
90 | | -.. literalinclude:: ../../examples/di_demo/example_di.py |
91 | | - :language: python |
92 | | - |
93 | | -Best explanation, ever |
94 | | -~~~~~~~~~~~~~~~~~~~~~~ |
95 | | - |
96 | | -Some times ago `user198313`_ posted awesome `question`_ about dependency |
97 | | -injection on `StackOverflow`_: |
98 | | - |
99 | | -.. note:: |
100 | | - |
101 | | - How to explain dependency injection to a 5-year-old? |
102 | | - |
103 | | -And `John Munsch`_ provided absolutely Great answer: |
104 | | - |
105 | | -.. note:: |
106 | | - |
107 | | - When you go and get things out of the refrigerator for yourself, you can |
| 5 | + :keywords: Python,DI,Dependency injection,Low coupling,High cohesion |
| 6 | + :description: This page provides a Python example of what is dependency injection. It tells |
| 7 | + about benefits of coupling and high cohesion. |
| 8 | + |
| 9 | +Dependency injection is a principle that helps to decrease coupling and increase cohesion. |
| 10 | + |
| 11 | +.. image:: images/coupling-cohesion.png |
| 12 | + |
| 13 | +What is coupling and cohesion? |
| 14 | + |
| 15 | +Coupling and cohesion are about how tough the components are tied. |
| 16 | + |
| 17 | +- **High coupling**. If the coupling is high it's like using a superglue or welding. No easy way |
| 18 | + to disassemble. |
| 19 | +- **High cohesion**. High cohesion is like using the screws. Very easy to disassemble and |
| 20 | + assemble back or assemble a different way. It is an alternative to high coupling. |
| 21 | + |
| 22 | +When the cohesion is high the coupling is low. |
| 23 | + |
| 24 | +High cohesion brings the flexibility. Your code becomes easier to change and to test. |
| 25 | + |
| 26 | +The example |
| 27 | +~~~~~~~~~~~ |
| 28 | + |
| 29 | +How does dependency injection helps to achieve high cohesion? |
| 30 | + |
| 31 | +Objects do not create each other anymore. They provide a way to inject the dependencies instead. |
| 32 | + |
| 33 | +Before: |
| 34 | + |
| 35 | +.. code-block:: python |
| 36 | +
|
| 37 | + import os |
| 38 | +
|
| 39 | +
|
| 40 | + class ApiClient: |
| 41 | +
|
| 42 | + def __init__(self): |
| 43 | + self.api_key = os.getenv('API_KEY') # <-- the dependency |
| 44 | + self.timeout = os.getenv('TIMEOUT') # <-- the dependency |
| 45 | +
|
| 46 | +
|
| 47 | + class Service: |
| 48 | +
|
| 49 | + def __init__(self): |
| 50 | + self.api_client = ApiClient() # <-- the dependency |
| 51 | +
|
| 52 | +
|
| 53 | + if __name__ == '__main__': |
| 54 | + service = Service() |
| 55 | +
|
| 56 | +
|
| 57 | +After: |
| 58 | + |
| 59 | +.. code-block:: python |
| 60 | +
|
| 61 | + import os |
| 62 | +
|
| 63 | +
|
| 64 | + class ApiClient: |
| 65 | +
|
| 66 | + def __init__(self, api_key: str, timeout: int): |
| 67 | + self.api_key = api_key # <-- the dependency is injected |
| 68 | + self.timeout = timeout # <-- the dependency is injected |
| 69 | +
|
| 70 | +
|
| 71 | + class Service: |
| 72 | +
|
| 73 | + def __init__(self, api_client: ApiClient): |
| 74 | + self.api_client = api_client # <-- the dependency is injected |
| 75 | +
|
| 76 | +
|
| 77 | + if __name__ == '__main__': |
| 78 | + service = Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT'))) |
| 79 | +
|
| 80 | +``ApiClient`` is decoupled from knowing where the options come from. You can read a key and a |
| 81 | +timeout from a configuration file or even get them from a database. |
| 82 | + |
| 83 | +``Service`` is decoupled from the ``ApiClient``. It does not create it anymore. You can provide a |
| 84 | +stub or other compatible object. |
| 85 | + |
| 86 | +Flexibility comes with a price. |
| 87 | + |
| 88 | +Now you need to assemble your objects like this |
| 89 | +``Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))``. The assembly code might get |
| 90 | +duplicated and it'll become harder to change the application structure. |
| 91 | + |
| 92 | +Here comes the ``Dependency Injector``. |
| 93 | + |
| 94 | +``Dependency Injector`` helps to assemble the objects. |
| 95 | + |
| 96 | +It provides you the container and the providers that help you describe objects assembly. When you |
| 97 | +need an object you get it from the container. The rest of the assembly work is done by the |
| 98 | +framework: |
| 99 | + |
| 100 | +.. code-block:: python |
| 101 | +
|
| 102 | + from dependency_injector import containers, providers |
| 103 | +
|
| 104 | +
|
| 105 | + class ApiClient: |
| 106 | +
|
| 107 | + def __init__(self, api_key: str, timeout: int): |
| 108 | + self.api_key = api_key |
| 109 | + self.timeout = timeout |
| 110 | +
|
| 111 | +
|
| 112 | + class Service: |
| 113 | +
|
| 114 | + def __init__(self, api_client: ApiClient): |
| 115 | + self.api_client = api_client |
| 116 | +
|
| 117 | +
|
| 118 | + class Container(containers.DeclarativeContainer): |
| 119 | +
|
| 120 | + config = providers.Configuration() |
| 121 | +
|
| 122 | + api_client = providers.Singleton( |
| 123 | + ApiClient, |
| 124 | + api_key=config.api_key, |
| 125 | + timeout=config.timeout.as_int(), |
| 126 | + ) |
| 127 | +
|
| 128 | + service = providers.Factory( |
| 129 | + Service, |
| 130 | + api_client=api_client, |
| 131 | + ) |
| 132 | +
|
| 133 | +
|
| 134 | + if __name__ == '__main__': |
| 135 | + container = Container() |
| 136 | + container.config.api_key.from_env('API_KEY') |
| 137 | + container.config.timeout.from_env('TIMEOUT') |
| 138 | +
|
| 139 | + service = container.service() |
| 140 | +
|
| 141 | +Retrieving of the ``Service`` instance now is done like this ``container.service()``. |
| 142 | + |
| 143 | +Objects assembling is consolidated in the container. When you need to make a change you do it in |
| 144 | +one place. |
| 145 | + |
| 146 | +When doing the testing you call the ``container.api_client.override()`` to replace the real API |
| 147 | +client with a mock: |
| 148 | + |
| 149 | +.. code-block:: python |
| 150 | +
|
| 151 | + from unittest import mock |
| 152 | +
|
| 153 | +
|
| 154 | + with container.api_client.override(mock.Mock()): |
| 155 | + service = container.service() |
| 156 | +
|
| 157 | +How to explain dependency injection to a 5-year-old? |
| 158 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 159 | + |
| 160 | +Some time ago `user198313`_ posted this `question`_ on the `StackOverflow`_. |
| 161 | + |
| 162 | +`John Munsch`_ provided a great answer: |
| 163 | + |
| 164 | + *When you go and get things out of the refrigerator for yourself, you can |
108 | 165 | cause problems. You might leave the door open, you might get something |
109 | 166 | Mommy or Daddy doesn't want you to have. You might even be looking for |
110 | | - something we don't even have or which has expired. |
| 167 | + something we don't even have or which has expired.* |
111 | 168 |
|
112 | | - What you should be doing is stating a need, "I need something to drink |
| 169 | + *What you should be doing is stating a need, "I need something to drink |
113 | 170 | with lunch," and then we will make sure you have something when you sit |
114 | | - down to eat. |
| 171 | + down to eat.* |
115 | 172 |
|
| 173 | +What's next? |
| 174 | +~~~~~~~~~~~~ |
116 | 175 |
|
117 | | -.. disqus:: |
| 176 | +Choose one of the following as a next step: |
118 | 177 |
|
| 178 | ++ Pass one of the tutorials: |
| 179 | + + :ref:`cli-tutorial` |
| 180 | + + :ref:`flask-tutorial` |
| 181 | + + :ref:`aiohttp-tutorial` |
| 182 | + + :ref:`asyncio-daemon-tutorial` |
| 183 | ++ Know more about the :ref:`providers` |
| 184 | ++ Go to the :ref:`contents` |
| 185 | + |
| 186 | +.. disqus:: |
119 | 187 |
|
120 | | -.. _Dependency injection: http://en.wikipedia.org/wiki/Dependency_injection |
121 | | -.. _Inversion of control: https://en.wikipedia.org/wiki/Inversion_of_control |
122 | | -.. _Dependency inversion: https://en.wikipedia.org/wiki/Dependency_inversion_principle |
123 | 188 | .. _StackOverflow: http://stackoverflow.com/ |
124 | 189 | .. _question: http://stackoverflow.com/questions/1638919/how-to-explain-dependency-injection-to-a-5-year-old/1639186 |
125 | 190 | .. _user198313: http://stackoverflow.com/users/198313/user198313 |
|
0 commit comments