动机:
在软件构建过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中做这样的改变,将会给子类带来很繁重的变更负担,甚至破坏原有设计。
如何在不更改类层次结构的前提下,在运行时根据需要透明的为类层次结构上的各个类动态添加新的操作,从而避免上述问题?
意图:
标识一个作用于某对象结构中的各元素的操作。它可以在不改变各元素的类的前提下定义作用于这些元素的新的操作。
- public abstract class Shape
- {
- public abstract void Draw();
- public abstract void MoveTo(Point p);
- }
- public class Rectangle : Shape
- {
- public override void Draw()
- {
- }
- }
- public class Circle : Shape
- {
- public override void Draw()
- {
- }
- }
- public class Line : Shape
- {
- public override void Draw()
- {
- }
- }
在上述代码中,我们有一个抽象基类Shape,他定义了抽象方法Draw,Rectangle、Circle、Line继承了Shape类的方法,并且实现了Draw方法,但是当我们需要增加一个方法MoveTo时,我们必不可少的需要在各个子类中重写MoveTo方法,那么现在就带来了一个问题,当我们基类的方法不能完全确定,也就是稳定的时候,我们如何来进行解藕。
- public abstract class Shape
- {
- public abstract void Draw();
- //预料到将来会引入新的操作
- public abstract void Accept(ShapeVisitor visitor);
- }
- public abstract class ShapeVisitor
- {
- //重载的关系,Visit方法参数不是基类型,是具体类型
- public abstract void Visit(Rectangle shape);
- public abstract void Visit(Circle shape);
- public abstract void Visit(Line shape);
- }
- public class MyVisitor : ShapeVisitor
- {
- public override void Visit(Rectangle shape)
- {
- //增加对Rectangle的操作
- }
- public override void Visit(Circle shape)
- {
- //增加对Circle的操作
- }
- public override void Visit(Line shape)
- {
- //增加对Line的操作
- }
- }
- public class Rectangle : Shape
- {
- public override void Draw()
- {
- }
- public override void Accept(ShapeVisitor visitor)
- {
- //这里是编译时确定的,不是运行时确定
- //如果这个调用写到Shape基类里,编译器编译的时候
- //不知道编译那个方法。编译会报错,因为没有
- //Visit(Shape shape)方法。
- visitor.Visit(this);
- }
- }
- public class Circle : Shape
- {
- public override void Draw()
- {
- }
- public override void Accept(ShapeVisitor visitor)
- {
- visitor.Visit(this);
- }
- }
- public class Line : Shape
- {
- public override void Draw()
- {
- }
- public override void Accept(ShapeVisitor visitor)
- {
- visitor.Visit(this);
- }
- }
- class App
- {
- ShapeVisitor visitor;
- public App(ShapeVisitor visitor)
- {
- this.visitor = visitor;
- }
- public void Process(Shape shape)
- {
- //两处多态:
- //1、Accept方法的调用对象Shape
- //2、Accept方法的参数Visitor
- shape.Accept(visitor);
- }
- }
- class Program
- {
- static void Main(string[] args)
- {
- App app = new App(new MyVisitor());
- app.Process(new Line());
- }
- }
现在,我们在Shape类中定义了一个Accept方法,这个方法也是一个抽象方法,并且Rectangle、Circle、Line实现了Accept方法。并且Accept方法有一个参数ShapeVisitor。现在转到ShapeVisitor类,定义了Visit方法,并且有三个重载,每个Visit方法的参数都是Shape的派生类。在Rectangle、Circle和Line三个类中,我们实现的Accept方法都是将this指针传递给Visit方法。
现在有一个具体的MyVisitor类继承于ShapeVisitor类,并且在此类中每个Visit方法的重载,根据传递的图形不同做具体的动作。
在App类中的process方法,根据传入的图形对象和访问者对象来形成了两处多态。当我们在Shape中需要增加一种方法的时候,我们不需要改写Shape类及其派生类,我们仅仅只需要增加一种Visitor类,并将新增的Visitor类传递到App里。
要点:
Visitor模式通过所谓的双重分发(double dispatch)来实现在不更改Element类层次结构的前提下,在运行时透明的为类层次结构上的各个类动态添加新的操作。
所谓双重分发即Visitor模式中间包括了两个多态分发:第一个为Accept方法的多态辨析;第二个为Visit方法的多态辨析(重载)
Visitor模式最大缺点在于扩展类层次结构(添加新的Element子类),会导致Visitor类的改变,因此Visitor模式使用户Element类层子结构稳定,而其中的操作却经常面临频繁改动。
当我们需要增加一个Shape的子类时,我们需要给ShapeVisitor类添加一个Visit函数,并且ShapeVisitor的每个派生类也必须添加。