简介
访问者模式(Visitor Pattern)是一种行为型设计模式,用于在不改变被访问元素的类的前提下,定义对这些元素的新操作。该模式通过将算法与数据结构分离,使得可以在不修改数据结构的情况下,增加新的操作。
结构
-
访问者(Visitor):定义了对于每个元素类所要执行的操作。这些操作可以是不同的,甚至是完全不同的,而且可以通过访问者模式来轻松地增加新的操作。
-
具体访问者(Concrete Visitor):实现了访问者接口定义的操作,对应于每一种具体的访问者。
-
元素(Element):定义了一个接受访问者的方法,通常是
accept(Visitor visitor)
,使得访问者可以访问它。 -
具体元素(Concrete Element):实现了接受访问者的方法,是被访问的对象,它定义了访问者具体访问的行为。
-
对象结构(Object Structure):可以包含多种不同类型的元素,提供了一个高层接口,让访问者可以访问它所包含的所有元素。
案例
假设有一个图形库,其中包含不同类型的图形对象:圆形(Circle)和矩形(Rectangle)。我们希望能够实现对这些图形对象执行不同的操作,比如计算它们的面积
using System; using System.Collections.Generic; // 图形对象接口 interface IShape { void Accept(IVisitor visitor); } // 圆形类 class Circle : IShape { public double Radius { get; } public Circle(double radius) { Radius = radius; } public void Accept(IVisitor visitor) { visitor.Visit(this); } } // 矩形类 class Rectangle : IShape { public double Width { get; } public double Height { get; } public Rectangle(double width, double height) { Width = width; Height = height; } public void Accept(IVisitor visitor) { visitor.Visit(this); } } // 访问者接口 interface IVisitor { void Visit(Circle circle); void Visit(Rectangle rectangle); } // 计算面积的访问者 class AreaVisitor : IVisitor { public double TotalArea { get; private set; } public void Visit(Circle circle) { TotalArea += Math.PI * Math.Pow(circle.Radius, 2); } public void Visit(Rectangle rectangle) { TotalArea += rectangle.Width * rectangle.Height; } } class Program { static void Main(string[] args) { var shapes = new List<IShape> { new Circle(5), new Rectangle(3, 4), new Circle(2) }; // 计算总面积 var areaVisitor = new AreaVisitor(); foreach (var shape in shapes) { shape.Accept(areaVisitor); } Console.WriteLine($"Total area: {areaVisitor.TotalArea}"); } }
如果增加了一个业务,需要绘制图形,那么只需加一个绘制图形的访问者
// 绘制访问者 class DrawVisitor : IVisitor { public void Visit(Circle circle) { Console.WriteLine($"Drawing circle with radius {circle.Radius}"); } public void Visit(Rectangle rectangle) { Console.WriteLine($"Drawing rectangle with width {rectangle.Width} and height {rectangle.Height}"); } }
class Program { static void Main(string[] args) { var shapes = new List<IShape> { new Circle(5), new Rectangle(3, 4), new Circle(2) }; // 计算总面积 var areaVisitor = new AreaVisitor(); foreach (var shape in shapes) { shape.Accept(areaVisitor); } Console.WriteLine($"Total area: {areaVisitor.TotalArea}"); // 绘制图形 var drawVisitor = new DrawVisitor(); foreach (var shape in shapes) { shape.Accept(drawVisitor); } } }
优点
-
分离关注点:访问者模式将数据结构与数据操作分离开来,使得可以在不改变数据结构的前提下定义新的操作。
-
开闭原则:符合开闭原则,允许向现有系统添加新的操作,而无需修改现有的代码。
-
易扩展性:可以通过添加新的具体访问者来轻松地扩展系统,而不会影响到原有的数据结构。
-
易维护性:由于各个操作被封装在具体访问者中,因此修改或扩展操作时不会影响其他部分的代码,提高了代码的可维护性。
缺点
-
增加新元素困难:如果需要添加新的元素类型,除了要修改元素接口,还需要修改所有的访问者类,这增加了系统的复杂性。
-
破坏封装性:访问者模式会将数据结构的内部细节暴露给具体访问者,使得数据结构的封装性降低。
-
不易理解:访问者模式的结构相对复杂,需要理解和掌握访问者、具体访问者、元素等多个角色,因此对于初学者来说不太容易理解。
-
适用范围有限:访问者模式通常适用于数据结构相对稳定,但经常需要在此数据结构上定义新操作的情况。如果数据结构频繁变化,访问者模式可能不太适用。
适用场景
-
操作与数据结构分离:当系统中的数据结构相对稳定,但经常需要定义新的操作或功能时,访问者模式可以将操作与数据结构分离,使得可以在不改变数据结构的情况下定义新的操作。
-
多个不相关的操作:当需要对一个数据结构进行多个不相关的操作,并且这些操作需要根据数据结构中的具体元素类型来执行不同的行为时,访问者模式能够为每个操作定义一个具体访问者,并在数据结构中分发这些访问者,以执行相应的操作。
-
数据结构稳定,但操作频繁变化:当数据结构相对稳定,但操作频繁变化时,可以使用访问者模式来封装变化的部分,使得可以轻松地扩展系统,而不会影响到现有的数据结构。
-
操作涉及多个不同的类:当需要对一个数据结构中的元素进行多种不同的操作,并且这些操作涉及到多个不同的类时,访问者模式可以将这些操作封装在不同的具体访问者中,使得每个具体访问者只关注自己的操作逻辑,提高了系统的可维护性和可扩展性。