访问者设计模式详解C/Java/JS/Go/Python/TS不同语言实现

Posted 刀法如飞-专注算法与设计模式

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了访问者设计模式详解C/Java/JS/Go/Python/TS不同语言实现相关的知识,希望对你有一定的参考价值。

简介

访问者模式(Visitor Pattern)是一种行为型模式。它封装一个访问者类,把各元素类的操作集合起来,目的是将数据结构与数据操作分离。在不改变原有元素类数据结构的前提下,改变了元素类的执行算法。

当某些较为稳定的东西(数据结构或算法),不想直接被改变但又想扩展功能,这时候适合用访问者模式。访问者模式的使用频率并不是很高,大多数情况下,你并不需要使用访问者模式,但是当你一旦需要使用它时,那你就是需要使用它了。

访问者模式有以下几个角色:

  • 结构对象(ObjectStructure):结构对象角色,这是访问者模式的基础角色,包含多个类或者接口.
  • 抽象元素(Element):定义一个接受访问操作accept(),以一个访问者Visitor作为参数。
  • 具体元素(ConcreteElement):实现抽象节点的accept()方法和处理操作,调用Vistor的访问方法实现具体功能。
  • 抽象访问者(Visitor):定义一个抽象接口,声明一个或多个访问操作,使得所有具体访问者都必须实现。
  • 具体访问者(ConcreteVisitor):具体访问者角色,实现Visitor声明的接口。

作用

  1. 在数据基础类里面有一个方法接受访问者,将自身引用传入访问者,从而把不变的固定起来,把变化的开放出去。
  2. 通过隔离类中变化的东西,固定不变的东西,符合单一职责原则,同时具备较好的扩展性和灵活性。

实现步骤

  1. 先创建基本元素抽象类Element,确定accept()抽象方法。
  2. 分别创建几个具体的元素类,实现抽象元素的accept方法。
  3. 在创建Visitor抽象接口,定义visit方法,调用具体元素。
  4. 创建1个或多个Visitor类,继承抽象接口,客户将以此去访问具体元素。
  5. 再创建对象结构类,这是核心入口类,负责组合各种元素,以及传递访问者Visitor。
  6. 客户调用时先创建对象结构类,再指定访问者类,通过访问这类调用具体元素类

UML

 

Java代码

结构对象

// ObjectStructure.java 结构对象(ObjectStructure)
public class ObjectStructure 

  // 可以想象为一台电脑,聚合了各种设备元素
  private String name = "Computer Structure";
  private List<Element> elements = new ArrayList<Element>();

  // 结构对象初始化聚合了其他元素
  public ObjectStructure() 
    addElement(new ConcreteElementA());
    addElement(new ConcreteElementB());
  

  public void addElement(Element element) 
    elements.add(element);
  

  // 传入访问者分发给其他元素
  public void accept(Visitor visitor) 
    System.out
        .println("ObjectStructure::accept() [visitor.class = " + visitor.getClass().getSimpleName() + " visitor.name = "
            + visitor.getName() + "]");
    for (Element element : elements) 
      element.accept(visitor);
    
  

  public String getName() 
    return this.name;
  


抽象访问者类

// Visitor.java 访问者Visitor抽象接口,定义不同的visit方法
public interface Visitor 
  public void visit(ConcreteElementA concreteElementA);

  public void visit(ConcreteElementB concreteElementB);

  public String getName();

具体访问者

// ConcreteVisitorA.java 具体访问者A
public class ConcreteVisitorA implements Visitor 

  // 假如由不同厂商是程序的访问者
  private String name = "Google Visitor";

  @Override
  public void visit(ConcreteElementA concreteElementA) 
    System.out.println(
        "ConcreteVisitorA::visit() [Element.class = " + concreteElementA.getClass().getSimpleName()
            + " Element.name = "
            + concreteElementA.getName() + "]");
    concreteElementA.operate();
  

  @Override
  public void visit(ConcreteElementB concreteElementB) 
    System.out.println("ConcreteVisitorA::visit() [Element.class = " + concreteElementB.getClass().getSimpleName()
        + " Element.name = "
        + concreteElementB.getName() + "]");
    concreteElementB.operate();
  

  public String getName() 
    return this.name;
  

