访问者模式(行为型)

前言


在大型体育馆中有各种体育项目,如羽毛球室,乒乓球室,篮球室等等,每种运动项目场地里面会有各种职业背景的体育爱好者,如学生,职工,老师;同时对于同一种职业背景的体育爱好者,可以访问多种运动项目。在软件开发中,有时候我们也需要处理像体育馆这样的不同的运动场所集合对象结构,在该对象结构中存储了多个不同类型的对象信息,而且对同一对象结构中的元素的操作方式并不唯一,可能需要提供多种不同的处理方式,还有可能增加新的处理方式等等。对于此类问题可以通过访问者模式来解决,因为其模式动机就是以不同的方式操作复杂对象结构。

访问者模式


访问者模式包含访问者被访问元素两个主要组成部分,这些被访问的元素通常具有不同的类型,且不同的访问者可以对它们进行不同的访问操作,访问者模式使得用户可以在不修改现有系统的情况下扩展系统的功能,为这些不同类型的元素增加新的操作

在使用访问者模式时,被访问元素通常不是单独存在的,它们存储在一个集合中,这个集合被称为对象结构,访问者通过遍历对象结构实现对其中存储的元素的逐个操作

意图

提供一个作用于某对象结构中的各元素的操作表示,它使我们可以在不改变个元素的类的前提下定义作用于这些元素的新的操作。访问者模式是一种对象行为型模式

参与者

  • Visitor
    抽象访问者,为该对象结构中ConcreteElement的每一个类声明一个visit操作。该操作的名字和特
    征标识了发送visit请求给该访问者的那个类。这使得访问者可以确定被访问元素的具体的类。这样访问者就可以通过该元素的特定接口直接访问它、

  • ConcreteVisitor
    具体访问者,实现每个由Visitor声明的操作。每个操作实现本算法的一部分,而该算法片断乃是
    对应于结构中对象的类。ConcreteVisitor为该算法提供了上下文并存储它的局部状态。这一状态常常在遍历该结构的过程中累积结果

  • Element
    定义一个accept操作,它以一个访问者为参数

  • ConcreteElement
    实现accept操作,该操作以一个访问者为参数

  • ObjectStructure
    能枚举它的元素
    可以提供一个高层的接口以允许该访问者访问它的元素
    可以是一个复合或是一个集合,如一个列表或一个无序集合

模式结构

visitor

代码实现

1.首先定义抽象元素类Element,并声明Accept()方法:

1
2
3
4
5
6
class Element
{
public:
virtual void Accept(Visitor *pv) = 0; // 该方法传入一个抽象的访问者对象指针
// virtual void Operation() = 0; 每个元素各种有自己的操作
};

2.再定义两个具体的元素类ConcreteElementAConcreteElementB,并实现Accept()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 具体数据元素类A
class ConcreteElementA : public Element
{
public:
virtual void Accept(Visitor *pv)
{
pv->VisitConcreteElementA(this); // 传入this给访问者的访问接口
}
void OperationA()
{
cout << "Concrete Element A OperatorA!!!" << endl;
}
};

// 具体数据元素类B
class ConcreteElementB : public Element
{
public:
virtual void Accept(Visitor *pv)
{
pv->VisitConcreteElementB(this);
}
virtual void OperationB()
{
cout << "Concrete Element B OperatorB!!!" << endl;
}
};

3.定义抽象访问者类Visitor,并定义两个访问两个具体元素的接口VisitConcreteElementA()VisitConcreteElementB(),每个接口都是以具体的元素为参数:

1
2
3
4
5
6
7
8
9
10
11
class Visitor
{
public:
virtual void VisitConcreteElementA(ConcreteElementA *pea) = 0;
//以具体的被访问元素为参数
virtual void VisitConcreteElementB(ConcreteElementB *peb) = 0;
//以具体的被访问元素为参数

// 若各有自己的操作,VisitConcreteElementA和VisitConcreteElementB
// 可以合并一个函数,但是参数必须是具体的Element,即重载
};

4.再定义两个具体的访问者类ConcreteVisitorAConcreteVisitorB,并实现VisitConcreteElementA()VisitConcreteElementB()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 具体访问者A
class ConcreteVisitorA : public Visitor
{
public:
virtual void VisitConcreteElementA(ConcreteElementA *pea)
{
cout << "Concrete Visitor A:" << endl;
pea->OperationA();
}
virtual void VisitConcreteElementB(ConcreteElementB *peb)
{
cout << "Concrete Visitor A:" << endl;
peb->OperationB();
}
// VisitConcreteElementA 和VisitConcreteElementB可以合并一个函数,
//但是参数必须是具体的Element,即重载
};

