• Giacomo Debidda
    • Articles
    • Projects
    • Services
    • Notes
    • Likes
    • Bookmarks
    • Photos
    • About
    • Contact

Adapter pattern in Python

26 Nov 2016 Giacomo Debidda
  • article
  • design patterns
  • Python

Table of Contents đź“‘
  1. # Adapter is a structural design pattern
  2. # How do we implement the Adapter Pattern?
  3. # 1. Object Adapter
  4. # 2. Class Adapter
  5. # Object adapter or Class Adapter?

Some weeks ago I decided to start studying design patterns and implementing them in Python. Design patterns and Head first in design patterns are constantly cited for being really good books. I added them to my reading list some time ago, but I still haven’t managed to read them so far. Nonetheless, I’ve read several blog posts, articles on Wikipedia and answers on Stack Overflow and started implementing some of these patterns.

Here we are going to see the Adapter pattern.

# Adapter is a structural design pattern

Structural design patterns are concerned with how classes and objects are composed to form larger structures. They help to use classes or methods which may not be usable directly, or they can ease the design by identifying a simple way to build relationships between entities.

Adapter allows a Client to access otherwise not directly accessible functionalities of a Supplier . Adapter makes things work after they are designed: it produces an interface for a single object or class, and adapts such class in a way that a Client can use it.

You have got this, and you need that.

# How do we implement the Adapter Pattern?

There are two ways of implementing the Adapter pattern:

  1. Object Adapter
  2. Class Adapter

The object adapter uses encapsulation, while the class adapter uses multiple inheritance (Python supports both encapsulation and multiple inheritance).

Let’s imagine that you have a smartphone (client) and you want to charge it. In order to charge a mobile phone you need a direct current (DC) and an input voltage of a few volts (from 3.7V to 5.2V I suppose), so you can’t simply plug it directly into a wall socket (supplier), which provides an alternate current (AC) and outputs either 230V (in Europe) or 120V (in the US). Namely, without a phone charger (the Adapter) you can’t charge your phone.

In the following code I implemented both a European Socket class and and an American Socket class because they will be useful later on when explaining the Class Adapter approach. For now you can ignore the USSocket class.

# Client
class Smartphone(object):

    max_input_voltage = 5

    @classmethod
    def outcome(cls, input_voltage):
        if input_voltage > cls.max_input_voltage:
            print("Input voltage: {}V -- BURNING!!!".format(input_voltage))
        else:
            print("Input voltage: {}V -- Charging...".format(input_voltage))

    def charge(self, input_voltage):
        """Charge the phone with the given input voltage."""
        self.outcome(input_voltage)


# Supplier
class Socket(object):
    output_voltage = None

class EUSocket(Socket):
    output_voltage = 230

class USSocket(Socket):
    output_voltage = 120

This is the current scenario:

ClientSupplier
SmartphoneEUSocket

If you take a Smartphone instance and you call charge with EUSocket.output_voltage as argument, you will fail at charging your phone.

smartphone = Smartphone()
smartphone.charge(EUSocket.output_voltage)
>>> Input voltage: 230V -- BURNING!!!

# 1. Object Adapter

Obvioulsy, you need a phone charger to charge your smarthone. You can think of this phone charger as a completely independent entity from the smartphone and the wall socket. This new entity encapsulates client and supplier, and allows you to call the charge method without changing anything, neither in the Smartphone class, nor the Socket class. The phone charger converts an alternate current, high voltage power supply, into a direct current, low voltage power supply that can be used to charge the smartphone.

class EUAdapter(object):
    """EUAdapter encapsulates client (Smartphone) and supplier (EUSocket)."""
    input_voltage = EUSocket.output_voltage
    output_voltage = Smartphone.max_input_voltage

The EUAdapter class is a Supplier to the Smartphone class, and at the same time it’s a Client to the EUSocket class.

ClientSupplier
SmartphoneEUAdapter
EUAdapterEUSocket

If you now take a Smartphone instance and call charge with EUAdapter.output_voltage as argument, you can finally charge your phone.

smartphone.charge(EUAdapter.output_voltage)
>>> Input voltage: 5V -- Charging...

# 2. Class Adapter

You can also think that the combination smartphone + phone charger defines a unique system which can directly use the wall socket.

You started with a Smartphone and a Socket, and now you want to define a system which inherits methods and attributes both from Smartphone and Socket. You have to use multiple inheritance.

With this approach you don’t create a new entity between the client and the supplier, but you redefine the client in a way that it can directly work with the supplier. You don’t have a Smartphone any longer, you have a new entity which is the combination of a Smartphone and a Socket.

