首页 理论教育 如何指挥工人工作-访问者模式

如何指挥工人工作-访问者模式

时间:2023-11-03 理论教育 版权反馈
【摘要】:第二十八章指挥工人工作——访问者模式28.1指挥工人工作时间:1月13日地点:大B房间人物:大B,小A大B:“假如你有10个全能工人,10样相同工作。工作都要做完,而且大喊一声所有人去工作。”这种访问的特点是集合类中的对象是同一类对象Person,他们拥有功能的方法run,我们调用的恰好是这个共同的方法。

如何指挥工人工作-访问者模式

第二十八章 指挥工人工作——访问者模式

28.1 指挥工人工作

时间:1月13日  地点:大B房间  人物:大B,小A

大B:“假如你有10个全能工人,10样相同工作。工作都要做完,而且大喊一声所有人去工作。”

小A:“嗯?”

大B:“当条件变了,工人不是全能,但是工作相同。”

小A:“ok,问题不大。”

大B:“条件再变,工作不是相同,但工人是全能。”

小A:“ok,问题不大。”

大B:“以上三种情况在现实生活中是很少发生的,最多的情况是这样:10个工人,每人会做一种工作,10样工作。你又一份名单写着谁做什么。但你不认识任何人。这个时候你怎么指挥呢?”

小A:“可以一个个的叫工人,然後问他们名字,认识他们,查名单,告诉他们做什么工作。”

大B:“这是个办法。还可以怎么指挥呢?”

小A:“你可以直接叫出他们名字,告诉他们干什么,不需要知到他是谁。”

大B:“看起来很简单。但如果你要指挥10万人呢 ?而且人员是流动的,每天的人不同,你每天拿到一张文档。”

小A:“这样的话就比较麻烦了。”

大B:“其实很简单,最常用的做法是,你把这份名单贴在墙上,然後大喊一声,所有人按照去看,按照自己的分配情况去做。”

小A:“嘿嘿!这个方法不错喔!”

大B:“这里利用的关键点是‘所有工人自己认识自己’,你不能苛求每个工人会做所有工作,不能苛求所有工作相同,但你能要求所有工人都认识自己。”

小A:“嗯。”

大B:“再想想我们开始的程序,每个工人对应着PA PB PC PD PE……所有的工人都使工人P,每个工人会做的东西不一样runPA runPB runPC,你有一份名单Visitor(重载)记录着谁做什么工作。”

小A:“为什么不把这些方法的方法名做成一样的,那就可以解决了。例如,我们每个PA ,PB ,PC都加入一个run 方法,然後run内部再调用自己对应的runPx()方法。”

大B:“有些时候从不同的角度考虑,或者因为实现的复杂度早成很难统一方法名。例如上边指挥人工作的例子的例子,其实run方法就是大叫一声去工作,因为每个工人只会做一种工作,所以能行,但我们不能要求所有人只能会做一种事情,这个要求很愚蠢。所以如果每个工人会干两种或者多种工作呢,也就是我PA 有runPA() walkPA()等等方法, PB有runPB() climbPB()等等。这个时候按照名单做事才是最好的办法。”

28.2 访问者模式

小A:“怎样去定义访问者模式?”

大B:“它的通俗定义是:在每个自定义对象中预定义一个Accept(请求访问)方法,这个方法会以对象为参数,调用Visitor(访问者)对象的visit方法来操作这个对象。C#与Java运用多次重载来实现自动匹配接口, 在JS中应该是内置了这种模式,所以真正的再仿效C#与Java去实现是多余的与笨拙的。在JS中,可以定义任意一个以this为目标替代符的函数,使所有的对象可以用call或者apply来临时以它们自身的名义运行,一旦代入后,就是函数为刀俎, 对象为鱼肉,任其妄为了。”

28.3 访问者模式的角色

在计算计技术领域的很多技术上看起来很高深的东西,其实就是现有社会中人的生活方式的一种映射。而且这种方式是简单的不能再简单的方式。

大B:“访问者模式会涉及到一些角色。”

小A:“喔?它会涉及到哪些角色?”

大B:“抽象访问者:声明一个或者多个访问操作,形成所有的具体元素都要实现的接口。具体访问者:实现抽象访问者所声明的接口。抽象节点:声明一个接受操作,接受一个访问者对象作为参量。具体节点:实现了抽象元素所规定的接受操作。结构对象:遍历结构中的所有元素,类似List Set等。”