// 具体访问者B
class ConcreteVisitorB : public Visitor
{
public:
virtual void VisitConcreteElementA(ConcreteElementA *pea)
{
cout << "Concrete Visitor B:" << endl;
pea->OperationA();
}
virtual void VisitConcreteElementB(ConcreteElementB *peb)
{
cout << "Concrete Visitor B:" << endl;
peb->OperationB();
}
};

5.最后定义对象结构ObjectStructure作为元素的集合,并提供元素增删接口和统一的高层Accept()接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class ObjectStructure
{
private:
list<Element*> m_ListElement;
public:
void Attach(Element *pe)
{
m_ListElement.push_back(pe);
}

void Detach(Element *pe)
{
list<Element*> ::iterator it = find(m_ListElement.begin(), m_ListElement.end(), pe);
if (it != m_ListElement.end())
{
m_ListElement.erase(it);
}
}

void Accept(Visitor *pv) // 高层各个元素统一的Accept接口
{
for (list<Element*> ::iterator it = m_ListElement.begin();
it != m_ListElement.end();
++it)
{
(*it)->Accept(pv);
}
}
};

6.测试访问者模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void VisitorTest_General()
{
ObjectStructure *pOS = new ObjectStructure();

Element *pEA = new ConcreteElementA();
Element *pEB = new ConcreteElementB();

pOS->Attach(pEA);
pOS->Attach(pEB);

Visitor *pVA = new ConcreteVisitorA();
Visitor *pVB = new ConcreteVisitorB();

pOS->Accept(pVA);
pOS->Accept(pVB);

cout << "After Detach ConcretElement A:" << endl;

pOS->Detach(pEA);
pOS->Accept(pVA);
pOS->Accept(pVB);

pOS->Detach(pEB);

SAFE_RELASE_POINTER(pOS);
SAFE_RELASE_POINTER(pEA);
SAFE_RELASE_POINTER(pEB);
SAFE_RELASE_POINTER(pVA);
SAFE_RELASE_POINTER(pVB);
}

7.运行结果:

Concrete Visitor A:
Concrete Element A OperatorA!!!
Concrete Visitor A:
Concrete Element B OperatorB!!!
Concrete Visitor B:
Concrete Element A OperatorA!!!
Concrete Visitor B:
Concrete Element B OperatorB!!!
After Detach ConcretElement A:
Concrete Visitor A:
Concrete Element B OperatorB!!!
Concrete Visitor B:
Concrete Element B OperatorB!!!

使用场景

  • 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作
  • 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor模式使得你可以将相关的操作集中起来 定义在一个类中
  • 当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作
  • 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好

优缺点

  • 优点
    • 符合单一职责原则:凡是适用访问者模式的场景中,元素类中需要封装在访问者中的操作必定是与元素类本身关系不大且是易变的操作,使用访问者模式一方面符合单一职责原则,另一方面,因为被封装的操作通常来说都是易变的,所以当发生变化时,就可以在不改变元素类本身的前提下,实现对变化部分的扩展
    • 扩展性良好:元素类可以通过接受不同的访问者来实现对不同操作的扩展
    • 将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中。类的职责更加清晰,有利于对象结构中元素对象的复用,相同的对象结构可以供多个不同的访问者访问
  • 缺点
    • 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”的要求
    • 破坏封装。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问

访问者方法模式具体实例


体育馆访问问题

使用访问者方法模式简单实现前言所述的体育馆访问问题

代码实现
1.定义抽象元素类Stadium,并声明接受访问方法Accept():

1
2
3
4
5
6
class Stadium
{
public:
virtual void Accept(SportsLover *pSL) = 0;
virtual void DoSports() = 0;
};

2.定义三个种具体的元素类BadmintonStadiumPingpongStadiumBascketballStadium,并实现接受访问方法Accept():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// BadmintonStadium
class BadmintonStadium : public Stadium
{
public:
virtual void Accept(SportsLover *pSL)
{
pSL->VisitBadmintonStadium(this);
//// 重载版
//pSL->Visit(this);
}
virtual void DoSports()
{
cout << "Doing Badminton Sport" << endl;
}
};

// PingpongStadium
class PingpongStadium : public Stadium
{
public:
virtual void Accept(SportsLover *pSL)
{
pSL->VisitPingpongStadium(this);
//// 重载版
//pSL->Visit(this);
}
virtual void DoSports()
{
cout << "Doing Ping Pong Sport" << endl;
}
};

class BascketballStadium : public Stadium
{
public:
virtual void Accept(SportsLover *pSL)
{
pSL->VisitBasketballStadium(this);
//// 重载版
//pSL->Visit(this);
}
virtual void DoSports()
{
cout << "Doing Bascket ball Sport" << endl;
}
};

