一、引言

今天我们开始讲“行为型”设计模式的第九个模式,该模式是【访问者模式】,英文名称是:Visitor Pattern。如果按老规矩,先从名称上来看看这个模式,我根本不能获得任何对理解该模式有用的信息,而且这个模式在我们的编码生活中使用的并不是很多。该模式的意图定义很抽象,第一次看了这个定义其实和没看没有什么区别,一头雾水,为了让大家更好的理解该模式的初衷,我们举个例子来说明模式。比如:当我们为了解决一个新的软件需求的时候,经过多个日以继夜的努力,最终通过一个完美(自己认为的)的软件设计解决了客户提出的新的需求,而且这个设计有完美的类层次结构,并且是符合OO的设计原则的,我们很开心,对自己设计的东西很有成就感。又过了一段时间,客户突然又有了一个新的需求,需要为现有的类层次结构里面的类增加一个新的操作(其实就是一个方法),怎么办?好办,在面向OO设计模式中有一个模式就是为了解决这个问题的,那就是“访问者模式”,可以为现有的类层次结构中的类轻松增加新的操作,我们继续吧,好好的了解一下该模式。

二、访问者模式的详细介绍

2.1、动机(Motivate)

在软件构建过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有设计。如何在不更改类层次结构的前提下,在运行时根据需要透明地为类层次结构上的各个类动态添加新的操作,从而避免上述问题?

2.2、意图(Intent)

表示一个作用于某对象结构中的各个元素的操作。它可以在不改变各元素的类的前提下定义作用于这些元素的新的操作。                                      ——《设计模式》GoF

2.3、结构图(Structure)

2.4、模式的组成
    
    可以看出,在访问者模式的结构图有以下角色:

(1)、抽象访问者角色(Vistor): 声明一个包括多个访问操作,多个操作针对多个具体节点角色(可以说有多少个具体节点角色就有多少访问操作),使得所有具体访问者必须实现的接口。

(2)、具体访问者角色(ConcreteVistor):实现抽象访问者角色中所有声明的接口,也可以说是实现对每个具体节点角色的新的操作。

(3)、抽象节点角色(Element):声明一个接受操作,接受一个访问者对象作为参数,如果有其他参数,可以在这个“接受操作”里在定义相关的参数。

(4)、具体节点角色(ConcreteElement):实现抽象元素所规定的接受操作。

(5)、结构对象角色(ObjectStructure):节点的容器,可以包含多个不同类或接口的容器。

2.5、访问者模式的代码实现

访问者这个模式在我们现实的编码生活中使用的并不是很多,我就直接贴代码,让大家看代码的结构吧。今天给大家两个代码实例,自己慢慢体会访问者吧。实现代码如下:

 namespace Vistor
{
//抽象图形定义---相当于“抽象节点角色”Element
public abstract class Shape
{
//画图形
public abstract void Draw();
//外界注入具体访问者
public abstract void Accept(ShapeVisitor visitor);
} //抽象访问者 Visitor
public abstract class ShapeVisitor
{
public abstract void Visit(Rectangle shape); public abstract void Visit(Circle shape); public abstract void Visit(Line shape); //这里有一点要说:Visit方法的参数可以写成Shape吗?就是这样 Visit(Shape shape),当然可以,但是ShapeVisitor子类Visit方法就需要判断当前的Shape是什么类型,是Rectangle类型,是Circle类型,或者是Line类型。
} //具体访问者 ConcreteVisitor
public sealed class CustomVisitor : ShapeVisitor
{
//针对Rectangle对象
public override void Visit(Rectangle shape)
{
Console.WriteLine("针对Rectangle新的操作!");
}
//针对Circle对象
public override void Visit(Circle shape)
{
Console.WriteLine("针对Circle新的操作!");
}
//针对Line对象
public override void Visit(Line shape)
{
Console.WriteLine("针对Line新的操作!");
}
} //矩形----相当于“具体节点角色” ConcreteElement
public sealed class Rectangle : Shape
{
public override void Draw()
{
Console.WriteLine("矩形我已经画好!");
} public override void Accept(ShapeVisitor visitor)
{
visitor.Visit(this);
}
} //圆形---相当于“具体节点角色”ConcreteElement
public sealed class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("圆形我已经画好!");
} public override void Accept(ShapeVisitor visitor)
{
visitor.Visit(this);
}
} //直线---相当于“具体节点角色” ConcreteElement
public sealed class Line : Shape
{
public override void Draw()
{
Console.WriteLine("直线我已经画好!");
} public override void Accept(ShapeVisitor visitor)
{
visitor.Visit(this);
}
} //结构对象角色
internal class AppStructure
{
private ShapeVisitor _visitor; public AppStructure(ShapeVisitor visitor)
{
this._visitor = visitor;
} public void Process(Shape shape)
{
shape.Accept(_visitor);
}
} class Program
{
static void Main(string[] args)
{
//如果想执行新增加的操作
ShapeVisitor visitor = new CustomVisitor();
AppStructure app = new AppStructure(visitor); Shape shape = new Rectangle();
shape.Draw();//执行自己的操作
app.Process(shape);//执行新的操作 shape = new Circle();
shape.Draw();//执行自己的操作
app.Process(shape);//执行新的操作 shape = new Line();
shape.Draw();//执行自己的操作
app.Process(shape);//执行新的操作 Console.ReadLine();
}
}
}

