重新认识访问者模式从实践到本质(重新认识访问者模式从实践到本质)

花匠人从多个角度为你分享重新认识访问者模式从实践到本质(重新认识访问者模式从实践到本质),让你更加了解重新认识访问者模式从实践到本质(重新认识访问者模式从实践到本质),包含农业百科相关的养殖方法和注意事项、病虫害防治、形状特征、文化等农业百科知识。

  本篇文章为你整理了关于重新认识访问者模式从实践到本质(重新认识访问者模式从实践到本质)的详细内容,包含有重新认识访问者模式从实践到本质 重新认识访问者模式从实践到本质的过程 ,希望能帮助你了解重新认识访问者模式从实践到本质。

  简介:虽然访问者模式不如singleton模式那样广为人知,但它是少数几个每个人都能说出名字的设计模式之一。然而,由于访问者模式的复杂性,人们很少在应用系统中使用它。经过本文的探索,我们一定会有新的认识,并发现其更加灵活和广泛的用途。

  虽然访问者模式没有singleton模式那样广为人知,但它是少数几个每个人都能说出名字的设计模式之一(其他的可能是观察者模式和工厂模式)。然而,由于访问者模式的复杂性,人们很少在应用系统中使用它。经过本文的探索,我们一定会有新的认识,并发现其更加灵活和广泛的用途。

  与一般介绍设计模式的文章不同,本文不会拘泥于死板的代码模板,而是直接从开源项目和应用系统的实践出发,同时对比其他类似的设计模式,最后在编程范式中阐述其本质。

  

