Design Pattern in Python (4): Adapter Pattern
Tags: python, design, pattern, adapter
Published: 2020-12-07 10:12:58

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](https://dev-to-uploads.s3.amazonaws.com/i/0kycc8k2ejafopvizmi9.PNG)

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](https://dev-to-uploads.s3.amazonaws.com/i/haewyihlhxpt466kb436.png)

To solve the issue, I am using an adapter to charge my laptop. This small gadget is really very practical for me. 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/2gdh0vbkf5gcl8eq0t1u.png)

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.

```python
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) 
```

### 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.

```python
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
```

### Charging simulation

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

```python
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)
```

Execution output:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/x9353txxpl2ibkx7y0ws.PNG)


## 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.

```python
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
```

### 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.

```python
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)          
```

### 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.

```python
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
```

### 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. 

```python
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)
```
Execution output:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/m7heaeov6c9d10zs4h9k.PNG)

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.