这是访问者模式第二种代码实例:

 namespace Visitor
{
//抽象访问者角色 Visitor
public abstract class Visitor
{
public abstract void PutTelevision(Television tv); public abstract void PutComputer(Computer comp);
} //具体访问者角色 ConcreteVisitor
public sealed class SizeVisitor : Visitor
{
public override void PutTelevision(Television tv)
{
Console.WriteLine("按商品大小{0}排放", tv.Size);
} public override void PutComputer(Computer comp)
{
Console.WriteLine("按商品大小{0}排放", comp.Size);
}
} //具体访问者角色 ConcreteVisitor
public sealed class StateVisitor : Visitor
{
public override void PutTelevision(Television tv)
{
Console.WriteLine("按商品新旧值{0}排放", tv.State);
} public override void PutComputer(Computer comp)
{
Console.WriteLine("按商品新旧值{0}排放", comp.State);
}
} //抽象节点角色 Element
public abstract class Goods
{
public abstract void Operate(Visitor visitor); private int nSize;
public int Size
{
get { return nSize; }
set { nSize = value; }
} private int nState;
public int State
{
get { return nState; }
set { nState = value; }
}
} //具体节点角色 ConcreteElement
public sealed class Television : Goods
{
public override void Operate(Visitor visitor)
{
visitor.PutTelevision(this);
}
} //具体节点角色 ConcreteElement
public sealed class Computer : Goods
{
public override void Operate(Visitor visitor)
{
visitor.PutComputer(this);
}
} //结构对象角色
public sealed class StoragePlatform
{
private IList<Goods> list = new List<Goods>(); public void Attach(Goods element)
{
list.Add(element);
} public void Detach(Goods element)
{
list.Remove(element);
} public void Operate(Visitor visitor)
{
foreach (Goods g in list)
{
g.Operate(visitor);
}
}
} class Program
{
static void Main(string[] args)
{
StoragePlatform platform = new StoragePlatform();
platform.Attach(new Television());
platform.Attach(new Computer()); SizeVisitor sizeVisitor = new SizeVisitor();
StateVisitor stateVisitor = new StateVisitor(); platform.Operate(sizeVisitor);
platform.Operate(stateVisitor); Console.Read();
}
}
}

三、访问者模式的实现要点:

Visitor模式通过所谓双重分发(double dispatch)来实现在不更改Element类层次结构的前提下,在运行时透明地为类层次结构上的各个类动态添加新的操作。所谓双重分发即Visitor模式中间包括了两个多态分发(注意其中的多态机制):第一个为accept方法的多态辨析;第二个为visit方法的多态辨析。

  设计模式其实是一种堵漏洞的方式,但是没有一种设计模式能够堵完所有的漏洞,即使是组合各种设计模式也是一样。每个设计模式都有漏洞,都有它们解决不了的情况或者变化。每一种设计模式都假定了某种变化,也假定了某种不变化。Visitor模式假定的就是操作变化,而Element类层次结构稳定。