一 Calcite 中的访问者模式

 

  开源类库经常使用访问者风格的API来屏蔽内部的复杂性。学习这些API可以先给我们一个直观的感受。

  方解石是用Java语言编写的基础数据库类库,被Hive、Spark等很多知名开源项目使用。其中,SQL解析模块提供了访问者模式的API,我们可以通过使用它的API在SQL中快速获取我们需要的信息,以SQL中用到的所有函数为例:

  importorg.apache .方解石. SQL . SQL call;importorg.apache .方解石. SQL . SQL function;importorg.apache .方解石. SQL . SQL node;importorg.apache .方解石. SQL . parser . sqlparseexception;importorg.apache .方解石. SQL . parser . SQL parser;importorg.apache .方解石. SQL . util . SQL basicvisitor;import Java . util . ArrayList;import Java . util . list;publicclasscalcetest { publicstaticvoidmain(String[]args)throwssqlpaseexception {

  字符串sql=& # 039从检测限3 & # 039;中选择concat(test-,upper(name))。

  SQL parser parser=SQL parser . create(SQL);

  SqlNode stmt=parser . parse stmt();

  function extractor function extractor=newFunctionExtractor();

  stmt . accept(function extractor);//[CONCAT,UPPER]system . out . println(function extractor . get functions());

  } privatestaticclasssfunctionextractorextends sqlbasicvisitorvoid { privatefinalList String functions=newArrayList();@ OverridepublicVoidvisit(SqlCall call){ if(call . get operator()instanceofSqlFunction){

  functions.add(call.getOperator()。getName());

  }returnsuper.visit(调用);

  } public list String get functions(){ return functions;

  }

  }

  代码中的FunctionExtractor是SqlBasicVisitor的一个子类,它重写了它的visit(SqlCall)方法来获取函数的名称并将其收集在functions中。

  除了visit(SqlCall),还可以通过visit(SQL teral)(常量)、visit(SqlIdentifier)(表名/列名)等实现更复杂的分析。

  

二 动手实现访问者模式

 

  我们尝试实现SqlVisitor的简化版本。

  首先,定义一个简化的SQL结构。

  从20岁的测试中选择upper (name );分解到该结构的层级关系如下图所示:

  我们直接用Java代码构建上图的结构:

  SqlNode SQL=newSelectNode(newFieldsNode(arrays . as list(newFunctionCallExpression(& # 039;upper & # 039,arrays . aslist(new id expression(& # 039;姓名& # 039;)

  ))

  )),

  arrays . aslist(& # 039;测试& # 039;)、new where node(arrays . as list(newoperatoexpression(new id expression(& # 039;年龄& # 039;),''newliteraxpression(& # 039;20')

  )))

  );这个类中也有同样的方法,就是accept:

  @ override public R R accept(SQL visitor R SQL visitor){ returnsql visitor . visit(this);

  }会通过多态性分布到SqlVisitor的不同访问方式:

  abstractclassSqlVisitorR { abstractRvisit(选择节点选择节点);abstractRvisit(FieldsNode FieldsNode);abstractRvisit(where node where node);abstractRvisit(我表达式我表达式);abstractRvisit(FunctionCallExpression FunctionCallExpression);abstractRvisit(运算符表达式运算符表达式);abstractRvisit(文字表达式文字表达式);

  }SQL结构相关的类如下:

  abstractclassSqlNode{//用来接收访问者的方法public abstract R R accept(SQL visator R SQL visitor);

  } classselectnodeextendessqlnode { privatefinalFieldsNode字段;privatefinalList字符串来自;privatefinalreworknodeywhere

  SelectNode(FieldsNode fields,List String from,where node where){ this。字段=字段;这从=从这。哪里=哪里

  } @覆盖public R R accept(SQL visitor R SQL visitor){ return SQL visitor。访(本);

  }//.得到方法省略} classfieldsnodeextendsqlnode {私有最终列表表达式字段;

  字段节点(列表字段){ this.fields=字段

  } @覆盖public R R accept(SQL visitor R SQL visitor){ return SQL visitor。访(本);

  }

  }节点扩展的类qlnode { private final list表达式条件;

  WhereNode(列出表达式条件){ this.conditions=条件

  } @覆盖public R R accept(SQL visitor R SQL visitor){ return SQL visitor。访(本);

  }

  } abstractclassexpressionextendsqlnode {

  } classidexpressionextends表达式{ private final string id受保护的表达式(字符串id){这个。id=id

  } @覆盖public R R accept(SQL visitor R SQL visitor){ return SQL visitor。访(本);

  }

  } class functioncallexpressionextends表达式{ private final string nameprivatefinalList表达式参数;

  函数调用表达式(字符串名称,列出表达式参数){ this.name=namethis.arguments=参数;

  } @覆盖public R R accept(SQL visitor R SQL visitor){ return SQL visitor。访(本);

  }

  } classliteralexpressionextends表达式{ private final string literal

  文字表达式(字符串文字){ this。文字=文字;

  } @覆盖public R R accept(SQL visitor R SQL visitor){ return SQL visitor。访(本);

  }

  }类运算符表达式扩展表达式{ private final expression leftprivatefinalstringoperatorprivatefinal expression权限;

  运算符表达式(左表达式,Stringoperator,右表达式){ this。左=左;this.operator=

  } @覆盖public R R accept(SQL visitor R SQL visitor){ return SQL visitor。访(本);

  }

  }导致这种现象的原因是,不同的访问方法互相之间只有参数不同,称为重载,而Java 语言(一种计算机语言,尤用于创建网站)语言(一种计算机语言,尤用于创建网站)的重载又被称为编译期多态,只会根据访问(本)中这在编译时的类型决定调用哪个方法,而它在编译时的类型就是SqlNode,尽管它在运行时可能是不同的子类。

  所以,我们可能经常会听说用动态语言写访问者模式会更加简单,特别是支持模式匹配的函数式程序设计语言(这在Java 18中已经有较好支持),后面我们再回过头来用模式匹配重新实现下本小节的内容,看看是不是简单了很多。

  接下来我们像之前一样,是使用SqlVisitor尝试解析出结构化查询语言中所有的函数调用。

  先实现一个SqlVisitor,这个SqlVisitor所作的就是根据当前节点的结构以此调用接受,最后将结果组装起来,遇到函数调用表达式时将函数名称添加到集合中:

  class函数extractorextends SQL visitor ListString { @ override ListString visit(select node select node){

  ListString RES=new ArrayList();

  RES . addall(选择节点。获取字段().接受(这个));

  res.addAll(selectNode.getWhere().接受(这个));返回者

  }@OverrideListString访问(字段节点字段节点){

  ListString RES=new ArrayList();对于(表达式字段:fieldsNode.getFields()) {

  RES . addall(字段。接受(这个));

  } returnres

  } @ OverrideListString访问(where node where node){

  ListString RES=new ArrayList();对于(表达式条件:whereNode.getConditions()) {

  RES . addall(条件接受(这个));

  } returnres

  } @ OverrideListString visit(I expression I expression){返回集合。空列表();

  } @ OverrideListString visit(FunctionCallExpression FunctionCallExpression){//获得函数名称ListString RES=new ArrayList();

  RES . add(functioncallexpression。getname());对于(表达式参数:functioncallexpression。get arguments()){

  RES . addall(参数。接受(这个));

  } returnres

  }@OverrideListString访问(运算符表达式运算符表达式){

  ListString RES=new ArrayList();

  RES . addall(运算符表达式。向左拐.接受(这个));

  RES . addall(运算符表达式。getright().接受(这个));返回者

  } @ OverrideListString visit(文字表达式文字表达式){返回集合。空列表();

  }

  }main中的代码如下:

  publistaticvoidmain(String[]args){//SQL定义SqlNode SQL=newSelectNode(//select//concat(& # 039;测试-& # 039;upper(name))newFieldsNode(arrays。aslist(newFunctionCallExpression(& # 039;concat)、数组。aslist(newliteraxpression(& # 039;测试-& # 039;),newFunctionCallExpression(& # 039;上),

  数组。aslist(新的id表达式(& # 039;姓名& # 039;))

  )

  ))

  )),//from testarrays . aslist(& # 039;测试& # 039;),//where age 20 new where node(数组。aslist(新运算符表达式(新I expression(& # 039;年龄& # 039;),''newliteraxpression(& # 039;20 ')

  )))

  );//使用FunctionExtractorFunctionExtractor函数提取器=newFunctionExtractor();

  列表字符串函数=SQL。接受(函数提取器);//[concat,upper]System.out.println(函数);

  }以上就是标准的访问者模式的实现,直观感受上比之前方解石的SqlBasicVisitor用起来麻烦多了,我们接下来就去实现SqlBasicVisitor。

  

