设计模式系列总览
设计模式 | 飞机票 |
---|---|
三大工厂模式 | 登机入口 |
策略模式 | 登机入口 |
委派模式 | 登机入口 |
模板方法模式 | 登机入口 |
观察者模式 | 登机入口 |
单例模式 | 登机入口 |
原型模式 | 登机入口 |
代理模式 | 登机入口 |
装饰者模式 | 登机入口 |
适配器模式 | 登机入口 |
建造者模式 | 登机入口 |
责任链模式 | 登机入口 |
享元模式 | 登机入口 |
组合模式 | 登机入口 |
门面模式 | 登机入口 |
桥接模式 | 登机入口 |
中介者模式 | 登机入口 |
迭代器模式 | 登机入口 |
状态模式 | 登机入口 |
解释器模式 | 登机入口 |
备忘录模式 | 登机入口 |
命令模式 | 登机入口 |
访问者模式 | 登机入口 |
软件设计7大原则和设计模式总结 | 登机入口 |
前言
设计模式系列到这里已经是23种设计模式的最后一个设计模式了,访问者模式也可以说是所有设计模式中最难的一种设计模式了,当然我们平常也很少会用到它。设计模式的作者是这么评价访问者模式的:大多情况下,你并不需要使用访问者模式,但是一旦需要使用它时,那就真的需要使用了。
什么是访问者模式
访问者模式(Vistor Pattern)是一种将数据结构与数据操作分离的的设计模式。是指封装一些作用于某种数据结构中的各种元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新操作,访问者模式属于行为型模式。
访问者模式的基本思想是针对系统中拥有的某些固定类型的对象结构(元素),在其内提供一个accept()方法用来接受访问者对象的访问。不同的访问者对同一元素的访问内容不同,使得相同的元素集合可以产生不同的元素结果。accept()方法可以接受不同的访问者对象,然后在内部将自己转发到访问者对象visit()方法内。
访问者模式的核心思想是解耦数据结构与数据操作,使得对元素的操作具备优秀的扩展性,我们可以通过扩展不同的数据操作类型(访问者)实现对相同元素的不同操作。
看了这一大堆的理论概念,是不是感觉有点晕,这个听起来确实是有点抽象,别急,继续往下看:
装逼时刻又到了:Talk is cheap,Show you the code,我们通过一个例子来看看访问者模式的写法。
访问者模式示例
我们以餐厅点菜的时候,顾客需要通过菜单查看点菜为例子,在这个例子里面顾客就是访问者,而菜品就是访问者需要访问的信息。
1、首先建立一个菜品(食谱)接口,只定义一个接受访问者来访问的方法:
package com.zwx.design.pattern.visitor;
public interface IRecipe {
void accept(ICustomer customer);
}
接下来我们只列举两种视频,一种红烧肉,一种时蔬。所以需要两个类:
package com.zwx.design.pattern.visitor;
public class Meat implements IRecipe {
@Override
public void accept(ICustomer customer) {
customer.visit(this);
}
public String getPrice(){
return "88元/份";
}
}
package com.zwx.design.pattern.visitor;
public class Cabbage implements IRecipe {
@Override
public void accept(ICustomer customer) {
customer.visit(this);
}
public String getPrice(){
return "44元/份";
}
}
这两个类里面除了实现了accept方法,另外都提供了一个查看价格的方法。
3、这时候我们需要建立一个抽象的访问者:
package com.zwx.design.pattern.visitor;
public interface ICustomer {
void visit(Meat meat);
void visit(Cabbage cabbage);
}
里面定义两个方法名相同的方法,但是参数不同,参数对应元素,理论上我们上面有几个菜品这里就需要定义几个方法,也就是方法个数和要访问元素的个数要相等。
4、接下来再建立一个顾客A的具体访问者角色来实现抽象访问者:
package com.zwx.design.pattern.visitor;
public class CustomerA implements ICustomer{
@Override
public void visit(Meat meat) {
System.out.println("肉类:" + meat.getPrice());
}
@Override
public void visit(Cabbage cabbage) {
System.out.println("时蔬:" + cabbage.getPrice());
}
}
现在访问者有了,菜品也有了,那么还少一个菜单,因为顾客需要通过菜单去查看菜品信息,然后点菜,所以我们还需要菜单类来管理所有菜品。
5、新建一个菜单类:
package com.zwx.design.pattern.visitor;
import java.util.ArrayList;
import java.util.List;
public class RestaurantMenu {
private List<IRecipe> recipeList = new ArrayList<>();
public RestaurantMenu(IRecipe recipe) {
recipeList.add(recipe);
}
public void addRecipe(IRecipe recipe){
recipeList.add(recipe);
}
public void display(ICustomer customer){
for (IRecipe recipe : recipeList){
recipe.accept(customer);
}
}
}
菜单类里面通过一个list维护了所有菜品,然后通过display方法来展示所有菜品信息。
6、接下来我们建立一个测试类来测试一下:
package com.zwx.design.pattern.visitor;
public class TestVistor {
public static void main(String[] args) {
IRecipe recipe = new Meat();
RestaurantMenu menu = new RestaurantMenu(recipe);
menu.addRecipe(new Cabbage());
menu.display(new CustomerA());
}
}
输出如下信息:
肉类:88元/份
时蔬:44元/份
这就是一个访问者模式,上面因为访问者肯定是经常变化的,但是我们只需要再增加顾客类就行了,非常方便。
如果大家是第一次接触访问者模式,可能会觉得示例还是有点抽象,那么我建议大家是对照例子自己多写几遍,慢慢就会找到感觉。
访问者模式角色
从上面示例中,我们可以得出访问者模式主要有5个角色:
- 抽象访问者(Vistor):接口或者抽象类都可以(如示例中的ICustomer)。这个角色主要是定义对具体元素的visit方法,参数就是具体元素,理论上来说方法数等于元素个数。所以说如果元素不稳定经常变化的话,那么访问者是要一直修改的,并不适合使用访问者模式,所以访问者模式适用于元素结构比较稳定的场景。
- 具体访问者(ConcreteVistor):实现对具体元素的访问(如示例中的CustomerA)。
- 抽象元素(Element):接口或者抽象类。定义了一个接受访问者访问的方法accept(如示例中的IRecipe)。
- 具体元素(ConcreteElement):提供接受访问者访问的具体实现,通常都是采用visitor.visit()来实现(如示例中的Cabbage和Meat)。
- 结构对象(ObjectStruture):用来维护元素,并提供一个方法来接受访问者访问所有的元素(如示例中的RestaurantMenu)。
访问者模式适用场景
访问者模式适用于以下场景:
- 1、数据结构要稳定,但是作用于数据结构上的操作经常变化(如上面示例中如果菜谱经常发生变化,那么每次变化都要修改访问者对象)。
- 2、需要对不同数据类型(元素)进行操作,而不使用分支判断具体类型的场景。
访问者模式优缺点
优点:
- 1、解耦了数据结构和数据操作,使得操作集合可以独立变化。
- 2、访问者角色非常易于扩展。
- 3、每种角色各司其职,符合单一职责原则。
缺点 - 1、增加元素类型困难,如果元素类型变化就需要修改访问者源码,违反了开闭原则,也不利于维护。
- 2、违背了依赖倒置原则,比如我们示例中的访问者接口,定义的方法依赖的是具体元素而不是抽象元素。
总结
本文主要介绍了GoF23种设计模式中的最后一种设计模式,也是最难的一种设计模式,堪称为设计模式的终极BOSS,通过了一个示例来示范了访问者模式的写法,最后归纳总结了访问者模式的角色及其优缺点。
请关注我,和孤狼一起学习进步。