设计模式在实际应用中的演化和改良(1)-Visitor

〔从这篇《模式的演化和改良》系列开始,我会不定期的更新这个系列的文章,皆在于分享自己在日常工作中的一些心得体会,并整理成较系统的文章,以此和大家沟通分享,也请大家指出不足或提出新的见解〕
基于OO中的职责单一原则,我们需要将一个类的自身行为和外部实体作用于它的行为区分开来。例如我们需要将“商品”类自身的行为(如“获取商品价格”),和其他实体作用于“商品”类的行为(如“统计所有商品的价格”或者“将商品持久化”)区分开来。如果用这些本不属于“商品”类的行为去污染“商品”类的话,我们会发现随着系统的维护,原始的“商品”类将会变得异常的复杂和不可理解,并且这些行为也会分散到“商品”类以及它的各个子类中去,使得维护和扩充这些行为变的复杂。基于这些原因,《设计模式》一书中提出了Visitor模式,将类和对类进行的一些操作解耦开来,使得我们可以方便的在不改变原始类的情况下定义对原始类的新操作。(结构图略,请自行参阅《设计模式》一书218页关于Visitor模式的描述)
在我们使用Visitor模式解耦类和对类进行的一些操作的同时,也引入了一些附作用(同样在《设计模式》一书中有详细的描述)。其中我觉得除了潜在的破坏封装的风险外,其最大的问题是不能使原始类和对其操作的Visitor类独立的进行变化,我们可以很容易的通过扩充新的Visitor类来扩充新的操作,但是却让增加新的原始类变的很困难,Visitor模式假设了原始类族群很少发生变化,但不幸的是在实际应用中这些原始类族群经常发生变化。
考虑设计一个对某些类进行持久化的方案,“持久化”方法并不属于某个特定类自身应具有的行为,另外这些“持久化”行为本身可能多种多样,例如我们可以有两种持久化方案:持久化到数据库或者持久化到Xml文档中;我们当然希望对这种持久化方案的选择不会影响到我们需要持久化的对象自身,于是我们按照Visitor模式得到了如下的结构:

点击图片看大图Visitor1


我们分离了Persist行为,使得我们比较容易扩充新的PersistentProvider而不用重新编译PersistentObject及其子类DerivePersistentObject1。然而这个设计假设了PersistentObject及其子类需要持久化的字段不容易发生变化,举个例子,如果PersistentObject还有一个子类DerivePersistentObject2,它需要持久化的字段也只能是Field1,Field2,Field3;如果它还有第四个字段Field4,那么这个字段无法获得持久化的支持。因为PersistentProvider只认识基类PersistentObject的3个字段Field1,Field2,Field3,当PersistentObject及其子类需要发生变化(加入新的可持久化字段)时,我们不得不修改每一个IPersistentProvider的实现类,这个代价显然是比较大的。
解决的办法是让IPersistentProvider不依赖于具体的PersistentObject类,而是依赖于自身所需要的某种抽象,并让PersistentObject 及其子类去实现这种抽象。考虑持久化一个数据所需要的常用信息:数据的名称,数据的值和数据的类型,把这些常用信息作为我们的抽象根本,我们可以得到如下的一个改良Visitor模式:

点击图片看大图Visitor2

我们使用IPersistentDataProvider来表示IPersistentProvider对需要持久化的类所提出的要求。即需要这些可以被持久化的类实现该接口,并通过该接口告诉IPersistentProvider需要持久化的主键和字段,这些主键和字段都是一种在持久化时所需要的一些通常的数据PersistentElement(数据名称,值和类型)。现在IPersistentProvider和PersistentObject可以独立的发生变化,IPersistentProvider依赖于自身所提出的抽象,扩展新的PersistentObject对象族,只用在新的类中实现IPersistentProvider所提出的那个抽象IPersistentDataProvider接口。无论是修改PersistentProvider以便扩充新的持久化方法,还是修改PersistentObject用以扩充需要持久化的类族,这两部分都可以单独的进行变化,并单独的编译。另外,用PersistentObject实现IPersistentDataProvider,降低了破坏PersistentObject封装性的风险。
当然,这个新的模型仍然存在一些缺陷:
1. 某些场景下可能我们没有办法抽象Visitor对象所需要的数据(即很难提取出类似IPersistentDataProvider这样的接口)。
2. Visitor类(IPersistentProvider及其实现类)的行为比较单一,例如只是持久化。如果Visitor类以后需要支持其他的特性,而这些新的特性和已有特性对目标类的要求差别比较大;例如扩充Visitor使其不只是支持持久化,还可以将目标类图形化,显然图形化要求目标类提供的数据不只是PersistentElement的集合这么简单。这时我们需要定义新的数据抽象,例如IGraphicDataProvider,并要求目标类实现IPersistentDataProvider接口的同时也实现IGraphicDataProvider接口。当Visitor的行为扩充可控时,这样做尚可,但如果Visitor的平行行为(功能完全不同的行为)过多时,我们可能会要求目标类实现过多的接口,从而增加目标类的变化复杂度。幸运的是,在大多数场景中Visitor的变化(特别是平行变化)都少于目标类本身的变化,这种改良Visitor模式在大多数场景下都很适用或者说可控。从另一个角度来说,如果Visitor类有过多的平行行为,根据职责单一原则,这些Visitor类本身的组织结构是不是也需要进行仔细的设计和划分呢?

posted @ 2009-04-28 22:15  hkbarton  阅读(1565)  评论(5编辑  收藏  举报