无废话C#设计模式之二十一:Visitor

无废话C#设计模式之二十一:Visitor

意图

实现通过统一的接口访问不同类型元素的操作,并且通过这个接口可以增加新的操作而不改变元素的类。

场景

想不出什么好例子,我们在组合模式的那个例子上进行修改吧。我们知道,无论是游戏大区、游戏服务器还是游戏的服务都是一个元素,只不过它们的层次不一样。对于这样的层次结构,我们使用了组合模式来统一各层的接口,这样对游戏大区的操作和对游戏服务器的操作对调用方来说没有什么两样。在现实中,组合模式的运用往往没有这么顺利:

l 如果元素需要增加新的操作,那么势必需要在抽象元素中增加接口,这个时候的改动就非常大了,几乎每一个具体元素都需要修改。

l 如果元素并没有统一的接口,并且树枝角色中有多种树叶角色,那么树枝角色势必需要根据树叶类型来调用不同的方法。

l 如果树叶角色的接口经常发生变动,那么一旦发生变动操作树叶的树枝角色也需要发生修改。

访问者模式可以解决这些问题。

示例代码

usingSystem;

usingSystem.Collections;

usingSystem.Collections.Generic;

usingSystem.Text;

namespaceVisitorExample

{

classProgram

{

staticvoidMain(string[] args)

{

Elementserver1 = newGameServer("GS1", "192.168.0.1");

server1.Add(newGameService("Lobby1", 1, "S5Lobby1", 100));

server1.Add(newGameService("Lobby2", 1, "S5Lobby2", 200));

server1.Add(newGameService("Gate1", 2, "S5Gate1"));

server1.Add(newGameService("DataExchange1", 3, "S5DataExchange1"));

server1.Add(newGameService("Rank1", 4, "S5Rank1"));

server1.Add(newGameService("Log1", 5, "S5Log1"));

Elementserver2 = newGameServer("GS2", "192.168.0.2");

server2.Add(newGameService("Lobby3", 1, "S5Lobby3", 150));

server2.Add(newGameService("Lobby4", 1, "S5Lobby4", 250));

server2.Add(newGameService("Gate2", 2, "S5Gate2"));

server2.Add(newGameService("DataExchange2", 3, "S5DataExchange1"));

server2.Add(newGameService("Rank2", 4, "S5Rank2"));

server2.Add(newGameService("Log2", 5, "S5Log2"));

Elementarea = newGameArea("电信区");

area.Add(server1);

area.Add(server2);

area.Accept(newStartGameVisitor()); //A1

area.Accept(newStopGameVisitor()); //B1

server1.Accept(newQueryPlayerCountVisitor());

server2.Accept(newQueryPlayerCountVisitor());

area.Accept(newQueryPlayerCountVisitor());

}

}

interfaceIVisitor{ }

interfaceIGameServiceVisitor

{

voidVisit(GameServicegameService);

}

interfaceIGameServerVisitor

{

voidVisit(GameServergameServer);

}

interfaceIGameAreaVisitor

{

voidVisit(GameAreagameArea);

}

classStartGameVisitor: IVisitor, IGameServiceVisitor, IGameServerVisitor, IGameAreaVisitor

{

publicvoidVisit(GameServicegameService)

{

//A9

gameService.StartGameService(this);

}

publicvoidVisit(GameServergameServer)

{

//A6

gameServer.StartGameServer(this);

}

publicvoidVisit(GameAreagameArea)

{

//A3

gameArea.StartGameArea(this);

}

}

classStopGameVisitor: IVisitor, IGameServiceVisitor, IGameServerVisitor, IGameAreaVisitor

{

publicvoidVisit(GameServicegameService)

{

//B7

Console.WriteLine(string.Format("{0} stopped", gameService.Name));

}

publicvoidVisit(GameServergameServer)

{

//B5

Console.WriteLine("=============Stopping the whole " + gameServer.Name + "=============");

for(inti = gameServer.ServiceList.Count - 1; i >= 0; i--)

{

gameServer.ServiceList[i].Accept(this);

}

Console.WriteLine("=============The whole " + gameServer.Name + " stopped=============");

}

publicvoidVisit(GameAreagameArea)

{

//B3

Console.WriteLine("=============Stopping the whole " + gameArea.Name + "=============");

foreach(GameServerelement ingameArea.ServerList)

{

element.Accept(this);

}

Console.WriteLine("=============The whole " + gameArea.Name + " stopped=============");

}

}

classQueryPlayerCountVisitor: IVisitor, IGameServerVisitor, IGameAreaVisitor

{

publicvoidVisit(GameServergameServer)

{

intplayerCount = 0;

foreach(GameServicegameService ingameServer)

{

if(gameService.ServiceType == 1)

playerCount += gameService.PlayerCount;

}

Console.WriteLine("=============Player Count on " + gameServer.Name + " is:" + playerCount);

}

publicvoidVisit(GameAreagameArea)

{

intplayerCount = 0;

foreach(GameServergameServer ingameArea)

{

foreach(GameServicegameService ingameServer)

{

if(gameService.ServiceType == 1)

playerCount += gameService.PlayerCount;

}

}

Console.WriteLine("=============Player Count on " + gameArea.Name + " is:" + playerCount);

}

}

abstractclassElement

{

protectedstringname;

publicstringName

{

get{ returnname; }

}

publicElement(stringname)

{

this.name = name;

}

publicabstractvoidAdd(Elementelement);

publicabstractvoidRemove(Elementelement);

publicabstractvoidAccept(IVisitorvisitor);

}

classGameService: Element, IComparable<GameService>

{

privateintserviceType;

publicintServiceType

{

get{ returnserviceType; }

set{ serviceType = value; }

}

privatestringserviceName;

privateintplayerCount;

publicintPlayerCount

{

get{ returnplayerCount; }

set{ playerCount = value; }

}

publicGameService(stringname, intserviceType, stringserviceName)

: base(name)

{

this.serviceName = serviceName;

this.serviceType = serviceType;

}

publicGameService(stringname, intserviceType, stringserviceName, intplayerCount)

: base(name)

{

this.serviceName = serviceName;

this.serviceType = serviceType;

this.playerCount = playerCount;

}

publicoverridevoidAdd(Elementelement)

{

thrownewApplicationException("xxx");

}

publicoverridevoidRemove(Elementelement)

{

thrownewApplicationException("xxx");

}

publicoverridevoidAccept(IVisitorvisitor)

{

//A8,B6

IGameServiceVisitorgameServiceVisitor = visitor asIGameServiceVisitor;

if(gameServiceVisitor != null) gameServiceVisitor.Visit(this);

}

publicintCompareTo(GameServiceother)

{

returnother.serviceType.CompareTo(serviceType);

}

publicvoidStartGameService(IVisitorvisitor)

{

//A10

Console.WriteLine(string.Format("{0} started", name));

}

}

classGameServer: Element

{

privatestringserverIP;

privateList<GameService> serviceList = new List<GameService>();

publicList<GameService> ServiceList

{

get{ returnserviceList; }

}

publicGameServer(stringname, stringserverIP)

: base(name)

{

this.serverIP = serverIP;

}

publicoverridevoidAdd(Elementelement)

{

serviceList.Add((GameService)element);

}

publicoverridevoidRemove(Elementelement)

{

serviceList.Remove((GameService)element);

}

publicoverridevoidAccept(IVisitorvisitor)

{

//A5,B5

IGameServerVisitorgameServerVisitor = visitor asIGameServerVisitor;

if(gameServerVisitor != null) gameServerVisitor.Visit(this);

}

publicIEnumerator<GameService> GetEnumerator()

{

foreach(GameServicegameService inserviceList)

yieldreturngameService;

}

publicvoidStartGameServer(IVisitorvisitor)

{

//A7

IGameServerVisitorgameServerVisitor = visitor asIGameServerVisitor;

if(gameServerVisitor != null)

{

serviceList.Sort();

Console.WriteLine("=============Starting the whole " + name + "=============");

foreach(ElementgameService inserviceList)

{

gameService.Accept(visitor);

}

Console.WriteLine("=============The whole " + name + " started=============");

}

}

}

classGameArea: Element

{

privateList<GameServer> serverList = new List<GameServer>();

publicList<GameServer> ServerList

{

get{ returnserverList; }

}

publicGameArea(stringname)

: base(name) { }

publicoverridevoidAdd(Elementelement)

{

serverList.Add((GameServer)element);

}

publicoverridevoidRemove(Elementelement)

{

serverList.Remove((GameServer)element);

}

publicoverridevoidAccept(IVisitorvisitor)

{

//A2,B2

IGameAreaVisitorgameAreaVisitor = visitor asIGameAreaVisitor;

if(gameAreaVisitor != null) gameAreaVisitor.Visit(this);

}

publicIEnumerator<GameServer> GetEnumerator()

{

foreach(GameServergameServer inserverList)

yieldreturngameServer;

}

publicvoidStartGameArea(IVisitorvisitor)

{

//A4

IGameAreaVisitorgameAreaVisitor = visitor asIGameAreaVisitor;

if(gameAreaVisitor != null)

{

Console.WriteLine("=============Starting the whole " + name + "=============");

foreach(ElementgameServer inserverList)

{

gameServer.Accept(visitor);

}

Console.WriteLine("=============The whole " + name + " started=============");

}

}

}

}

