记得刚开始学编程的时候就看过设计模式相关的书籍,虽然当时有很多地方都不理解,但是建立了早期对架构设计的意识,让后面的学习和工作中受益匪浅,最近两年也一直在做架构设计方面的工作,解开了之前很多的困惑,也形成了一些自己的思想,我需要把自己零散的想法系统的整理出来,如果能对大家有帮助当然更好了。为什么选择设计模式?因为架构设计是一个很宽泛甚至对于有些人是一个很空虚的领域,设计模式的好处就在早就已经有些很成熟被大家认可的模式。既然已经有了很成熟的模式我为什么还要重复造轮子呢?目前网上很多关于设计模式的文章都是讲解各个模式的规则、目的,很少有直接应用到实际工程的例子,所以我在设计模式后面加了个最佳实践,目的就是通过工程实践把自己在架构设计方面的积累转换成大家容易理解和熟悉的设计模式来表达出来。
(1)、单一职责原则(Single Responsibility Principle,简称SRP)
就一个类而言,应该仅有一个引起它变化的原因。通俗的说,即一个类只负责一项职责。这是一个备受争议却又及其重要的原则,因为单一职责的划分界限并不是总是那么清晰,很多时候都是需要靠个人经验来界定,如何划分一个类、一个函数的职责,每个人都有自己的看法,这需要根据个人经验、具体的业务逻辑而定。但是它也有一些基本的指导原则,例如两个完全不一样的功能就不应该放在一个类中。
(2)、开闭原则(Open Close Principle,简称OCP)
对扩展开放,对修改关闭。开闭原则是让程序更稳定、更灵活的一个基本保证。程序中的对象(类、模块、函数等)应该对于扩展是开放的,但是,对于修改是封闭的,在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会将错误引入原本已经经过测试的旧代码中,破坏原有系统。因此,当软件需要变化时,我们应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码。
(3)、接口隔离原则(InterfaceSegregation Principles,简称ISP)
客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来,只有专注地为一个模块提供定制服务,才能建立最小的依赖关系,提高内聚,减少对外交互,使接口用最少的方法去完成最多的事情。接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化,所以一定要适度。
(4)、里氏替换原则(Liskov Substitution Principle,简称LSP)
所有引用基类的地方必须能透明地使用其子类的对象。面向对象的语言的三大特点是继承、封装、多态,里氏替换原则就是依赖于继承、多态这两大特性。里氏替换原则简单来说就是,所有引用基类的地方必须能透明地使用其子类的对象。通俗点讲,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是反过来就不行了,有子类出现的地方,父类未必就能适应。说了那么多,其实最终总结就两个字:抽象。
(5)、依赖倒置原则(Dependence Inversion Principle,简称DIP)
依赖反转原则指代了一种特定的解耦形式,使得高层次的模块不依赖于低层次的模块的实现细节的目的,依赖模块被颠倒了。这种表达真的让人很难理解,简单点:依赖倒置原则在 Java 语言中的表现就是:模块间通过接口依赖,实现类之间不发生直接的依赖关系。再简单点:面向接口编程,或者说是面向抽象编程
(6)、迪米特原则(Law of Demeter,简称LOD)
也称为最少知识原则(Least Knowledge Principle):一个对象应该对其他对象有最少的了解,也就是关于如何松耦合,一个类应该对自己需要耦合或调用的类知道得最少,类的内部如何实现、如何复杂都与调用者或者依赖者没关系,调用者或者依赖者只需要知道他需要的方法即可,其他的我一概不关心。类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
简单工厂模式 又称为 静态工厂模式
模式场景:在一个披萨店中,要根据不同客户的口味,生产不同的披萨,如素食披萨、希腊披萨等披萨。
凡是看到一个工厂Factory,然后一个静态方法,静态方法一个参数,那么很可能就是用到了 简单工厂 模式。
client不需要知道具体产品,只需要知道具体工厂 即可。
在披萨实例中,如果我想根据地域的不同生产出不同口味的披萨,如纽约口味披萨,芝加哥口味披萨。如果利用简单工厂模式,我们需要两个不同的工厂,NYPizzaFactory、ChicagoPizzaFactory。在该地域中有很多的披萨店,他们并不想依照总店的制作流程来生成披萨,而是希望采用他们自己的制作流程。这个时候如果还使用简单工厂模式,因为简单工厂模式是将披萨的制作流程完全承包了。那么怎么办?
这样解决:将披萨的制作方法交给各个披萨店完成,但是他们只能提供制作完成的披萨,披萨的订单处理仍然要交给披萨工厂去做。也就是说,我们将createPizza()方法放回到PizzaStore中,其他的部分还是保持不变。
示例有:日志记录器
凡是看到AbstractProduct、AbstractFactory、ConcretetFactory ,而且AbstractFactory 可以只能生产一个产品, 很可能就是用到了 工厂方法 模式。
依然是披萨店。为了要保证每家加盟店都能够生产高质量的披萨,防止使用劣质的原料,我们打算建造一家生产原料的工厂,并将原料运送到各家加盟店。但是加盟店都位于不同的区域,比如纽约、芝加哥。纽约使用一组原料,芝加哥使用另一种原料。在这里我们可以这样理解,这些不同的区域组成了原料家族,每个区域实现了一个完整的原料家族。
各个区域的加盟店的同一款产品是一个维度,是产品 等级结构
某个区域的加盟店的所有款产品是一个维度,是产品 族。
抽象工厂类是不会涉及 加盟店的,抽象产品类的所有产品构成 产品族。
加盟店是具体的工厂类,是对抽象工厂类的实现。
凡是看到AbstractProduct、AbstractFactory、ConcretetFactory ,而且AbstractFactory 可以生产多个产品, 很可能就是用到了 抽象工厂 模式。
建造者模式构建复杂对象就像造汽车一样,是一个一个组件一个一个步骤创建出来的,它允许用户通过制定的对象类型和内容来创建他们,但是用户并不需要知道这个复杂对象是如何构建的,它只需要明白通过这样做我可以得到一个完整的复杂对象实例。
KFC里面一般都有好几种可供客户选择的套餐,它可以根据客户所点的套餐,然后在后面做这些套餐,返回给客户的事一个完整的、美好的套餐。(构造过程抽象统一为buildFood,buildDrink)
示例有:游戏角色设计
凡是看到出现以Director、Builder结尾、或包含build、 construct方法 的类, 很可能就是用到了 建造者模式。
Ctrl+C、Ctrl+V、克隆、复制简历、复制Xxx。。。
示例有:大同小异的工作周报、带附件的周报
凡是看到出现以Prototype结尾、或包含clone、copy方法 的类, 很可能就是用到了 原型模式。
如果制造出多个实例,会导致很多问题产生的情况。
通常是重量级的类,比如tomcat的ServletContext,Hibernate的SessionFactory,Spring的BeanFactory,通常只有一个。
单例模式一般通过静态的实例变量来实现,可以想象的是,如果这个全局唯一的 静态的实例变量 能够保证其创建的 其他Subject(比如Bean)也是只有一个, 那么那些Subject 也是单例的, 尽量“那些Subject” 本身并本身 单例模式~ 比如, 我们Spring 容器中的单例的Bean。 单例的Bean 并不是设计模式中单例模式, 只是其scope是 Spring 容器唯一。这个是两码事,大家应该还 是能够区分清楚的吧!
Spring 容器何保证,其单例的Bean,每次获取得到的 都是同一个? 一般,我们的通过缓存, 即缓存到内存。
通过“ Spring 容器中的单例的BeanSpring 容器中的单例的Bean ”这样的方式, 我们实际上可以实现一个 单例链或 单例实例链: 最开始的某些对象是静态单例的,其创建的bean 不是单例,不是静态,却是唯一的,bean 可以 创建其他 “ bean 不是单例,不是静态,却是唯一的 ”, 过程反复, 就形成了一个链条。
当然我们说全局, 这个局有多大,仍然是个相对的概念。
示例有:太多
凡是看到 Singleton getSingleton, 很可能就是用到了 单例模式。 虽然实践中 不一定使用 Singleton 这个词, 但这个是常见的。是良好的实际。
我们工作电压11V的电脑,需要在 220V的 输电电压下 工作, 我们就需要 电源适配器; 机器人想要模拟人的操作,那么也需要进行 某些适配工作。
示例有:没有源码的算法库
Target 虽然常见,但是我们一般不会使用这个词,而是直接使用我们实际的类的名称。凡是看到出现以Adapter、Adaptee结尾、或包含request,specialRequest 方法 的类, 很可能就是用到了 适配器 模式。尽管如此,我们实际中可能不会使用 request,specialRequest 这样的词, 而是直接、具体的 接口名字。
假设图形类有两个关键属性: 形状、颜色。我们有 正方形、长方形、圆形,有三种颜色:白色、灰色、黑色,那么我们可以画出3*3=9中图形。 那么 我们是不是就需要九个 class类呢? 不需要的,通过桥接模式,我们只需要 3+3 = 6 个类。
可以想象,如果形状、颜色 更多,那么 桥接模式 就非常有效的减少了类的数量。
桥接模式 好像和 装饰模式有异曲同工之妙,它们都可以有效的减少了类的数量, 防止类数量爆炸。 但是这里的例子中,装饰模式是不合适的, 因为 形状、颜色 是两个独立的属性,没有任何相关关系的。
桥接模式 是对 不同属性, 从不同维度的 拆分。属性之间 仅仅是一个 聚合关系。
装饰模式 是对 不同行为,围绕某个主要行为 进行 装饰。需要有继承or实现关系。
示例有:跨平台图像浏览系统
凡是看到出现以RefinedAbstraction、Implementor结尾 的类, 很可能就是用到了 桥接模式。 虽然实际中 很可能不会使用 RefinedAbstraction、Implementor 这样的词来命名我们的类名,但是, 仍然可以做一个判断。
凡是涉及文件系统的浏览遍历、操作等的, 比如 杀毒软件对 文件系统进行杀毒。
语法树,比如XML,HTML,JSON 常常存在 “容器” 元素, 就很适合 组合模式 。比如 HTML 语法树,我们可以把 div 当做一个 容器,div 可以嵌套各种子元素。
示例有:Configuration、Bean、Component
凡是看到出现以Composite、Leaf、combination、assembly、group结尾、或包含filter、 handle、intercept 方法 以及 add、remove、getChild 方法 的类, 很可能就是用到了 装饰器模式。
需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
装饰者模式的关键词, 一定是“动态、灵活”, 通过继承,我们可以做很多事情,但是显得十分臃肿,十分不动态,类数量(m*n)爆炸。装饰者模式可以避免这些, 将类数量 变化 m + n
其实,装饰模式和适配器模式都是“包装模式(Wrapper Pattern)”。 另外代理模式,好像也有包装 功能,但是要求、侧重点是不同的。
示例有:图形界面构件库
凡是看到出现以Decorator、Filter、Interceptor 、Wrapper 结尾、或包含filter、 handle、intercept 方法 的类, 很可能就是用到了 装饰器模式。
非常笼统而宽泛的模式
示例有:B/S系统的首页等等
凡是看到出现以Facade、System、Manager 结尾 的类, 很可能就是用到了 外观/门面模式。 没错,这里有个Manager ,Manager 是一个范畴很大的词。
其实就是 对一系列类似的 对象, 实现了 单例模式, 然后统一管理。
示例有:ThreadLocal,Redis缓存。http、redis、mq、kafka、String常量池、数据库连接池、缓冲池
凡是看到出现以Flyweight 结尾 的类, 很可能就是用到了享元模式。实践中,我们可能不会使用Flyweight这样的词,而是具体的 “ 元 ” 的类名, 而判断是否是 享元, 关键是 判断是否有一个 map 存在, 其 get 方法是否会判断“元” 是否已经存在,是否包含了 put 操作。
web 代理服务器、Nginx反向代理,JDK动态代理,CGlib 动态代理
1、 远程代理:为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实。
2、 虚拟代理:通过使用过一个小的对象代理一个大对象。这样就可以减少系统的开销。
3、 保护代理:用来控制对真实对象的访问权限。
示例有:收费商务信息查询系统
凡是以Proxy 结尾,或看到出现包含preRequest,postRequest 等 preXxx、postXxx 方法 的类,或看到 InvocationHandler, 很可能就是用到了代理模式。
凡是那些涉及 不同等级层次的 操作、流程,不同等级有不同权限、功能,或需要 层层审批。那么就适合。
示例有:采购单的分级审批、请假流程、报销审批流程。。。
凡是看到出现以Chain、pipeline 结尾、或包含handler,handleRequest 方法 的类, 很可能就是用到了责任链模式。
Invoker (调用者)本来可以直接调用Receiver(接收者), 但是Invoker 觉得麻烦,不想、不需或不能 管太多 ,那么我们引入一个 Command 中间层,那么 就实现了解耦。
一些场景:控制面板、遥控器
示例有:自定义功能键、撤销操作、宏命令
凡是看到出现以Command、Order 、Invoker、Receiver 结尾、或包含execute、call、action 方法 的类, 很可能就是用到了 命令模式。
语法分析解释权、抽象语法树,翻译机。对不懂的外国语进行 翻译。等等
示例有:机器人控制程序
凡是看到出现以Expression/ translate/ interpret 结尾、或包含 translate/ interpret 方法 的类, 很可能就是用到了 解释器模式。
Aggregate和 Iterator 分离。为什么需要分离? Iterator 不需要知道Aggregate的数据是怎么来的, 可以按照需要的 方式进行遍历。 比如对电视机的各个有限电视台的遍历,我们可以 按照 各个省、地方电视台的顺序,一个省的所有电视台遍历完了之后,再其他省; 也可以按照 先遍历所有省的 娱乐频道,然后遍历所有省的 经济频道,然后。。
HaspMap的 迭代器。
示例有:JDK内置迭代器、销售管理系统中数据的遍历
凡是看到出现以Iterator、Loop 结尾、或包含hasNext,next,hasMore方法 的类, 很可能就是用到了 迭代器模式。
中介,就是我们平常所理解的 中介。适合某些 双方/多方 不宜 直接对话的情况。 中介者, 当然,需要一定的 权威性、权力、公信度。
同事之间 虽然可以直接1对1,沟通,但某些时刻需要通过部门负责人来 中介、协调。同学之间 需要通过 辅导员、班主任来沟通、 统一领导、指挥。男方和 女方 通过婚介所 进行联谊。 租房者与房屋所有者之间通过 某些 中介机构 来交互、沟通。求职者和 用人企业之间 通过求职网站、猎头 充当中介。 国与国之间的关系异常复杂,联合国 可以算是一个 中介。等等
实际生活中,我们常常把 中介和 代理混为一谈,因为有时候确实是 很难区分,但是设计模式中,我们不为,因为,我们需要满足uml 的限制,当然,我们也不一定拘泥于 uml。 Agent 到底是代理 还是中介?讨论这个问题没有意义。
示例有:协调者
凡是以Mediator、Colleague、middleMan,Agent 结尾的类, 很可能就是用到了 中介 模式。当然,colleague的语义比较窄, 我们可能并不会使用这个词,而是其他类似的词。比如 同窗,同僚,co-author,co-worker,counterparter 。
某些对象,你想保存下来, 但是对象的的内部状态比较复杂, 你记不住, 不方便直接操作器状态, 这个时候,我们可以使用 备忘录(Memento)把它,“录”下来,以防“备忘”,
但是呢,备忘录模式的 备忘录本身没有备忘的 功能,备忘的功能 由 负责人(Caretaker) 提供。所以说,备忘录模式 跟我们 实际生活 用到的 备忘录, 其实并不对等。 如果,非要对应起来的话,那么 “实际生活 用到的 备忘录 ” 对应 备忘录模式的Caretaker,但是“ 实际生活 用到的 备忘录 ” 并没有 restore 功能, 这个需要人来做,相对于是 save、 restore 功能分离了。
场景: 需要撤销动作的 对象
示例有:游戏存档
Originator这个词很少见,Mememto 也不多见,只要看到了它们,或者save 同时 restore 这个关键字, 很可能就是用到了 备忘录模式。
观察者模式又称为发布-订阅模式。
观察者? 太多了。观察? 太常见的动作了!所以 观察者模式 是我们时时刻刻都在用到的,只不过,用得太多,没感觉了,向空气一样感觉不到它的存在了!比如吃饭,看饭下菜;你看电视,然后随着剧情喜怒哀乐;开车根据路标指示做不同的操作;观察明天的天气,看看去哪里玩 合适;观察研究不同股票的表现,决定买入还是卖出还是等待;随心而动、随性而为。。
jdk是有提供 Observeable,Listener、消息队列MQ。。。
使用观察者模式,天然的 就将 被观察目标 和 观察者 解耦了! 但是呢,我们实际软件开发的过程中,并不是说 用眼睛观察一下,看一下就完了(貌似只消耗了一些光子,没有和 Subject产生 任何关系),并不是说 Subject和 Observer 完全没有关系,Subject 是必须依赖Observer 的(一般来说,Subject对象 拥有 Observer 对象的集合)。
另外,我们需要注意,实际软件开发中 观察者 是可以出现异常的(观察到事件,但是处理发生了异常), 这个时候呢, 应该观察者Observer 是不应该影响到 Subject 的继续运行的,也就是说 Observer 需要处理所有异常, 而不应该 抛给Subject 。
观察者模式 和 监听模式 几乎是一个意思。 观察者 就是 监听器。
观察者模式 和 事件驱动 几乎可以认为是 同义词。观察者模式 中虽然 没有体现出来 事件, 但是 具体主题发生改变时,会notify 所有的 监听者、观察者, 给所有的观察者发出通知, 这个通知 其实就是 可以认为是一个 事件。
示例有:XxxListener
凡是看到出现以Event、Listener结尾、或包含notify、update、observe、onXxxEvent方法 的类, 很可能就是用到了 观察者模式。
状态模式 主要是把 对象的 状态 独立了出来,成了 状态类, 每个状态 都需要处理当前状态下的 一些具体事情,对外表现不同的行为,所以 State 有一个 handle 方法, 每个ConcreteState 需要实现它。
状态模式 的核心要义并不是 状态类, 而是 不同状态之间的切换。Context 发起状态切换,ConcreteState 完成具体的操作。
酒店房间Room有预定book、取消预定unbook、入住checkIn(可继续细分为住满,未住满)、单人状态等待双人拼房、更换房间、退房checkOut等 必要的接口操作,有 空闲、预定、入住 等状态, 不同状态 对 客户请求是不一样的,空闲状态是可以预定、直接入住的,但是不能退房。预定状态可以 入住、更换房间、取消预定,但是不能退房,已入住状态 不能再 预定、取消预定、入住, 只有一个退房操作。
Context可以自己直接转换状态,但一般来说是交给State 去handle。具体来说,Room可以提供一个 setState 接口,或者提供 多个具体的接口:book,unbook, checkIn,checkOut等,显然后者更加合理。
实际呢? 我看到使用更多的是Constants、Enum 这样的常量、枚举类,它们其实是可以转换为状态模式的。 而且如果 行为比较复杂, 改为 状态模式 应该会更好一些。
状态模式 是否 可以完全的 取代 枚举类 ? 当然是不能的,比如我一个很简单的东西, 一个星期有7天,分别是Monday、Tuesday、Wednesday、Thursday、Friday、Saturday、Sunday, 这样简单的枚举,并不涉及任何复杂的具体的 行为、操作, 用一个枚举就蛮好的,星期几一般不会用作为一个状态,不会影响对象的行为。没必要状态模式。春夏秋冬,东南西北,都不是状态。
状态模式 关键是状态的选取,状态一定是影响对象 对请求的响应的。关键要理解 状态是否可以独立出来,状态改变对 对象行为的 影响。
状态模式 说白了就是 把一个很大的if else 语句块,变成了 比较小的if else 语句块,但是 if else 语句块 仍然存在,不符合 开闭原则。
示例有:银行系统中的账户类设计
凡是以State结尾的类, 很可能就是用到了 状态模式。当然,这个是不一定的,我们可能会使用诸如 Status、Way、Method、Operation 这样的次。
策略模式关键是需要理解 策略。这里的策略,并不是说指挥三军打仗的 谋略,也不一定要 国家颁布了 新的房地产发展规划指南 才能算是策略。而是说 做一件事情的 不同的方式,方法。 比如我吃饭,可以拿筷子吃,也可以直接手抓着吃,可以拿盘子盛着,可以拿碗,可以拿杯,也可以直接拿锅; 去韩国旅行,可以坐飞机、铁路、公路,海路等等,都可以是策略。
当然,我们还可以把策略理解为 算法。我需要排序一个数组,可以冒泡算法、可以选择算法等等。 我们关注目的,只要目标达成了,就好了,不同的策略 给与我们不同的选择,让我们 灵活切换。
示例有:电影票打折方案
凡是以Strategy结尾的类, 很可能就是用到了 策略模式。或者algorithm 结尾。
如果你做了很多类似的,有一定重复工作的事情 但又不完全相同的,比较固定的操作流程, 而且以后也还要经常做,那么可以考虑 提取出来一个模板方法,把高层级的某些 操作顺序、流程 固定化,然后 把具体的不同的工作 在实际操作的时候 去细化去完成,那么这就是 模板方法模式。
模板方法模式 强调 模板。必须是能够提取出来一个做事、操作的模板, 才比较合适。
一些场景有:比如,每个人都要经历 出生-学校读书-工作-退休-去世 等过程,这些基本是固定的, 但是具体的操作非常不一样。 当然这样的例子 太宽泛了, 不容易进行细致讨论。
比如,很多人的每天就是 起床-洗漱-早餐-坐公交/地铁去公司-工作-午餐/午休-工作-坐公交/地铁回家-晚餐-休息-睡觉 的这样的routine , 对这些人,我们可以套用一个模板(当然,对于其他人,这个模板可能就不适用了)
往细一点说,我们java 使用数据库,都有固定的几个步骤: 连接数据库、获取datasource、获取connection、创建statement、执行sql、获取结果resultset、关闭connection,除了 执行sql 按照具体情况很大不同之外,其他步骤基本是相同的,因为spring 为我们封装了JDBCTemplate。等等。
示例有:JDBCTemplat、银行利息计算模块
凡是XxxTemplate这样的 以Template结尾的类, 很可能就是用到了 模板方法模式。
现在 我们(作为游客,Vistor)去景区旅游。景区有很多景点(Element),每个景点呢需要收取不同的票价、有不同的景观、有不同的接待(accept)方式,游客(Vistor)呢,可以选择 任意一些景点,然后进行 游览(visit)。我们可以都选择独立游或自驾游,但是这样呢,景区会比较乱而 不好做规划。我们可以选择导游。导游((ObjectStructure))可以帮助我们完成这个过程。
Element景点的一般操作是:制定门票价格、接受 游客 进入游玩(accept,accept方法的参数正是 Visitor 游客)
ConcreteElement具体景点的作用是:设置具体的 门票价格,提供各种游玩场所和设施,接受游玩。
Vistor游客的一般操作是:买票、访问(visit ,visit 方法的参数正是 Element景点)某个 / 某些 景点 ... ;
ConcreteVisitor具体游客的作用是:花实际的价钱买票,选择具体的 景点,而不是全部。
ObjectStructure导游的作用是:对象结构,包含了一个景区List (提供add、remove接口,可以动态增删),能够枚举各个景点,然后 同样提供 accept 接待访问的接口, 然后 遍历 list 以供游客访问景区。
这样,我们可以 方便的增加 景点,和 游客。 好像没有任何的问题。
但是,uml 中 Vistor游客的访问方法的参数 是具体景点ConcreteElement,而不是Element。可以认为ConcreteElementA是 歌剧表演、B是山水、C是舞蹈魔术 ,Vistor 做了这么一个限定,提供三个接口,任何游客可以访问3个类型景点。 每个具体类型景点 需要标明自己是类型,(比如 门口标志 需要特定服装才能进入、仅限儿童进入等等)也就是说,景区,为了牟利,并不管你是 儿童还是老人,都可以给你推荐 儿童乐园。 景区 尽量让游客把每个景点都至少玩一遍。
Vistor游客也可以分类,比如儿童、年轻人、老人,儿童有儿童的游玩方式、年轻人、老人游玩方式都不同。
还可以这样分类: A儿童景点,B是年轻人景点,C是老人景点。
这个模式有一点不好的是, 不管Vistor是否愿意,它需要把所有具体的Element的访问接口准备好。当然,这仅仅是准备,并一定要实际的去访问, 提供访问的可能性。但这个是比较麻烦的,因为游客可能根本不需要去访问它(不管怎么样,景区还是要把 景点 推广出去)。如果我们 增加了一个类型 景点,比如惊险刺激类的过山车,那么 所有的ConcreteElement都要做变化, 破坏了开闭原则。
这个例子可能并不太好,实际情况,我们可以有那种不得不访问的情况,比如 买房过程,一般来说,必须要去 房产销售中心商谈、去商品房的实际地点看看毛坯房、 去房管局登记 / 缴税费 / 拿发票 等, 每一个步骤的接口 都是开放的, 买房者需要去准备(去实现之),虽然不一定马上就去做(不一定立即调用 各个 visit 方法)。如果政府规定了买房必须去中国银行 报备,那么 买房者还需要去准备 一个 访问 中国银行的接口。
另外,访问者模式 的访问,不一定的 访问,也可以是 交互、。。
示例有:OA系统中员工数据汇总
凡是看到 Visit、ObjectStructure, 很可能就是用到了 访问者方法模式。