// ConcreteVisitorB.java 具体访问者B
public class ConcreteVisitorB implements Visitor 

  // 假如由不同厂商是程序的访问者
  private String name = "Apple Visitor";

  @Override
  public void visit(ConcreteElementA concreteElementA) 
    System.out.println(
        "ConcreteVisitorB::visit() [Element.class = " + concreteElementA.getClass().getSimpleName()
            + " Element.name = "
            + concreteElementA.getName() + "]");
    concreteElementA.operate();
  

  @Override
  public void visit(ConcreteElementB concreteElementB) 
    System.out.println(
        "ConcreteVisitorB::visit() [Element.class = " + concreteElementB.getClass().getSimpleName()
            + " Element.name = "
            + concreteElementB.getName() + "]");
    concreteElementB.operate();
  

  public String getName() 
    return this.name;

  

抽象元素类

// Element.java 抽象元素(Element),定义accept方法,传入抽象访问者
abstract class Element 
  public abstract void accept(Visitor visitor);

具体元素实现类

// ConcreteElementA.java 具体的元素实现者A
public class ConcreteElementA extends Element 
  // 可以设想为显示器
  private String name = "Monitor Element";

  @Override
  public void accept(Visitor visitor) 
    System.out
        .println(
            "ConcreteElementA::accept() [visitor.class = " + visitor.getClass().getSimpleName() + " visitor.name = "
                + visitor.getName() + "]");
    visitor.visit(this);
  

  public void operate() 
    System.out.println("ConcreteElementA::operate() [" + this.getName() + "]");
  

  public String getName() 
    return this.name;
  

// ConcreteElementB.java 具体的元素实现者B
public class ConcreteElementB extends Element 
  private String name = "Keyboard Element";

  @Override
  public void accept(Visitor visitor) 
    System.out.println(
        "ConcreteElementB::accept() [visitor.class = " + visitor.getClass().getSimpleName() + " visitor.name = "
            + visitor.getName() + "]");
    visitor.visit(this);
  

  public void operate() 
    System.out.println("ConcreteElementB::operate() [" + this.getName() + "]");
  

  public String getName() 
    return this.name;
  

测试调用

  /**
   * 访问者模式是当客户需要访问具体各元素Element时,先建立一个访问者Visitor作为媒介
   * 客户基于对象结构ObjectStructure,调用accept(),接受传入的访问者
   * 对象结构向其他元素负责分发访问者,元素对象接受之后会将自己回传给访问者,从而访问者可以访问具体元素
   */
    ObjectStructure structure = new ObjectStructure();
    // 接受访问者A,把访问者传递给具体元素
    structure.accept(new ConcreteVisitorA());

    System.out.println("====");
    // 接受访问者B,把访问者传递给具体元素
    structure.accept(new ConcreteVisitorB());

Go代码

结构对象

// ObjectStructure.go 结构对象(ObjectStructure)
type ObjectStructure struct 
  name     string
  elements []Element


func (o *ObjectStructure) AddElement(e Element) 
  o.elements = append(o.elements, e)


// 传入访问者分发给其他元素
func (o *ObjectStructure) Accept(v Visitor) 
  fmt.Println(
    "ObjectStructure::Accept() [Visitor.name = " +
      v.GetName() + "]")

  // 通知全部元素成员接受访问者
  for i := 0; i < len(o.elements); i++ 
    o.elements[i].Accept(v)
  

  // for _, ele := range o.elements 
  //   ele.Accept(v)
  // 


func (o *ObjectStructure) GetName() string 
  o.name = "Computer Structure"
  return o.name


// 结构对象的初始化函数
func (o *ObjectStructure) Init() 
  // 可以想象为一台电脑,聚合了各种设备元素
  fmt.Println("ObjectStructure::Init() ", o.GetName())
  // 定义一个对象数组,长度可选
  o.elements = make([]Element, 0, 100)

  // 结构对象初始化聚合了其他元素
  o.AddElement(&ConcreteElementA)
  o.AddElement(&ConcreteElementB)

抽象访问者类

// Visitor.go 访问者Visitor抽象接口,定义不同的visit方法
type Visitor interface 
  VisitA(e *ConcreteElementA)
  VisitB(e *ConcreteElementB)
  GetName() string

具体访问者

// ConcreteVisitorA.go 具体访问者A
type ConcreteVisitorA struct 
  name string


func (v *ConcreteVisitorA) GetName() string 
  v.name = "Google Visitor(struct=ConcreteVisitorA)"
  return v.name


