Visitor design pattern

Posted by JimmyD on Sun, 21 Nov 2021 07:18:19 +0100

Usage scenario of visitor mode

1. The object structure is relatively stable, but it is often necessary to define new operations on this object structure.
2. You need to perform many different and irrelevant operations on objects in an object structure, and you don't want to modify these classes when adding new operations.

Visitor pattern separates data structure (Element) from data operation (Visitor).

Visitor pattern class diagram

Element: an abstract class that provides a method (accept) to receive visitors.
ElementA and ElementB: specific Element classes. Different classes are derived from Element according to different business types. The specific data structure is defined here.
Visitor: an abstract class that defines the access behavior of each Element.
Visitor a and visitor B: specific visitors call different processing functions by passing in different Element objects. Specific data operations are carried out here.
The accept method in element needs a Visitor parameter. By passing in different visitors to perform different data operations, it realizes the separation of data structure and data operations. Element depends on visitors.

Examples of visitor patterns

In the trading system, we usually carry out some operations such as stock trading and fund stock inquiry, so I distinguish these behaviors as follows:
Order: purchase and sale request message of shares
CancelOrder: cancellation request message
QueryOrder: query order request message
QueryFund: query fund request message
QueryShare: query share request message
The above operations have some common operations, such as risk control inspection and transaction execution, but there are also some differences in specific risk control inspection items or transaction execution between different businesses. Therefore, we save the common data required for risk control inspection and transaction execution in different processes, and perform different specific operations according to different requests.
BusinessExecuteProcess: transaction execution class
RiskCheckProcess: risk control inspection class

Relational class diagram

Business analysis

Only Order and QueryFund request messages are shown above. After receiving different request messages, the program will perform different operations in the business execution and risk control inspection objects, realizing the separation of data structure and data operation. The operations between different request messages do not affect each other.

Process analysis

Through the factory method, the system identifies different types of messages, and then performs specific risk control inspection and transaction execution on different messages, and then hands them to other modules to process the request message.

code implementation

#include <iostream>


class Process;

class Message {
public:
    Message() {}
    virtual ~Message(){};
    virtual void accept(Process* p) = 0;
};

class Order : public Message {
public:
    Order() {}
    virtual ~Order() override {}
    virtual void accept(Process* p) override;
};

class CancelOrder : public Message {
public:
    CancelOrder() {}
    virtual ~CancelOrder() override {}
    virtual void accept(Process* p) override;
};

class QueryOrder : public Message {
public:
    QueryOrder() {}
    virtual ~QueryOrder() override {}
    virtual void accept(Process* p) override;
};

class QueryFund : public Message {
public:
    QueryFund() {}
    virtual ~QueryFund() override {}
    virtual void accept(Process* p) override;
};

class QueryShare : public Message {
public:
    QueryShare() {}
    virtual ~QueryShare() override {}
    virtual void accept(Process* p) override;
};

class Process {
public:
    Process() {}
    virtual ~Process() {};
    virtual void ConcreteProcess(Order*) = 0;
    virtual void ConcreteProcess(CancelOrder*) = 0;
    virtual void ConcreteProcess(QueryOrder*) = 0;
    virtual void ConcreteProcess(QueryFund*) = 0;
    virtual void ConcreteProcess(QueryShare*) = 0;
};

void Order::accept(Process* p) {
    p->ConcreteProcess(this);
}

void CancelOrder::accept(Process* p) {
        p->ConcreteProcess(this);
}

void QueryOrder::accept(Process* p) {
        p->ConcreteProcess(this);
}

void QueryFund::accept(Process* p) {
        p->ConcreteProcess(this);
}

void QueryShare::accept(Process* p) {
        p->ConcreteProcess(this);
}

class BusinessExecuteProcess : public Process {
public:
    BusinessExecuteProcess() {}
    virtual ~BusinessExecuteProcess() override {}
    void ConcreteProcess(Order*) override {
        std::cout << "Order in BusinessExecuteProcess" << std::endl;
    }
    void ConcreteProcess(CancelOrder*) override {
        std::cout << "CancelOrder in BusinessExecuteProcess" << std::endl;
    }
    void ConcreteProcess(QueryOrder*) override {
        std::cout << "QueryOrder in BusinessExecuteProcess" << std::endl;
    }
    void ConcreteProcess(QueryFund*) override {
        std::cout << "QueryFund in BusinessExecuteProcess" << std::endl;
    }
    void ConcreteProcess(QueryShare*) override {
        std::cout << "QueryShare in BusinessExecuteProcess" << std::endl;
    }
};

class RiskCheckProcess : public Process {
public:
    RiskCheckProcess() {}
    virtual ~RiskCheckProcess() override {}
    void ConcreteProcess(Order*) override {
        std::cout << "Order in RiskCheckProcess" << std::endl;
    }
    void ConcreteProcess(CancelOrder*) override {
        std::cout << "CancelOrder in RiskCheckProcess" << std::endl;
    }
    void ConcreteProcess(QueryOrder*) override {
        std::cout << "QueryOrder in RiskCheckProcess" << std::endl;
    }
    void ConcreteProcess(QueryFund*) override {
        std::cout << "QueryFund in RiskCheckProcess" << std::endl;
    }
    void ConcreteProcess(QueryShare*) override {
        std::cout << "QueryShare in RiskCheckProcess" << std::endl;
    }
};

class Server {
public:
    Server() {
        business_execute_process_ = new BusinessExecuteProcess();
        risk_check_process_ = new RiskCheckProcess();
    }
    ~Server() {}
    void ReceiveMessage(Message* msg) {
        // Risk control inspection
        msg->accept(risk_check_process_);
        // Business execution
        msg->accept(business_execute_process_);
    }
private:
    BusinessExecuteProcess* business_execute_process_;
    RiskCheckProcess* risk_check_process_;
};

int main() {
    Server s;
    // Message is omitted here. The corresponding request message is generated according to the factory method
    s.ReceiveMessage(new Order);
    s.ReceiveMessage(new CancelOrder);
    s.ReceiveMessage(new QueryOrder);
    s.ReceiveMessage(new QueryFund);
    s.ReceiveMessage(new QueryShare);

    return 0;
}

The output results are as follows

Order in RiskCheckProcess
Order in BusinessExecuteProcess
CancelOrder in RiskCheckProcess
CancelOrder in BusinessExecuteProcess
QueryOrder in RiskCheckProcess
QueryOrder in BusinessExecuteProcess
QueryFund in RiskCheckProcess
QueryFund in BusinessExecuteProcess
QueryShare in RiskCheckProcess
QueryShare in BusinessExecuteProcess

Topics: C++