Skip to content

Instantly share code, notes, and snippets.

@metal3d
Last active April 12, 2016 01:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save metal3d/edce016017444a3be533a782a68a0c5f to your computer and use it in GitHub Desktop.
Save metal3d/edce016017444a3be533a782a68a0c5f to your computer and use it in GitHub Desktop.
DI python
# Why DI Framework are useful
Dependency injection is back in fashion. That's probably the result AngularJS, Symfony2, and many Java frameworks success that offer a "DI" implementation. But what is "DI", and why are we able to *not use* "DI" framework with Python ?
## What is dependency injection
Dependency injection is a pattern, or it is rather a way to implement dependencies between classes. The approach is basically to avoid classes to directly instanciate objects, and make use of interfaces and/or factories. That's all, nothing more complicated.
To illustrate the definition, it's simpler to look at an example.
We have a website selling some products. After the user filled up cart, he should pay the bill. But we want to propose several payment methods
- Paypal
- Stripe
- Credit card
Each payment method have its own API, scenario and implementation. So you can imagine the difficulty to prepare views and how to check payment to save information in database.
Anyway, you are not a bad developer and you created classes that have the same implementation - you don't have "interfaces" in python, but it's not very important. So for this example, we can use classes that contains a "charge" method:
```python
class PayPalPayment(object):
def __init__(self):
# call api to authenticate payment
pass
def charge(self, amount):
# charge payement, paypal will redirect user
# in a payment page...
```
Other payment class should have some identical method signature.
And you often see something like the above code:
```python
class Cart(object):
def __init__(self):
self.amount = 0.0
pass
def payment(self, choosed_method):
# very bad implementation
if choosed_method == "paypal":
p = PayPalPayment()
p.charge(self.amount)
# and so on
elif choosed_method == "stripe":
#...
```
You now realize that something is wrong. If you want to add another payment method, it will be complicated. You will need to create a case for each payment method you've added. So, it's definitely a bad idea.
> Dependecy Injection will help.
Erase everything we've made, and let's start at Cart class that uses "DI":
```python
class Cart(object):
def __init__(self):
self.amount = 0.0
pass
def payment(self, choosed_method):
# better, the payment method is injected
p = choosed_method()
p.charge()
```
So, what is "choosed_method" ?
It's a **factory**, nothing more.
But, before going further, let's speaking about Python constructors
## Python constructors are... factories
Yes ! whatever you may think about constructors in Python, keep in mind that there are factories and nothing more.
Just to be sure that everything is ok with that strange idea, have a look on this line:
```python
a = b()
```
Now, ask yourself:
> is "a" the result of "b" function, or "a" an instance of "b" class ?
And the answer is... **the result of the function**. Whatever the type of "a", we called a "b" function.
If "b" is a class, calling "b()" returns a "b" instance. And this is the exact definition of a factory. So, in this case, the answer of the question is **"both the return value of "b" function and an instance of "b" class".**
And this is strongly interesting for the next section.
Now, get back to our example.
## Back to payment
We will later see that we can still use "string" as parameter, but for now we have to understand some concepts.
So, for now, instead of having to send the "payment method" as string, we can pass the "type" of payment to use in our Cart class.
```python
c = Cart()
c.amount = 15.00
c.payment(PaypalPayment)
# or
c.payment(StripePayment)
```
Inside Cart::payment() method, "choosed_method" will be "PaypalPayment" or "StripePayment", and it will be **"called" as factory**. Because the call of those classes will create a new instance.
But what is very nice is that we can give a "function" instead, that is an implemented factory. Example:
```python
def test_payment():
if stripe_is_mandatory:
return Stripe()
elif paypal_is_mandatory:
return PaypalPayment()
return CreditCart()
```
We can inject this:
```python
c = Cart()
c.amount = 15.00
# give the factory
c.payment(other_payment)
# or, with classes definitions
c.payment(PaypalPayment)
# ...
c.payment(StripePayment)
```
And we have nothing to change in our Cart::payment method !
## Factories can return factories
A lot of dependency injection frameworks propose that kind of call:
```python
payment = get_payment("paypal")
```
That very useful because you may use a form value to know wich object to use.
This isn't complicated to implement in python, we only have to create a "get_payment" factory that returns factory **but not an instance**.
```python
def get_payment(method):
if method == None:
# we can have a default_method somewhere
if default_method is not None:
return default_method
return CreditCard
if method=="paypal":
return PaypalPayment
if method == "stripe":
return StripePayment:
```
Now we can use a string to charge consumer:
```python
c = Cart()
c.amount = 15.00
c.payment("paypal")
```
And inside Cart class, you may implement "payment" method:
```python
class Cart(object):
def __init__(self):
self.amount = 0.0
pass
def payment(self, choosed_method):
p = get_payment(choosed_method)
p.charge()
```
But that's not very useful. We only moved the same code as the beginning of the article in a separated function. The good way to implement dependency injection is to call "get_payment" when we need it.
The second possible implementation is to send the factory instead of the name. So you should call
```python
c = Cart()
c.amount = 15.00
c.payment(get_payment("paypal"))
# or
c.payment(get_payment())
```
Now, we are closer to common "DI". In Cart class, "payment" method get the type of "Payment" and will instanciate it at time.
So, we're now back to the "better" implementation we've seen earlier:
```python
class Cart(object):
def __init__(self):
self.amount = 0.0
pass
def payment(self, choosed_method):
# better, the payment method is injected
p = choosed_method()
p.charge()
```
At last, we can be able to accept both string and factories. That way, we allow several usages of the "payment" method.
```python
class Cart(object):
def __init__(self):
self.amount = 0.0
pass
def payment(self, choosed_method):
p = None
if type(choosed_method) in (str, basestring):
p = get_payment(choosed_method)
else:
p = choosed_method()
p.charge()
```
## So, the DI definition is ?
To be precise, DI means that your working class (Cart in our example) shouldn't directly create dependent objects. It must be able to call factories.
There is no problem to use DI framework, but it's often not so useful and you probably only need to create you class with a bit more attention to accept injection.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment