How do you make your code flexible?
Well, there are many ways to do that, and one of them is by moving one or more of its functionalities outside of it.
This makes your code behave dynamically different, based on what it was fed with.
Sounds confusing? Yep, that’s what I thought too when I first learned of it decades ago — Dependency Injection.
Since we’d be moving some functionalities outside, our code now “depends” on an external resource to perform its task, right? And we do that by “injecting” the dependency to it.
Totally unclear, I understand. Let’s take a real world example.
Real-world Example
An ATM machine is a good example.
You go to an ATM machine and in order to interact with that machine, you have to insert or “inject” your card to its card slot. The “dependency” of the ATM machine in this case is a card (debit or credit).
The behavior and even mechanics of the transactions would depend on the card provided, but the interface of the ATM, even the menus are exactly the same — with maybe some reminders if you’re using credit card or a card issued by another bank.
Just imagine if the logic of the ATM is “hard-coded” in it, the banks would probably have one ATM per card or other bank 🙂
Another good example, and one which we’ll cover is a web browser search engine. Almost all modern web browsers allow you to specify which search engine to use when you type something in their search (or URL) textbox, and we can illustrate or simulate this using the Dependency Injection pattern.
Creating a Generic Web Browser class
So let’s begin by creating a web browser class that can accept a search engine class when its initialized:
class Browser:
""" A generic web browser """
def __init__(self, search_engine):
""" require a search engine upon instantiation """
self.searchengine = search_engine
def search(self, search_text):
""" delegate the search to the engine """
return self.searchengine.search(search_text)
Next, let’s create 2 search engines:
class GooberEngine:
""" A super fast search engine """
def search(self, search_text):
print(f"({self.__class__.__name__}) is searching for {search_text} ...")
return []
class AstalaVistaEngine:
""" An old search engine """
def search(self, search_text):
print(f"({self.__class__.__name__}) is searching for {search_text} ...")
return []
Finally, let’s test the web browser with those 2 engines and see if it works or not 🙂
Browser(GooberEngine()).search('dog')
Browser(AstalaVistaEngine()).search('cat')
And here’s the result when you run this code:
Of course this isn’t a real web browser and search engine, but hopefully this shows you how Dependency Injection can help you write flexible applications — one that can be extended even after it has been released to production.
Expanded Use Case
The previous example was kind of fixed, so here’s how you could make this dynamic:
# here's how you can make the selection dynamic:
# create a dictionary of engines
# that could come from a JSON file for example:
search_engines = {
'goober': GooberEngine(),
't800': AstalaVistaEngine()
}
print("Which Search Engine do you want to use?")
print("-" * 40)
for name in search_engines:
print(f"\t{name}")
print("\n")
engine_name = input("Enter the name: ")
try:
engine = search_engines[engine_name]
except Exception as e:
print(f"Engine {engine_name} is not supported.")
else:
print(f"Search Engine is now set to {type(engine).__name__}")
I’ll run this several times and try first the 2 valid selections and then something that isn’t on the list:
That’s it for now! Go ahead and explore more and try thinking of other uses for Dependency Injection! 🙂
As usual, the source codes used are in my GitHub repository below:
https://github.com/vegitz/codes/tree/master/0014%20Dependency%20Injection%20Python