Design mode_ 23 visitor mode

23 visitor mode

23.1 concept

Encapsulates some operations that act on each element in a data structure. New operations that act on these elements can be defined without changing the data structure.

23.2 structure

Abstract visitor role: defines the behavior of accessing each element. Its parameters are the accessible elements. Theoretically, the number of methods is the same as the number of elements. Visitor mode requires that the number of elements cannot be changed.
Specific visitor role: give the specific behavior generated when accessing each element class.
Specific element role: provides the specific implementation of the access method. Usually, it uses the method provided by the visitor to access the element.
Object structure role: the object structure mentioned in the definition. The object structure is an abstract description, specifically a class with container properties or composite object properties, which contains a group of elements, and these elements can be iterated for visitors to access.

23.3 realization

23.3.1 UML diagram

23.3.2 code

AnimalAndPerson.h:

#ifndef _ANIMALANDPERSON_H_
#define _ANIMALANDPERSON_H_

#include<iostream>
#include<string>
#include<list>
using namespace std;

class Person;

class Animal {
private:
	string name;
public:
	Animal(string name);
	virtual void accept(Person* person) = 0;
	string getName();
};

class Cat : public Animal {
public:
	Cat(string name);
	void accept(Person* person);
};

class Dog : public Animal {
public:
	Dog(string name);
	void accept(Person* person);
};

class Person {
private:
	string name;
public:
	Person(string name);
	virtual void feed(Dog* dog) = 0;
	virtual void feed(Cat* cat) = 0;
	string getName();
};

class Host : public Person {
public:
	Host(string name);
	void feed(Dog* dog);
	void feed(Cat* cat);
};

class Guest : public Person {
public:
	Guest(string name);
	void feed(Dog* dog);
	void feed(Cat* cat);
};

class Home {
private:
	list<Animal*> animals;
public:
	void addAnimal(Animal* animal);
	void deleteAnimal(Animal* animal);
	void action(Person* person);
};

#endif

AnimalAndPerson.cpp:

#include"AnimalAndPerson.h"

//Element role
Animal::Animal(string name) {
	this->name = name;
}
string Animal::getName() {
	return this->name;
}

Cat::Cat(string name) : Animal(name) {}
void Cat::accept(Person* person) {
	person->feed(this);
	cout << this->getName() << "Ate it" << person->getName() << "Fed food" << endl;
	cout << endl;
}

Dog::Dog(string name) : Animal(name) {}
void Dog::accept(Person* person) {
	person->feed(this);
	cout << this->getName() << "Ate it" << person->getName() << "Fed food" << endl;
	cout << endl;
}

//Concrete Visitor 
Person::Person(string name) {
	this->name = name;
}
string Person::getName() {
	return this->name;
}


Host::Host(string name) : Person(name) {}
void Host::feed(Dog* dog) {
	cout << this->getName() << "Hey, here you are" << dog->getName() << "food" << endl;
}
void Host::feed(Cat* cat) {
	cout << this->getName() << "Hey, here you are" << cat->getName() << "food" << endl;
}

Guest::Guest(string name) : Person(name) {}
void Guest::feed(Dog* dog) {
	cout << this->getName() << "Hey, here you are" << dog->getName() << "food" << endl;
}
void Guest::feed(Cat* cat) {
	cout << this->getName() << "Hey, here you are" << cat->getName() << "food" << endl;
}

//Object structure role
void Home::addAnimal(Animal* animal) {
	this->animals.push_back(animal);
}
void Home::deleteAnimal(Animal* animal) {
	this->animals.remove(animal);
}
void Home::action(Person* person) {
	for (list<Animal*>::iterator it = animals.begin(); it != animals.end(); it++) {
		(*it)->accept(person);
	}
}

main.cpp:

#include"AnimalAndPerson.h"

int main() {
	Home* home = new Home();
	Person* host = new Host("master");
	Person* guest = new Guest("guest");
	Animal* dogOne = new Dog("Siberian Husky");
	Animal* dogTwo = new Dog("Koki");
	Animal* cat = new Cat("Ragdoll");
	home->addAnimal(dogOne);
	home->addAnimal(dogTwo);
	home->addAnimal(cat);
	home->action(host);
	home->deleteAnimal(dogTwo);
	home->action(guest);
	return 0;
}

23.4 advantages and disadvantages

23.4.1 advantages

Good scalability. Add new functions to the elements in the object structure without modifying the elements in the object structure.
Good reusability. The general functions of the whole object structure are defined by visitors, so as to improve the degree of reuse.
Separate irrelevant behavior. Separate irrelevant behaviors through visitors and encapsulate relevant behaviors to form a visitor. In this way, the function of each visitor is relatively single.

23.4.2 disadvantages

Object structure changes are difficult. Every time a new element class is added, corresponding specific operations must be added to each specific access class, which violates the "opening and closing principle".
Violation of the principle of inversion of dependence. The visitor pattern relies on concrete classes rather than abstract classes.

23.5 usage scenarios

Object structure is relatively stable, but its operation algorithm often changes.
Objects in the object structure need to provide a variety of different operations that do not want to be closed, and the changes of these operations should not affect the object structure.

23.6 distribution

23.6.1 definitions

The declared type of a variable is called the static type of the variable, and the real type of the object referenced by the variable is also called the actual type of the variable. For example, Animal* cat = new Cat();, The static type of Cat is Animal *, and the actual type is Cat *. Selecting methods based on the type of object is called dispatch. Dispatch is divided into static dispatch and dynamic dispatch.
Static dispatch: occurs during compilation. Dispatch occurs according to static type information. Overloading is static dispatch.
Dynamic dispatch: occurs in the run-time. Dynamic dispatch dynamically replaces a method. Override to dynamic dispatch.

23.6.2 double dispatch

The essence of double dispatch to realize dynamic binding is to add the coverage link in the inheritance system in front of the overloaded method delegation. Because the coverage is dynamic, overloading is dynamic.

For example, if the above example is written with the following code, the program will report an error. The * it passed to the feed() function is of type Animal * and the feed cannot be recognized.

//AnimalAndPerson.cpp
void Cat::accept(Person* person) {
	cout << this->getName() << "Ate it" << person->getName() << "Fed food" << endl;
}

void Host::feed(Dog* dog) {
	cout << this->getName() << "Hey, here you are" << dog->getName() << "food" << endl;
	dog->accept(this);
	cout << endl;
}

void Home::action(Person* person) {
	for (list<Animal*>::iterator it = animals.begin(); it != animals.end(); it++) {
		person->feed(*it); //Error
	}
}

//main.cpp
Animal* cat = new Cat("Ragdoll");

However, if the feed() function is invoked in the Cat class, the Animal* type is allocated dynamically to Cat*.

//AnimalAndPerson.cpp
void Cat::accept(Person* person) {
	person->feed(this); //Calling the feed function in the Cat class
	cout << this->getName() << "Ate it" << person->getName() << "Fed food" << endl;
	cout << endl;
}

void Host::feed(Dog* dog) {
	cout << this->getName() << "Hey, here you are" << dog->getName() << "food" << endl;
}

void Home::action(Person* person) {
	for (list<Animal*>::iterator it = animals.begin(); it != animals.end(); it++) {
		(*it)->accept(person);
	}
}

//main.cpp
Animal* cat = new Cat("Ragdoll");

return overview of design patterns;

Back to design mode overview

Keywords: C++ Design Pattern

Added by Perry Mason on Fri, 25 Feb 2022 12:43:55 +0200