Categories
Python

Composition over Inheritance

Inheritance

If you know basic OOP, you know what Inheritance is. When one class extends another, the child class inherits the parent class and thus the child class has access to all the variables and methods on the parent class.

class Duck:
    speed = 30

    def fly(self):
        return "Flying at {} kmph".format(self.speed)


class MallardDuck(Duck):
    speed = 20


if __name__ == "__main__":
    duck = Duck()
    print(duck.fly())

    mallard = MallardDuck()
    print(mallard.fly())

Here the `MallardDuck` extends `Duck` and inherits the `speed` class variable along with the `fly` method. We override the `speed`in the child class to suit our needs. When we call `fly` on the mallard duck, it uses the `fly` method inherited from the parent. If we run the above code, we will see the following output:

Flying at 30 kmph
Flying at 20 kmph

This is inheritance in a nutshell.

Composition

Let’s first see an example:

class GmailProvider:
    def send(self, msg):
        return "Sending `{}` using Gmail".format(msg)


class YahooMailProvider:
    def send(self, msg):
        return "Sending `{}` using Yahoo Mail!".format(msg)


class EmailClient:
    email_provider = GmailProvider()
    
    def setup(self):
        return "Initialization and configurations"

    def set_provider(self, provider):
        self.email_provider = provider

    def send_email(self, msg):
        print(self.email_provider.send(msg))


client = EmailClient()
client.setup()

client.send_email("Hello World!")

client.set_provider(YahooMailProvider())
client.send_email("Hello World!")

Here we’re not implementing the email sending functionality directly inside the `EmailClient`. Rather, we’re storing a type of email provider in the `email_provider` variable and delegating the responsibility of sending the email to this provider. When we have to `send_email`, we call the `send` method on the `email_provider`. Thus we’re composing the functionality of the `EmailClient` by sticking composable objects together. We can also swap out the email provider any time we want, by passing it a new provider to the `set_provider` method.

Composition over Inheritance

Let’s implement the above `EmailClient` using inheritance.

class EmailClient:
    def setup(self):
        return "Initializions and configurations!"

    def send_email(self, msg):
        raise NotImplementedError("Use a subclass!")


class GmailClient(EmailClient):
    def send_email(self, msg):
        return "Sending `{}` from Gmail Client".format(msg)


class YahooMailClient(EmailClient):
    def send_email(self, msg):
        return "Sending `{}` from YMail! Client".format(msg)


client = GmailClient()
client.setup()

client.send_email("Hello!")

# If we want to send using Yahoo, we have to construct a new client

yahoo_client = YahooMailClient()
yahoo_client.setup()

yahoo_client.send_email("Hello!")

Here, we created a base class `EmailClient` which has the `setup` method. Then we extended the class to create `GmailClient` and `YahooMailClient`. Things got interesting when we wanted to start sending emails using Yahoo instead of Gmail. We had to create a new instance of `YahooMailClient` for that purpose. The initially created `client` was no longer useful for us since it only knows how to send emails through Gmail.

This is why composition is often favoured over inheritance. By delegating the responsibility to the different composable parts, we form loose coupling. We can swap out those components easily when needed. We can also inject them as dependencies using dependency injection. But with inheritance, things get tightly coupled and not easily swappable.

7 replies on “Composition over Inheritance”

Since functions are first class objects in Python, why not simplify even further and pass in send functions rather than adapter classes in the composition example.

In the current example, yes, that is possible. However, I would imagine, in a real life scenario, the classes would have some setup/configurations of their own too. So we would need to properly initialize them before passing on.

I see, fair point. Although one would pass the “parent” class into the function so that it could access any attributes it needed for the proper implementation. I believe your composition example is a “strategy” pattern. Luciano Ramalho’s recent book Fluent Python makes reference to this pattern and suggests the use of functions in place of using classes for the “strategy” components. I thought he had a good point so that is why I was asking your thoughts.

Interesting. I haven’t read the book. I am going to explore it soon. Thanks for pointing out!

Yes, it’s an example of Strategy Pattern.

Comments are closed.