三 访问者模式与观察者模式

 

  在使用方解石实现的函数提取器中,每次方解石解析到函数就会调用我们实现的访问(SqlCall),称它为监听(SqlCall)似乎比访问更加合适。这也显示了访问者模式与观察者模式的紧密联系。

  在我们自己实现的函数提取器中,绝大多数代码都是在按照一定的顺序遍历各种结构,这是因为访问者模式给予了使用者足够的灵活性,可以让实现者自行决定遍历的顺序,或者对不需要遍历的部分进行剪枝。

  但是我们的需求解析出结构化查询语言中所有的函数,并不关心遍历的顺序,只要在经过函数时通知一下我们即可,对于这种简单需求,访问者模式有点过度设计,观察者模式会更加合适。

  大多数使用访问者模式的开源项目会给标准访问者提供一个默认实现,比如方解石的SqlBasicVisitor,默认实现会按照默认的顺序对结构化查询语言结构进行遍历,而实现者只需要重写它关心的部分就行了,这样就相当于在访问者模式的基础上又实现了观察者模式,即不丢失访问者模式的灵活性,也获得观察者模式使用上的便利性。

  我们给自己的实现也来添加一个SqlBasicVisitor吧:

  classsqlbasicvisitorextends sqlvisitorr { @ OverrideR visit(select node select node){

  selectNode.getFields().接受(这个);

  selectNode.getWhere().接受(这个);returnnull

  }@OverrideR访问(字段节点字段节点){for(表达式字段:fieldsNode.getFields()) {

  字段.接受(这个);

  } returnnull

  } @ OverrideR访问(where node where node){ for(表达式条件:whereNode.getConditions()) {

  条件。接受(这);

  } returnnull

  } @ OverrideR visit(I表达式I表达式){ return null

  } @ OverrideR访问(FunctionCallExpression FunctionCallExpression){ for(表达式参数:FunctionCallExpression。get arguments()){

  参数. accept(这);

  } returnnull

  } @ OverrideR visit(运算符表达式运算符表达式){

  operatorExpression.getLeft().接受(这个);

  operatorExpression.getRight().接受(这个);returnnull

  } @ OverrideR visit(文字表达式文字表达式){ return null

  }

  }SqlBasicVisitor给每个结构都提供了一个默认的访问顺序,使用这个类我们来实现第二版的函数提取器:

  类函数提取器2 extendssqlbasicvisitorvoid { private final list String functions=new ArrayList();@ overridevidvisit(FunctionCallExpression FunctionCallExpression){

  功能。add(functioncallexpression。getname());返回超级。visit(functionCallExpression);

  }公共列表字符串get functions(){ return functions;

  }

  }它的使用如下:

  class main { publicstaticvoidmain(String[]args){

  SqlNode SQL=newSelectNode(newFieldsNode(arrays。aslist(newFunctionCallExpression(& # 039;concat)、数组。aslist(newliteraxpression(& # 039;测试-& # 039;),newFunctionCallExpression(& # 039;上),

  数组。aslist(新的id表达式(& # 039;姓名& # 039;))

  )

  ))

  )),

  arrays . aslist(& # 039;测试& # 039;)、新的where节点(数组。aslist(newopertoexpression(新的id表达式(& # 039;年龄& # 039;))、''newliteraxpression(& # 039;20 ')

  )))

  );

  函数提取器2函数提取器=newfunctionextractor 2();

  SQL。接受(函数提取器);

  系统。出去。println(函数提取器。get functions());

  }

  }

