【设计模式系列】行为型模式之Visitor模式

概要
当要为某个类扩展功能时,怎么做?太简单了,想扩展什么功能就加什么样的方法就得了呗!那如果这个类属于某个你不能轻易修改的Lib时,又怎么做呢?还是很简单,OO有很多用于扩展的概念和模式,最原始的如继承、组合也是一种扩展。确实如此,而今天,让我们再来讨论提供一种很灵活扩展方式的----Visitor模式。

目的
为某些类提供新的功能和方法,而不需要修改这些类。

实例
假设我们有很多设备,比如移动电话,PC,Pad等,这些设备有提供了一些功能,比如开机,关机,连网,断网等,用类图和简单的代码来描述,如下所示:

class Device {
public:
     virtual void PowerOn() = 0;
     virtual void PowerOff() = 0;
     virtual void ConnectNetwork() = 0;
     virtual void DisconnectNetwork() = 0;
};

Class MobilePhone : public Device {
public:
     virtual void PowerOn() {
          ......
     }
     virtual void PowerOff()  {
          ......
     }
     virtual void ConnectNetwork()  {
          ......
     }
     virtual void DisconnectNetwork()  {
          ......
     }
};
因为PC和Pad类与MobilePhone类似,所以这里省略它们的代码。

这时我们需要为各种Device添加一个新方法,功能是在开机(PowerOn)的同时连接上网络(ConnectNetwork),本来其实只需要在类中添加一个PowerOnWithNetwork的方法来实现需要的功能就可以了,但是如果现有代码属于黑箱的Lib,我们不能去修改的话,怎么办呢?可以通过Adapter模式来实现,但是这样会引入多个新的类,对既存代码会有影响。确实如果照上面的实现,要在如此苛刻的条件下对现有类追加方法还真不容易,那么如果我们在设计上面这些类时就引入了Visitor模式的话,扩展是否就显得很简单了呢?

基于Visitor模式的实现如下:

class IVisitor{
public:
     virtual void Visit(const MobilePhone*) = 0;
     virtual void Visit(const PC*) = 0;
     virtual void Visit(const Pad*) = 0;
};

class PowerOnWithNetwork : public IVisitor{
public:
     virtual void Visit(const MobilePhone*) {
          MobilePhone->PowerOn();
          MobilePhone->ConnectNetwork();
     }
     virtual void Visit(const PC*)  {
          MobilePhone->PowerOn();
          MobilePhone->ConnectNetwork();
     }
     virtual void Visit(const Pad*)  {
          MobilePhone->PowerOn();
          MobilePhone->ConnectNetwork();
     }
};

class IElement {
public:
     virtual void Accept(IVisitor* visitor) = 0;
};

Class MobilePhone : public Device, public IElement {
public:
     virtual void PowerOn() {
          ......
     }
     virtual void PowerOff()  {
          ......
     }
     virtual void ConnectNetwork()  {
          ......
     }
     virtual void DisconnectNetwork()  {
          ......
     }
     virtual void Accept(IVisitor* visitor)  {
          visitor->Visit(this);
     }
};
Class PC: public Device, public IElement {
public:
     ......
     virtual void Accept(IVisitor* visitor)  {
          visitor->Visit(this);
     }
};
Class Pad: public Device, public IElement {
public:
     ......
     virtual void Accept(IVisitor* visitor)  {
          visitor->Visit(this);
     }
};

这里定义了两个接口,都是不能被修改的Lib本身在设计时为了便于使用者对现有功能进行扩展而设计的接口,IVisitor用于扩展功能,IElement则让每种Device具备一个带有IVisitor类型参数的Accept方法。当我们需要为各种Device扩展方法时,可以先从IVisitor继承来实现你需要的功能,比如我们需要PowerOnWithNetwork的功能,我们就添加子类PowerOnWithNetwork,而PowerOnWithNetwork中实现的各种设备的Visit方法其实就是实际PowerOnWithNetwork功能,同时各种Device都继承于接口IElement,在Accept方法中调用本身设备对应的Visit方法。

实际需要PowerOnWithNetwork功能时,可以这样调用:
IVisitor* visitor = new PowerOnWithNetwork();
MobilePhone* phone = new MobilePhone();
phone->Accept(visitor);
在调用Accept时,把PowerOnWithNetwork作为参数,实际执行的功能其实就是方法Visit(const MobilePhone*)。

这里可以考虑下,如果我们还要添加一个在关机前先断开网络的方法时,应该怎么做?很简单与PowerOnWithNetwork类似,从IVisitor继承再实现一个假设名为DisconnectBeforePowerOff的类就OK了。

应用
Visitor模式提供了一种非常灵活的方法来在不改变既存代码的情况下扩展类的方法。但是它也有一个缺点,就是当你从IVisitor类继承来进行扩展时,如前面的例子所示,每种Device其实都会被改变,这让扩展本身存在一定耦合,而你也很难只针对某一种设备来进行扩展。
另外,Visitor模式经常会跟Compositor模式结合来使用,当你的某个IElement是Compositor时,对应的Accept方法就需要遍历调用Compositor中所有对象的Accept。
原文地址:https://www.cnblogs.com/secbook/p/2655102.html