28.4 在什么情况下应当使用访问者模式

小A:“在什么情况下应当使用访问者模式?”

大B:“访问者模式应该用在被访问类结构比较稳定的时候,换言之系统很少出现增加新节点的情况。因为访问者模式对开-闭原则的支持并不好,访问者模式允许在节点中加入方法,是倾斜的开闭原则,类似抽象工厂。”

28.5 访问者模式的缺点

小A:“访问者模式有什么缺点?”

大B:“1、增加节点困难2、破坏了封装。因为访问者模式的缺点和复杂性,很多设计师反对使用访问者模式。个人感觉应该在了解的情况下考虑衡量选择。”

28.6 visitor模式准备

大B:“静态分派,动态分派,多分派,单分派是visitor模式准备。”

小A:“visitor模式准备?能不能详细讲讲,我不明白。”

大B:“可以。”(www.xing528.com)

1、静态分派:

(1)定义:发生在编译时期,分派根据静态类型信息发生,重载就是静态分派

(2)什么是静态类型:变量被声明时的类型是静态类型

什么是动态类型:变量所引用的对象的真实类型

(3)有两个类,BlackCat ,WhiteCat都继承自Cat

如下调用

运行结果是:

feed cat

feed cat

这样的结果是因为重载是静态分派,在编译器执行的,取决于变量的声明类型,因为wc,bc都是Cat所以调用的都是feed(Cat cat)的函数。

2、动态分派

定义:发生在运行期,动态分派,动态的置换掉某个方法。

还是上边类似的例子:

这个时候的结果是:

black cat eat

这样的结果是因为在执行期发生了向下转型,就是动态分派了。

3、单分派:

定义:根据一个宗量的类型进行方法的选择。

4、多分派:

(1)定义:根据多于一个宗量的类型对方法的选择。

(2)说明:多分派其实是一系列的单分派组成的,区别的地方就是这些但分派不能分割。

(3)C++ ,Java都是动态单分派,静态多分派语言

小A:“访问同一类型的集合类是我们最常见的事情了,我们工作中这样的代码太常见了。”

这种访问的特点是集合类中的对象是同一类对象Person,他们拥有功能的方法run,我们调用的恰好是这个共同的方法。在大部份的情况下,这个是可以的,但在一些复杂的情况,如被访问者的继承结构复杂,被访问者的并不是同一类对象,也就是说不是继承自同一个根类。方法名也并不相同。例如Java GUI中的事件就是一个例子。

例如这样的问题,有如下类和方法:

有一个集合类List

大B:“要求能访问到每个类的对应的方法。我们第一反应应该是这样的。”

大B:“当数目变多的时候,维护if else是个费力气的事情:仔细分析if,else做的工作,首先判断类型,然後根据类型执行相应的函数。”

小A:“如何才能解决这两个问题呢?”

大B:“首先想到的是Java的多态,多态就是根据参数执行相应的内容,能很容易的解决第二个问题,我们可以写这样一个类。”

大B:“这样只要调用run方法,传入对应的参数就能执行了。”

小A:“还有一个问题就是判断类型。”

大B:“由于重载(overloading)是静态多分配。Java语言本身是支持‘静态多分配’的。所以造成重载只根据传入对象的定义类型,而不是实际的类型,所以必须在传入前就确定类型,这可是个难的问题,因为在容器中对象全是Object,出来后要是判断是什么类型必须用if (xx instanceof xxx)这种方法。”

小A:“如果用这种方法启不是又回到了原点,有没有什么更好的办法呢?我们知道Java还有另外一个特点,覆写(overriding),而覆写是‘动态单分配’的,那如何利用这个来实现呢?”

大B:“看下边这个方法: 我们让上边的一些类PA PB PC PD PE都实现一个接口P,加入一个方法,accept()。”

然后在visitor中加入一个方法

// 这样你在遍历中可以这样写

大B:“首先执行的是‘把自己传入2’,在这里由于Java的特性,实际执行的是子类的accept(),也就是实际类的accept然後是‘把自己传入1’,在这里再次把this传入,就明确类型,ok我们巧妙的利用overriding解决了这个问题。其实归纳一下第二部分,一个关键点是‘自己认识自己’,是不是很可笑。”

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