写点什么

VistorPattern- 访问者模式

作者:梁歪歪 ♚
  • 2022 年 6 月 05 日
  • 本文字数:2550 字

    阅读完需:约 8 分钟

VistorPattern-访问者模式

访问者模式

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


访问者模式的基本思想是针对系统中拥有的某些固定类型的对象结构(元素),在其内提供一个 accept 方法用来接受访问者对象的访问。不同的访问者对同一元素的访问内容不同,使得相同的元素集合可以产生不同的元素结果。accept 方法可以接受不同的访问者对象,然后在内部将自己转发到访问者对象 visit 方法内。


访问者模式的核心思想是解耦数据结构与数据操作,使得对元素的操作具备优秀的扩展性,我们可以通过扩展不同的数据操作类型(访问者)实现对相同元素的不同操作。


示例:我们以餐厅点菜(顾客--访问者,食谱--访问者需要访问的信息)来举例说明...


  • 食谱接口IRecipe.java


package cn.liangyy.visitor;
/** * 食谱接口 */public interface IRecipe { /** * 食谱接受访问者顾客的访问,参数就是顾客 * @param customer */ void accept(ICustomer customer);}
复制代码


  • 红烧肉类Meat.java


package cn.liangyy.visitor;
/** * 红烧肉类 */public class Meat implements IRecipe { /** * 食谱接受访问者顾客的访问,参数就是顾客 * @param customer */ @Override public void accept(ICustomer customer) { customer.visit(this); }
/** * 获取价格 * @return */ public String getPrice(){ return "45元/份"; }}
复制代码


  • 蔬菜类Cabbage.java


package cn.liangyy.visitor;
/** * 蔬菜类 */public class Cabbage implements IRecipe { /** * 食谱接受访问者顾客的访问,参数就是顾客 * @param customer */ @Override public void accept(ICustomer customer) { customer.visit(this); }
/** * 获取价格 * @return */ public String getPrice(){ return "16元/份"; }}
复制代码


  • 抽象的访问者-顾客接口ICustomer.java


package cn.liangyy.visitor;
/** * 抽象的访问者-顾客 * 访问者中的方法个数应该与和访问数据结构中的数据种类数相同 * (被访问者访问的数据结构 --> 菜) */public interface ICustomer { /** * 访问肉类菜 * @param meat */ void visit(Meat meat);
/** * 访问蔬菜类菜 * @param cabbage */ void visit(Cabbage cabbage);}
复制代码


  • 访问者类顾客 ACustomerA.java


package cn.liangyy.visitor;
/** * 具体访问者-顾客A */public class CustomerA implements ICustomer { /** * 访问肉类菜 * @param meat */ @Override public void visit(Meat meat) { System.out.println("肉类:"+meat.getPrice()); }
/** * 访问蔬菜类菜 * @param cabbage */ @Override public void visit(Cabbage cabbage) { System.out.println("时蔬:"+cabbage.getPrice()); }}
复制代码


  • 结构对象-菜单类RestaurantMenu.java


package cn.liangyy.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); } }}
复制代码


  • 测试TestVistor.java


package cn.liangyy.visitor;
/** * 访问者模式-测试 */public class TestVistor { public static void main(String[] args) { //创建一道肉类菜 IRecipe recipe = new Meat(); //将肉类菜初始化到菜单中 RestaurantMenu menu = new RestaurantMenu(recipe); //再添加一道素菜 menu.addRecipe(new Cabbage()); //顾客A开始访问菜单中的元素(菜) menu.display(new CustomerA()); }}
复制代码


由上述例子,我们可以看到,如果扩展访问者,非常简单,但是如果要新增一道菜,那么所有的访问者都必须修改源码了,因为每一道菜都在访问者中对应一个方法,所以访问者模式的前提是 数据结构(菜)非常稳定。


访问者模式角色(五个角色)


  • 抽象访问者(Vistor):接口或者抽象类都可以(如示例中的 ICustomer)。这个角色主要是定义对具体元素的 visit 方法,参数就是具体元素,理论上来说方法数等于元素个数。所以如果元素不稳定经常变化的话,那么访问者是要一直修改的,并不适合使用访问者模式。

  • 具体访问者(ConcreteVistor):实现对具体元素的访问(如示例中的 CustomerA)。

  • 抽象元素(Element):接口或者抽象类。定义了一个接受访问者访问的方法 accept(如示例中的 IRecipe)。

  • 具体元素(ConcreteElement):提供接受访问者访问的具体实现,通常都是采用 visitor.visit() 来实现(如示例中的 Cabbage 和 Meat)。

  • 结构对象(ObjectStruture):用来维护元素,并提供一个方法来接受访问者访问所有的元素(如示例中的 RestaurantMenu)。


访问者模式适用场景


  • 数据结构要稳定,但是作用于数据结构上的操作经常变化(如上面示例中菜的种类要稳定,但是顾客可以不断变化)。

  • 需要对不同数据类型(元素)进行操作,而不使用分支判断具体类型的场景。


访问者模式优点


  • 解耦了数据结构和数据操作,使得操作集合可以独立变化,非常容易扩展。

  • 每种角色各司其职,符合单一职责原则。


访问者模式缺点


  • 增加元素类型困难,一旦增加元素就必须要修改访问者源码,违反了开闭原则,也不利于维护。

  • 违背了依赖倒置原则,比如我们示例中的访问者接口,定义的 visit 方法依赖的是具体元素而不是抽象元素。

发布于: 刚刚阅读数: 6
用户头像

梁歪歪 ♚

关注

时间带走了年少轻狂,无知沉淀了冷暖自知。 2021.07.22 加入

互联网行业摸爬滚打的Java后生一枚,希望在InfoQ能够充实自己的同时,结识更多大牛。

评论

发布
暂无评论
VistorPattern-访问者模式_设计模式_梁歪歪 ♚_InfoQ写作社区