模式简介
访问者模式是一种行为型设计模式,用于对结构稳定的对象进行功能扩展,通常对象通过向外提供固定逻辑的接口方法来完成内外交互,但是这种固定逻辑可能无法适应多变的需求,尤其是对于不同的调用者需要不同的交互逻辑的情况时,我们不可能为每个访问对象都编写一个单独的方法来交互,那么这种情况就可以考虑采用访问者模式。访问者模式将具体的交互逻辑交给访客,访客们实现统一的接口,被访问者则通过该接口方法进行统一的调用。
常见的应用场景:结构稳定的对象的扩展、不同对象的不同交互逻辑、避免类膨胀和对象结构的复杂处理等。
模式结构
-
访问者(Visitor): 定义了对每个元素访问的操作,这样可以通过访问者在不改变元素类的前提下定义新的操作。
-
具体访问者(ConcreteVisitor): 实现了访问者接口,提供了对元素的具体操作。
-
元素(Element): 定义了一个接受访问者的接口,通常包含一个
accept
方法,该方法接受访问者作为参数。 -
具体元素(ConcreteElement): 实现了元素接口,实现了
accept
方法,通常还包含自己的业务逻辑。 -
对象结构(Object Structure): 包含了一组元素,通常提供一个接受访问者的方法,允许访问者访问其中的元素。
工作原理
-
定义访问者接口,包含对每个具体元素的访问方法。
-
创建具体访问者类,实现访问者接口,定义具体的操作逻辑。
-
定义元素接口,包含
accept
方法。 -
创建具体元素类,实现元素接口,实现
accept
方法,并将具体访问者传递给访问者的访问方法。 -
创建对象结构类,用于存储和管理元素。
-
客户端使用访问者模式,创建具体元素和具体访问者对象,并将具体访问者对象传递给对象结构进行遍历。
代码示例(C#)
提示:可在本栏目的资源篇“设计模式代码示例合集”下载所有完整代码资源。
房屋:House.cs
namespace VisitorPattern;
// 房屋结构
abstract class HousePart
{
public string name; // 房屋结构名称
public HousePart(string name)
{
this.name = name;
}
public abstract void Introduce(); // 介绍房屋结构
}
// 房屋
abstract class House
{
protected string name; // 房屋名称
protected HousePart kitchen; // 厨房
protected HousePart toilet; // 厕所
protected HousePart bedroom; // 卧室
public House(string name, HousePart kitchen, HousePart toilet, HousePart bedroom)
{
this.name = name;
this.kitchen = kitchen;
this.toilet = toilet;
this.bedroom = bedroom;
}
protected abstract void WelcomeAndIntroduce(IVisitor visitor); // 欢迎访客并介绍房屋结构
public abstract IVisitor Accept(IVisitor visitor); // 接收访客的访问
}
// 公寓
class Apartment : House
{
public Apartment(string name, HousePart kitchen, HousePart toilet, HousePart bedroom) :
base(name, kitchen, toilet, bedroom)
{ }
public override IVisitor Accept(IVisitor visitor)
{
WelcomeAndIntroduce(visitor);
visitor.Visit();
return visitor;
}
protected override void WelcomeAndIntroduce(IVisitor visitor)
{
Console.WriteLine($"Hello {visitor.name}, welcome to my {name}.");
kitchen.Introduce();
bedroom.Introduce();
toilet.Introduce();
}
}
// 别墅
class Villa : House
{
public Villa(string name, HousePart kitchen, HousePart toilet, HousePart bedroom) :
base(name, kitchen, toilet, bedroom)
{ }
public override IVisitor Accept(IVisitor visitor)
{
WelcomeAndIntroduce(visitor);
visitor.Visit();
return visitor;
}
protected override void WelcomeAndIntroduce(IVisitor visitor)
{
Console.WriteLine($"Hello {visitor.name}, welcome to my {name}.");
kitchen.Introduce();
bedroom.Introduce();
toilet.Introduce();
}
}
// 厨房
class Kitchen : HousePart
{
public Kitchen(string name) : base(name) { }
public override void Introduce()
{
Console.WriteLine($"Welcome to {name}. This is a place for cooking.");
}
}
// 厕所
class Toilet : HousePart
{
public Toilet(string name) : base(name) { }
public override void Introduce()
{
Console.WriteLine($"Welcome to {name}. This is a place to return to nature.");
}
}
// 卧室
class Bedroom : HousePart
{
public Bedroom(string name) : base(name) { }
public override void Introduce()
{
Console.WriteLine($"Welcome to {name}. This is a place to sleep.");
}
}
访客:Visitor.cs
namespace VisitorPattern;
// 访客
interface IVisitor
{
// 访客名称
string name { get; }
// 看看厨房
void VisitKitchen();
// 看看厕所
void VisitToilet();
// 看看卧室
void VisitBedroom();
// 到处看看
void Visit();
// 看完了,该走了
void VisitEnd();
}
// 儿童
class Child : IVisitor
{
public string name => _name;
private string _name;
public Child(string name)
{
_name = name;
}
public void Visit()
{
VisitBedroom();
VisitKitchen();
VisitToilet();
}
public void VisitBedroom()
{
Console.WriteLine($"The child {_name} plays in the bedroom.");
}
public void VisitEnd()
{
Console.WriteLine("-----------------------------------");
}
public void VisitKitchen()
{
Console.WriteLine($"The child {_name} feels hungry, so he wants to find some foods to eat in the kitchen.");
}
public void VisitToilet()
{
Console.WriteLine($"Beacuse of drinking much juice, the child {_name} wants to return to nature.");
}
}
// 女士
class Woman : IVisitor
{
public string name => _name;
private string _name;
public Woman(string name)
{
_name = name;
}
public void Visit()
{
VisitToilet();
VisitBedroom();
VisitKitchen();
}
public void VisitEnd()
{
Console.WriteLine("-----------------------------------");
}
public void VisitBedroom()
{
Console.WriteLine($"The woman {_name} feels sleepy so that she is fast asleep in the bedroom.");
}
public void VisitKitchen()
{
Console.WriteLine($"The woman {_name} feels a little hungry after a sleep and she find some cookies in the refrigerator of kitchen luckily.");
}
public void VisitToilet()
{
Console.WriteLine($"The woman {_name} wants to return to nature beacuse of drinking much wine last night.");
}
}
测试代码:Program.cs
// ************* 15.访问者模式测试 **************
using VisitorPattern;
Kitchen kitchen = new Kitchen("kitchen");
Toilet toilet = new Toilet("toilet");
Bedroom bedroom = new Bedroom("bedroom");
Apartment apartment = new Apartment("apartment", kitchen, toilet, bedroom);
Villa villa = new Villa("villa", kitchen, toilet, bedroom);
Child child = new Child("Bob");
Woman woman = new Woman("Amy");
// 孩子和女士访问公寓
apartment.Accept(child).VisitEnd();
apartment.Accept(woman).VisitEnd();
// 孩子和女士访问别墅
villa.Accept(child).VisitEnd();
villa.Accept(woman).VisitEnd();
代码解说
上述代码模拟了不同类型的访客来游览不同类型的房屋。
每个房屋都需要继承House基类,其中WelcomeAndIntroduce方法用于介绍这是什么类型的房屋以及房屋中的各个房间用来做什么,Accept方法用于接待访客。
房屋的结构一旦确定就不会再进行改变了,如果我们不采用访问者模式,那么接待不同的访客我们需要通过修改或添加房屋中的接待方法,这就违背了开闭原则。我们把接待方法改为访问方法,我们规定每个访客都需要一个Visit方法,以及与房屋结构相适应的VisitKitchen、VisitToilet、VisitBedroom等方法。
对房屋而言只需要直接调用参观的访客的Visit方法即可,毕竟访客要怎么参观是访客要做的事情,我们交给访客自己去定义就行了,房屋只需要知道访客是否要参观即可。有一个问题就是,为什么我这里需要添加一个Visit方法,原则上只需要VisitKitchen、VisitToilet、VisitBedroom这三个方法就能够达到我们的目的了,这是因为我考虑到访客他自己的参观顺序,这个参观顺序也是访客自己决定的,作为房屋是不能够去限定访客的参观顺序的,所以添加了Visit方法。
如果这篇文章对你有帮助,请给作者点个赞吧!