|
| 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