行为型模式之访问者模式

  • 定义
  • 场景
  • 模式结构和说明
  • 项目介绍
    • 设计
  • 示例代码
  • 模式讲解
    • 1. 模式的功能
    • 2. 调用通路
    • 3. 两次分发技术

定义

表示一个作用于某对象结构中的各元素的操作。它让我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

意图: 主要将数据结构和数据操作分离
主要解决:稳定的数据结构和易变的操作耦合问题。
何时使用

  1. 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,使用访问者模式将这些封装到类中。
  2. 数据结构稳定,作用于数据结构的操作经常变化的时候
  3. 当一个数据结构中,一些元素类需要负责与其不相关的操作的时候,为了将这些操作分离出去,以减少这些元素类的职责时,可以使用访问者模式。
  4. 有时在对数据结构上的元素进行操作的时候,需要区分具体的类型,这时使用访问者模式可以针对不同的类型,在访问者类中定义不同的操作,从而去除掉类型判断。
    如何解决:在被访问的类里面加一个对外提供接待访问者的接口。

上面都是一堆通用的定义或者说是古板的说法,大家肯定还是蒙圈,下面来使用场景说明

场景

比如有一个农场(结构体),里面包括木头,牛羊,空闲的土地(结构体力的元素),现在有两个需求:
需求一: 我要在这里生活,所以要建房子、生火做饭
需求二: 我要在这里开工厂,所以要建厂房、生产火腿肠
这些需求都是不同人对农场里的材料有着不同的需求

现在咱们来实现上面的需求,通常是在这个类里添加2个方法,一个方法建房子生火做饭,另一个方法建厂房生产火腿.但这样大家想想会有上面问题呢?如果这时候有军队提出需求,需要建堡垒、生产肉罐头。我们就需要再加一个新方法来满足它。
这样就会出现一些问题:

  1. 不符合开放封闭原则,类过于复杂,不利于维护
  2. 功能越多,类就越臃肿

那么怎么解决上面的问题呢?
通过上面的例子,我们知道这个农场是稳定的(里面就是木材、牛羊和土地),但是大家的需求不一样,导致对这个农场进行的操作也不一样。
那我们可不可以把农场和对农场的操作分离出来,不同的人来访问这个农场就会进行不同的操作


农民来访问它,建房子生火做饭
商人来访问它,建厂房生产火腿
军人访问它,建堡垒生产肉罐头

好处: 操作集合从数据结构中分离出来了,可以相对独立自由的演化。

上面的解决方案就是访问者模式,其关键点在于不改变各元素,在这个前提下定义新操作是访问者模式精髓中的精髓

模式结构和说明

Visitor: 访问者,为所有的访问者对象声明一个visit方法,用来代表为对象结构添加的功能,理论上可以代表任意的功能
ConcreteVisitor:具体的访问者实现对象,实现要真正被添加到对象结构中的功能
Element:抽象的元素对象,对象结构的顶层接口,定义接受访问的操作
ConcreteElement:具体元素对象,对象结构中具体对象,也是被访问的对象,通常会回调访问者的真实功能,同事开放自身的数据供访问者使用
ObjectStructure:对象结构,通常包含多个被访问的对象,它可以遍历这多个被访问的对象,也可以让访问者访问它的元素.可以是一个符合或者是一个集合,如一个列表或者无序集合

项目介绍

在屏幕上涂鸦,即把手指滑动的轨迹绘制出来

设计

大家想想,你要绘制轨迹,就需要把手指在屏幕上划过的点记录下来,理论上我们可以使用所知的任何数据结构来存储线条和点等.但是如果全部都要用多维数组来保存,使用和解析时就需要进行很多类型检查,而且数据结构并不一致和可靠,需要大量的调试
如果一种数据结构可以保存独立的点,又可以把点保存为子节点的线条,可以使用树。把每一个点和线条都组合到树中,而我们又希望能够统一的对待(处理)树上的任意节点,这就可以通过组合模式来实现了。

