[design mode] Bridge mode

Posted by Stille on Mon, 07 Feb 2022 13:53:24 +0100

Bridge mode

motivation

  • Due to the inherent implementation logic of some types, they have two changing dimensions, and even multiple latitude changes.
  • How to deal with this "multi-dimensional change"? How to use object-oriented technology to make types change easily in two or more directions without introducing additional complexity?

Pattern definition

Separate the abstract part (business function) from the implementation part (platform implementation), so that they can change independently—— Design pattern GoF

SHOW ME THE CODE

Initial code

bridge1.cpp

class Messager{
public:
    virtual void Login(string username, string password)=0;
    virtual void SendMessage(string message)=0;
    virtual void SendPicture(Image image)=0;

    virtual void PlaySound()=0;
    virtual void DrawShape()=0;
    virtual void WriteText()=0;
    virtual void Connect()=0;
    
    virtual ~Messager(){}
};


//Platform implementation

class PCMessagerBase : public Messager{
public:
    
    virtual void PlaySound(){
        //**********
    }
    virtual void DrawShape(){
        //**********
    }
    virtual void WriteText(){
        //**********
    }
    virtual void Connect(){
        //**********
    }
};

class MobileMessagerBase : public Messager{
public:
    
    virtual void PlaySound(){
        //==========
    }
    virtual void DrawShape(){
        //==========
    }
    virtual void WriteText(){
        //==========
    }
    virtual void Connect(){
        //==========
    }
};



//Business abstraction

class PCMessagerLite : public PCMessagerBase {
public:
    
    virtual void Login(string username, string password){
        
        PCMessagerBase::Connect();
        //........
    }
    virtual void SendMessage(string message){
        
        PCMessagerBase::WriteText();
        //........
    }
    virtual void SendPicture(Image image){
        
        PCMessagerBase::DrawShape();
        //........
    }
};



class PCMessagerPerfect : public PCMessagerBase {
public:
    
    virtual void Login(string username, string password){
        
        PCMessagerBase::PlaySound();
        //********
        PCMessagerBase::Connect();
        //........
    }
    virtual void SendMessage(string message){
        
        PCMessagerBase::PlaySound();
        //********
        PCMessagerBase::WriteText();
        //........
    }
    virtual void SendPicture(Image image){
        
        PCMessagerBase::PlaySound();
        //********
        PCMessagerBase::DrawShape();
        //........
    }
};


class MobileMessagerLite : public MobileMessagerBase {
public:
    
    virtual void Login(string username, string password){
        
        MobileMessagerBase::Connect();
        //........
    }
    virtual void SendMessage(string message){
        
        MobileMessagerBase::WriteText();
        //........
    }
    virtual void SendPicture(Image image){
        
        MobileMessagerBase::DrawShape();
        //........
    }
};


class MobileMessagerPerfect : public MobileMessagerBase {
public:
    
    virtual void Login(string username, string password){
        
        MobileMessagerBase::PlaySound();
        //********
        MobileMessagerBase::Connect();
        //........
    }
    virtual void SendMessage(string message){
        
        MobileMessagerBase::PlaySound();
        //********
        MobileMessagerBase::WriteText();
        //........
    }
    virtual void SendPicture(Image image){
        
        MobileMessagerBase::PlaySound();
        //********
        MobileMessagerBase::DrawShape();
        //........
    }
};


void Process(){
        //Compile fashion accessories
        Messager *m =
            new MobileMessagerPerfect();
}



Deja vu feeling, let's look at the expansion scale. If so n n n platforms, each with m m m versions. Then the number of classes 1 + n + m ∗ n 1+n+m*n 1+n+m∗n

The code also has redundancy, such as

virtual void Login(string username, string password){

    PCMessagerBase::PlaySound();
    //********
    PCMessagerBase::Connect();
    //........
}

virtual void Login(string username, string password){

    MobileMessagerBase::PlaySound();
    //********
    MobileMessagerBase::Connect();
    //........
}

Optimization 1: inheritance to combination

Similar to the Decorator design pattern, we can think of using pointers to optimize inheritance to composition

Make the following modifications to PCMessagerLite (see the steps in the Decorator design pattern for each step)

//class PCMessagerLite : public PCMessagerBase {
class PCMessagerLite{
    Message* messager;//=new PCMessagerBase();
public:
    
    virtual void Login(string username, string password){
        
        //PCMessagerBase::Connect();
        messager->Connect();
        //........
    }
    virtual void SendMessage(string message){
        
        //PCMessagerBase::WriteText();
        messager->WriteText();
        //........
    }
    virtual void SendPicture(Image image){
        
        //PCMessagerBase::DrawShape();
        messager->DrawShape();
        //........
    }
};

