Design Pattern in Python (6): Mediator Pattern
Tags: design, pattern, python, mediator
Published: 2020-12-16 14:18:49

##Introduction

Today I worked a little on Mediator Pattern. This pattern is adopted to reduce communication complexity between multiple objects. Instead of implementing inside each class the direct communication with other classes (strong coupling), a mediator object is implemented and each class can call this mediator object to communicate with others. This can bring loose coupling between classes and make it easier to develop and extend the program.
Mediator pattern is classified as a **behavioral pattern**.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/yzoioeosyt3tsq3ytdmw.PNG)



## Real life example
Currently I started using a very great application [**Too Good To Go**]
(https://en.wikipedia.org/wiki/Too_Good_To_Go) in my daily life.  This application connects customers to restaurants and stores that have unsold, surplus food. Its objective is to reduce food waste and protect our earth. I can buy "food baskets" of nearby stores on this app with a very low price. What a great idea! 

See, I have reserved a basket for this evening:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/jr7o3qlfyds4rnfx5lmz.jpg)

This app is in fact a very good example for **Mediator pattern**. Let's see how clients should do to query and buy available food baskets in shops without using this app (without mediator):


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


Now by using this **Too Good To Go** app, clients now can query and buy available food baskets in this way:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/bqukhir87t30wxm1fn8g.PNG)


## Exercise in Python
Thus I would like to simulate the idea of **Too Good To Go** in my exercise. Go.


Define a **BasketInfo** class:
```python 
## Basket of Too good to go
class BasketInfo:
    """Food Basket info"""

    def __init__(self, location, price,  address, Shop):
        self.__location = location
        self.__price = price
        self.__address = address
        self.__Shop = Shop
        
    def getLocation(self):
        return self.__location
        
    def getAddress(self):
        return self.__address

    def getShopName(self):
        return self.__Shop.getName()

    def showInfo(self, isShowShop = True):
        print(" ++ Location: {}".format(self.__location) )
        print(" ++ Price: {}" .format( str(self.__price) + " euros") )
        print(" ++ Address: {}" .format( self.__address) )
        print(" ++ Shop: " + self.getShopName() if isShowShop else "") 
        print()
```

The **BasketPlatformApp** class which serves as Mediator:
```python
import difflib

## Check the similarity of two strings
def get_equal_rate(str1, str2):
   return difflib.SequenceMatcher(None, str1, str2).quick_ratio()
   
   
class BasketPlatformApp:
    """Too Good To Go platform"""

    def __init__(self, name):
        self.__BasketInfos = []
        self.__name = name

    def getName(self):
        return self.__name

    def addBasketInfo(self, BasketInfo):
        self.__BasketInfos.append(BasketInfo)

    def removeBasketInfo(self, BasketInfo):
        for info in self.__BasketInfos:
            if(info == BasketInfo):
                self.__BasketInfos.remove(info)

    def getSearchCondition(self, description):
        return description

    def getMatchInfos(self, searchCondition):
        print(self.getName(), " shows suitable baskets for you：")
        suitables = []
        for info in self.__BasketInfos:
            if get_equal_rate(searchCondition, info.getLocation()) > 0.9:
                info.showInfo(False)
                suitables.append(info)
        return  suitables

    def addBasket(self, BasketInfo):
        print(self.getName(), " has a new avaible Basket \n  -- Provided by ", BasketInfo.getShopName(), ",\n  -- Located at: ", BasketInfo.getAddress())

    def addBaskets(self):
        for info in self.__BasketInfos :
            self.addBasket(info)

```

Now define BasketShop and Customer classes. They will not communicate directly with each other. Their communication shall be done via a mediator. Note that they do not implement each other in their code:
```python

class BasketShop:
    """ BasketShop class """

    def __init__(self, name):
        self.__name = name
        self.__BasketInfo = None

    def getName(self):
        return self.__name

    def setBasketInfo(self, address, location, price):
        self.__BasketInfo = BasketInfo(location, price,  address, self)

    def publishBasketInfo(self, App):
        App.addBasketInfo(self.__BasketInfo)
        print(self.getName() + " pushes a Basket on ", App.getName(), ": ")
        self.__BasketInfo.showInfo()


class Customer:
    """User of TooGoodToGO"""

    def __init__(self, name):
        self.__name = name

    def getName(self):
        return self.__name

    def findBasket(self, description, App):
        print("User " + self.getName() + ", searching a backet with info: " + description )
        print()
        return App.getMatchInfos(App.getSearchCondition(description))

    def viewBasket(self, BasketInfos):
        size = len(BasketInfos)
        return BasketInfos[size-1]

    def buyBasket(self, BasketInfo, App):
        """ command Basket on App """
        print(self.getName(), " made a new command on ", App.getName(), " for a basket in ",  BasketInfo.getShopName())

```

Now launch a simulation of **Mediator** pattern:
```python

if __name__ == "__main__":
    myAPP = BasketPlatformApp("Too Good To Go")
    Paul = BasketShop("Paul");
    Paul.setBasketInfo("La Defense Parvis 15, 92000, Haut-Seine", "4 temps commercial center", 3.99)
    Paul.publishBasketInfo(myAPP)
    Auchan = BasketShop("Auchan")
    Auchan.setBasketInfo("22 Rue Alma, 92240, Courbevoie", "Supermarcket A2Pas" , 4.0)
    Auchan.publishBasketInfo(myAPP)
    Sushi = BasketShop("Sushi Shop")
    Sushi.setBasketInfo("La Defense Parvis 15, 92000, Haut-Seine", "4 temps commercial center", 6.99)
    Sushi.publishBasketInfo(myAPP)
    print()

    myAPP.addBaskets()
    print()

    jemaloQ = Customer("jemaloQ")
    BasketInfos = jemaloQ.findBasket("4 temps commercial center", myAPP)
    print()
    print("Searching available baskets for you ……")
    print()
    AppropriateBasket = jemaloQ.viewBasket(BasketInfos)
    jemaloQ.buyBasket(AppropriateBasket, myAPP)
```


### Execution output；

Food shops pushes available basket to APP platform:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ppyug5gktj4f5m7ndkqx.PNG)

 **Too good to Go** updates its available baskets:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/96rtql202o8i5i0lunfb.PNG)


A **Too good to Go** user searches and buy food basket:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/m4k92c433k6iyiy6kuve.PNG)