四 访问者模式与责任链模式

 

  空对地导弹也是一个提供访问者模式应用程序接口的类库,用来解析与生成Java 语言(一种计算机语言,尤用于创建网站)语言(一种计算机语言,尤用于创建网站)类文件,能想到的所有Java 语言(一种计算机语言,尤用于创建网站)语言(一种计算机语言,尤用于创建网站)知名开源项目都有他的身影,Java8的希腊字母的第11个表达式特性甚至都是通过它来实现的。如果只是能解析与生成Java 语言(一种计算机语言,尤用于创建网站)语言(一种计算机语言,尤用于创建网站)类文件,ASM或许还不会那么受欢迎,更重要的是它优秀的抽象,它将常用的功能抽象为一个个小的访问者工具类,让复杂的字节码操作变得像搭积木一样简单。

  假设需要按照如下方式修改类文件:

  删除名字属性给所有属性添加@NonNull注解但是出于复用和模块化的角度考虑,我们想把两个步骤分别拆成独立的功能模块,而不是把代码写在一起。在空对地导弹中,我们可以分别实现两个小访问者,然后串在一起,就变成能够实现我们需求的访问者了。

  删除名字属性的访问者:

  classdeletefieldvisitorextendssvisitor {//删除的属性名称,对于我们的需求,它就是姓名& # 039;privatefinalstring删除字段名称;publicDeleteFieldVisitor(class visitor class visitor,String deleteFieldName){super(操作码ASM9,class visitor);这个。删除字段名称=删除字段名称;

  } @ OverridepublicFieldVisitorvisitField(在access中字符串名称,字符串描述符,字符串签名,对象值){ if(name。等于(删除字段名)){//不再向下游传递该属性,对于下游来说,就是被'删除了returnnull

  }//super.visitField会去继续调用下游访问者的visitField方法returnsuper.visitField(访问、名称、描述符、签名、值);

  }

  } class addannotationvisitorendsclassvisitor { publicadannotationvisitor(class visitor class visitor){ super(操作码ASM9,class visitor);

  } @ OverridepublicFieldVisitorvisitField(在access中字符串名称,字符串描述符,字符串签名,对象值){

  外地访客外地访客=超级。访问字段(访问、名称、描述符、签名、值);//向下游访问者额外传递一个@NonNull注解外地访客。visitan符号(& # 039;javax/annotation/Nonnull & # 039;假);returnfieldVisitor

  }

  }在主要的中我们将它们串起来使用:

  public class asmtest { publicstaticvoidmain(String[]args)抛出URISyntaxException,IOException {

  路径cls路径=路径。获取(ASM测试。班级。获取资源(& # 039;/访问者DP/用户。班& # 039;)。toURI());

  byte[]cls bytes=文件。读取所有字节(cls路径);//串联visitor//final visitor=DeleteFieldVisitor-AddAnnotationVisitor-class writer//class writer本身也是班级访问者的子类新的类编写器(类编写器.计算_帧);

  class visitor final visitor=newDeleteFieldVisitor(newAddAnnotationVisitor(CW),& # 039;姓名& # 039;);//ClassReader就是被访问的对象类读取器Cr=新的类读取器(cls字节);

  cr.accept(finalVisitor,ClassReader .SKIP_DEBUG ClassReader .SKIP _ FRAMES);

  byte[]bytes=CW . tobytearray();

  Files.write(clsPath,bytes);

  }

  }

  通过访问者模式和责任链模式的结合,我们不再需要在一个访问者中编写所有的逻辑,而是可以拆分多个一般访问者,通过组合实现更加多样化的需求。

  

五 访问者模式与回调模式

 

  回调可以看作是设计模式的设计模式,很多设计模式都有它的思路,比如观察者模式下的观察者,命令模式下的命令,状态模式下的状态,本质上都可以看作是一个回调函数。

  访问者模式中的访问者显然也是一个回调。与其他回调模式最大的区别是访问者模式是一个带有导航的回调。我们通过传入的对象结构给实现者下一次回调的导航,实现者根据导航决定下一次回调的顺序。

  如果我想先访问fieldA,再访问fieldB,最后访问fieldC,对应访问者的实现是:

  访问(某个对象某个对象){

  someobject . field a . accept(this);

  someobject . field b . accept(this);

  someobject . field c . accept(this);

  }这个实际应用就是HATEOAS(超媒体作为应用状态的引擎)。Hateoas风格的HTTP接口不仅会返回用户请求的数据,还会包含用户接下来要访问的URL。如果把整个应用的API比作一个家,如果用户请求客厅的数据,接口会返回客厅的数据,而且& # 039;厨房& # 039;连接到客厅和卧室和浴室的URL:

  这样做的好处是,资源的URL可以无缝升级和替换(因为这些URL都是服务器返回的),开发者可以在没有文档的情况下通过导航来学习API的使用,从而解决了API组织混乱的问题。一个更实际的例子可以在如何得到一杯咖啡中找到。

  

六 实际应用

 

  1 复杂的嵌套结构访问

  现在的toB应用为了满足不同企业稀奇古怪的定制需求,提供了越来越复杂的配置功能。配置项不再是简单的正交独立关系,而是递归嵌套,这就是访客模式发挥作用的场合。

  钉钉审批的流程配置是一个非常复杂的结构:

  简化的审批流程模型如下:

  模型和流程配置的对应关系如下:

  除了像普通节点一样通过next连接下一个节点,RouteNode包含的每个条件都是一个完整的流程配置(递归定义),可见审批节点模型是一个复杂的嵌套结构。

  除了复杂的整体结构,每个节点的配置也相当复杂:

  面对如此复杂的配置,最好通过配置来分析两方包(以下简称SDK)对应用层屏蔽配置的复杂度。如果SDK只向应用层返回一个图结构,应用层就要感知节点之间的关联,每次都需要编写一个容易出错的遍历算法。这时候访客模式就成了我们最好的选择。

  访客模式的实现套路和之前一样,所以我赢了& # 039;不要多说。让& # 039;举一个应用层的例子:

  模拟:用户不用实际运行进程就能看到进程的执行分支,方便调试ClassProcessSimulator ProcessConfigurVisitor { private list string traces=new ArrayList();@ Overridepublicvoidvisit(StartNode StartNode){ if(StartNode . next!=null) {

  start node . next . accept(this);

  }

  } @ overridepublicidvisit(route node路由节点){//为(condition node条件节点:路由节点)计算分支。条件){if (evalcondition(条件节点。条件)){

  conditionNode.accept(这);

  }

  }if(routeNode.next!=null) {

  routeNode.next.accept(此);

  }

  } @ Overridepublicvoidvisit(condition node condition node){ if(condition node . next!=null) {

  condition node . next . accept(this);

  }

  } @ overridepublicovoidsvisit(approve node approve node){//记录模拟中访问过的审批节点traces . add(approve node . id);if(approveNode.next!=null) {

  approveNode.next.accept(此);

  }

  }

  }2 SDK 隔离外部调用

  为了保证软件开发工具包(软件开发工具包)的纯粹性,一般软件开发工具包(软件开发工具包)中都不会去调用外部接口,但是为了实现一些需求又不得不这么做,此时我们可以将外部调用放在应用层访问者的实现中,然后传入软件开发工具包(软件开发工具包)中执行相关逻辑。

  在上面提到的流程仿真中过程,条件计算常会包括外部接口调用,比如通过连接器调用一个用户指定接口决定流程分支,为了保证流程配置解析软件开发工具包(软件开发工具包)的纯粹性,不可能在软件开发工具包(软件开发工具包)包中进行调用的,因此就在访问者中调用。

  