代码执行结果如下图:

代码说明

代码从组合模式的例子修改过来,有一点乱,一点一点来分析:

l IVisitor是一个空接口,目的是为了抽象所有的访问者,抽象层次相当于Element

l IGameServerVisitorIGameServiceVisitor以及IGameAreaVisitor定义了访问者的访问操作(操作接口)。在这里,我们并没有把它们合并为一个接口,因为访问者可能仅针对一部分元素,或一个层次的元素,不一定都针对所有元素。

l StartGameVisitorStopGameVisitor以及QueryPlayCountVisitor是具体的访问者。它们根据自己的需求实现不同的操作接口,虽然都是访问者但是它们的目的不太一样,见下。

l Element类型就是抽象构件(抽象元素),它给组合对象以及单个对象提供了一个一致的接口,使得它们都能有一致的行为。唯一和组合模式不同的是,在这里定义了一个接受访问者的接口。

l GameServiceGameServer以及GameArea都是具体的元素。从组合角度来看,GameServerGameArea是树枝,GameService是树叶。看一下Accept()方法,对于具体元素来说,它应该明确接受特定层次的访问者,如果类型转换正确的话,那么调用访问者的访问方法。

l 这个例子中的StartGameVisitor的目的是统一元素的接口。注意到GameServiceGameServer以及GameArea中开启服务的方法都不同(并没有实现统一的接口),这样的话,高层的元素需要直接耦合低层元素的某个方法。通过访问者,我们使得它们直接和访问者的统一方法耦合,由访问者再适配不同的方法。可以从代码中以A打头的数字看出开启服务的整个流程。

