Design Pattern in Python (4): Adapter Pattern

zqiu

Z. QIU

Posted on December 7, 2020

Design Pattern in Python (4): Adapter Pattern

Today I would like to do some coding work on Adapter Pattern in Python.
Adapter is one of the Structural Patterns.

It works as a bridge between two incompatible interfaces. This pattern involves a single class which is responsible to join functionalities of independent or incompatible interfaces.

Alt Text

I found easily one real life example of "adapter" in my room. I have a laptop of brand Redmi which has a chinese type power plug. I am living in France and thus I have only European type socket at home. It's not possible to insert directly my laptop's plug into a french socket.
Alt Text

To solve the issue, I am using an adapter to charge my laptop. This small gadget is really very practical for me.
Alt Text

Thus I would like to code this example in Python today. Let's go!

1st simulation: Incompatible issue

Socket simulation

Firstly I define several classes for sockets including PowerSocket base class and its concrete classes.

class PowerSocket():
    """
       PowerSocket base class 
    """
    def __init__ (self, holeNum, Shape, Volt):
        self.__num_holes = holeNum
        self.__hole_shape = Shape
        self.__volt = Volt

    def getHoleNum (self):
        return self.__num_holes 

    def getHoleShape (self):
        return self.__hole_shape 

    def getVolt (self):
        return self.__volt 

### some concrete PowerSocket classes
class chineseSocket(PowerSocket):
    def __init__ (self):
        super().__init__( 3, "FLAT", 220)  
class europeanSocket(PowerSocket):
    def __init__ (self):
        super().__init__( 2, "ROUND", 220)          
class taiwaneseSocket(PowerSocket):
    def __init__ (self):
        super().__init__( 2, "FLAT", 110) 
Enter fullscreen mode Exit fullscreen mode

Laptop simulation

Now a class of my Laptop with its plug class. Method charge() of RedmiLaptop class shall check if socket is compatible with its power plug.

class chinise3pinPlug():
    def __init__ (self):
        self.pins = 3
        self.volt = 220
        self.pinshape = "FLAT"    

class RedmiLaptop():

    def __init__ (self):
        self.plug = chinise3pinPlug()


    def charge(self, socket, powerInWatt):
        res = False     
        if (isinstance(socket, PowerSocket)):
            res = (self.plug.pins == socket.getHoleNum() )  and \
            (self.plug.pinshape == socket.getHoleShape() ) and  \
            (self.plug.volt == socket.getVolt() )
        else:
            print ("Socket is not instance of PowerSocket")

        if res:
            current = round(powerInWatt / self.plug.volt, 2)
            print("Start charging... Power: {} watt; Socket current: {} am ...".format(str(powerInWatt), str(current)))
        else:
            print("Socket and plug not compatible, impossible to charge.")
        return res
Enter fullscreen mode Exit fullscreen mode

Charging simulation

Now launch the simulation. I move to 3 different areas in this simulation and see if I can charge my laptop there.

if __name__ == "__main__":

    laptop = RedmiLaptop() # instance of my Redmi Laptop

    # I am in china mainland
    chSocket = chineseSocket()
    laptop.charge(socket=chSocket, powerInWatt=235)

    # I am in France
    euSocket = europeanSocket()
    laptop.charge(socket=euSocket, powerInWatt=235)

    # I am in Taipei 
    twSocket = taiwaneseSocket()
    laptop.charge(socket=twSocket, powerInWatt=235)
Enter fullscreen mode Exit fullscreen mode

Execution output:
Alt Text

2nd Simulation: adapter usage

Adapter simulation

Now I introduce a SocketAdapter base class as adapter interface for socket conversion. I define a AnyToChineseAdapter concrete class to simulate a multi-usage converter for chinese-type plug of my laptop. AnyToChineseAdapter has a output socket of chinese type. It implements a core method convert() which is responsible to convert different socket interfaces to chinese type, namely it bridges various socket types to chinese-type plugs.

class SocketAdapter():
    """
       SocketAdapter base class 
    """
    def __init__ (self ):
        pass

    def convert(self ):
        pass

    def getSocket (self):
        pass