七 使用 Java18 实现访问者模式

 

  回到最初的命题,用访问者模式获得结构化查询语言中所有的函数调用。前面说过,用函数式编程语言中常见的模式匹配可以更加方便地实现,而最新的Java18中已经对此有比较好的支持。

  从Java 14开始,Java支持了一种新的记录数据类型,示例如下:

  //密封表示胶囊类型,即表示只允许是当前文件中数字和AddsealedinterfaceExpression {//记录关键字代替类,用于定义记录数据类型记录号(整数值)实现表达式{}recordAdd(intleft,intright)实现表达式{}

  }测试记录一旦实例化,字段就是不可变的,并且它的等于和哈希码方法会被自动重写,只要内部的字段都相等,它们就是相等的:

  publicstaticvoidmain(String[]args){

  num n1=新num(2);//n1。值=10;这行代码会导致编译不过num N2=新num(2);//真系统。出去。println(n1。equals(N2));

  }更加方便的是,利用Java 18中最新的模式匹配功能,可以拆解出其中的属性:

  public interval(Expression e){ return switch(e){ caseNum(int value)-value;caseAdd(intleft,in right)左右;

  };

  }我们首先使用记录类型重新定义我们的结构化查询语言结构:

  sealedinterfaceSqlNode {

  记录SelectNode(FieldsNode fields,ListStringfrom,WhereNode where)实现SqlNode {}

  记录字段节点(列表表达式字段)实现qlNode {}

  记录WhereNode(列出表达式条件)实现SqlNode {}

  sealedinterfaceexpressionextendsqlnode {

  记录I表达式(Stringid)实现表达式{}

  记录函数CallExpression(Stringname,CallExpression参数)实现表达式{}

  记录文字表达式(字符串eral)实现表达式{}

  记录运算符表达式(表达式左,字符串运算符,表达式右)实现表达式{}

  }

  }然后利用模式匹配,一个方法即可实现之前的访问,获得所有函数调用:

  public liststring提取函数(SqlNode SqlNode){ return switch(SqlNode){ caseSelectNode(fields node fields,ListStringfrom,WhereNode where) - {

  ListString RES=new ArrayList();

  RES . addall(提取函数(字段));

  RES . addall(提取函数(where)).返回者

  }caseFieldsNode(列表表达式字段)- {

  ListString RES=new ArrayList();对于(表达式字段:字段){

  RES . addall(提取函数(字段));

  } returnres

  }caseWhereNode(列出表达式条件)- {

  ListString RES=new ArrayList();对于(表达式条件:条件){

  res.addAll(extractFunctions(条件));

  } returnres

  } casei表达式(Stringid)-集合。空列表();caseFunctionCallExpression(字符串名称,列表表达式参数)- {//获得函数名称ListString RES=new ArrayList();

  res.add(名称);对于(表达式参数:参数){

  res.addAll(extractFunctions(自变量));

  } returnres

  } caseLiteralExpression(string eral)-集合。空列表();caseOperatorExpression(表达式左,字符串运算符,表达式右)- {

  ListString RES=new ArrayList();

  res.addAll(extractFunctions(左));

  res.addAll(extractFunctions(右));返回者

  }

  }

  }对比第二节的代码,最大的区别是sqlNode.accept(visitor)被替换成了对extractFunctions的递归调用。此外,原本由类封装的行为也变成了更轻的功能。我们将在下一节讨论它的深层含义。

  

