Skip to content

Commit a1285ba

Browse files
committed
bring accross two ioc posts by david
1 parent b12a5ac commit a1285ba

File tree

2 files changed

+596
-0
lines changed

2 files changed

+596
-0
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
layout: post
2+
title: What is Inversion of Control and Why Does it Matter?
3+
author: David
4+
description: >
5+
Inversion of Control is a principle for improving the modularity of software. It's a powerful strategy
6+
for simplifying systems that might otherwise become tangled and difficult to understand.
7+
image: upside-down.jpg
8+
9+
10+
When I first learned to program, the code I wrote all followed a particular pattern: I wrote instructions to the computer
11+
that it would execute, one by one. If I wanted to make use of utilities written elsewhere, such as in a third party library,
12+
I would call those utilities directly from my code. Code like this could be described as employing the 'traditional flow of control'.
13+
Perhaps it's just my bias, but this still seems to me to be the *obvious* way to program.
14+
15+
Despite this, there is a wider context that the majority of the code I write today runs in; a context where *control is being inverted*.
16+
This is because I'm usually using some kind of framework, which is passing control to my code, despite having no direct dependency on it.
17+
Rather than my code calling the more generic code, the framework allows me to plug in custom behaviour.
18+
Systems designed like this are using what is known as *[Inversion of Control](https://en.wikipedia.org/wiki/Inversion_of_control)*
19+
(IoC for short).
20+
21+
This situation can be depicted like so: the generic framework providing points where the custom code can insert its behaviour.
22+
23+
{% include content_illustration.html image="why-di/framework-plugins.png" alt="Framework with custom behaviours plugged in" %}
24+
25+
Even though many of us are familiar with coding in the context of such a framework, we tend to be reticent to apply the
26+
same ideas in the software that *we* design. Indeed, it may seem a bizarre or even impossible thing to do. It is certainly
27+
not the 'obvious' way to program.
28+
29+
But IoC need not be limited to frameworks --- on the contrary, it is a particularly useful tool in a programmer's belt.
30+
For more complex systems, it's one of the best ways to avoid our code getting into a mess. Let me tell you why.
31+
32+
# Striving for modularity
33+
34+
Software gets complicated easily. Every programmer has experienced tangled, difficult-to-work with code.
35+
Here's a diagram of such a system:
36+
37+
{% include content_illustration.html image="why-di/big.png" alt="A single complicated system" %}
38+
39+
Perhaps not such a helpful diagram, but some systems can feel like this to work with: a forbidding mass
40+
of code that feels impossible to wrap one's head around.
41+
42+
A common approach to tackling such complexity is to break up the system into smaller, more manageable parts.
43+
By separating it into simpler subsystems, the aim is to reduce complexity and allow us to think more clearly
44+
about each one in turn.
45+
46+
{% include content_illustration.html image="why-di/modular.png" alt="A system composed of small simple modules" %}
47+
48+
We call this quality of a system its *modularity*, and we can refer to these subsystems as *modules*.
49+
50+
# Separation of concerns
51+
52+
Most of us recognise the value of modularity, and put effort into organising our code into smaller parts. We have to
53+
decide what goes into which part, and the way we do this is by the *separation of concerns*.
54+
55+
This separation can take different forms. We might organize things by feature area
56+
(the authentication system, the shopping cart, the blog) or by level of detail
57+
(the user interface, the business logic, the database), or both.
58+
59+
When we do this, we tend to be aiming at modularity. Except for some reason, the system remains complicated.
60+
In practice, working on one module needs to ask questions of another part of the system,
61+
which calls another, which calls back to the original one. Soon our heads hurt and we need to have
62+
a lie down. What's going wrong?
63+
64+
## Separation of concerns is not enough
65+
66+
The sad fact is, if the only organizing factor of code is separation of concerns, a system will not be
67+
modular after all. Instead, separate parts will tangle together.
68+
69+
Pretty quickly, our efforts to organise what goes into each module are undermined by the *relationships between those
70+
modules*.
71+
72+
This is naturally what happens to software if you don't think about relationships. This is because in the real world
73+
things *are* a messy, interconnected web. As we build functionality, we realise that one module needs to know about
74+
another. Later on, that other module needs to know about the first. Soon, everything knows about everything else.
75+
76+
{% include content_illustration.html image="why-di/complicated-modular.png" alt="A complicated system with lots of arrows between the modules" %}
77+
78+
The problem with software like this is that, because of the web of relationships, it is not a collection of smaller
79+
subsystems. Instead, it is a single, large system - and large systems tend to be more complicated than smaller ones.
80+
81+
# Improving modularity through decoupling
82+
83+
The crucial problem here is that the modules, while appearing separate, are *tightly coupled* by their dependencies
84+
upon one other. Let's take two modules as an example:
85+
86+
{% include content_illustration.html image="why-di/a-b-cycle.png" alt="Arrows pointing in both directions between A and B" %}
87+
88+
In this diagram we see that ``A`` depends on ``B``, but ``B`` also depends upon ``A``. It's a
89+
circular dependency. As a result, these two modules are in fact no less complicated than a single module.
90+
How can we improve things?
91+
92+
## Removing cycles by inverting control
93+
94+
There are a few ways to tackle a circular dependency. You may be able to extract a shared dependency into a separate
95+
module, that the other two modules depend on. You may be able to create an extra module that coordinates the two modules,
96+
instead of them calling each other. Or you can use inversion of control.
97+
98+
At the moment, each module calls each other. We can pick one of the calls (let's say ``A``'s call to ``B``) and invert
99+
control so that ``A`` no longer needs to know anything about ``B``. Instead, it exposes a way of plugging into its
100+
behaviour, that ``B`` can then exploit. This can be diagrammed like so:
101+
102+
{% include content_illustration.html image="why-di/plugin.png" alt="B plugging into A" %}
103+
104+
Now that ``A`` has no specific knowledge of ``B``, we think about ``A`` in isolation. We've just reduced our mental overhead,
105+
and made the system more modular.
106+
107+
The tactic remains useful for larger groups of modules. For example, three modules may depend upon each other, in
108+
a cycle:
109+
110+
{% include content_illustration.html image="why-di/abc_cycle.png" alt="Arrows pointing from A to B to C, and back to A" %}
111+
112+
In this case, we can invert one of the dependencies, gaining us a single direction of flow:
113+
114+
{% include content_illustration.html image="why-di/plugin-3.png" alt="B plugging into A" %}
115+
116+
Again, inversion of control has come to the rescue.
117+
118+
# Inversion of control in practice
119+
120+
In practice, inverting control can sometimes feel impossible. Surely, if a module needs to call another, there is no way
121+
to reverse this merely by refactoring? But I have good news. You should *always* be able to avoid circular dependencies
122+
through some form of inversion (if you think you've found an example where it isn't, please tell me).
123+
It's not always the most obvious way to write code, but it can make your code base significantly easier to work with.
124+
125+
There are several different techniques for *how* you do this. One such technique that is often
126+
talked about is dependency injection. I will cover some of these techniques in [part two of this series]({% link _posts/2019-08-03-ioc-techniques.md %}).
127+
128+
There is also more to be said about how to apply this approach across the wider code base: if the system consists of
129+
more than a handful of files, where do we start? Again, I'll cover this later in the series.
130+
131+
# Conclusion: complex is better than complicated
132+
133+
If you want to avoid your code getting into a mess, it's not enough merely to separate concerns. You must control the
134+
*relationships* between those concerns. In order to gain the benefits of a more modular system, you will sometimes need
135+
to use inversion of control to make control flow in the opposite direction to what comes naturally.
136+
137+
The [Zen of Python](https://en.wikipedia.org/wiki/Zen_of_Python) states:
138+
139+
Simple is better than complex.
140+
141+
But also that
142+
143+
Complex is better than complicated.
144+
145+
I think of inversion of control as an example of choosing the complex over the complicated. If we don't use it when
146+
it's needed, our efforts to create a simple system will tangle into complications. Inverting dependencies allows us,
147+
at the cost of a small amount of complexity, to make our systems less complicated.
148+
149+
# Further information
150+
151+
- Part two of this series: [Three Techniques for Inverting Control, in Python]({% link _posts/2019-08-03-ioc-techniques.md %}).
152+
153+
154+

0 commit comments

Comments
 (0)