Since you are getting the output_voltage from a Socket, you have to define a method transform_voltage to convert a high voltage AC to a low voltage DC. Then you need to override the charge method inherited from Smartphone and call transform_voltage before calling the outcome method.

I decided to have two subclasses of Socket to make this example a bit closer to the real world. When you take a Smartphone and a Socket, you define a system which will work for that Smartphone and that specific type of Socket (e.g. USSocket), but will not work with the same Smartphone and a different type of Socket (EUSocket).

class CannotTransformVoltage(Exception):
    """Exception raised by the SmartphoneAdapter.

    This exception represents the fact that an adapter can not provide the
    right voltage to the Smartphone if the voltage of the Socket is wrong."""
    pass


class SmartphoneAdapter(Smartphone, Socket):

    @classmethod
    def transform_voltage(cls, input_voltage):
        if input_voltage == cls.output_voltage:
            return cls.max_input_voltage
        else:
            raise CannotTransformVoltage(
                "Can\'t transform {0}-{1}V. This adapter transforms {2}-{1}V."
                .format(input_voltage, cls.max_input_voltage,
                        cls.output_voltage))

    @classmethod
    def charge(cls, input_voltage):
        try:
            voltage = cls.transform_voltage(input_voltage)
            cls.outcome(voltage)
        except CannotTransformVoltage as e:
            print(e)


class SmartphoneEUAdapter(SmartphoneAdapter, EUSocket):
    """System (smartphone + adapter) for a European Socket.

    Note: SmartphoneAdapter already inherited from Smartphone and Socket, but by re-inheriting from EUSocket we redefine all the stuff inherited from Socket.
    """ pass


class SmartphoneUSAdapter(SmartphoneAdapter, USSocket):
    """System (smartphone + adapter) for an American Socket."""
    pass

Here are the two classes you are dealing with:

ClientSupplier
SmartphoneEUAdapterEUSocket

If you now take a SmartphoneEUAdapter instance and call charge with EUSocket.output_voltage as argument, you can see that you can charge your phone. However, if you take the same instance and call charge with USSocket.output_voltage as argument, you get a CannotTransformVoltage exception. In the latter case, you are using the wrong Adapter for a particular Supplier.

smarthone_with_eu_adapter = SmartphoneEUAdapter()
smarthone_with_eu_adapter.charge(EUSocket.output_voltage)
>>> Input voltage: 5V -- Charging...

smarthone_with_eu_adapter.charge(USSocket.output_voltage)
>>> Can't transform 120-5V. This adapter transforms 230-5V.

# Object adapter or Class Adapter?

There are two strong reasons to prefer the Object Adapter over the Class Adapter:

  • loose coupling
  • multiple inheritance is tricky

With the Object Adapter you have loose coupling, so the Client is not required to know anything about the Supplier. The Smartphone doesn’t care where it gets its 5 volts. As long as it gets them, it will charge.

With the Class Adapter you lose this property, because you have a new entity which is defined by the Client and the Supplier, and it works only for this specific type of Client and specific type of Supplier (e.g. SmartphoneEUAdapter doesn’t work with a USSocket). This means that you have created an interface which allows you to use the Client and the Supplier, but where Client and Supplier are strongly coupled. Since you usually want to design interfaces to uncouple things, this is not a desired property.

Another reason why I decided to define two subclasses of Socket is to show that multiple inheritance can be tricky. As we can see in the code above, SmartphoneAdapter already contains all attributes and methods from Smartphone and Socket. However, since what you really want to use are the subclasses of Socket, namely EUSocket and USSocket, you need to re-inherit when you subclass SmartphoneAdapter. You can use a different strategy and create SmartphoneEUAdapter by directly inheriting from Smartphone and EUSocket, but then you would need to do the same for SmartphoneUSAdapter, which needs to inherit from Smartphone and USSocket. This will result in duplicate code, because you would need to write transform_voltage and charge twice.

You need the code? Grab it here!


  • article
  • design patterns
  • Python

🗣️ Let's have a chat!

Each week, I carve out some time for anyone to contact me. Feel free to reach out to me. I'll do my best to help you with whatever you need.

Reserve your spot here:
https://cal.com/giacomodebidda/30min

If no time slot fits you, send me a DM on LinkedIn or Twitter.

Webmentions

Did you mention this blog post on your website? Let me know the URL of your article using the form below.

Upon form submission, your webmention will be sent to Webmention.io.

    Webmentions collected by Bridgy.

    No webmentions to show.

    • Articles feed
    • Notes feed
    • Talks feed
    • GitHub
    • Twitter
    • Linkedin
    • Mastodon
    • Stack Overflow
    Copyright © 2020 – 2024 Giacomo Debidda – All rights reserved