访问者模式(Visitor)
-
介绍:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
-
优点:
扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。 -
缺点:
状态模式的使用必然会增加系统的类与对象的个数。
状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应类的源码。 -
主要角色
抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。
#include "iostream"
#include "string"
#include "vector"
using namespace std;
class Visitor;
//抽象元素
class Element
{
public:
Element(){}
virtual void accept(Visitor *) = 0;
void SetPrice(int nPrice)
{
price = nPrice;
}
int GetPrice()
{
return price;
}
void SetNum(int nNum)
{
num = nNum;
}
int GetNum()
{
return num;
}
void SetName(string strName)
{
name = strName;
}
string GetName()
{
return name;
}
private:
int price;
int num;
string name;
};
class Apple;
class Book;
// 抽象访问者
class Visitor
{
public:
Visitor(){}
virtual void visit(Apple *) = 0;
virtual void visit(Book *) = 0;
};
//具体元素
class Apple : public Element
{
public:
Apple()
{
SetPrice(0);
SetNum(0);
SetName("");
}
Apple(string name, int price)
{
SetPrice(price);
SetNum(0);
SetName(name);
}
void accept(Visitor *visitor)
{
visitor->visit(this);
}
};
//具体元素
class Book : public Element
{
public:
Book()
{
SetPrice(0);
SetNum(0);
SetName("");
}
Book(string name, int price)
{
SetPrice(price);
SetNum(0);
SetName(name);
}
void accept(Visitor *visitor)
{
visitor->visit(this);
}
};
//具体访问者
class Customer : public Visitor
{
public:
Customer(){ name = ""; }
Customer(string strName) :name(strName){}
void SetNum(Apple *A, int num)
{
A->SetNum(num);
}
void SetNum(Book *A, int num)
{
A->SetNum(num);
}
void visit(Apple* apple)
{
printf(" %s \t单价: \t%d 元/kg\n", apple->GetName().c_str(), apple->GetPrice());
}
void visit(Book* book)
{
printf(" 《%s》\t单价: \t%d 元/本\n", book->GetName().c_str(), book->GetPrice());
}
private:
string name;
};
//具体访问者
class Cashier :public Visitor
{
public:
Cashier(){}
void 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 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);
}
};
//对象角色 购物车
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;
};
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");
return 1;
}
运行结果:
商品名:红富士苹果, 数量:2, 加入购物车成功!
商品名:花牛苹果, 数量:4, 加入购物车成功!
商品名:红楼梦, 数量:1, 加入购物车成功!
商品名:终结者, 数量:3, 加入购物车成功!
红富士苹果 单价: 7 元/kg
花牛苹果 单价: 5 元/kg
《红楼梦》 单价: 129 元/本
《终结者》 单价: 49 元/本
红富士苹果 总价: 14 元
花牛苹果 总价: 20 元
《红楼梦》 总价: 129 元
《终结者》 总价: 147 元
请按任意键继续. . .