class AnyToChineseAdapter(SocketAdapter):
    """
       A concrete SocketAdapter class that can convert any socket to chinese socket
    """

    def __init__ (self):
        super().__init__()
        self.__outSocket = chineseSocket()
        self.__voltRatio = 1
        self.__plug = ""

    def convert(self, fromSocket):
        res = True
        if  isinstance (fromSocket,  chineseSocket):
            self.__voltRatio = 1
            self.__plug = "Chinese format Plug"
            print("Chinese to Chinese using {}".format(self.__plug))
        elif isinstance (fromSocket,  europeanSocket):
            self.__voltRatio = 1
            self.__plug = "European format Plug"
            print("European to Chinese using {}".format(self.__plug))
        elif isinstance (fromSocket,  taiwaneseSocket):
            self.__voltRatio = 2
            self.__plug = "Taiwanese format Plug"
            print("Taiwanese to Chinese using {}".format(self.__plug))
        # elif     isinstance (fromSocket,  someSocket):
        #    do converting stuff...
        else:        
            print("Unknown socket, cannot choose plug format and volt convertion ratio")
            res = False
        return res

    def getSocket(self):
        return self.__outSocket

    def getVoltRatio(self):
        return self.__voltRatio
Enter fullscreen mode Exit fullscreen mode

Socket simulation

I define PowerSocket base class and its concrete classes. This part is almost the same as in the first simulation. I define one extra class martianSocket for simulating socket on Mars.

class PowerSocket():
    """
       PowerSocket base class 
    """
    def __init__ (self, holeNum, Shape, Volt):
        self.__num_holes = holeNum
        self.__hole_shape = Shape
        self.__volt = Volt

    def getHoleNum (self):
        return self.__num_holes 

    def getHoleShape (self):
        return self.__hole_shape 

    def getVolt (self):
        return self.__volt 


### some concrete PowerSocket classes
class chineseSocket(PowerSocket):
    def __init__ (self):
        super().__init__( 3, "FLAT", 220)
class europeanSocket(PowerSocket):
    def __init__ (self):
        super().__init__( 2, "ROUND", 220)   
class taiwaneseSocket(PowerSocket):
    def __init__ (self):
        super().__init__( 2, "FLAT", 110)  
class martianSocket(PowerSocket):
    def __init__ (self):
        super().__init__( 2, "FLAT", 300)          
Enter fullscreen mode Exit fullscreen mode

Laptop simulation

A modification in Laptop class regarding the 1st simulation is that now it has a private member __adapter which is an instance of AnyToChineseAdapter. A new method addAdapter should be called to attach a SocketAdapter instance.

class chinise3pinPlug():
    def __init__ (self):
        self.pins = 3
        self.volt = 220
        self.pinshape = "FLAT"    

class RedmiLaptop():
    def __init__ (self):
        self.plug = chinise3pinPlug()
        self.__adapter = None

    def addAdapter(self, adpt):
        self.__adapter = adpt

    def charge(self, inSocket, powerInWatt):
        res = False   
        if (isinstance(inSocket, PowerSocket)) :
            if self.__adapter.convert(inSocket):
                socket = self.__adapter.getSocket()
                res = (self.plug.pins == socket.getHoleNum() )  and \
                (self.plug.pinshape == socket.getHoleShape() ) and  \
                (self.plug.volt == socket.getVolt() )
            else:
                res = False 
        else:
            print ("Socket is not instance of PowerSocket")

        if res:
            current = round(powerInWatt / self.plug.volt, 2) * self.__adapter.getVoltRatio()
            print("Start charging... Power: {} watt; Socket current: {} am ...".format(str(powerInWatt), str(current)))
        else:
            print("Socket and plug not compatible, impossible to charge.")
        return res
Enter fullscreen mode Exit fullscreen mode

Charging simulation

Now launch the 2nd simulation. I move firstly to 3 different areas on earth and finally go on Mars in this simulation and see if I can charge my laptop there.

if __name__ == "__main__":

    redmiAd = AnyToChineseAdapter()
    laptop = RedmiLaptop()
    laptop.addAdapter(redmiAd)


    # I am in china mainland
    chSocket = chineseSocket()
    laptop.charge(chSocket, powerInWatt=235)

    # I am in France
    euSocket = europeanSocket()
    laptop.charge(euSocket, powerInWatt=235)

    # I am in Taipei 
    twSocket = taiwaneseSocket() 
    laptop.charge(twSocket, powerInWatt=235)

    # I am on Mars
    msSocket = martianSocket()
    laptop.charge(msSocket, powerInWatt=235)
Enter fullscreen mode Exit fullscreen mode

Execution output:
Alt Text

See, I can charge my laptop with chinese, european and taiwanese sockets. However, I cannot charge it on Mars, since the adapter does not (yet) have conversion method for martian socket type.

💖 💪 🙅 🚩
zqiu
Z. QIU

Posted on December 7, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related