当前位置: 首页 > news >正文

温州中小企业网站建设自己编写网站

温州中小企业网站建设,自己编写网站,东莞东城招聘网最新招聘,武山建设局网站简介#xff1a;访问者模式在设计模式中的知名度虽然不如单例模式#xff0c;但也是少数几个大家都能叫得上名字的设计模式了。不过因为访问者模式的复杂性#xff0c;人们很少在应用系统中使用#xff0c;经过本文的探索#xff0c;我们一定会产生新的认识#xff0c;发…简介访问者模式在设计模式中的知名度虽然不如单例模式但也是少数几个大家都能叫得上名字的设计模式了。不过因为访问者模式的复杂性人们很少在应用系统中使用经过本文的探索我们一定会产生新的认识发现其更加灵活广泛的使用方式。 作者 | 悬衡 来源 | 阿里技术公众号 访问者模式在设计模式中的知名度虽然不如单例模式但也是少数几个大家都能叫得上名字的设计模式了另外几个可能就是“观察者模式”“工厂模式” 了。不过因为访问者模式的复杂性人们很少在应用系统中使用经过本文的探索我们一定会产生新的认识发现其更加灵活广泛的使用方式。 和一般介绍设计模式的文章不同本文不会执着于死板的代码模板而是直接从开源项目以及应用系统中的实践出发同时对比其他类似的设计模式最后阐述其在编程范式中的本质。 一 Calcite 中的访问者模式 开源类库常常利用访问者风格的 API 屏蔽内部的复杂性从这些 API 入手学习能够让我们先获得一个直观感受。 Calcite 是一个 Java 语言编写的数据库基础类库诸如 HiveSpark 等诸多知名开源项目都在使用。其中 SQL 解析模块提供了访问者模式的 API我们利用它的 API 可以快速获取 SQL 中我们需要的信息以获取 SQL 中使用的所有函数为例 import org.apache.calcite.sql.SqlCall; import org.apache.calcite.sql.SqlFunction; import org.apache.calcite.sql.SqlNode; import org.apache.calcite.sql.parser.SqlParseException; import org.apache.calcite.sql.parser.SqlParser; import org.apache.calcite.sql.util.SqlBasicVisitor;import java.util.ArrayList; import java.util.List;public class CalciteTest {public static void main(String[] args) throws SqlParseException {String sql select concat(test-, upper(name)) from test limit 3;SqlParser parser SqlParser.create(sql);SqlNode stmt parser.parseStmt();FunctionExtractor functionExtractor new FunctionExtractor();stmt.accept(functionExtractor);// [CONCAT, UPPER]System.out.println(functionExtractor.getFunctions());}private static class FunctionExtractor extends SqlBasicVisitor Void {private final List String functions new ArrayList();Overridepublic Void visit(SqlCall call) {if (call.getOperator() instanceof SqlFunction) {functions.add(call.getOperator().getName());}return super.visit(call);}public List String getFunctions() {return functions;}} } 代码中 FunctionExtractor 是 SqlBasicVisitor 的子类并且重写了它的 visit(SqlCall) 方法获取函数的名称并收集在了 functions 中。 除了 visit(SqlCall) 外还可以通过 visit(SqlLiteral)常量visit(SqlIdentifier)表名/列名等等实现更加复杂的分析。 有人会想为什么 SqlParser不直接提供类似于 getFunctions 等方法直接获取 SQL 中的所有函数呢在上文的示例中getFunctions 可能确实更加方便但是 SQL 作为一个很复杂的结构getFunctions 对于更加复杂的分析场景是不够灵活的性能也是更差的。如果需要完全可以很简单地实现一个如上文的 FunctionExtractor 来满足需求。 二 动手实现访问者模式 我们尝试实现一个简化版的 SqlVisitor。 先定义一个简化版的 SQL 结构。 将 select upper(name) from test where age 20; 拆解到这个结构上层级关系如图 我们直接在 Java 代码中将上图的结构构造出来 SqlNode sql new SelectNode(new FieldsNode(Arrays.asList(new FunctionCallExpression(upper, Arrays.asList(new IdExpression(name))))),Arrays.asList(test),new WhereNode(Arrays.asList(new OperatorExpression(new IdExpression(age),,new LiteralExpression(20))))); 这个类中都有一个相同的方法就是 accept Override public R R accept(SqlVisitor R sqlVisitor) {return sqlVisitor.visit(this); }这里会通过多态分发到 SqlVisitor 不同的 visit 方法上 abstract class SqlVisitor R {abstract R visit(SelectNode selectNode);abstract R visit(FieldsNode fieldsNode);abstract R visit(WhereNode whereNode);abstract R visit(IdExpression idExpression);abstract R visit(FunctionCallExpression functionCallExpression);abstract R visit(OperatorExpression operatorExpression);abstract R visit(LiteralExpression literalExpression); } SQL 结构相关的类如下 abstract class SqlNode {// 用来接收访问者的方法public abstract R R accept(SqlVisitor R sqlVisitor); }class SelectNode extends SqlNode {private final FieldsNode fields;private final List String from;private final WhereNode where;SelectNode(FieldsNode fields, List String from, WhereNode where) {this.fields fields;this.from from;this.where where;}Overridepublic R R accept(SqlVisitor R sqlVisitor) {return sqlVisitor.visit(this);}//... get 方法省略 }class FieldsNode extends SqlNode {private final List Expression fields;FieldsNode(ListExpression fields) {this.fields fields;}Overridepublic R R accept(SqlVisitor R sqlVisitor) {return sqlVisitor.visit(this);} }class WhereNode extends SqlNode {private final List Expression conditions;WhereNode(List Expression conditions) {this.conditions conditions;}Overridepublic R R accept(SqlVisitor R sqlVisitor) {return sqlVisitor.visit(this);} }abstract class Expression extends SqlNode {}class IdExpression extends Expression {private final String id;protected IdExpression(String id) {this.id id;}Overridepublic R R accept(SqlVisitor R sqlVisitor) {return sqlVisitor.visit(this);} }class FunctionCallExpression extends Expression {private final String name;private final List Expression arguments;FunctionCallExpression(String name, List Expression arguments) {this.name name;this.arguments arguments;}Overridepublic R R accept(SqlVisitor R sqlVisitor) {return sqlVisitor.visit(this);} }class LiteralExpression extends Expression {private final String literal;LiteralExpression(String literal) {this.literal literal;}Overridepublic R R accept(SqlVisitor R sqlVisitor) {return sqlVisitor.visit(this);} }class OperatorExpression extends Expression {private final Expression left;private final String operator;private final Expression right;OperatorExpression(Expression left, String operator, Expression right) {this.left left;this.operator operator;this.right right;}Overridepublic R R accept(SqlVisitor R sqlVisitor) {return sqlVisitor.visit(this);} } 有的读者可能会注意到每个类的 accept 方法的代码都是一样的那为什么不直接写在父类 SqlNode 中呢如果尝试一下就会发现根本无法通过编译因为我们的 SqlVisitor 中根本就没有提供 visit(SqlNode)即使添加了 visit(SqlNode)通过了编译程序的运行结果也是不符合预期的因为此时所有的 visit 调用都会指向 visit(SqlNode)其他重载方法就形同虚设了。 导致这种现象的原因是不同的 visit 方法互相之间只有参数不同称为“重载”而 Java 的 “重载” 又被称为 “编译期多态”只会根据 visit(this) 中 this 在编译时的类型决定调用哪个方法而它在编译时的类型就是 SqlNode尽管它在运行时可能是不同的子类。 所以我们可能经常会听说用动态语言写访问者模式会更加简单特别是支持模式匹配的函数式程序设计语言这在 Java 18 中已经有较好支持后面我们再回过头来用模式匹配重新实现下本小节的内容看看是不是简单了很多。 接下来我们像之前一样是使用 SqlVisitor 尝试解析出 SQL中所有的函数调用。 先实现一个 SqlVisitor这个 SqlVisitor 所作的就是根据当前节点的结构以此调用 accept最后将结果组装起来遇到 FunctionCallExpression 时将函数名称添加到集合中 class FunctionExtractor extends SqlVisitor List String {OverrideList String visit(SelectNode selectNode) {ListString res new ArrayList();res.addAll(selectNode.getFields().accept(this));res.addAll(selectNode.getWhere().accept(this));return res;}OverrideList String visit(FieldsNode fieldsNode) {List String res new ArrayList();for (Expression field : fieldsNode.getFields()) {res.addAll(field.accept(this));}return res;}OverrideList String visit(WhereNode whereNode) {List String res new ArrayList();for (Expression condition : whereNode.getConditions()) {res.addAll(condition.accept(this));}return res;}OverrideList String visit(IdExpression idExpression) {return Collections.emptyList();}OverrideList String visit(FunctionCallExpression functionCallExpression) {// 获得函数名称List String res new ArrayList();res.add(functionCallExpression.getName());for (Expression argument : functionCallExpression.getArguments()) {res.addAll(argument.accept(this));}return res;}OverrideList String visit(OperatorExpression operatorExpression) {List String res new ArrayList();res.addAll(operatorExpression.getLeft().accept(this));res.addAll(operatorExpression.getRight().accept(this));return res;}OverrideList String visit(LiteralExpression literalExpression) {return Collections.emptyList();} } main 中的代码如下 public static void main(String[] args) {// sql 定义SqlNode sql new SelectNode( //select// concat(test-, upper(name))new FieldsNode(Arrays.asList(new FunctionCallExpression(concat, Arrays.asList(new LiteralExpression(test-),new FunctionCallExpression(upper,Arrays.asList(new IdExpression(name))))))),// from testArrays.asList(test),// where age 20new WhereNode(Arrays.asList(new OperatorExpression(new IdExpression(age),,new LiteralExpression(20)))));// 使用 FunctionExtractorFunctionExtractor functionExtractor new FunctionExtractor();List String functions sql.accept(functionExtractor);// [concat, upper]System.out.println(functions);} 以上就是标准的访问者模式的实现直观感受上比之前 Calcite 的 SqlBasicVisitor 用起来麻烦多了我们接下来就去实现 SqlBasicVisitor。 三 访问者模式与观察者模式 在使用 Calcite 实现的 FunctionExtractor 中每次 Calcite 解析到函数就会调用我们实现的 visit(SqlCall) 称它为 listen(SqlCall) 似乎比 visit 更加合适。这也显示了访问者模式与观察者模式的紧密联系。 在我们自己实现的 FunctionExtractor 中绝大多数代码都是在按照一定的顺序遍历各种结构这是因为访问者模式给予了使用者足够的灵活性可以让实现者自行决定遍历的顺序或者对不需要遍历的部分进行剪枝。 但是我们的需求 “解析出 SQL 中所有的函数”并不关心遍历的顺序只要在经过“函数”时通知一下我们即可对于这种简单需求访问者模式有点过度设计观察者模式会更加合适。 大多数使用访问者模式的开源项目会给“标准访问者”提供一个默认实现比如 Calcite 的 SqlBasicVisitor默认实现会按照默认的顺序对 SQL 结构进行遍历而实现者只需要重写它关心的部分就行了这样就相当于在访问者模式的基础上又实现了观察者模式即不丢失访问者模式的灵活性也获得观察者模式使用上的便利性。 我们给自己的实现也来添加一个 SqlBasicVisitor 吧 class SqlBasicVisitor R extends SqlVisitor R {OverrideR visit(SelectNode selectNode) {selectNode.getFields().accept(this);selectNode.getWhere().accept(this);return null;}OverrideR visit(FieldsNode fieldsNode) {for (Expression field : fieldsNode.getFields()) {field.accept(this);}return null;}OverrideR visit(WhereNode whereNode) {for (Expression condition : whereNode.getConditions()) {condition.accept(this);}return null;}OverrideR visit(IdExpression idExpression) {return null;}OverrideR visit(FunctionCallExpression functionCallExpression) {for (Expression argument : functionCallExpression.getArguments()) {argument.accept(this);}return null;}OverrideR visit(OperatorExpression operatorExpression) {operatorExpression.getLeft().accept(this);operatorExpression.getRight().accept(this);return null;}OverrideR visit(LiteralExpression literalExpression) {return null;} } SqlBasicVisitor 给每个结构都提供了一个默认的访问顺序使用这个类我们来实现第二版的 FunctionExtractor class FunctionExtractor2 extends SqlBasicVisitor Void {private final List String functions new ArrayList();OverrideVoid visit(FunctionCallExpression functionCallExpression) {functions.add(functionCallExpression.getName());return super.visit(functionCallExpression);}public List String getFunctions() {return functions;} } 它的使用如下 class Main {public static void main(String[] args) {SqlNode sql new SelectNode(new FieldsNode(Arrays.asList(new FunctionCallExpression(concat, Arrays.asList(new LiteralExpression(test-),new FunctionCallExpression(upper,Arrays.asList(new IdExpression(name))))))),Arrays.asList(test),new WhereNode(Arrays.asList(new OperatorExpression(new IdExpression(age),,new LiteralExpression(20)))));FunctionExtractor2 functionExtractor new FunctionExtractor2();sql.accept(functionExtractor);System.out.println(functionExtractor.getFunctions());} } 四 访问者模式与责任链模式 ASM 也是一个提供访问者模式 API 的类库用来解析与生成 Java 类文件能想到的所有 Java 知名开源项目都有他的身影Java8 的 Lambda 表达式特性甚至都是通过它来实现的。如果只是能解析与生成 Java 类文件ASM 或许还不会那么受欢迎更重要的是它优秀的抽象它将常用的功能抽象为一个个小的访问者工具类让复杂的字节码操作变得像搭积木一样简单。 假设需要按照如下方式修改类文件 删除 name 属性给所有属性添加 NonNull 注解 但是出于复用和模块化的角度考虑我们想把两个步骤分别拆成独立的功能模块而不是把代码写在一起。在 ASM 中我们可以分别实现两个小访问者然后串在一起就变成能够实现我们需求的访问者了。 删除 name 属性的访问者 class DeleteFieldVisitor extends ClassVisitor {// 删除的属性名称, 对于我们的需求它就是 nameprivate final String deleteFieldName;public DeleteFieldVisitor(ClassVisitor classVisitor, String deleteFieldName) {super(Opcodes.ASM9, classVisitor);this.deleteFieldName deleteFieldName;}Overridepublic FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {if (name.equals(deleteFieldName)) {// 不再向下游传递该属性, 对于下游来说就是被 删除了return null;}// super.visitField 会去继续调用下游 Visitor 的 visitField 方法return super.visitField(access, name, descriptor, signature, value);} } 给所有属性添加 NonNull 注解的访问者 class AddAnnotationVisitor extends ClassVisitor {public AddAnnotationVisitor(ClassVisitor classVisitor) {super(Opcodes.ASM9, classVisitor);}Overridepublic FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {FieldVisitor fieldVisitor super.visitField(access, name, descriptor, signature, value);// 向下游 Visitor 额外传递一个 NonNull 注解fieldVisitor.visitAnnotation(javax/annotation/Nonnull, false);return fieldVisitor;} } 在 main 中我们将它们串起来使用 public class AsmTest {public static void main(String[] args) throws URISyntaxException, IOException {Path clsPath Paths.get(AsmTest.class.getResource(/visitordp/User.class).toURI());byte[] clsBytes Files.readAllBytes(clsPath);// 串联 Visitor// finalVisitor DeleteFieldVisitor - AddAnnotationVisitor - ClassWriter// ClassWriter 本身也是 ClassVisitor 的子类 ClassWriter cw new ClassWriter(ClassWriter.COMPUTE_FRAMES);ClassVisitor finalVisitor new DeleteFieldVisitor(new AddAnnotationVisitor(cw), name);// ClassReader 就是被访问的对象ClassReader cr new ClassReader(clsBytes);cr.accept(finalVisitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);byte[] bytes cw.toByteArray();Files.write(clsPath, bytes);}} 通过访问者模式与责任链模式的结合我们不再需要将所有的逻辑都写在一个访问者中我们可以拆分出多个通用的访问者通过组合他们实现更加多种多样的需求。 五 访问者模式与回调模式 “回调” 可以算是“设计模式的设计模式”了大量设计模式中都有它的思想诸如观察者模式中的“观察者”“命令模式”中的“命令”“状态模式” 中的 “状态” 本质上都可以看成一个回调函数。 访问者模式中的“访问者”显然也是一个回调和其他回调模式最大的不同是“访问者模式” 是一种带有“导航”的回调我们通过传入的对象结构给实现者下一步回调的“导航”实现者根据“导航”决定下一步回调的次序。 如果我想先访问 fieldA再访问 fieldB最后 fieldC对应到访问者的实现就是 visit(SomeObject someObject) {someObject.fieldA.accept(this);someObject.fieldB.accept(this);someObject.fieldC.accept(this); } 这在实际中的应用就是 HATEOAS Hypermedia as the Engine of Application StateHATEOAS 风格的 HTTP 接口除了会返回用户请求的数据外还会包含用户下一步应该访问的 URL如果将整个应用的 API 比作一个家的话假如用户请求客厅的数据那么接口除了返回客厅的数据外还会返回与客厅相连的 厨房“卧室”与“卫生间”的 URL 这样做的好处是可以无缝地升级与更换资源的 URL因为这些 URL 都是服务端返回的而且开发者在不需要文档的情况下顺着导航可以摸索学会 API 的使用解决了 API 组织混乱的问题。关于 HATEOAS 更实际的例子可以见 How to GET a Cup of Coffee[1]。 六 实际应用 之前举的例子可能都更偏向于开源基础类库的应用那么在更加广泛的应用系统中它要如何应用呢 1 复杂的嵌套结构访问 现在的 toB 应用为了满足不同企业的稀奇古怪的定制需求提供的配置功能越來越复杂配置项之间不再是简单的正交独立的关系而是相互嵌套递归正是访问者模式发挥的场合。 钉钉审批的流程配置就是一个十分复杂的结构 做过简化的审批流模型如下 模型和流程配置的对应关系如下图 RouteNode 除了像普通节点一样通过 next 连接下一个节点其中包含的每个 condition 又是一个完整的流程配置递归定义由此可见审批节点模型是复杂的嵌套结构。 除了整体结构复杂外每个节点的配置也相当复杂 面对如此复杂的配置最好能通过配置解析二方包下文中都简称为 SDK对应用层屏蔽配置的复杂性。如果 SDK 只是返回一个图结构给应用层的话应用层就不得不感知节点之间的关联并且每次都需要编写容易出错的遍历算法此时访问者模式就变成了我们的不二之选。 访问者模式的实现套路和之前一样的就不多说了我们举个应用层例子 仿真让用户在不实际运行流程的情况下就能看到流程的执行分支方便调试 class ProcessSimulator implements ProcessConfigVisitor {private List String traces new ArrayList();Overridepublic void visit(StartNode startNode) {if (startNode.next ! null) {startNode.next.accept(this);}}Overridepublic void visit(RouteNode routeNode) {// 计算出满足条件的分支for (CondtionNode conditionNode : routeNode.conditions) {if (evalCondition(conditionNode.condition)) {conditionNode.accept(this);}}if (routeNode.next ! null) {routeNode.next.accept(this);}}Overridepublic void visit(ConditionNode conditionNode) {if (conditionNode.next ! null) {conditionNode.next.accept(this);}}Overridepublic void visit(ApproveNode approveNode) {// 记录下在仿真中访问到的审批节点traces.add(approveNode.id);if (approveNode.next ! null) {approveNode.next.accept(this);}} } 2 SDK 隔离外部调用 为了保证 SDK 的纯粹性一般 SDK 中都不会去调用外部接口但是为了实现一些需求又不得不这么做此时我们可以将外部调用放在应用层访问者的实现中然后传入 SDK 中执行相关逻辑。 在上面提到的流程仿真中过程条件计算常会包括外部接口调用比如通过连接器调用一个用户指定接口决定流程分支为了保证流程配置解析 SDK 的纯粹性不可能在 SDK 包中进行调用的因此就在访问者中调用。 七 使用 Java18 实现访问者模式 回到最初的命题用访问者模式获得 SQL 中所有的函数调用。前面说过用函数式编程语言中常见的模式匹配可以更加方便地实现而最新的 Java18 中已经对此有比较好的支持。 从 Java 14 开始Java 支持了一种新的 Record 数据类型示例如下 // sealed 表示胶囊类型, 即 Expression 只允许是当前文件中 Num 和 Add sealed interface Expression {// record 关键字代替 class, 用于定义 Record 数据类型record Num(int value) implements Expression {}record Add(int left, int right) implements Expression {} } TestRecord 一旦实例化字段就是不可变的并且它的 equals 和 hashCode 方法会被自动重写只要内部的字段都相等它们就是相等的 public static void main(String[] args) {Num n1 new Num(2);// n1.value 10; 这行代码会导致编译不过Num n2 new Num(2);// trueSystem.out.println(n1.equals(n2)); } 更加方便的是利用 Java 18 中最新的模式匹配功能可以拆解出其中的属性 public int eval(Expression e) {return switch (e) {case Num(int value) - value;case Add(int left, int right) - left right;}; } 我们首先使用 Record 类型重新定义我们的 SQL 结构 sealed interface SqlNode {record SelectNode(FieldsNode fields, List String from, WhereNode where) implements SqlNode {}record FieldsNode(List Expression fields) implements SqlNode {}record WhereNode(List Expression conditions) implements SqlNode {}sealed interface Expression extends SqlNode {record IdExpression(String id) implements Expression {}record FunctionCallExpression(String name, List Expression arguments) implements Expression {}record LiteralExpression(String literal) implements Expression {}record OperatorExpression(Expression left, String operator, Expression right) implements Expression {}} } 然后利用模式匹配一个方法即可实现之前的访问获得所有函数调用 public List String extractFunctions(SqlNode sqlNode) {return switch (sqlNode) {case SelectNode(FieldsNode fields, List String from, WhereNode where) - {List String res new ArrayList();res.addAll(extractFunctions(fields));res.addAll(extractFunctions(where));return res;}case FieldsNode(List Expression fields) - {List String res new ArrayList();for (Expression field : fields) {res.addAll(extractFunctions(field));}return res;}case WhereNode(List Expression conditions) - {List String res new ArrayList();for (Expression condition : conditions) {res.addAll(extractFunctions(condition));}return res;}case IdExpression(String id) - Collections.emptyList();case FunctionCallExpression(String name, List Expression arguments) - {// 获得函数名称List String res new ArrayList();res.add(name);for (Expression argument : arguments) {res.addAll(extractFunctions(argument));}return res;}case LiteralExpression(String literal) - Collections.emptyList();case OperatorExpression(Expression left, String operator, Expression right) - {List String res new ArrayList();res.addAll(extractFunctions(left));res.addAll(extractFunctions(right));return res;}} } 对比一下第二小节的代码最大的区别就是 sqlNode.accept(visitor) 被换成了对 extractFunctions 的递归调用。另外就是原本通过类来封装的行为变成了更加轻量的函数。我们将在下一小节探讨其更加深入的含义。 八 重新认识访问者模式 在 GoF 的设计模式原著中对访问者模式的描述如下 表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。从这句话可以看出访问者模式实现的所有功能本质上都可以通过给每个对象增加新的成员方法实现利用面向对象多态的特性父结构调用并且聚合子结构相应方法的返回结果以之前的抽取 SQL 所有函数为例这一次不用访问者实现而是在每个类中增加一个 extractFunctions 成员方法 class SelectNode extends SqlNode {private final FieldsNode fields;private final List String from;private final WhereNode where;SelectNode(FieldsNode fields, List String from, WhereNode where) {this.fields fields;this.from from;this.where where;}public FieldsNode getFields() {return fields;}public List String getFrom() {return from;}public WhereNode getWhere() {return where;}public List String extractFunctions() {ListString res new ArrayList();// 继续调用子结构的 extractFunctionsres.addAll(fields.extractFunctions());res.addAll(selectNode.extractFunctions());return res;} } 访问者模式本质上就是将复杂的类层级结构中成员方法全部都抽象到一个类中去 这两种编写方式有什么区别呢Visitor 这个名字虽然看起来像名词但是从前面的例子和论述来看它的实现类全部是关于操作的抽象从模式匹配的实现方式中就更能看出这一点ASM 中甚至将 Visitor 作为一个个小操作的抽象进行排列组合因此两种编写方式也对应两种世界观 面向对象认为操作必须和数据绑定到一起即作为每个类的成员方法存在而不是单独抽取出来成为一个访问者函数式编程将数据和操作分离将基本操作进行排列组合成为更加复杂的操作而一个访问者的实现就对应一个操作 这两个方式在编写的时候看起来区别不大只有当需要添加修改功能的时候才能显现出他们的天壤之别假设现在我们要给每个类增加一个新操作 这种场景看起来是增加访问者更加方便。那么再看下一个场景假设现在要在类层级结构中增加一个新类 这两个场景对应了软件的两种拆分方式一种是按照数据拆分一种是按照功能点拆分以阿里双十一的各个分会场与功能为例盒马饿了么和聚划算分别作为一个分会场参与了双十一的促销他们都需要提供优惠券订单和支付等功能。 虽然在用户看来 盒马饿了么和聚划算是三个不同的应用但是底层系统可以有两种划分方式 任何一种划分方式都要承受该种方式带来的缺点。所有现实中的应用不论是架构还是编码都没有上面的例子那么极端而是两种混用。比如 盒马饿了么聚划算 都可以在拥有自己系统的同时复用优惠券这样的按功能划分的系统。对应到编码也是一样软件工程没有银弹我们也要根据特性和场景决定是采用面向对象的抽象还是访问者的抽象。更多的时候需要两者混用将部分核心方法作为对象成员利用访问者模式实现应用层的那些琐碎杂乱的需求。 原文链接 本文为阿里云原创内容未经允许不得转载。
http://wiki.neutronadmin.com/news/85748/