定义父类型Mark协议。Vertex、Dot和Stroke都是Mark的具体类。
Mark: 不论线条还是点,其实都是在介质上留下的标志(Mark),它为所有具体类定义了属性和方法。
Dot: 点,组件只有一个点,那么它会表现为一个实心圆,在屏幕上代表一个点。
Vertex: 顶点,连接起来的一串顶点,被绘制成连接起来的线条。
Stroke: 线条,一个线条实体,包含了若干的Vertex子节点
这样当客户端基于接口来操作具体类的时候,可以统一对待每个具体类,而不必在客户端作类型检查。Mark对象又有add方法,可以把其它Mark对象加为自己的子节点,形成组合体。

示例代码

  1. 定义Mark协议,这儿的Mark也是我们要进行访问的元素Element
@protocol Mark <NSObject>/**/
@property (nonatomic,assign) CGPoint location;
/**/
@property (nonatomic,assign) CGSize size;
/**/
@property (nonatomic,strong) id<Mark> lastChild;@optional
- (void)addMark:(id<Mark>)mark;- (void)removeMark:(id<Mark>)mark;- (void)acceptMarkVisitor:(id<MarkVisitor>)visitor;@end
  1. 定义Mark的具体类(Vertex、Dot和Stroke)
//由于它就是一个圆点,不会真的有添加和移除子节点功能
@implementation Dot@synthesize lastChild;@synthesize size;@synthesize location;- (void)acceptMarkVisitor:(id<MarkVisitor>)visitor
{[visitor visitDot:self];
}@end
//它只是线条中的一个顶点,也不会有添加和移除子节点功能
@implementation Vertex@synthesize lastChild;@synthesize location;@synthesize size;
- (void)acceptMarkVisitor:(id<MarkVisitor>)visitor
{[visitor visitVertex:self];
}
@end
@implementation Stroke
@dynamic location;
- (void)setLocation:(CGPoint)location
{}- (CGPoint)location
{if ([self.children count] > 0) {id<Mark> child = [self.children objectAtIndex:0];return [child location];}return CGPointZero;
}@synthesize lastChild;@synthesize size;- (instancetype)init
{self = [super init];if (self) {_children = [NSMutableArray array];}return self;
}- (void)addMark:(id<Mark>)mark
{[self.children addObject:mark];
}- (void)removeMark:(id<Mark>)mark
{[self.children removeObject:mark];
}- (id<Mark>)lastChild
{return [self.children lastObject];
}- (void)acceptMarkVisitor:(id<MarkVisitor>)visitor
{for (id<Mark> dot in self.children) {[dot acceptMarkVisitor:visitor];}[visitor visitStroke:self];
}@end

这里有3中类型的元素Element,它们分别都在自己的acceptMarkVisitor方法里调用visitor的visit方法,并把自己self作为参数传递出去

  1. 定义抽象的Visitor接口
@protocol  Mark;
@class Dot,Vertex,Stroke;
@protocol MarkVisitor <NSObject>- (void)visitMark:(id<Mark>)mark;- (void)visitDot:(Dot *)dot;- (void)visitVertex:(Vertex *)vertex;- (void)visitStroke:(Stroke *)stroke;@end
  1. 定义具体的访问者,MarkRender绘制访问者,它是对这些点和先进行绘制操作的
@interface MarkRender()@property (nonatomic, assign) CGContextRef context;
/**/
@property (nonatomic,assign) BOOL shouldMoveContextToDot;@end@implementation MarkRender- (instancetype)initWithCGContext:(CGContextRef)context
{self = [super init];if (self) {_context = context;_shouldMoveContextToDot = YES;}return self;
}- (void)visitMark:(id<Mark>)mark
{}
- (void)visitDot:(Dot *)dot
{CGFloat x = dot.location.x;CGFloat y = dot.location.y;CGRect frame = CGRectMake(x, y, 2, 2);CGContextSetFillColorWithColor(self.context, [UIColor blackColor].CGColor);CGContextFillEllipseInRect(self.context, frame);
}
- (void)visitVertex:(Vertex *)vertex {CGFloat x = vertex.location.x;CGFloat y = vertex.location.y;if (self.shouldMoveContextToDot) {CGContextMoveToPoint(self.context, x, y);_shouldMoveContextToDot = NO;} else {CGContextAddLineToPoint(self.context, x, y);}}
- (void)visitStroke:(Stroke *)stroke
{CGContextSetStrokeColorWithColor(self.context, [UIColor blueColor].CGColor);CGContextSetLineWidth(self.context, 1);CGContextSetLineCap(self.context, kCGLineCapRound);CGContextStrokePath(self.context);self.shouldMoveContextToDot = YES;
}
  1. 客户端调用