(1)、访问者模式的主要优点有:

1】、访问者模式使得添加新的操作变得容易。如果一些操作依赖于一个复杂的结构对象的话,那么一般而言,添加新的操作会变得很复杂。而使用访问者模式,增加新的操作就意味着添加一个新的访问者类。因此,使得添加新的操作变得容易。

2】、访问者模式使得有关的行为操作集中到一个访问者对象中,而不是分散到一个个的元素类中。这点类似与”中介者模式”。

3】、访问者模式可以访问属于不同的等级结构的成员对象,而迭代只能访问属于同一个等级结构的成员对象。

 (2)、访问者模式的主要缺点有:

1】、增加新的元素类变得困难。每增加一个新的元素意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中添加相应的具体操作。具体来说,Visitor模式的最大缺点在于扩展类层次结构(增添新的Element子类),会导致Visitor类的改变。因此Visitor模式适用于“Element类层次结构稳定,而其中的操作却经常面临频繁改动”。

(3)、在下面的情况下可以考虑使用访问者模式:

1】、如果系统有比较稳定的数据结构,而又有易于变化的算法时,此时可以考虑使用访问者模式。因为访问者模式使得算法操作的添加比较容易。

2】、如果一组类中,存在着相似的操作,为了避免出现大量重复的代码,可以考虑把重复的操作封装到访问者中。(当然也可以考虑使用抽象类了)

3】、如果一个对象存在着一些与本身对象不相干,或关系比较弱的操作时,为了避免操作污染这个对象,则可以考虑把这些操作封装到访问者对象中。

四、.NET 访问者模式的实现

在现在的Net框架里面,如果要想给现有的类增加新的方法,有了新的方式,那就是“扩展方法”,使用起来和实例方法是一样一样的,而且在Net框架里面,微软自己也写了很多的扩展方法给我们使用。我目前还没有学习到Net的框架类库里面有“访问者模式”实现,看来自己还需努力,革命尚未成功啊。

五、总结

访问者模式写完了,这个模式刚开始理解起来还是挺麻烦的,但是,如果我们多看几个实例代码,完全掌握也不是问题。随着C#语言的发展,设计模式里面的很多东西,我们可以通过C#语言的一些特性做更好的替代。我们写设计模式刚开始要慢慢来,一步一步的照猫画虎的来写代码,等我们熟练掌握了模式的核心意思,我们就要写符合C#风格和特性的模式代码了,或者说我们要用C#来写设计模式了,写出来的代码会更棒。

