Decoration mode (Democrator) – single responsibility class, structural mode

In some cases, we may “excessively use inheritance to extend the functionality of objects”. Due to the static characteristics introduced by inheritance for types, this extension method lacks flexibility; and with the increase of subclasses (the increase of extension functions) ), the combination of various subclasses (combination of extended functions) will lead to the expansion of more subclasses.

How to enable “expansion of object functions” to be dynamically implemented as needed? At the same time, avoid the subcategory expansion problem caused by “increasing extended functions”? So that the impact of any “functional expansion changes” will be minimal?

Dynamically (compositionally) add some additional responsibilities to an object. In terms of adding functionality, the Decorator pattern is more flexible than generating subclasses (inheritance) (eliminating duplicate code & reducing the number of subclasses)

What do you say about this model? It is used to solve the problem of too many subclasses due to programmers’ unclear ideas about class responsibilities. You can use this flow when you want to inherit from a base class from multiple dimensions. Let’s give an example.

The scenario here is to operate on streams, which is the base class Stream. Three simple interfaces are defined in the stream: read, write, and seek. There are many types of streams, such as file stream FileStream, network stream NetworkStream, and memory stream MemoryStream on the bus. These three streams can be inherited from Stream.

The above is expanded from the types of streams. When we want to expand based on the nature of the traffic, such as encrypting Crypto or buffering, are these two operations required for all three flows? That is to create six types: CryptoFileStream, CryptoNetworkStream, CryptoMemoryStream, BufferFileStream, BufferNetworkStream, and BufferMemoryStream. The inheritance relationship of these streams is like this.

Count how many classes there are. There are ten in total. If you want to add new functions, there will be more. If we add another dimension, it will increase exponentially! This is the consequence of inheriting a disagreement. code show as below

//Business operations
class Stream{
public:
    virtual char Read(int number)=0;
    virtual void Seek(int position)=0;
    virtual void Write(char data)=0;
    
    virtual ~Stream(){}
};

//Subject class
class FileStream: public Stream{
public:
    virtual char Read(int number){
        //read file stream
    }
    virtual void Seek(int position){
        //Locate the file stream
    }
    virtual void Write(char data){
        //write file stream
    }

};

class NetworkStream :public Stream{
public:
    virtual char Read(int number){
        //Read network stream
    }
    virtual void Seek(int position){
        //Locate network flow
    }
    virtual void Write(char data){
        //Write network stream
    }
    
};

class MemoryStream :public Stream{
public:
    virtual char Read(int number){
        //read memory stream
    }
    virtual void Seek(int position){
        //Locate the memory stream
    }
    virtual void Write(char data){
        //write memory stream
    }
    
};

//Extended operation
class CryptoFileStream :public FileStream{
public:
    virtual char Read(int number){
       
        //Additional encryption operations...
        FileStream::Read(number);//Read file stream
        
    }
    virtual void Seek(int position){
        //Additional encryption operations...
        FileStream::Seek(position);//Locate the file stream
        //Additional encryption operations...
    }
    virtual void Write(byte data){
        //Additional encryption operations...
        FileStream::Write(data);//Write file stream
        //Additional encryption operations...
    }
};

class CryptoNetworkStream : :public NetworkStream{
public:
    virtual char Read(int number){
        
        //Additional encryption operations...
        NetworkStream::Read(number);//Read network stream
    }
    virtual void Seek(int position){
        //Additional encryption operations...
        NetworkStream::Seek(position);//Locate the network stream
        //Additional encryption operations...
    }
    virtual void Write(byte data){
        //Additional encryption operations...
        NetworkStream::Write(data);//Write network stream
        //Additional encryption operations...
    }
};

class CryptoMemoryStream : public MemoryStream{
public:
    virtual char Read(int number){
        
        //Additional encryption operations...
        MemoryStream::Read(number);//Read memory stream
    }
    virtual void Seek(int position){
        //Additional encryption operations...
        MemoryStream::Seek(position);//Locate the memory stream
        //Additional encryption operations...
    }
    virtual void Write(byte data){
        //Additional encryption operations...
        MemoryStream::Write(data);//Write memory stream
        //Additional encryption operations...
    }
};

class BufferedFileStream : public FileStream{
    //...
};

class BufferedNetworkStream : public NetworkStream{
    //...
};

class BufferedMemoryStream : public MemoryStream{
    //...
}




class CryptoBufferedFileStream :public FileStream{
public:
    virtual char Read(int number){
        
        //Additional encryption operations...
        //Additional buffering operations...
        FileStream::Read(number);//Read file stream
    }
    virtual void Seek(int position){
        //Additional encryption operations...
        //Additional buffering operations...
        FileStream::Seek(position);//Locate the file stream
        //Additional encryption operations...
        //Additional buffering operations...
    }
    virtual void Write(byte data){
        //Additional encryption operations...
        //Additional buffering operations...
        FileStream::Write(data);//Write file stream
        //Additional encryption operations...
        //Additional buffering operations...
    }
};



void Process(){

        //Compile time assembly
    CryptoFileStream *fs1 = new CryptoFileStream();

    BufferedFileStream *fs2 = new BufferedFileStream();

    CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream();

}

Let’s see how it can be improved using decoration mode?

