设计模式
软件设计模式的意义
所有开发人员都应该接触过软件设计模式这个概念,看过《设计模式-可复用的对象软件的基础》这本书,在面试中都被问过: 你用过哪些设计模式这种问题。但很大可能也就仅此而已了。
为什么好像只能从框架中找到设计模式的应用示例,而实施项目中很少看到?
是项目太简单了,用不着;是技术人员水平太差了,不会写;以业务价值最优先,先实现功能,设计不重要;设计做的费时费力也看不到,没有绩效,没意义。
上面这些理由都是现实项目中实实在在的理由,并不仅仅是面对询问时的借口,而是很多开发人员的真是想法。无需列举各种各样的数据,以及一条条的格言来证明设计模式是多么多么的重要,能带来多么多么好的效果。即使引经据典写了很多,最终的结果可能就是屏幕上的一划而过。甚至对设计模式是否有必要学习都持怀疑太多,认为这些都是八股文,看了也就是为了面试。
下面是对软件设计模式的意义的一些个人看法,有不同看法的恳请交流和指教。
设计模式的底层思维
设计模式的是建立在面向对象的思维基础上的。面向对象设计中最重要的就是抽象和封装。设计模式的目的为了在满足需求的前提下,做到不破坏或则提供更好的封装性的同时提供扩展性,也就是常说的对修改封闭对扩展开放的开闭原则。
《系统架构:复杂产品的设计与开发》中提到系统设计的第一要点: 识别并隔离系统的稳定点和变化点,比如Linux的内核层和用户层。 很多软件设计模式的核心目的也是分隔稳定点和变化点,提供一种灵活实现变化点带来的扩展性以及不破坏稳定部分代码(不破话封装)的带来的可靠性。也就是常常说的解耦。
设计模式讲述的是特定需求场景下的某种模式,源自对系统设计的思考。 只有有了系统设计的思维,真正的去思考系统的稳定点和变化点时,才有设计模式存在的意义。如果有了这个想法,那么恭喜你,开始从纯粹的开发人员转变为系统设计师角色了。
要理解设计模式的意义,首先思维上要从开发人员转变为设计人员。
设计师和资深施工人员的争论
有一个调侃词: 反人类的设计。 指的是那些偏离了实际的实施环境的设计。如下图: 一个不透明的带刻度的杯子。
制造业中已经形成了高度的分工,设计和实施通常是分开的, 而在软件实施中, 产品研发很多情况下还是同一个团队完成设计和编码,采购软件产品或服务时,也通常是由供应商全部完成软件的设计和实施。再加上系统功能测试,最终用户看到的交付版本看起来都是满足用户需求的设计和实现。 Everything is OK!
那软件产品里面都有设计吗?答案是不一定。
很多小工坊模式的实体产品生产并不需要设计,或根据经验,或所谓的山寨就可以生产出各式各样的产品。 软件开发也一样。很多时候是由经验丰富的软件开发人员根据自身经验独自完成整个项目或功能的构建,本应该出现的设计评审往往由于项目规模和定位,甚至是由于对敏捷开发的误会,追求最快的产品迭代等各种原因而省略了(当然项目交付汇报时依然是要有设计阶段的哈)。
这里有常见争议点:在有限的复杂度下,既然程序员都能够自行完成项目,还有必要增加设计人员或者说设计流程吗?设计人员的产出有资深程序员的经验更优秀吗?
答案见仁见智的。个人认为还是必要的,软件产品尤其是企业信息化软件和实体产品的有一个本质区别:企业信息化软件是永远处于变化中的。实体产品生产出来后就没法改变了, 如果需要修改则需要重新设计, 开模。 而开模的费用是昂贵的,而且已经生产的产品也只能丢弃了,人们能看得到设计不完善带来的代价,因此通常都会有明确的设计环节,即使是再小的零件都有图纸,由车间工人按照图纸进行加工。企业信息化软件伴随着企业规模,流程以及业务的调整需要持续的变更,也因此带来了IT、软件二开等相关的工作(感恩珍贵的工作岗位)。从需求用户的角度,软件天生就应该可以持续修改的,运维和二开的开始阶段往往是可以很快满足变更需求的,也更加深了软件可以轻易修改的假象。
系统软件开发是减少混乱度的过程,所以它本身是处于亚稳定状态的。软件维护是提高混乱度的过程,即使是最熟练的软件维护工作,也只是放换了系统退化到非稳态的进程。--《人月神话》
直到有一天,维护人员发现整个产品都变成了一个“大泥球”,即使是一个看起来很简单的需求变更实施的代价都是巨大甚至是不可完成的。因此在各方决策人员的讨论下做出了下面的结论:“当前软件架构上落后了,无法满足我们的业务需求了,我们需要重新实施新的项目来满足企业运营需求”。 于是如同轮回一般开始了新的技术产品选型、供应商选择、实施、成功上线。但似乎很难回答上个产品的架构是哪里不能满足需求了。
回到上面没有看到软件设计模式的前面2个理由:
- 是项目太简单了,用不着: 这个理由当然是不成立的,但是这里强调的是作为开发人员自身思想上必须正视这种错误的观念。从开发管理和软件工程的角度,这个理由从来没有存在过。
- 是技术人员水平太差了,不会写: 这个理由是真实的,尤其是企业信息化软件人力外包的场景下,真实的编码人员往往连项目经验都无法保障,更别说能形成设计思维了。这个问题的解决不在于这些开发人员,需要有专门的leader或资深员工从编码工作中解放出来负责设计,这些角色也应该真正正式软件设计的重要性,而不仅是开发管理。如果都是一肩挑那那就寄希望于开发人员能够有设计的思维,认识到设计工作的客观存在性了。
总结
软件设计很重要,因此软件设计模式很重要,开发人员如果不知从何开始设计,可以通过先思考确定系统功能(业务)的稳定点和变化点,在结合了解的设计模式去选择最佳实践开始。
软件设计模式对开发人员实在的好处
软件设计模式是一门沟通工具,能够给开发人员带来的下面真真切切的好处:
- 模式项目协作沟通工具。开发人员的沟通语言就是模式,可以通过设计模式的去和业务、BA沟通编码的设计,当面对不够详细的需求文档时,不如使用软件设计模式来和BA讲解自己的设计思路,明确真正的需求。
- 设计模式是技术人员之间的沟通工具。设计模式对阅读开源框架源码、理解第三方产品有莫大的好处。比如:你看到了xxxxBuilder的类, 通常是构造器模式,也就明白了这个类的目的就是构造特定对象, 其他类似典型的xxxFactory, xxxVisitor, xxxAdaptor, xxxEventListener等。同理,我们自己开发的代码是如果能使用模式,遵照模式去定义类,也能够极大的提升自身代码的质量和可阅读性。
设计模式是汇报的工具。模式也是表达开发产出的最佳方式:契合业务需求的设计模式通常是最佳实践,能在满足需求的情况下提供更好的扩展性和灵活性,是最能表达开发产出和开发人员能力的工具。当然也能很好的向领导汇报工作。
创建型
抽象工厂(abstract factory)
- 使用场景
需要创建一组相关的或相互依赖的对象是,将对象的关联关系的维护职责从调用者转移到抽象工厂中。
-
- 一个系统要独立于他的产品的创建、组合和表示时
- 一个系统要有多个产品系列中的一个来配置时
- 当你要强调一些列相关的产品对象的设计以便进行联合使用时。
- 当你提供一个产品类库,而只想显示他们的接口而不是实现时
- 结构
AbstractFactory:声明创建相关抽象产品对象的操作接口
ConcreteFactory: 实现了创建具体产品对象的操作
AbstractProduct: 一类产品对象的操作接口
ConcreteProduct: 定义被工厂创建的具体产品对象。
Client: 只使用AbstractFactory接口创建对象。只是用对象抽象接口来操作对象,而不关心具体的对象组合形式和具体的对象实现。
- 说明
如某个手机型号TYPEA, 需要A1型摄像头,B1型CPU。 而另一个型号TYPEB需要A2型摄像头,B2型CPU。 我们在创建TYPEA对象时,普通模式是TYPEA = new(A1) + new(B1), TYPEB= new(A2)+ new(B2)。 当产品组件多且组合模式复杂时,难以维持并且暴露大量细节。改用抽象工厂的实现则TYPEA和TYPEB工厂提供相同的newCamera和newCpu接口, 需要TYPEA的型号相关组件,则使用TYPEA工厂的响应接口创建,需要TYPEB相关产品,则需要TYPEB工厂。
抽象工厂可以理解为对工厂方法模式的一个组合改进, 就是对某一个组合提供工厂,而不是对每一个产品提供工厂方法。
构造器(builder)
- 使用场景
- 当创建复杂对象的算法应该独立于对象的组成部分以及他们的装配方式时。还是手机的例子,每个手机的成分可能都差不多,但是组合的结构方式比较负载,这个结构算法可以使用构造器
- 当构造过程必须允许被构造对象有不同的表示时。
- 结构
Director: 负责构建和使用Builder对象
Builder: 为创建一个Product对象的各个部件指定抽象接口
ConcreteBuilder: 1) 实现Builder接口,转配该产品的各个部件 2) 提供一个获取产品的接口
Product: 复杂对象
- 说明
各个类似的池化对象基本都是使用builder模式, 根据不同的参数配置可以呈现不同的效果。并且构造过程复杂。如数据库连接池构建连接。
HttpClientBuilder
HttpClientBuilder.create() .setDefaultRequestConfig(requestConfig) .setConnectionManager(connectionManager) .build(); |
工厂方法(factory method)
- 使用场景
- 当一个类不知道它所必须创建的对象的类的时候
- 当一个类希望它的子类来指定它所创建的对象的时候
- 当类创建对象的职责委托个多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候
- 结构
- 说明
工厂方法的目的是将对象的创建过程抽离出来,当对象的创建过程较为复杂时,不只是简单new一个对象,而是需要组合不同的对象,进行初始化时需要考虑工厂方法。
工厂方法和构造者模式的区别:工厂方法通常是针对一类对象而言,可以同步实现不同的工厂方法来创建不同的对象,而构造者强调的是某一个对象的构建算法以及通过不同参数构建出某一类对象的参数化差异。
Spring Context对bean的创建就是工厂方法进行Bean的创建。AbstractBeanFactory提供的GetBean方法管理Bean二级单例池、处理FactoryBean等情况。
原型(prototype)
- 使用场景
原型模式指的是通过克隆来创建新对象的对象创建方式。下列场景可以考虑原型模式:
- 系统不想直接创建对象,抽离兑现刚创建过程。其他工厂方法和构造者模式也是为了这个目的。
- 对象的构建过程比较复杂,创建成本较大。比如当前对象是通过初始状态后的复杂运算而得的当前状态。
- 对象的状态优先,可以预先创建原型,使用时通过克隆来获取对应的对象。
- 结构
Prototype: 克隆接口,支持克隆模式的类实现该接口
ConcretePrototype: 克隆实现类
Client:调用对象的克隆接口创建新对象
- 说明
常见的集合的clone就是一种运用方式。
深拷贝和浅拷贝
单例模式(singleton)
- 使用场景
保证一个一个类只有一个实例
-
- 类只能有一个实例,并且提供一个公共方法来获取该实例。
- 结构
- 说明
懒汉模式和饿汉模式的区别
Spring的单例池
protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) { synchronized(this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; } |
结构型
适配器(adapter)
- 使用场景
- 现存的类接口不符合需求
- 创建一个通用的适配接口来隔离其他不兼容的类,保持统一的协作方式
- 需要使用现有对象适配为新的接口,接口适配和对象适配的区别在于适配器是直接使用适配对象,还是同时实现适配接口(自身是适配对象)
- 结构
Target: 客户使用的目标接口
Client:客户
Adaptee:已经存在的接口或对象
Adapter: 适配的实现类
- 说明
Slf4j 日志框架相当于 JDBC 规范,提供了一套打印日志的统一接口规范。它只定义了接口,并没有提供具体的实现,需要配合其他日志框架(log4j、logback……)来使用。是一个典型的适配器示例
桥接(bridge)
- 使用场景
按定义的解释为将抽象和实现解绑,两者可以独自演变。从架构的维度去理解就是将对象设计使用分层思想,两层之间通过依赖组合进行桥接。不同层次可以按照层次设计的本意进行演变,低层次的演变不影响高层次的对外协定(接口),层次之间如果保持接口约定不变还可以各自演变。
- 结构
Abstract:抽象层的接口,对象包含Implementor类型对象的引用
RefinedAbstraction: 扩充由Abstraction定义的接口
Implementor:定义实现类的接口,该接口和Abstraction的接口可以一直也可以完全不同,Implementor接口仅提供基本操作,而Abstraction则定义了基于这些基本操作的较高层次的操作。
ConcreteImplementor:实现Implementor接口。
- 说明
示例:JDBC中的DriverManger和Driver两个层次
领域驱动中的领域层接口和服务层接口: 订单服务接口及订单领域的头行接口。
组合模式(composite)
- 使用场景、
- 部分和整体层次结构
- 忽略组合对象和单个对象的不同,统一使用组合结构中的所有对象,如树状结构
- 结构
Componet: 组合中的对象接口;在适当的情况,实现所有类共有接口的缺省行为;声明一个接口用于访问和管理Component的子组件;递归接口可以访问父部件
Leaf: 在组合中表示叶节点对象,叶节点没有子节点
Composite: 定义有子部件的部件行为;子部件有关的操作;存储子部件
Client: 通过Component接口操作组合部件。
- 说明
常见树形结构、如组织结构,文件目录结构。
装饰(decorator)
- 使用场景
- 在不影响其他对象的情况下, 以动态、透明的方式给单个对象添加职责
- 处理可以撤销的职责
- 当不能采用生成子类的方法进行扩充时。
- 结构
Component: 定义一个对象接口
ConcreteComponent: 定义一个对象,可以给这个对象添加一些职责
Decorator:维持一个指向Component对象的指针,并定义一个与Component接口一致的接口
ConcreteDecorator:向组件添加职责
- 说明
Java IO类库是: InputStream, BufferedInputStream, DataInputStream
- 装饰器类和原始类继承同样的父类, 可以对原始类嵌套多个装饰器
- 装饰器类是对功能的增强
外观(facade)
- 使用场景
- 为一个复杂子系统提供一个简单接口, 隐藏子系统的演化细节
- 客户程序和抽象类实现部分存在很大依赖性, 引入外观模式进行隔离,提高子系统的独立性和可移植性
- 在层次结构中, 使用façade模式定义每层的入口,仅通过入口进行通讯,简化依赖。
- 结构
Façade: 系统对外服务入口,负责进行请求转发
子系统类: 实现具体功能,YOU
- 说明
服务对外公开接口,封层接口, 微服务网关接口等都是门面模式的一个形式。
享元(flyweigth)
- 使用场景
- 程序使用大量对象时,造成很大的存储开销
- 对象的大多数状态都可以变为外部状态。
- 如果删除对象的外部状态,可以用相对较少的共享对象取代多组对象。
- 应用程序不依赖于对象标识。
- 结构
Flyweight: 享元接口,通过该接口可以对享元进行状态变更
ConcreteFlyweight: 享元接口实现类,负责内部状态的存储
UnsharedConcreteFlyweight:不可共享的享元模式
FlyweightFactory: 享元对象管理工厂
Client:使用享元对象的客户。存储享元对象的外部信息。
- 说明
享元就是某一类对象的共享元数据,共享状态或共有状态和模式。享元模式是在存在大量对象,或则对象状态创建开销较大是减少对象创建花销。有两种使用模式:1. 抽取对象中的共享元素,减少对象开销。2. 享元作为对象状态,用于对象池管理,减少创建和销毁的代价。
各种类型的对象池管理就是享元模式的例子。
代理(proxy)
- 使用场景
- 远程代理
- 虚代理:创建开销很大的对象时,由代理决定创建对象的时机。
- 保护代理
- 智能代理:附加功能; AOP
- 结构
Proxy:代理保持目标对象的引用,提供和目标对象一致的访问方法。负责目标对象的创建和销毁,增加其他功能。
- 说明
为其他对象提供一种代理以控制对某个对象的访问。通过代理使用对象而不是直接使用对象, 那肯定是直接使用对象可能带来问题: 1. 目标对象的使用比较复杂,业务系统需要专业的代理。现实中的中介就是这个目标, 开发中的比如GRPC的调用代理, Feign调用代理。 Spring 代理对象。2. 目标对象创建的代价比较大或容易对对象造成损害,如果交由用户使用容易带来问题。 等
代理模式和装饰器模式的区别:
两种的实施目的和实施者不同: 装饰器模式是对原对象业务功能的增强,通常是目标对象业务功能实施者负责。 而代理模式通常是在对象功能之外进行额外的控制,而非业务增强, 通常是框架开发人员进行。如同系统中功能性需求和非功能性需求的区别。在实现中的表象也通常是代理有框架自动提供,如同Spring 代理对象,以及各种RPC自动生成的调用代理。 而装配器需要针对特定目的显示定义。
行为模式
行为模式主要描述算法和对象之间的职责分配。
责任链(chain of responsibility)
- 使用场景
- 多个对象可以处理请求,通过某种方式管理对象处理请求的顺序。
- 请求者和处理请求的对象解耦
- 可以动态指定处理请求的对象。
- 结构
Handler: 处理请求的接口, 包括后续Handler的引用
ConcreteHandler: 请求处理接口实现对象。
Client:使用处理对象的客户
- 说明
责任链模式是最常用和模式了。 Spring MVC, Spring Cloud Gateway,几乎大部分框架都使用责任链对请求的处理提供扩展性和框架功能。
命令(command)
- 使用场景
对对象的操作具有固定模式和固定范围,将对对象的操作抽象为特定命令对象,包括动作和参数。
- 对通过传递命令对象来进行请求的传递。如用于回调。
- 可以对命令兑现改进型管理,如排队、排序、记录(修改日记)、回撤等。
- 多个命令可以组成事务。
- 结构
Command:命令接口
ConcreteCommand:命令实现了,包括接收者、动作、参数等
Client:使用命令的客户端
Invoker: 由Client触发的执行请求。
Receiver:命令的接收者,作用对象。
- 说明
比如MQ消息中的命令消息类型。使用中需要注意对命令进行编排和管理。避免命令的混乱,造成系统的异常和无法维护。
解释器(interpreter)
- 使用场景
解释器是对某种模式语言的解释实现。核心思想是:如果某一种特定类型的问题发生的频率足够高,就可能指的将该问题的各个实例表述为一个简单语言的一个句子。并未该语言构建一个解释器来通过解释句子来解决问题。
- 结构
AbstractExpression:抽象表达式,声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点共享
TerminalExpression:终结符表达式,实现与文法中的终结符相关的解释操作。一个句子的每个终结符需要该类的一个实例。
NonterminalExpression(非终结符表达式)
Context:上下文,一些全局信息
Client:客户 构建句子,调用解释器
- 说明
AOP中的注解,切面表达式,值集解释器等例子
Mybatis等SQL解析
低代码平台。
迭代器(iterator)
- 使用场景
提供一个方法顺序方位一个聚合对象中的各个元素,而由不暴露该对象的内部表示。
支持对聚合对象的多种遍历。
为遍历遍历不同的聚合结构提供一个统一的接口
迭代器模式目标是聚合对象。
- 结构
Iterator:迭代器,定义访问和遍历聚合的接口
ConcreateIterator:具体的迭代器
Aggregate:聚合
ConcreteAggregate:具体聚合
- 说明
Java 集合的Iterator接口
中介者(mediator)
- 使用场景
用一个中介对象来封装一系列的对象交互。中介者解耦相关对象, 并可以协调或改变它们之间交互的方式。
- 一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解。
- 一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。
- 想定制一个分布在多个类中的行为, 而又不想生成太多子类。
- 结构
Mediator:中介者,定义用于和各同事对象通信的接口
ConcreteMediator:中介实现
Colleague:同事类。具体业务实现,需要和中介通讯完成业务操作。
- 说明
中介模式和代理模式的区别: 从分类来看中介模式是行为性模式, 而代理模式是结构性模式, 也就是说代理模式是改变对象之间的相互结构。比如A同B通讯,改为A同B的代理通讯。并没有改变A和B之间的通讯协议。而中介是行为模式, 是通过在A同B之间引入一个中介协议,两种之间直接通讯的协议改为各自同中介的通讯协议。代理通常是一对一的,而中介在多对多复杂场景下进行重构解耦,降低复杂度的方法。另外代理的目的通常不是业务逻辑,而中介通常会改变算法逻辑。
备忘录(memento)
- 使用场景
在不破话封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
- 必须保存一个对象在某个时刻的(部分)状态, 这样以后需要时可以恢复到先前的状态。
- 为了达到目的1,但是又不想将对象的这些状态直接通过接口暴露到客户端负责, 这样会暴露对象的实现细节,破坏封装性。
- 结构
Memento:备忘录
Originator:触发器,根据特定逻辑触创建备忘录和获取备忘录
Caretaker:负责人。 备忘录管理器
- 说明
镜像,存档。
观察者(observer)
- 使用场景
消息通知、对象的一种一对多依赖关系,当对象状态发生改变时,可以通知到依赖改对象的对象。
- 对象之间存在依赖,对象的改变需要通知其它对象,但不确定具体的目标对象时
- 结构
Subject: 目标对象,目标对象管理其观察者,提供注册和删除功能, 负责观察者自身状态的变化
Observer:观察者
ConcreteSubject: 目标实现
ConcreteObserver: 具体观察实现
- 说明
观察者模式是最常见的设计模式之一,消息中间件、 Spring ApplicationListener接口(ApplicationConatext发布ApplciationEvent是,Application Listener Bean将被通知)
状态(state)
- 使用场景
当对象内部状态发生改变是, 改变其行为。
- 对象的行为取决于它的状态, 需要在运行时刻根据状态改变它的行为。
- 一个操作中含有大量的分支的条件语句, 分支行为由状态决定。
- 结构
Context:拥有多种状态的对象
State:状态接口,封装特定状态下的Context行为
ConcreteState:具体状态实现。
- 说明
状态模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,独立进行变化。
企业各类单据天然拥有状态行为, 比如不同状态下的关闭行为,编辑行为。
使用分支和状态模式的选择还是在于业务复杂度,直接使用状态模式也可能造成过度设计。但长期新建分支不可避免的形成上帝类和破坏封装和拒绝修改的原则。
策略(Strategy)
- 使用场景
定义一些列的算法, 形成可替换的机制,使得算法可以独立于客户请求而变化。
- 许多相关的类仅仅是行为有异,需要根据配置来改变行为
- 需要使用算法的一个辩题, 比如同类算法
- 多个条件分支语句可以考虑使用策略模式优化。
- 结构
Strategy:策略接口, 算法接口, Context通过算法接口来调用某种策略的算法。
ConreteStrategy:策略具体实现
Context: 负责管理策略配置和调用策略接口实施算法。
- 说明
如审批实现中可能有:自动审批、外部第三方审批接口、内部审批实现等不同策略。
策略和状态都是对多条件分支进行重构的策略,两种的区别在:状态强调的是对象自身状态的变化而带来的行为变化, 而策略是对同一个状态行为实现不同可替换算法的方法。
模板方法(template method)
- 使用场景
定义一个操作中算法的骨架,而将一些步骤延迟到子类中。模板方法模式实现在不改变一个算法的接口的前提下重新定义算法的某些特定步骤。
- 一次性实现一个算法的不变部分,并将可变的行为留个子类来实现。
- 各自类中公共的行为应该被提取出来并集中到一个公共父类中已避免代码的重复。重构的方法是:首先识别现有代码中的不同之处, 并将不同之处分离为新的操作。最后用调用一个新的操作的模板方法来替换这些不同的代码。
- 控制子类的扩展。模板方法只在特定的点调用hook操作。
- 结构
AbstractClass:抽象类,定义抽象的,可扩展的操作方法, 具体子类重新实现这些方法;实现一个算法的框架代码。框架实现就是模板方法负责调用子类可扩展的方法。
ConcreteClass:具体实现子类, 实现特定算法步骤。
- 说明
模板方法是一种代码复用的基本技术。进行二开的时候尤其需要考虑使用模板方法,而非全盘拷贝实现。
访问者(visitor)
- 使用场景
表示一个作用于某对象结构中各个元素的操作。
- 一個对象结构中包含很多类对象, 它们有不同的接口,需要实施一些依赖于其具体类的操作。
- 需要对一个对象结构中的对象进行很多不同且不相关的操作,而不想让这些操作污染这些对象的类。Visitor将操作相关的内容定义在一个类总,而不影响目标对象类。
- 这些操作类可以独自演变而不影响对象的结构。
- 结构
Visitor: 访问者接口,定义范文元素的接口
ConcreteVisitor: 访问者实现类
Element:元素
ConcreteElement:具体元素
Client: 目标客户使用Visitor访问Element。Client可能维护元素和Visitor的集合,或则通过一个维护元素的对象来访问元素,如集合对象。
- 说明
访问者模式在平常开发中比较少见, 底层思维逻辑还是将变化点和不变点区分开来。
访问者模式com.sun.tools.classfile.Annotation中定义的Visitor类。针对不同的类型实现不同的访问方法。
public interface Visitor<R, P> { R visitPrimitive(Annotation.Primitive_element_value var1, P var2); R visitEnum(Annotation.Enum_element_value var1, P var2); R visitClass(Annotation.Class_element_value var1, P var2); R visitAnnotation(Annotation.Annotation_element_value var1, P var2); R visitArray(Annotation.Array_element_value var1, P var2); } |