- (void)drawRect:(CGRect)rect
{CGContextRef context = UIGraphicsGetCurrentContext();MarkRender *render = [[MarkRender alloc] initWithCGContext:context];[self.mark acceptMarkVisitor:render];
}

在这个例子中,用绘制节点对象的访问者(MarkRender)拓展了Mark家族类,这样就可以把他们显示到屏幕上,如果你还有别的业务,比如访问Mark组合体每个节点,对它实施仿射变换(旋转、缩放、平移等),那就增加一个访问者,在不改变组合结构的前提下,我们扩展了它的功能

模式讲解

1. 模式的功能

访问者模式能给一系列对象,透明的添加新功能。从而避免在维护期间,对这一系列对象进行修改,而且还能变相实现复用访问者所具有的功能。

2. 调用通路

访问者之所以能实现"为一系列对象透明的添加新功能",注意透明的,也就是这一系列对象是不知道被添加功能的.重要的就是依靠通用方法,访问者这边说要去访问,就提供一个访问的方法,如visit方法;而对象那边说,好的,我接受你的访问,提供一个接受访问的方法,如accept方法.这两个方法并不代表任何具体的功能,只是构成一个调用的通路,那么真正的功能实现在哪里呢?又如何调用到呢?

很简单,就在accept方法里面,回调visit的方法,从而回调到访问者的具体实现上,而这个访问者的具体实现的方法才是要添加的新的功能。

3. 两次分发技术

访问者模式能够实现在不改变对象结构的情况下,就能给对象结构中的类增加功能,实现这个效果所使用的核心技术就是两次分发的技术。

  1. 把具体的访问者对象传递给结构对象,比如上面例子中的MarkRenderer传递给了Mark对象,MarkRender只是某一个具体的visitor,客户端也可以传递其它的visitor过来,做不一样的操作。
  2. 当Mark接到具体的visitor对象过来后,具体的Mark实例会根据自己的类型调用visitor对应的方法,并把自己(self)作为参赛传递过去,这就完成了第二次分派。

两次分发技术使得客户端的请求不再被静态的绑定在元素对象上,这个时候真正执行什么样的功能同时取决于访问者类型和元素类型,就算是同一种元素类型,只要访问者类型不一样,最终执行的功能也不会一样,这样一来,就可以在元素对象不变的情况下,通过改变访问者的类型,来改变真正执行的功能。

两次分发技术还有一个优点,就是可以在程序运行期间进行动态的功能组装和切换,只需要在客户端调用时,组合使用不同的访问者对象实例即可。

Demo地址