Using a combination method, Crypto and Buffer do not inherit from classes such as FileStream, but inherit from Stream, but they contain a member variable Stream *stm; then use the constructor to assign a value to stm, so that I don’t have to worry about whether stm is a file stream. Or what kind of stream, the Crypto class I am responsible for is encryption, and I am in charge of other things. How many categories are there now, six, not exponential. code show as below:

//Business operations
class Stream{

public:
    virtual char Read(int number)=0;
    virtual void Seek(int position)=0;
    virtual void Write(char data)=0;
    
    virtual ~Stream(){}
};

//Subject class
class FileStream: public Stream{
public:
    virtual char Read(int number){
        //read file stream
    }
    virtual void Seek(int position){
        //Locate the file stream
    }
    virtual void Write(char data){
        //write file stream
    }

};

class NetworkStream :public Stream{
public:
    virtual char Read(int number){
        //Read network stream
    }
    virtual void Seek(int position){
        //Locate network flow
    }
    virtual void Write(char data){
        //Write network stream
    }
    
};

class MemoryStream :public Stream{
public:
    virtual char Read(int number){
        //read memory stream
    }
    virtual void Seek(int position){
        //Locate the memory stream
    }
    virtual void Write(char data){
        //write memory stream
    }
    
};

//Extended operation


class CryptoStream: public Stream {
    
    Stream* stream; //...

public:
    CryptoStream(Stream* stm):stream(stm){
    
    }
    
    
    virtual char Read(int number){
       
        //Additional encryption operations...
        stream->Read(number);//Read file stream
    }
    virtual void Seek(int position){
        //Additional encryption operations...
        stream::Seek(position);//Locate the file stream
        //Additional encryption operations...
    }
    virtual void Write(byte data){
        //Additional encryption operations...
        stream::Write(data);//Write file stream
        //Additional encryption operations...
    }
};



class BufferedStream : public Stream{
    
    Stream* stream; //...
    
public:
    BufferedStream(Stream* stm):stream(stm){
        
    }
    //...
};





void Process(){

    //Runtime assembly
    FileStream* s1=new FileStream();
    CryptoStream* s2=new CryptoStream(s1);
    
    BufferedStream* s3=new BufferedStream(s1);
    
    BufferedStream* s4=new BufferedStream(s2);
    
    

}

Of course we can go one step further and add an abstract class. Because both CryptoStream and BufferStream use the same stm, a DemocratorStream is added so that both CryptoStream and BufferStream inherit from this abstract class. code show as below:

//Business operations
class Stream{

public:
    virtual char Read(int number)=0;
    virtual void Seek(int position)=0;
    virtual void Write(char data)=0;
    
    virtual ~Stream(){}
};

//Subject class
class FileStream: public Stream{
public:
    virtual char Read(int number){
        //read file stream
    }
    virtual void Seek(int position){
        //Locate the file stream
    }
    virtual void Write(char data){
        //write file stream
    }

};

class NetworkStream :public Stream{
public:
    virtual char Read(int number){
        //Read network stream
    }
    virtual void Seek(int position){
        //Locate network flow
    }
    virtual void Write(char data){
        //Write network stream
    }
    
};

class MemoryStream :public Stream{
public:
    virtual char Read(int number){
        //read memory stream
    }
    virtual void Seek(int position){
        //Locate the memory stream
    }
    virtual void Write(char data){
        //write memory stream
    }
    
};

//Extended operation

clas DecoratorStream: public Stream{
protected:
    Stream* stream; //...
    
    DecoratorStream(Stream * stm):stream(stm){
    
    }
    
};

class CryptoStream: public DecoratorStream {
 

public:
    CryptoStream(Stream* stm):DecoratorStream(stm){
    
    }
    
    
    virtual char Read(int number){
       
        //Additional encryption operations...
        stream->Read(number);//Read file stream
    }
    virtual void Seek(int position){
        //Additional encryption operations...
        stream::Seek(position);//Locate the file stream
        //Additional encryption operations...
    }
    virtual void Write(byte data){
        //Additional encryption operations...
        stream::Write(data);//Write file stream
        //Additional encryption operations...
    }
};



class BufferedStream : public DecoratorStream{
    
    Stream* stream; //...
    
public:
    BufferedStream(Stream* stm):DecoratorStream(stm){
        
    }
    //...
};




void Process(){

    //Runtime assembly
    FileStream* s1=new FileStream();
    
    CryptoStream* s2=new CryptoStream(s1);
    
    BufferedStream* s3=new BufferedStream(s1);
    
    BufferedStream* s4=new BufferedStream(s2);
    
    

}

Now let’s understand the meaning of the two words decoration. Whether it is Crypto or Buffer, they are functional extensions. So how to extend elegantly is what the decoration model needs to do. The author guesses that this is also the reason why Teacher Li Jianzhong named DemocratorStream DemocratorStream.

Final summary:

By using composition instead of inheritance, the Decorator pattern achieves the ability to dynamically extend object functions at runtime, and can expand multiple functions as needed. It avoids the “poor flexibility” and “multiple subclass derivation problems” caused by inheritance.

The Decorator class shows an inheritance relationship of is-a Component on the interface, that is, the Decorator class inherits the interface of the Component class. But in terms of implementation, it is manifested as a has-a Component combination relationship, that is, the Decorator class uses another Component class.

The purpose of the Decorator pattern is not to solve the problem of “multiple inheritance derived from multiple subclasses”. The key point of the application of the Decorator pattern is to solve the “extended functions of the main class in multiple directions” – which is the meaning of “decoration”