为了尽可能演示出Visitor 设计模式的强大之处,在此举一个开发中的场景
例如 开发A组 负责做log功能,而B组需要A组暴露一个API,可以拿到所有的log。
A组的代码实现可能是如下这样的:
A组有两个选择,再加一个API,还是把这个责任分发回B组,因为显然显示颜色的逻辑B组更熟悉。当然是分发责任,可如何分发,访问者模式来了。
1.在Log基类中添加一行:
3.给B组一个接口(如果没有返回值,可以把T拿掉):
1. 外部的访问者实现这个接口,就可以获取每个子类
2. 如果类层次变化,外部可以知道
3. 外部调用不用写大量的switch-case 或 if else臃肿的判断逻辑了
A组工作完成。接下来是B组要做的事情了——实现访问者接口。
实现也许是这样的:
于是只需要添加另一个Visitor:
以下是完整的代码:
例如 开发A组 负责做log功能,而B组需要A组暴露一个API,可以拿到所有的log。
A组的代码实现可能是如下这样的:
public abstract class OrderLog{
public string Content {get;set;}
public OrderLog(string content){
Content = content;
}
}
public class PlaceOrderLog :OrderLog{
public PlaceOrderLog(string content,string orderedBy):base(content)
{
OrderedBy = orderedBy;
}
public string OrderedBy {get;set;}
}
public class MakePaymentLog :OrderLog{
public MakePaymentLog(string content, string paymentGateway, string payedBy):base(content)
{
PaymentGateway = paymentGateway;
PayedBy = payedBy;
}
public string PaymentGateway {get;set;}
public string PayedBy {get;set;}
}
public class OrderCompleteLog : OrderLog{
public OrderCompleteLog(string content, DateTime completeDate):base(content)
{
OrderCompleteDate = completeDate;
}
public DateTime OrderCompleteDate {get;set;}
}
public class OrderLogger{
public static OrderLogger Do
{
get
{
return new OrderLogger();
}
}
public IEnumerable<OrderLog> GetLogs(){
return new List<OrderLog>{
new PlaceOrderLog("place order log","ordered by"),
new MakePaymentLog("make payment log","paypal", "payedBy"),
new OrderCompleteLog("order complete log",DateTime.Now)
};
}
}
B组用法: var logs = OrderLogger.Do.GetLogs();
B组新需求来了,需要A组再做一个API,可以把颜色信息加入LOG实体中,比如类型A返回红色,类型B返回绿色 A组有两个选择,再加一个API,还是把这个责任分发回B组,因为显然显示颜色的逻辑B组更熟悉。当然是分发责任,可如何分发,访问者模式来了。
1.在Log基类中添加一行:
public abstract T Visit<T>(ILogVisitor<T> visitor);
2.其他log子类中相应的实现它: public override T Visit<T>(ILogVisitor<T> visitor){
return visitor.Visit(this);
}
为什么加这个方法?相当于给外界开一个入口,这样它们可以进来,然后把自身的信息传递给它们完成相应逻辑。 3.给B组一个接口(如果没有返回值,可以把T拿掉):
public interface ILogVisitor<T>{
T Visit(PlaceOrderLog log);
T Visit(MakePaymentLog log);
T Visit(OrderCompleteLog log);
}
为什么加这个接口? 1. 外部的访问者实现这个接口,就可以获取每个子类
2. 如果类层次变化,外部可以知道
3. 外部调用不用写大量的switch-case 或 if else臃肿的判断逻辑了
A组工作完成。接下来是B组要做的事情了——实现访问者接口。
实现也许是这样的:
public enum OrderLogColor{Red,Green}
public class OrderLogColorVisitor:ILogVisitor<OrderLogColor>{
public OrderLogColor Visit(PlaceOrderLog log){
return OrderLogColor.Red;
}
public OrderLogColor Visit(MakePaymentLog log){
return OrderLogColor.Green;
}
public OrderLogColor Visit(OrderCompleteLog log){
return OrderLogColor.Green;
}
}
逻辑很简单,对于不同的类型返回不同颜色。接下来就是调用部分:var logs = OrderLogger.Do.GetLogs();
show colors
var colorVisitor = new OrderLogColorVisitor();
foreach(var log in logs){
Console.WriteLine(log.Visit<OrderLogColor>(colorVisitor));
}
可以看到,拿到一个日志集合后,循环过程只需调用Visit函数,就进入了相应的log类,然后在log类中,调用Visit函数完成Visit逻辑。执行结果: Red
Green
Green
接下来,B组需要一个新的任务,对不同的日志类型,格式化显示不同的字符串。 于是只需要添加另一个Visitor:
public class OrderLogFormattedVisitor : ILogVisitor <string> {
public string Visit(PlaceOrderLog log){
return "this is place order log formatted information ";
}
public string Visit(MakePaymentLog log){
return "this is make payment log formatted information ";
}
public string Visit(OrderCompleteLog log){
return "this is order complete log formatted information ";
}
}
调用: var logs = OrderLogger.Do.GetLogs();
var formatVisitor = new OrderLogFormattedVisitor();
foreach(var log in logs){
Console.WriteLine(log.Visit<string>(formatVisitor));
}
看出访问者模式的作用了吗?它把职责隔离的同时,有效的封装了变化。在以上例子中,A组只负责做LOG API,而B组负责拿到LOG后的事情。 在开发中会经常遇到相互调用的情况,每当这个时候,首先考虑职责分配,用接口隔离工作,决定调用的入口。这便是访问者模式的强大之处。 以下是完整的代码:
void Main()
{
var logs = OrderLogger.Do.GetLogs();
show colors
var colorVisitor = new OrderLogColorVisitor();
foreach(var log in logs){
Console.WriteLine(log.Visit<OrderLogColor>(colorVisitor));
}
show formatted logs
var formatVisitor = new OrderLogFormattedVisitor();
foreach(var log in logs){
Console.WriteLine(log.Visit<string>(formatVisitor));
}
}
team A job
public abstract class OrderLog{
public string Content {get;set;}
public OrderLog(string content){
Content = content;
}
public abstract T Visit<T>(ILogVisitor<T> visitor);
}
public class PlaceOrderLog :OrderLog{
public PlaceOrderLog(string content,string orderedBy):base(content)
{
OrderedBy = orderedBy;
}
public string OrderedBy {get;set;}
public override T Visit<T>(ILogVisitor<T> visitor){
return visitor.Visit(this);
}
}
public class MakePaymentLog :OrderLog{
public MakePaymentLog(string content, string paymentGateway, string payedBy):base(content)
{
PaymentGateway = paymentGateway;
PayedBy = payedBy;
}
public string PaymentGateway {get;set;}
public string PayedBy {get;set;}
public override T Visit<T>(ILogVisitor<T> visitor){
return visitor.Visit(this);
}
}
public class OrderCompleteLog : OrderLog{
public OrderCompleteLog(string content, DateTime completeDate):base(content)
{
OrderCompleteDate = completeDate;
}
public DateTime OrderCompleteDate {get;set;}
public override T Visit<T>(ILogVisitor<T> visitor){
return visitor.Visit(this);
}
}
public class OrderLogger{
public static OrderLogger Do
{
get
{
return new OrderLogger();
}
}
public IEnumerable<OrderLog> GetLogs(){
return new List<OrderLog>{
new PlaceOrderLog("place order log","ordered by"),
new MakePaymentLog("make payment log","paypal", "payedBy"),
new OrderCompleteLog("order complete log",DateTime.Now)
};
}
}
now team B want a API , return different log color based on different type
team A give team B a visitor interface , ask them to implement
public interface ILogVisitor<T>{
T Visit(PlaceOrderLog log);
T Visit(MakePaymentLog log);
T Visit(OrderCompleteLog log);
}
team B come out with the implementations
public enum OrderLogColor{Red,Green}
public class OrderLogColorVisitor:ILogVisitor<OrderLogColor>{
public OrderLogColor Visit(PlaceOrderLog log){
return OrderLogColor.Red;
}
public OrderLogColor Visit(MakePaymentLog log){
return OrderLogColor.Green;
}
public OrderLogColor Visit(OrderCompleteLog log){
return OrderLogColor.Green;
}
}
in future
if team B want more , just implement the ILogVisitor interface
for example : formatted log
public class OrderLogFormattedVisitor : ILogVisitor <string> {
public string Visit(PlaceOrderLog log){
return "this is place order log formatted information ";
}
public string Visit(MakePaymentLog log){
return "this is make payment log formatted information ";
}
public string Visit(OrderCompleteLog log){
return "this is order complete log formatted information ";
}
}
whats next
whenever team B want , he just add more visitor to accept different log types returned from teamA