C#设计模式之二十二访问者模式(Visitor Pattern)【行为型】的更多相关文章

  1. 二十四种设计模式:访问者模式(Visitor Pattern)

    访问者模式(Visitor Pattern) 介绍表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作. 示例有一个Message实体类,某些对象对 ...

  2. 乐在其中设计模式(C#) - 访问者模式(Visitor Pattern)

    原文:乐在其中设计模式(C#) - 访问者模式(Visitor Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 访问者模式(Visitor Pattern) 作者:webabc ...

  3. 访问者模式(Visitor Pattern)——操作复杂对象结构

    模式概述 在软件开发中,可能会遇到操作复杂对象结构的场景,在该对象结构中存储了多个不同类型的对象信息,而且对同一对象结构中的元素的操作方式并不唯一,可能需要提供多种不同的处理方式,还有可能增加新的处理 ...

  4. C#设计模式——访问者模式(Visitor Pattern)

    一.概述由于需求的改变,某些类常常需要增加新的功能,但由于种种原因这些类层次必须保持稳定,不允许开发人员随意修改.对此,访问者模式可以在不更改类层次结构的前提下透明的为各个类动态添加新的功能.二.访问 ...

  5. [设计模式] 23 访问者模式 visitor Pattern

    在GOF的<设计模式:可复用面向对象软件的基础>一书中对访问者模式是这样说的:表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作.访问 ...

  6. 十一个行为模式之访问者模式(Visitor Pattern)

    定义: 提供一个作用于某对象结构(通常是一个对象集合)的操作的接口,使得在添加新的操作或者在添加新的元素时,不需要修改原有系统,就可以对各个对象进行操作. 结构图: Visitor:抽象访问者类,对元 ...

  7. 访问者模式-Visitor Pattern

    1.主要优点 访问者模式的主要优点如下: (1) 增加新的访问操作很方便.使用访问者模式,增加新的访问操作就意味着增加一个新的具体访问者类,实现简单,无须修改源代码,符合“开闭原则”. (2) 将有关 ...

  8. 设计模式 ( 二十 ) 访问者模式Visitor(对象行为型)

    设计模式 ( 二十 ) 访问者模式Visitor(对象行为型) 1.概述 在软件开发过程中,对于系统中的某些对象,它们存储在同一个集合collection中,且具有不同的类型,而且对于该集合中的对象, ...

  9. 访问者模式 Visitor 行为型 设计模式(二十七)

    访问者模式 Visitor    <侠客行>是当代作家金庸创作的长篇武侠小说,新版电视剧<侠客行>中,开篇有一段独白:  “茫茫海外,传说有座侠客岛,岛上赏善罚恶二使,每隔十年 ...

  10. 备忘录模式 Memento 快照模式 标记Token模式 行为型 设计模式(二十二)

    备忘录模式 Memento   沿着脚印,走过你来时的路,回到原点.     苦海翻起爱恨   在世间难逃避命运   相亲竟不可接近   或我应该相信是缘份   一首<一生所爱>触动了多少 ...

随机推荐

  1. x86平台上的Windows页表映射机制

    首先,在x86架构的处理器上,一个正常页面大小为4KB,非PAE模式下,CR3持有页目录页面的物理地址,PDE和PTE格式相同大小为4字节.此时每个页表页面包含1024个PTE,可以映射1024个页面 ...

  2. Java实现Html转PDF的方法

    写在前面 以下路径问题根据项目结构自己修改,以下是我使用spring boot打成jar包的写法. 一.需求背景 在前端编辑器中输入任意的文本,包括css样式变化,保存为html文本.通过Java后台 ...

  3. javac 实现原理

    javac 概述 javac 是jdk bin目录下的一个脚本. 用于编译 java程序的源代码,但是 其实现的本质 是基于 jdk 标准类库中的 javac类库实现,所以java的编译器实质上是一个 ...

  4. 苹果快速的修复了Mac OS High Sierra 上出现了root的漏洞

    最近苹果因为Mac最新系统 Mac OS High Sierra 上出现了root的漏洞走上了风口浪尖,不过还好,在一封苹果给科技媒体'9to5 Mac'的回复中得知,苹果在接收到报告之后,立即展开修 ...

  5. ccf 火车购票

    import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class Main2 { pu ...

  6. JAVAFX-4 开发应用

    JavaFx 形状 Node类是所有JavaFX场景图形节点的基本基类.它提供了转换和应用效果到任何节点的能力. javafx.scene.shape.Shape类是Node类的子类.所有较旧的Jav ...

  7. [Machine Learning]学习笔记-Logistic Regression

    [Machine Learning]学习笔记-Logistic Regression 模型-二分类任务 Logistic regression,亦称logtic regression,翻译为" ...

  8. 【转】千万不要在JS中使用连等赋值操作

    原文链接 千万不要在JS中使用连等赋值操作   目录 前言 赋值顺序? 连续赋值能拆开写么? 后记 前言 文章标题这句话原本是在国外某JavaScript规范里看到的,当时并没有引起足够的重视,直到最 ...

  9. Android OpenGL ES 开发(三): OpenGL ES 定义形状

    在上篇文章,我们能够配置好基本的Android OpenGL 使用的环境.但是如果我们不了解OpenGL ES如何定义图像的一些基本知识就使用OpenGL ES进行绘图还是有点棘手的.所以能够在Ope ...

  10. UnityShader-菲涅尔反射(Fresnel Reflection)

    菲涅耳公式(或菲涅耳方程),由奥古斯丁·让·菲涅耳导出.用来描述光在不同折射率的介质之间的行为.由公式推导出的光的反射称之为"菲涅尔反射".菲涅尔公式是光学中的重要公式,用它能解释 ...