行为型模式之访问者模式相关推荐

  1. 图解Java设计模式学习笔记——行为型模式(模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式)

    一.模板方法模式(模板模式)--钩子方法 1.需求-豆浆制作问题 编写制作豆浆的程序,说明如下: 制作豆浆的流程选材-->添加配料-->浸泡-->放到豆浆机打碎. 通过添加不同的配料 ...

  2. 行为型模式:访问者模式

    前方高能:<一故事一设计模式>PDF 电子书已经上线,关注公众号即可获取. 文章首发: 行为型模式:访问者模式 十一大行为型模式之十一:访问者模式. 简介 姓名 :访问者模式 英文名 :V ...

  3. 行为型模式【访问者模式】

    行为型模式[访问者模式] 访问者模式是一种将数据结构与数据操作分离的设计模式.是指封装一些作用于某些数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作.属于行为型模式 ...

  4. 设计模式(行为型模式)——访问者模式(Visitor)

    2019独角兽企业重金招聘Python工程师标准>>> 访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化.访问者模式适用于数据结构相对稳定算法又易变化的系 ...

  5. 设计模式之观察者模式、中介者模式、迭代器模式、访问者模式、备忘录模式、解释器模式

    前言 这是设计模式的最后一章,包含了剩余的 行为型模式 中的 观察者模式.中介者模式.迭代器模式.访问者模式.备忘录模式.解释器模式 系列文章 第一章:7种设计原则之单一职责原则.接口隔离原则.依赖倒 ...

  6. 桥梁模式和访问者模式

    桥梁模式 将抽象部分与实现部分分离,使得他们两部分可以独立的变化. 有这样一个例子:我们希望可以在windows和Linux下都可以查看BMP.JPEG.GIF类型文件.纯粹的继承就需要2*3个子类而 ...

  7. 设计模式之适配器模式、委派模式、访问者模式、工厂模式、桥接模式(双维度扩展)

    设计模式之适配器模式.委派模式.访问者模式.工厂模式.观察者-发布订阅模式 设计模式分类: 适配器模式(Adapter Pattern) 定义 使用场景 代码实现 写法一:类适配器 写法二:对象适配器 ...

  8. [导入]C#面向对象设计模式纵横谈(24):(行为型模式) Visitor 访问者模式.zip(10.41 MB)...

    讲座内容: 本培训课程探讨GoF23之 Visitor 访问者模式的意图.动因.原理.应用场景与C#语言实现,以及该模式在.NET框架程序设计中的具体应用. 课程讲师: 李建忠 上海祝成信息科技有限公 ...

  9. 三、行为型模式【访问者模式、备忘录模式、命令模式、解释器模式、中介模式】

    访问者模式 允许一个或者多个操作应用到一组对象上,解耦操作和对象本身,保持类职责单一.满足开闭原则以及应对代码的复杂性. 多态是一种动态绑定,可以在运行时获取对象的实际类型,来运行实际类型对应的方法. ...

最新文章

  1. 【Discuz】云平台服务:出了点小错,由于站点ID/通信KEY等关键信息丢失导致Discuz!云平台服务出现异常
  2. 企业级Java应用最重要的4个性能指标
  3. 马克.扎克伯格的执行力
  4. 自动转换会出现的问题
  5. java的lookingat_Java Matcher.lookingAt()部分匹配字符串
  6. LaTeX 的对参考文献的处理
  7. 几种常用范数与距离的关系
  8. 在HTML中,如何设置新窗口打开和在原窗口打开
  9. 红色警报 (25 分)【测试点分析】【两种解法】
  10. 设计模式--责任链模式(COR)
  11. 如何提取幻灯片表格_如何查看对Google文档,表格或幻灯片文件的最新更改
  12. 插画在UI的应用体验,太美好了!这样的模板让你的用户更加喜欢!
  13. Radware;医疗行业数字转型5大关键注意事项
  14. Mac 基本教程和vim + Awesome Mac
  15. 极米旗舰级新品极米H5强势来袭:画质性能在线,硬核配置拉满
  16. 如何高效阅读一篇英文学术类论文?
  17. 2017 东北四省赛热身赛 C
  18. python安装后桌面没有显示图标_Win10安装软件后找不到软件图标如何解决
  19. 孔雀东南飞用mysql存储_社区考试公共基础知识:乐府双璧
  20. 从键盘输入某年某月某日,判断这一天是这一年的第几天啊?

热门文章

  1. 51单片机c语言编写计算器仿真,基于51单片机的计算器设计c程序代码加proteus仿真...
  2. 青龙面板--多功能羊毛脚本
  3. 秒杀常见问题解决思路
  4. 黑马旅游网项目详细思路和完整代码整理 -附源码
  5. linux 进程的fg,36.每日一个Linux命令----先后台进程切换(fg、bg、jobs、)
  6. swift语言特性,swift语法介绍,swift使用技巧
  7. DEEPIN/UOS双屏显示及分辨率设置(主屏、复制、扩展、副屏)
  8. 关于BMA连接器,你想知道的都在这里
  9. 谷歌又摊上大事了,被曝欠薪1亿美元
  10. 搭建FTP服务器备份vCenter