DesignPattern/24.VisitorPattern/24.VisitorPattern.md

388 lines
11 KiB
Markdown
Raw Normal View History

2024-04-14 03:32:16 +00:00
# 访问者模式,从双十一购物开始说起
```
欧耶这周Jungle的作业终于做完了作业是什么呢就是完成一个习题册。Jungle做完之后得让家长检查习题册并签字第二天交到学校组长得初步检查作业是否做完、家长是否签字然后老师会评阅作业是否正确并给出评分。
就是这么一个习题册,这是经了多少人的手啊!
Jungle——完成习题册上的题
Jungle家长——检查儿子的作业并在习题册上签字
组长——初步检查Jungle的习题册是否完成
老师——评阅习题册,给出评分。
```
同样一个对象习题册不同的人都去访问它并且访问的方式不同Jungle是为了完成作业Jungle爸爸是为了签字组长是为了检查Jungle是否完成而老师是为了评分。 每一个人都扮演了访问者的角色。
什么?访问者?
## 1.访问者模式简介
类似于上述的习题册,软件设计中也需要这样的类似于习题册的对象结构,不同的对象对应不同的处理。设计模式中,访问者模式就是为了以不同的方式来操作复杂的对象结构。
访问者模式是一种较为复杂的行为型设计模式,具有访问者和被访问元素两个主要的角色。被访问的元素常常有不同的类型,不同的访问者可以对它们提供不同的访问方式。被访问元素通常不是单独存在,而是以集合的形式存在于一个对象结构中,访问者可以遍历该对象结构,以逐个访问其中的每一个元素。
```
访问者模式:
表示一个作用于某对象结构中的各个元素的操作。访问者模式让用户可以在不改变各元素的前提下定义作用于这些元素的新操作。
```
## 2.访问者模式结构
访问者模式的结构相对较复杂,角色有如下几个:
- Visitor抽象访问者抽象类声明了访问对象结构中不同具体元素的方法由方法名称可知该方法将访问对象结构中的某个具体元素
- ConcreteVisitor具体访问者访问某个具体元素的访问者实现具体的访问方法
- Element抽象元素抽象类一般声明一个accept()的方法用于接受访问者的访问accept()方法常常以一个抽象访问者的指针作为参数;
- ConcreteElement具体元素针对具体被访问的元素实现accept()方法;
- ObjectStructure对象结构元素的集合提供了遍历对象结构中所有元素的方法。对象结构存储了不同类型的元素对象以供不同的访问者访问。
访问者模式的UML结构图如下:
![avatar](https://github.com/FengJungle/DesignPattern/blob/master/24.VisitorPattern/1.Picture/%E8%AE%BF%E9%97%AE%E8%80%85%E6%A8%A1%E5%BC%8FUML%E5%9B%BE.png)
从上图和前述可以看出,访问者模式中有两个层次结构:
- 访问者的层次结构抽象访问者和具体访问者不同的具体访问者有不同的访问方式visit()方式);
- 被访问元素的层次结构抽象元素和具体元素不同的具体元素有不同的被访问方式accept()方式)
正式由于有这两个层次结构,在增加新的访问者时,不必修改已有的代码,通过继承抽象访问者即可实现扩展,符合开闭原则,系统扩展性较好。但是在增加新的元素时,既要修改抽象访问者类(增加访问新增元素方法的声明),又要修改具体访问者(增加新的具体访问者类),不符合开闭原则。
访问者模式的示例代码如下:
```
#ifndef __DEMO_H__
#define __DEMO_H__
// 抽象访问者 Visitor
class Visitor
{
public:
virtual ~Visitor() {}
virtual void visit(ConcreteElementA *) = 0;
virtual void visit(ConcreteElementB *) = 0;
};
// 具体访问者 ConcreteVisitor
class ConcreteVisitor : public Visitor
{
public:
// 实现一种针对特定元素的访问操作
void visit(ConcreteElementA *)
{
// 元素A的访问操作代码
}
void visit(ConcreteElementB *)
{
// 元素A的访问操作代码
}
};
// 抽象元素
class Element
{
public:
virtual ~Element() {}
// 声明抽象方法,以一个抽象访问者的指针作为函数参数
virtual void accept(Visitor *) = 0;
};
// 具体元素
class ConcreteElement : public Element
{
public:
void accept(Visitor *visitor)
{
visitor->visit(this);
}
};
// 对象结构
class ObjectStructure
{
public:
// 提供接口接受访问者访问
void accept(Visitor *visitor)
{
// 遍历访问对象结构中的元素
for ()
{
elementList[i]->accept(visitor);
}
}
void addElement() {}
void removeElement() {}
private:
list<Element *> elementList;
};
#endif
```
## 3.访问者模式代码实例
```
Jungle作为一名顾客去超市购物加入购物车的商品包括两种苹果和两本书结账时收银员需要计算各个商品的的价格。本例Jungle采用访问者模式来模拟该过程。
```
本例中客户Jungle和收银员都会去访问商品但关心的地方不同Jungle关心的是苹果和书的单价、品牌等收银员关注的是商品的价格。因此客户Customer和收银员Cashier是具体访问者而苹果Apple和书Book是具体被访问元素而购物车则是对象结构。本例的UML图如下
![avatar](https://github.com/FengJungle/DesignPattern/blob/master/24.VisitorPattern/1.Picture/%E8%AE%BF%E9%97%AE%E8%80%85%E6%A8%A1%E5%BC%8F%E5%AE%9E%E4%BE%8BUML%E5%9B%BE.png)
### 3.1.元素类
#### 3.1.1.抽象元素
```
// 抽象元素
class Element
{
public:
Element(){};
virtual ~Element(){}
virtual void accept(Visitor*) = 0;
void setPrice(int iPrice){
this->price = iPrice;
}
int getPrice(){
return this->price;
}
void setNum(int iNum){
this->num = iNum;
}
int getNum(){
return num;
}
void setName(string iName){
this->name = iName;
}
string getName(){
return this->name;
}
private:
int price;
int num;
string name;
};
```
#### 3.1.2.具体元素Apple
```
// 具体元素Apple
class Apple :public Element
{
public:
Apple();
Apple(string name, int price);
void accept(Visitor*);
};
```
实现:
```
Apple::Apple(){
setPrice(0);
setNum(0);
setName("");
}
Apple::Apple(string name, int price){
setPrice(price);
setNum(0);
setName(name);
}
void Apple::accept(Visitor* visitor){
visitor->visit(this);
}
```
#### 3.1.3.具体元素Book
```
// 具体元素Book
class Book :public Element
{
public:
Book();
Book(string name, int price);
void accept(Visitor*);
};
```
实现:
```
Book::Book(){
setPrice(0);
setNum(0);
setName("");
}
Book::Book(string iName, int iPrice){
setPrice(iPrice);
setNum(0);
setName(iName);
}
void Book::accept(Visitor* visitor){
visitor->visit(this);
}
```
### 3.2.访问者
#### 3.2.1.抽象访问者
```
// 前向声明
class Element;
class Apple;
class Book;
// 抽象访问者
class Visitor
{
public:
Visitor(){};
virtual ~Visitor(){}
// 声明一组访问方法
virtual void visit(Apple*) = 0;
virtual void visit(Book*) = 0;
};
```
#### 3.2.2.具体访问者Customer
```
// 具体访问者:顾客
class Customer :public Visitor
{
public:
Customer();
Customer(string iName);
void setNum(Apple*, int);
void setNum(Book*, int);
void visit(Apple* apple);
void visit(Book* book);
private:
string name;
};
```
实现:
```
Customer::Customer(){
this->name = "";
}
Customer::Customer(string iName){
this->name = iName;
}
void Customer::setNum(Apple* apple, int iNum){
apple->setNum(iNum);
}
void Customer::setNum(Book* book, int iNum){
book->setNum(iNum);
}
void Customer::visit(Apple* apple){
int price = apple->getPrice();
printf(" %s \t单价: \t%d 元/kg\n", apple->getName().c_str(), apple->getPrice());
}
void Customer::visit(Book* book){
int price = book->getPrice();
string name = book->getName();
printf(" 《%s》\t单价: \t%d 元/本\n", book->getName().c_str(), book->getPrice());
}
```
#### 3.2.3.具体访问者Cashier
```
class Cashier :public Visitor
{
public:
Cashier();
void visit(Apple* apple);
void visit(Book* book);
};
```
实现:
```
Cashier::Cashier(){
}
void Cashier::visit(Apple* apple){
string name = apple->getName();
int price = apple->getPrice();
int num = apple->getNum();
int total = price*num;
printf(" %s 总价: %d 元\n", name.c_str(), total);
}
void Cashier::visit(Book* book){
int price = book->getPrice();
string name = book->getName();
int num = book->getNum();
int total = price*num;
printf(" 《%s》 总价: %d 元\n", name.c_str(), total);
}
```
### 3.3.购物车ShoppingCart
```
class ShoppingCart
{
public:
ShoppingCart(){}
void addElement(Element* element){
printf(" 商品名:%s, \t数量%d, \t加入购物车成功\n", element->getName().c_str(), element->getNum());
elementList.push_back(element);
}
void accept(Visitor* visitor){
for (int i = 0; i < elementList.size(); i++){
elementList[i]->accept(visitor);
}
}
private:
vector<Element*>elementList;
};
```
### 3.4.客户端代码示例及结果
```
#include "Element.h"
#include "Visitor.h"
#include "ShoppingCart.h"
#include <Windows.h>
int main()
{
Apple *apple1 = new Apple("红富士苹果", 7);
Apple *apple2 = new Apple("花牛苹果", 5);
Book *book1 = new Book("红楼梦", 129);
Book *book2 = new Book("终结者", 49);
Cashier *cashier = new Cashier();
Customer *jungle = new Customer("Jungle");
jungle->setNum(apple1, 2);
jungle->setNum(apple2, 4);
jungle->setNum(book1, 1);
jungle->setNum(book2, 3);
ShoppingCart *shoppingCart = new ShoppingCart();
shoppingCart->addElement(apple1);
shoppingCart->addElement(apple2);
shoppingCart->addElement(book1);
shoppingCart->addElement(book2);
printf("\n\n");
shoppingCart->accept(jungle);
printf("\n\n");
shoppingCart->accept(cashier);
printf("\n\n");
system("pause");
delete apple1;
delete apple2;
delete book1;
delete book2;
delete cashier;
delete jungle;
delete shoppingCart;
apple1 = nullptr;
apple2 = nullptr;
book1 = nullptr;
book2 = nullptr;
cashier = nullptr;
jungle = nullptr;
shoppingCart = nullptr;
return 0;
}
```
上述代码运行结果如下:
![avatar](https://github.com/FengJungle/DesignPattern/blob/master/24.VisitorPattern/1.Picture/%E8%BF%90%E8%A1%8C%E5%9B%BE1.png)
## 4.总结
访问者模式的结构相对较复杂,在实际应用中使用频率较低。如果系统中存在一个复杂的对象结构,且不同的访问者对其具有不同的操作,那么可以考虑使用访问者模式。访问者模式的特点总结如下:
- 优点:
- 增加新的访问者很方便,即增加一个新的具体访问者类,定义新的访问方式,无需修改原有代码,符合开闭原则;
- 被访问元素集中在一个对象结构中,类的职责更清晰,利于对象结构中元素对象的复用;
- 缺点:
- 增加新的元素类很困难,增加新的元素时,在抽象访问者类中需要增加一个对新增的元素方法的声明,即要修改抽象访问者代码;此外还要增加新的具体访问者以实现对新增元素的访问,不符合开闭原则;
- 破坏了对象的封装性,访问者模式要求访问者对象访问并调用每一个元素对象的操作,那么元素对象必须暴露自己的内部操作和状态,否则访问者无法访问。