3.定义抽象的访问者类SportsLover,并声明访问具体元素的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Abstract Visitor
class SportsLover
{
public:

virtual void VisitBadmintonStadium(BadmintonStadium *pS) = 0;
virtual void VisitPingpongStadium(PingpongStadium *pS) = 0;
virtual void VisitBasketballStadium(BascketballStadium *pS) = 0;
//// 重载版
//virtual void Visit(BadmintonStadium *pS) = 0;
//virtual void Visit(PingpongStadium *pS) = 0;
//virtual void Visit(BascketballStadium *pS) = 0;
};

4.定义两个具体的访问者类StudentsTeachers,并实现相应的访问方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// Stduent Visitor
class Students : public SportsLover
{
public:

virtual void VisitBadmintonStadium(BadmintonStadium *pS)
{
cout << "students visit: ";
pS->DoSports();
}
virtual void VisitPingpongStadium(PingpongStadium *pS)
{
cout << "students visit: ";
pS->DoSports();
}
virtual void VisitBasketballStadium(BascketballStadium *pS)
{
cout << "students visit: ";
pS->DoSports();
}

//// 重载版
//virtual void Visit(BadmintonStadium *pS)
//{
// cout << "students visit: ";
// pS->DoSports();
//}
//virtual void Visit(PingpongStadium *pS)
//{
// cout << "students visit: ";
// pS->DoSports();
//}
//virtual void Visit(BascketballStadium *pS)
//{
// cout << "students visit: ";
// pS->DoSports();
//}
};

// Teachers Visitor
class Teachers : public SportsLover
{
public:

virtual void VisitBadmintonStadium(BadmintonStadium *pS)
{
cout << "teachers visit: ";
pS->DoSports();
}
virtual void VisitPingpongStadium(PingpongStadium *pS)
{
cout << "teachers visit: ";
pS->DoSports();
}
virtual void VisitBasketballStadium(BascketballStadium *pS)
{
cout << "teachers visit: ";
pS->DoSports();
}
//// 重载版
//virtual void Visit(BadmintonStadium *pS)
//{
// cout << "teachers visit: ";
// pS->DoSports();
//}
//virtual void Visit(PingpongStadium *pS)
//{
// cout << "teachers visit: ";
// pS->DoSports();
//}
//virtual void Visit(BascketballStadium *pS)
//{
// cout << "teachers visit: ";
// pS->DoSports();
//}
};

5.定义对象结构StadiumContainer,并声明和实现增删元素的接口及统一的高层Accept()接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class StadiumContainer
{
private:
vector<Stadium*> m_VectorStadium;
public:
void Attach(Stadium *ps)
{
m_VectorStadium.push_back(ps);
}
void Detach(Stadium *ps)
{
vector<Stadium*> :: iterator it = find(m_VectorStadium.begin(), m_VectorStadium.end(), ps);
if (it != m_VectorStadium.end())
{
m_VectorStadium.erase(it);
}
}
void Accept(SportsLover *pSL)
{
for (vector<Stadium*> :: iterator it = m_VectorStadium.begin();
it != m_VectorStadium.end();
++it)
{
(*it)->Accept(pSL);
}
}
};

6.测试访问者模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void VisitorTest_Sports()
{
StadiumContainer *pSC = new StadiumContainer();

BadmintonStadium *pBS = new BadmintonStadium();
PingpongStadium *pPS = new PingpongStadium();
BascketballStadium *pBkS = new BascketballStadium();

pSC->Attach(pBS);
pSC->Attach(pPS);
pSC->Attach(pBkS);

SportsLover *pSS = new Students();
SportsLover *pST = new Teachers();

pSC->Accept(pSS);
pSC->Accept(pST);

cout << "Detach Ping Pong Stadium !!!" << endl;
pSC->Detach(pPS);

pSC->Accept(pSS);
pSC->Accept(pST);

SAFE_RELASE_POINTER(pSC);
SAFE_RELASE_POINTER(pBS);
SAFE_RELASE_POINTER(pBkS);
SAFE_RELASE_POINTER(pPS);
SAFE_RELASE_POINTER(pSS);
SAFE_RELASE_POINTER(pST);
}

7.运行结果:

students visit: Doing Badminton Sport
students visit: Doing Ping Pong Sport
students visit: Doing Bascket ball Sport
teachers visit: Doing Badminton Sport
teachers visit: Doing Ping Pong Sport
teachers visit: Doing Bascket ball Sport
Detach Ping Pong Stadium !!!
students visit: Doing Badminton Sport
students visit: Doing Bascket ball Sport
teachers visit: Doing Badminton Sport
teachers visit: Doing Bascket ball Sport