func (v *ConcreteVisitorA) VisitA(e *ConcreteElementA) 
  fmt.Println(
    "ConcreteVisitorA::VisitA() [Element.name = " + e.GetName() + "]")
  e.Operate()


func (v *ConcreteVisitorA) VisitB(e *ConcreteElementB) 
  fmt.Println(
    "ConcreteVisitorA::VisitB() [Element.name = " + e.GetName() + "]")
  e.Operate()

// ConcreteVisitorB.go 具体访问者B
type ConcreteVisitorB struct 
  name string


func (v *ConcreteVisitorB) GetName() string 
  v.name = "Apple Visitor(struct=ConcreteVisitorB)"
  return v.name


func (v *ConcreteVisitorB) VisitB(e *ConcreteElementB) 
  fmt.Println(
    "ConcreteVisitorB::VisitB() [Element.name = " + e.GetName() + "]")
  e.Operate()


func (v *ConcreteVisitorB) VisitA(e *ConcreteElementA) 
  fmt.Println(
    "ConcreteVisitorB::VisitA() [Element.name = " + e.GetName() + "]")
  e.Operate()

抽象元素类

// Element.go 抽象元素(Element),定义accept方法,传入抽象访问者
// go无抽象类,用interface替代
type Element interface 
  Accept(v Visitor)
  Operate()
  GetName() string

具体元素实现类

// ConcreteElementA.go 具体的元素实现者A
type ConcreteElementA struct 
  name string


func (c *ConcreteElementA) GetName() string 
  c.name = `Monitor Element(struct=ConcreteElementA)`
  return c.name


func (e *ConcreteElementA) Accept(v Visitor) 
  fmt.Println(
    "ConcreteElementA::Accept() [Visitor.name = " + v.GetName() + "]")
  v.VisitA(e)


func (e *ConcreteElementA) Operate() 
  fmt.Println("ConcreteElementA::Operate() [" + e.GetName() + "]")

// ConcreteElementB.go 具体的元素实现者B
type ConcreteElementB struct 
  name string


func (c *ConcreteElementB) GetName() string 
  c.name = "Keyboard Element(struct=ConcreteElementB)"
  return c.name


func (e *ConcreteElementB) Accept(v Visitor) 
  fmt.Println(
    "ConcreteElementB::Accept() [Visitor.name = " + v.GetName() + "]")
  v.VisitB(e)


func (e *ConcreteElementB) Operate() 
  fmt.Println("ConcreteElementB::Operate() [" + e.GetName() + "]")

测试调用

func main() 
  fmt.Println("test start:")

  /**
   * 访问者模式是当客户需要访问具体各元素Element时,先建立一个访问者Visitor作为媒介
   * 客户基于对象结构ObjectStructure,调用Accept(),接受传入的访问者
   * 对象结构向其他元素负责分发访问者,元素对象接受之后会将自己回传给访问者,从而访问者可以访问具体元素
   */
  structure := src.ObjectStructure
  structure.Init()
  // 接受访问者A,把访问者传递给具体元素
  structure.Accept(&src.ConcreteVisitorA)

  fmt.Println("====")
  // 接受访问者B,把访问者传递给具体元素
  structure.Accept(&src.ConcreteVisitorB)

更多语言版本

不同语言设计模式源码:https://github.com/microwind/design-pattern

Android学习之访问者模式详解

