访问者模式
定义(GoF):表示一个作用于某对象结构中的各种操作,它使你在不改变各个元素类的前提下定义作用于这些元素的新操作。
先通过结构图,来了解访问者模式
应用场景
我们先看一下访问者模式中都有哪些角色
实际上访问者模式中有三类对象,访问者、元素对象、对象结构,核心要做的事情都是在具体的访问者中进行
- 抽象访问者:定义了一些抽象方法,方法数量通常与具体的元素对象个数相同,同时这些方法会依赖于具体元素对象。
- 具体访问者对象:继承抽象访问者类,实现抽象方法,并根据当前访问者表达的含义,构建成员方法的逻辑。
- 抽象元素:可以对某一类对象进行抽象,并且这些对象种类比较稳定,同时定义一个抽象方法,方法会依赖于抽象访问者,通常此方法名被定义为Accept。
- 具体元素对象:继承抽象元素类,实现抽象方法,通常方法内逻辑比较固定,通过访问者调用访问者方法visitor,并将当前元素类的引用作为参数传入其中。
- 对象结构:对元素对象进行集合存储,向外提供对元素对象的添加、删除等操作,一般会提供一个方法,参数是访问者对象,通过访问者对象的Visit方法,对集合中元素进行遍历调用。
访问者模式适用于对象种类相对稳定的场景,它解决的实际上是将对象与作用于对象的行为进行分离解耦,使得在不更改对象结构的前提下,可以更灵活地对作用于对象的行为操作进行更改。
示例应用
场景一:
比如一天有三个重要时刻,分别为早中晚,我们对Harley、Baffett进行调查,观察他们在这些时刻都做了什么事情。
因为早中晚是相对稳定的时刻,我们可以将早中晚三个时刻声明三个元素对象,把Harley、Buffett声明为两个具体访问者对象,然后会在具体访问者对象中描述访问者的具体行为。
示例结构图
Environment:
Visual Studio Code、.Net 6
相关命令:
// 创建项目文件夹
mkdir DesignPattern
// 创建项目文件
dotnet new sln
// 创建访问者控制台文件夹
mkdir DesignPattern.Visitor.Console
// 创建访问者控制台项目
dotnet new console
// 运行项目
dotnet run
代码片段一
public abstract class TimeElement
{
public TimeSpan Begin { get; set; }
public TimeSpan End { get; set; }
public abstract void Accept(Visitor visitor);
}
public class MorningTimeElement : TimeElement
{
public MorningTimeElement()
{
Begin = TimeSpan.FromHours(6);
End = TimeSpan.FromHours(11);
}
public override void Accept(Visitor visitor)
{
visitor.Visit(this);
}
}
public class MorningTimeElement : TimeElement
{
public MorningTimeElement()
{
Begin = TimeSpan.FromHours(6);
End = TimeSpan.FromHours(11);
}
public override void Accept(Visitor visitor)
{
visitor.Visit(this);
}
}
public class AfterNoonElement : TimeElement
{
public AfterNoonElement()
{
Begin = TimeSpan.FromHours(13);
End = TimeSpan.FromHours(18);
}
public override void Accept(Visitor visitor)
{
visitor.Visit(this);
}
}
代码片段二
public abstract class Visitor
{
public abstract void Visit(MorningTimeElement timeElement);
public abstract void Visit(NoonTimeElement timeElement);
public abstract void Visit(AfterNoonElement timeElement);
}
public class HarleyVisitor : Visitor
{
public override void Visit(MorningTimeElement timeElement)
{
System.Console.WriteLine($"Harley 在 {timeElement.Begin:hh\\:mm} 起床,然后开始学习、工作,在 {timeElement.End:hh\\:mm}开始午休");
}
public override void Visit(NoonTimeElement timeElement)
{
System.Console.WriteLine($"Harley 在 {timeElement.Begin:hh\\:mm} 准备吃午餐,并在{timeElement.End:hh\\:mm}前进行午睡");
}
public override void Visit(AfterNoonElement timeElement)
{
System.Console.WriteLine($"Harley 在 {timeElement.Begin:hh\\:mm} 开始工作,在 {timeElement.End:hh\\:mm} 下班休息");
}
}
public class BuffettVisitor : Visitor
{
public override void Visit(MorningTimeElement timeElement)
{
System.Console.WriteLine($"Buffett 在 {timeElement.Begin:hh\\:mm} 还在睡梦中,大约45分钟后起床,然后开始准备开会,在 {timeElement.End:hh\\:mm}开始午休");
}
public override void Visit(NoonTimeElement timeElement)
{
System.Console.WriteLine($"Buffett 在 {timeElement.Begin:hh\\:mm} 准备吃午餐,并在{timeElement.End:hh\\:mm}前进行准备下午会议");
}
public override void Visit(AfterNoonElement timeElement)
{
System.Console.WriteLine($"Buffett 在 {timeElement.Begin:hh\\:mm} 开始进行下午会议,在 {timeElement.End:hh\\:mm} 下班休息");
}
}
代码片段三
public class ObjectStructure
{
private IList<TimeElement> elements = new List<TimeElement>();
public void Add(TimeElement element) => elements.Add(element);
public void Delete(TimeElement element) => elements.Remove(element);
public void Accept(Visitor visitor)
{
foreach (var item in elements)
{
item.Accept(visitor);
}
}
}
客户端
Console.WriteLine("Hello, Harley!");
Console.OutputEncoding = System.Text.Encoding.UTF8;
var morningElement = new MorningTimeElement();
var noonElement = new NoonTimeElement();
var afterNoonElement = new AfterNoonElement();
var objectStructure = new ObjectStructure();
objectStructure.Add(morningElement);
objectStructure.Add(noonElement);
objectStructure.Add(afterNoonElement);
var buffettVisitor = new BuffettVisitor();
var harleyVisitor = new HarleyVisitor();
System.Console.WriteLine("******buffett visitor");
objectStructure.Accept(buffettVisitor);
System.Console.WriteLine("******harley visitor");
objectStructure.Accept(harleyVisitor);
Console.Read();
在控制台执行dotnet run,结果如下:
总结
访问者模式是行为型设计模式,关注的是对象与行为的分离。适用于对象结构相对稳定,作用于对象结构上的操作又可以独立演化的场景。在对象结构上增加操作就是创建一个具体的访问者对象,并且完全遵循了开闭原则,降低了对象与行为的耦合性,但是它同时也有很大的缺点,不适用于对象结构易于变化的场景,同时违反了依赖倒置原则、迪米特法则。
所以,只有在业务比较合适的场景,才能发挥访问者模式的优势。
优点:
- 增加新的操作很容易,只需要添加一个具体的访问者对象,无需变动对象结构、元素对象。
- 遵循了开闭原则,使得增加新的处理逻辑变得简单。
- 遵循了里氏替换原则。
缺点:
- 对象结构增加新的对象会很困难。
- 违反了依赖倒置原则,抽象不依赖于细节,细节依赖于抽象,因为在抽象访问者的方法中,依赖了具体元素的实现。
- 违反了迪米特法则,最小知道原则。
References:
标签:timeElement,对象,Visitor,void,Visit,设计模式,public,访问者 From: https://www.cnblogs.com/harley-chang/p/16667973.html《Head First 设计模式》
《大话设计模式》