提供統一介面訪問不同類型元素,並能在不修改元素類的前提下新增操作。
承接 Composite 模式的遊戲伺服器架構範例:遊戲大區、伺服器、服務均為元素,層次不同。Composite 統一了介面,但實務上常遇到:
Visitor 模式可解決上述問題。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace VisitorExample
{
class Program
{
static void Main(string[] args)
{
Element server1 = new GameServer("GS1", "192.168.0.1");
server1.Add(new GameService("Lobby1", 1, "S5Lobby1", 100));
server1.Add(new GameService("Lobby2", 1, "S5Lobby2", 200));
server1.Add(new GameService("Gate1", 2, "S5Gate1"));
server1.Add(new GameService("DataExchange1", 3, "S5DataExchange1"));
server1.Add(new GameService("Rank1", 4, "S5Rank1"));
server1.Add(new GameService("Log1", 5, "S5Log1"));
Element server2 = new GameServer("GS2", "192.168.0.2");
server2.Add(new GameService("Lobby3", 1, "S5Lobby3", 150));
server2.Add(new GameService("Lobby4", 1, "S5Lobby4", 250));
server2.Add(new GameService("Gate2", 2, "S5Gate2"));
server2.Add(new GameService("DataExchange2", 3, "S5DataExchange1"));
server2.Add(new GameService("Rank2", 4, "S5Rank2"));
server2.Add(new GameService("Log2", 5, "S5Log2"));
Element area = new GameArea("電信區");
area.Add(server1);
area.Add(server2);
area.Accept(new StartGameVisitor()); // A1
area.Accept(new StopGameVisitor()); // B1
server1.Accept(new QueryPlayerCountVisitor());
server2.Accept(new QueryPlayerCountVisitor());
area.Accept(new QueryPlayerCountVisitor());
}
}
interface IVisitor { }
interface IGameServiceVisitor { void Visit(GameService gameService); }
interface IGameServerVisitor { void Visit(GameServer gameServer); }
interface IGameAreaVisitor { void Visit(GameArea gameArea); }
class StartGameVisitor : IVisitor, IGameServiceVisitor, IGameServerVisitor, IGameAreaVisitor
{
public void Visit(GameService gameService) { gameService.StartGameService(this); } // A9
public void Visit(GameServer gameServer) { gameServer.StartGameServer(this); } // A6
public void Visit(GameArea gameArea) { gameArea.StartGameArea(this); } // A3
}
class StopGameVisitor : IVisitor, IGameServiceVisitor, IGameServerVisitor, IGameAreaVisitor
{
public void Visit(GameService gameService)
{
Console.WriteLine($"{gameService.Name} stopped"); // B7
}
public void Visit(GameServer gameServer)
{
Console.WriteLine($"=============Stopping the whole {gameServer.Name}============="); // B5
for (int i = gameServer.ServiceList.Count - 1; i >= 0; i--)
gameServer.ServiceList[i].Accept(this);
Console.WriteLine($"=============The whole {gameServer.Name} stopped=============");
}
public void Visit(GameArea gameArea)
{
Console.WriteLine($"=============Stopping the whole {gameArea.Name}============="); // B3
foreach (GameServer element in gameArea.ServerList)
element.Accept(this);
Console.WriteLine($"=============The whole {gameArea.Name} stopped=============");
}
}
class QueryPlayerCountVisitor : IVisitor, IGameServerVisitor, IGameAreaVisitor
{
public void Visit(GameServer gameServer)
{
int playerCount = 0;
foreach (GameService gs in gameServer)
if (gs.ServiceType == 1) playerCount += gs.PlayerCount;
Console.WriteLine($"=============Player Count on {gameServer.Name} is: {playerCount}");
}
public void Visit(GameArea gameArea)
{
int playerCount = 0;
foreach (GameServer gs in gameArea)
foreach (GameService s in gs)
if (s.ServiceType == 1) playerCount += s.PlayerCount;
Console.WriteLine($"=============Player Count on {gameArea.Name} is: {playerCount}");
}
}
abstract class Element
{
protected string name;
public string Name => name;
public Element(string name) { this.name = name; }
public abstract void Add(Element element);
public abstract void Remove(Element element);
public abstract void Accept(IVisitor visitor);
}
class GameService : Element, IComparable<GameService>
{
public int ServiceType { get; set; }
public string ServiceName { get; private set; }
public int PlayerCount { get; set; }
public GameService(string name, int serviceType, string serviceName) : base(name)
{ ServiceType = serviceType; ServiceName = serviceName; }
public GameService(string name, int serviceType, string serviceName, int playerCount) : this(name, serviceType, serviceName)
{ PlayerCount = playerCount; }
public override void Add(Element element) => throw new ApplicationException("Leaf cannot add");
public override void Remove(Element element) => throw new ApplicationException("Leaf cannot remove");
public override void Accept(IVisitor visitor)
{
var v = visitor as IGameServiceVisitor;
v?.Visit(this); // A8, B6
}
public int CompareTo(GameService other) => other.ServiceType.CompareTo(ServiceType);
public void StartGameService(IVisitor visitor) => Console.WriteLine($"{Name} started"); // A10
}
class GameServer : Element
{
public string ServerIP { get; }
public List<GameService> ServiceList { get; } = new List<GameService>();
public GameServer(string name, string serverIP) : base(name) { ServerIP = serverIP; }
public override void Add(Element element) => ServiceList.Add((GameService)element);
public override void Remove(Element element) => ServiceList.Remove((GameService)element);
public override void Accept(IVisitor visitor)
{
var v = visitor as IGameServerVisitor;
v?.Visit(this); // A5, B5
}
public IEnumerator<GameService> GetEnumerator()
{
foreach (var s in ServiceList) yield return s;
}
public void StartGameServer(IVisitor visitor)
{
var v = visitor as IGameServerVisitor;
if (v != null)
{
ServiceList.Sort();
Console.WriteLine($"=============Starting the whole {Name}============="); // A7
foreach (Element s in ServiceList) s.Accept(visitor);
Console.WriteLine($"=============The whole {Name} started=============");
}
}
}
class GameArea : Element
{
public List<GameServer> ServerList { get; } = new List<GameServer>();
public GameArea(string name) : base(name) { }
public override void Add(Element element) => ServerList.Add((GameServer)element);
public override void Remove(Element element) => ServerList.Remove((GameServer)element);
public override void Accept(IVisitor visitor)
{
var v = visitor as IGameAreaVisitor;
v?.Visit(this); // A2, B2
}
public IEnumerator<GameServer> GetEnumerator()
{
foreach (var s in ServerList) yield return s;
}
public void StartGameArea(IVisitor visitor)
{
var v = visitor as IGameAreaVisitor;
if (v != null)
{
Console.WriteLine($"=============Starting the whole {Name}============="); // A4
foreach (Element s in ServerList) s.Accept(visitor);
Console.WriteLine($"=============The whole {Name} started=============");
}
}
}
}
Element。Accept(IVisitor) 接受訪問者。Accept() 中以型別轉換確認訪問者類型,正確則呼叫對應 Visit()。Accept 與雙重分派機制(參考 B 開頭註解)。QueryPlayerCountVisitor 僅實作 Server 與 Area 訪問,不需理會 Service。Visitor 主要針對有層次關係的對象結構,且需在設計階段預留 Accept 介面。
Accept() 接受訪問者。ObjectStructure 角色封裝管理。Accept 方法。