本文和大家分享的主要是android设计模式中的访问者模式相关内容,一起来看看吧,希望对大家学习android有所帮助。
访问者模式
访问者模式属于行为模式。
访问者模式中属于相对比较复杂的一类,它会在A中让B访问,而实际在B中实际调用的是A的方法.
class A {
public void method1(){
System.out.println("AAAAAA");
}
public void method2(B b){
b.showA(this);
}
}
class B {
public void showA(A a){
a.method1();
}
}
A a = new A();
a.method1();
a.method2(new B());
这样的例子就是一个简单的访问者模式,但是还没有表达清楚访问者的含义。
访问者模式主要用于数据结构相对稳定的情况下,将数据的后续使用操作与数据结构进行分离。
例如:
电商项目中,在每一笔订单的资金方面的收支情况,资金的 收支 作为一个 数据结构 是不会改变的。
而对于资金的情况,财务会对其进行了解,了解的过程 财务 就是一个 访问者 ,而访问者不仅局限于财务
作为公司的大BOSS也可以对其访问了解情况.(访问者的变化)
而财务了解的是每一单的资金状况,BOSS了解的直接是总体资金状态(数据操作的使用变化)
使用场景
· 使用的对象结构中的对象变动很小,并且要在其基础上进行新的操作
· 对象中存在重复代码操作,可以将其封装
· 对对象结构中的对象进行不同的数据操作,同事保证在不增加新的操作时修改这些类
代码示例
(一)允许访问者访问的接口
public interface Capital {
//允许的访问者
void  accept(Visitor visitor);
}
(二)访问者访问的数据内容接口
public interface Visitor {
//查看收入
void readIncomeCapital(IncomeCapital incomeCapital);
//查看支出
void readExpenditureCapital(ExpenditureCapital expenditureCapital);
}
(三)资金的收入支出标识
public class VisitConfig {
public  static  final  String INCOME="income";//收入
public  static  final  String EXPENDITUR="expenditure";//收入
}
(四)资金的收入状况
public class IncomeCapital implements Capital {
private String status;//订单的标记 支出还是收入
private double price;//金钱
public IncomeCapital() {
}
public IncomeCapital(String status, double price) {
this.status = status;
this.price = price;
}
@Override
public void accept(Visitor visitor) {
visitor.readIncomeCapital(this);
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
(五)资金的支出情况
public class ExpenditureCapital implements Capital {
private String status;//订单的标记 支出还是收入
private double price;//金钱
public ExpenditureCapital() {
}
public ExpenditureCapital(String status, double price) {
this.status = status;
this.price = price;
}
@Override
public void accept(Visitor visitor) {
visitor.readExpenditureCapital(this);
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
(五)资金管理统计添加
public class CapitalNote {
private Listcapitals=new ArrayList<>();
//添加资金的收入和支出
public  void add(Capital capital){
capitals.add(capital);
}
//添加访问者
public  void acceptVistor(Visitor visitor){
for (Capital capital : capitals){
capital.accept(visitor);
}
}
}
调用方式
//第一单
IncomeCapital capital1=new IncomeCapital(VisitConfig.INCOME,1000);//收入1000
ExpenditureCapital capital2=new ExpenditureCapital(VisitConfig.EXPENDITUR,1000);//支出1000
//第二单
IncomeCapital capital3=new IncomeCapital(VisitConfig.INCOME,5000);//收入1000
ExpenditureCapital capital4=new ExpenditureCapital(VisitConfig.EXPENDITUR,1000);//支出1000
//添加账单
CapitalNote capitalNote=new CapitalNote();
capitalNote.add(capital1);
capitalNote.add(capital2);
capitalNote.add(capital3);
capitalNote.add(capital4);
//财务和BOSS访问
BossVistor bossVistor=new BossVistor();
FinanceVisitor financeVisitor=new FinanceVisitor();
capitalNote.acceptVistor(bossVistor);
capitalNote.acceptVistor(financeVisitor);
//总消费状态
bossVistor.showIncomeCapital();
bossVistor.showExpenditureCapital();
显示结果
第1个订单收入:1000.0
第1个订单支出:1000.0
第2个订单收入:5000.0
第2个订单支出:1000.0
一共收入资金:6000.0
一共支出资金:2000.0
将所有的统计收支状况就行了计算显示. 如果有其他的需求按照此案例进行单独的修改即可(如纯利润等等)
总结
· 优点
· 数据结构与数据内容隔离,针对业务变化操作数据内容而不会影响数据结构
· 封装的操作方法如果有变化或者拓展可以在不改变原本结构的状态下进行拓展增强
· 将访问者与被访问者完全隔离
· 缺点
· 访问者可以了解到具体的元素对象
· 如果有新增的元素类,都需要修改访问者很麻烦
· 在使用过程中并没有将对象抽象而使用了具体的对象,修改不便

来源:简书

以上是关于访问者设计模式详解C/Java/JS/Go/Python/TS不同语言实现的主要内容,如果未能解决你的问题,请参考以下文章

访问者设计模式详解C/Java/JS/Go/Python/TS不同语言实现

ThinkPHP中URL访问模式详解

架构师内功心法,外界访问系统内部唯一通道的门面模式详解

架构师内功心法,外界访问系统内部唯一通道的门面模式详解

Java设计模式之十一种行为型模式(附实例和详解)

单例模式(单例设计模式)详解