相关文章:

  • 国外网站建设品牌wordpress企业中文模板
  • 动漫网站做毕业设计简单吗市场调研网站有哪些
  • 触屏版网站模板张家界网站建设公司
  • 网站空间商排行榜成都装修设计公司首选
  • 网站设计公司 无锡海口自助建站系统
  • 淘宝关键词排名查询网站市场营销策略方案
  • 建设招标网 手机官方网站wordpress分类图标列表
  • 网站结构和布局区别游戏网站开发过程
  • 新手做网站视频大理微网站建设
  • 成都建立网站的公司浙江综合网站建设配件
  • 项目建设备案网站在百度上怎么发布广告
  • 网站建设必备条件wordpress怎么做
  • 公司官网用什么建站程序新中式装修效果图
  • 岳阳手机网站建设商场设计理念
  • wordpress网站邀请码宁波网站推广找哪家
  • 网站开发进度计划是什么做网站便宜还是app便宜
  • 受欢迎的网站开发微信开发流程四步
  • 手机做网站的步骤成都网站建设博客
  • 阿里巴巴的网站怎么做的外贸公司起名
  • 上海工程建设造价信息网站品牌营销策划有限公司
  • 佛山做礼物的网站做小程序商城
  • php网站模板下载ppt素材免费网站
  • 在百度网站备案查询上显示未备案是什么意思wordpress插件漏洞利用
  • 运城网站制作公司网站开发制作合同
  • 漳州市城乡建设局网站工程项目管理软件app
  • 厦门营销型网站买网站服务器要多少钱
  • 找人开发软件去什么网站广州app定制公司
  • 做钢材的都用什么网站郑州市域名服务公司
  • 单页面网站怎么优化每天做特卖的网站是哪个
  • 十大创意网站中国建筑官网一测