八 重新认识访问者模式

 

  在最初的GoF设计模式中,访问者模式描述如下:

  表示作用于对象结构中的元素的操作。它允许你在元素上定义新的操作而不改变它们的类。

  从这句话可以看出,visitor模式实现的所有功能,本质上都可以通过给每个对象添加新的成员方法来实现。使用面向对象的多态性,父结构调用并聚集子结构相应方法的返回结果。以前面提取的所有SQL函数为例,这一次,每个类都添加了一个extractFunctions成员方法,而不是visitors:

  classselectnodeextendessqlnode { privatefinalFieldsNode字段;privatefinalList字符串来自;privatefinalWhereNodewhere

  SelectNode(FieldsNode fields,List String from,where node where){ this . fields=fields;this.from=fromthis.where=where

  } publicFieldsNode get fields(){ return fields;

  } public list String get from(){ return from;

  } public where node get where(){ return where;

  } public list String extract functions(){

  list RES=new ArrayList();//继续调用子结构& # 039;提取函数。addall(字段。extract functions());

  RES . addall(select node . extract functions());returnres

  }

  } visitor模式本质上是将一个复杂的类层次结构中的所有成员方法抽象成一个类:

  面向对象:认为操作必须与数据绑定,即作为每个类的成员方法存在,而不是作为访问者提取& # 039;s函数式编程:数据和操作分离,将基本操作排列组合成更复杂的操作,而一个访问者& # 039;的实现对应于一个操作。这两种方法在写的时候看起来差别不大,只有加入修改函数才能显示出它们的天壤之别。假设我们现在想给每个类添加一个新操作。

  这种场景似乎更方便增加访客。然后看下一个场景。假设您想在类层次结构中添加一个新类:

  这两种场景对应了两种拆分软件的方式,一种是根据数据,一种是根据功能点。以阿里双十一的各种场地和功能为例:盒马、饿了么、聚划算分别作为场地参与了双十一的促销活动,都需要提供优惠券、订单和支付。

  虽然盒马、饿了么和聚划算在用户眼中是三个不同的应用,但是底层系统可以分为两种方式:

  任何一种划分都会承担这种方式带来的弊端。所有真正的应用,无论是架构还是编码,都不像上面的例子那么极端,而是两者的混合。比如盒马、饿了么、聚划算,可以在拥有自己系统的同时,复用优惠券等功能划分的系统。编码也是如此。软件工程中没有灵丹妙药。

  ,我们也要根据特性和场景决定是采用面向对象的抽象,还是访问者的抽象。更多的时候需要两者混用,将部分核心方法作为对象成员,利用访问者模式实现应用层的那些琐碎杂乱的需求。

   本文为阿里云原创内容,未经允许不得转载。