l 这个例子中的StopGameVisitor体现了访问者模式最主要的作用,那就是为元素增加新的操作。这得益于访问者统一Accept接口以及双重分派的机制。可以从代码中以B打头的数字看出关闭服务的整个流程。

l 由于本例中把Visitor的接口按照功能分成了小接口,并且还有一个抽象顶层的空接口。这样,我们就可以为具体访问者实现需要的接口,并且为层次中增加元素也变得不是那么困难。从QueryPlayerCountVisitor中可以看到,具体访问者可以某些元素的新操作,而无需实现所有元素的新操作。

l 访问者模式还有一个应用是使不同类型元素组成的集合的遍历访问变得简单、符合开闭原则。由于GameServer的子元素一般只有GameService而不会有GameServiceGameAgent,所以本例没有体现这样的应用。

l 访问者模式的结构比较复杂,变化也比较多。一般不管怎么样变化,主要还是依靠访问者模式双重分派和统一的Accept接口来实现。访问者模式的效果有的时候也类似适配器、装饰模式等,由于往往用于操作集合所以一般也通常会和迭代器、组合模式一起使用。

何时采用

访问者模式适用下面的情况:

l 对象结构中包含很多类型,这些类型没有统一的接口,而我们又希望使得对象的操作进行统一。

l 希望为对象结构中的类型新增操作,并且不希望改变原有的代码。

l 对象结构中的一些类型之间发生耦合,而它们的实现又经常会发生变动。

l 针对结构中同一层次的不同类型甚至是不同层次的类型进行迭代。

从这里可以看出,访问者模式的主要适用还是针对对象结构(往往有层次关系),并且需要在设计的时候实现为各类型预留接受访问者的接口。

实现要点

l 每个元素都需要设置Accept()方法来接受访问者。

l 两次多态分发,确定访问者以及访问者中的方法。

l 如果一个结构层次中有多个类型的元素,那么可以通过一个ObjectStructure的角色进行封装。

注意事项

l 访问者模式一个主要的缺点就是难以扩展对象结构,其实,这点是可以通过一些变化进行化解的。

l 访问者模式第二个缺点是需要过多暴露对象的内部元素,否则访问者难以对对象进行实质性的操作。

l 第三个缺点是需要实现考虑到这样的需求并且提前设置接受访问者的方法。


 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值