Now it is found that PCMessagerLite and MoblieMessagerLite can be merged

Optimization 2: split abstract base classes

Another problem is that PCMessagerBase is an abstract class (pure virtual base class), but we don't cover all virtual functions

Message* messager;//=new PCMessagerBase(); This is actually not true

It can be found that several pure virtual functions in the Messager should not be put together, but should be separated

class Messager{
public:
    virtual void Login(string username, string password)=0;
    virtual void SendMessage(string message)=0;
    virtual void SendPicture(Image image)=0;

    virtual void PlaySound()=0;
    virtual void DrawShape()=0;
    virtual void WriteText()=0;
    virtual void Connect()=0;
    
    virtual ~Messager(){}
};

Add a MessagerImp

class Messager{
protected:
     MessagerImp* messagerImp;//...
public:
    virtual void Login(string username, string password)=0;
    virtual void SendMessage(string message)=0;
    virtual void SendPicture(Image image)=0;
    
    virtual ~Messager(){}
};

// Different change directions (business and platform), so it is divided into two categories
class MessagerImp{
public:
    virtual void PlaySound()=0;
    virtual void DrawShape()=0;
    virtual void WriteText()=0;
    virtual void Connect()=0;
    
    virtual MessagerImp(){}
};

Like the Decorator design pattern, promote a field and then add a constructor

bridge2.cpp

class Messager{
protected:
     MessagerImp* messagerImp;//... The field refers to the parent class
public:
    virtual void Login(string username, string password)=0;
    virtual void SendMessage(string message)=0;
    virtual void SendPicture(Image image)=0;
    
    virtual ~Messager(){}
};

// Different change directions (business and platform), so it is divided into two categories
class MessagerImp{
public:
    virtual void PlaySound()=0;
    virtual void DrawShape()=0;
    virtual void WriteText()=0;
    virtual void Connect()=0;
    
    virtual MessagerImp(){}
};


//Platform implementation n
class PCMessagerImp : public MessagerImp{
public:
    
    virtual void PlaySound(){
        //**********
    }
    virtual void DrawShape(){
        //**********
    }
    virtual void WriteText(){
        //**********
    }
    virtual void Connect(){
        //**********
    }
};

class MobileMessagerImp : public MessagerImp{
public:
    
    virtual void PlaySound(){
        //==========
    }
    virtual void DrawShape(){
        //==========
    }
    virtual void WriteText(){
        //==========
    }
    virtual void Connect(){
        //==========
    }
};



//Business abstraction m

//Number of classes: 1+n+m

class MessagerLite :public Messager {

    
public:
    
    virtual void Login(string username, string password){
        
        messagerImp->Connect();
        //........
    }
    virtual void SendMessage(string message){
        
        messagerImp->WriteText();
        //........
    }
    virtual void SendPicture(Image image){
        
        messagerImp->DrawShape();
        //........
    }
};



class MessagerPerfect  :public Messager {
    
   
public:
    
    virtual void Login(string username, string password){
        
        messagerImp->PlaySound();
        //********
        messagerImp->Connect();
        //........
    }
    virtual void SendMessage(string message){
        
        messagerImp->PlaySound();
        //********
        messagerImp->WriteText();
        //........
    }
    virtual void SendPicture(Image image){
        
        messagerImp->PlaySound();
        //********
        messagerImp->DrawShape();
        //........
    }
};




void Process(){
    //Running fashion
    MessagerImp* mImp=new PCMessagerImp();
    Messager *m =new Messager(mImp);
}



Become the present scale 1 + n + m 1+n+m 1+n+m, the problem has been effectively solved

The change direction of platform implementation and business abstraction is different. They should be separated so that they can change independently

Structural design

Red is stable and blue changes

Summary of key points

  • The Bridge pattern uses the "composite relationship between objects" to decouple the inherent binding relationship between abstraction and implementation, so that abstraction and implementation can change along their respective dimensions. The so-called abstraction and implementation change along their respective latitudes, that is, "subclassing" them.
  • Bridge mode is sometimes similar to multi inheritance schemes, but multi inheritance schemes often violate the principle of single responsibility (that is, a class has only one reason for change), and the reusability is poor. The bridge pattern is a better solution than the multi inheritance scheme.
  • Bridge mode is generally applied in "two very strong change dimensions". Sometimes a class also has more than two change dimensions. At this time, bridge extension mode can be used.

Topics: C++ Design Pattern Back-end