以上就是花匠人为你整理的重新认识访问者模式从实践到本质(重新认识访问者模式从实践到本质),如果你还想了解更多农业百科知识,请持续关注花匠人。

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。

相关文章阅读

  • 暴雨过后,小心病虫太猖狂(暴雨过后这些疾病要当心)
  • 菖蒲有毒吗它有哪些功效(菖蒲有毒吗它有哪些功效呢)
  • 澳洲宝石鲈育苗注意事项(澳洲宝石鲈育苗注意事项图片)
  • 52度五粮液市场价一般多少一瓶啊(52度五粮液现在什么价格)
  • 艾草泡脚有什么作用(用艾草叶泡脚有哪些好处可以治疗类风湿吗)
  • 病猪咋喂药(怎么给猪吃药)
  • 8月6日小麦震荡盘整博弈加剧;玉米继续下跌(小麦受灾后,玉米销量上升是什么变动)
  • 变叶木掉叶子是怎么回事(变叶木掉叶子是怎么回事儿)
  • 吡虫啉的作用 施用几小时见效 药效多长时间(吡虫啉最佳的使用方法是什么)
  • 病害造成洋葱黄化干枯的防治方法(病害造成洋葱黄化干枯的防治方法有哪些)
  • 白花小松叶子发蔫是怎么回事 原因及解决办法都在这里哦(白花小松徒长怎么办)
  • 百合花是多年生吗(百合花是多年生长吗)
  • 槟榔吃起来为什么很难受(槟榔吃起来为什么很难受呀)
  • 茶叶巧利用可治牛羊病(茶叶喂牛有什么好处)
  • 安徽理工大学赴临泉县调研乡村振兴工作(安徽理工大学学校领导)
  • 10月主要农事活动有哪些 应该种植什么蔬菜(10月主要农事活动有哪些 应该种植什么蔬菜和水果)
  • 芭蕉是乔木还是灌木植物(芭蕉是乔木还是灌木植物图片)
  • 5种“小盆景”又香又长寿小苗很常见养大“值千元”(小盆景种什么品种)
  • 八角的采收与加工(八角的采收与加工方法)
  • 草莓上的小麻点是什么(草莓上的小麻点是什么颜色)
  • 留言与评论(共有 条评论)
       
    验证码: