设计模式
14、组合模式(Composite)
- 抽象问题:对于这种具有整体与部分关系,并能组合成树形结构的对象结构,如何才能够以一个统一的方式来进行操作呢?
- 组合模式的定义:将对象组合成树型结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性;
- 组合模式解决问题的思路就是引入一个抽象的组件对象,作为父对象;组合模式的关键在于这个抽象类;
模式讲解
- 1、认识
- 组合模式的目的是:让客户端不再区分操作的是组合对象还是叶子对象,而是以一个统一的方式来访问;通过一个抽象的组件类;
- 最大化Component定义:Component中的方法主要是两种对象对外方法的和,(与类的设计原则相冲突:对某些子对象没有意义的方法提供默认的实现或是抛出不支持该功能的例外)
- 大多数情况下,Component中会持有子对象节点的集合
- 对象树和递归关联(对象本身的递归)
- 2、安全性和透明性
- 透明性
- 实现:在Component中声明管理子组件的操作,并在Component中为这些方法提供默认的实现。
- 把管理子组件的操作定义在Component中,那么客户端只需面对Component,无需关系具体的组件类型;
- 安全性
- 管理子组件的操作定义在Composite中;
- 更加看重透明性;
- 透明性
- 3、父组件引用
- Component
- 添加一个属性来记录组件的父组件对象,同时提供get/set方法;
- 添加一个能获取一个组件所包含的子组件对象的方法,提供给实现当某个组件被删除时,把它的子组件对象上移一层;
- Composite
- 添加子组件的方法实现中,加入对父组件的引用实现
- 删除子组件的方法实现中,重新设置删除子组件的父组件功能;
- Component
- 4、环状引用
- A包含B,B包含C,C包含A;
- 应该考虑要检查并避免出现环状引用;
- 5、组合模式优缺点
- 优点
- 定义了包含基本对象和组合对象的类层次结构;
- 统一了组合对象和叶子对象
- 简化了客户端调用
- 更容易扩展
- 组合模式的缺点是很难限制组合中的组件类型
- 优点
- 6、思考组合模式
- 组合模式的本质:统一叶子对象和组合对象;
- 何时选用组合模式
- 想表示对象的部分-整体层次结构,即整体和部分的操作统一起来;
- 统一使用组合结构中的所有对象;
- 7、相关模式
- 组合模式和装饰模式:
- 组合模式和享元模式:可组合使用,(如果组合模式中出现大量相似组件对象,考虑使用享元模式来来帮组缓存组件对象,这样可以减少内存的需要)
- 组合模式和迭代器模式:迭代器模式来遍历组合对象的子对象集合
- 组合模式和访问者模式:可组合使用
- 组合模式和职责链模式:组合模式来构建这条链
- 组合模式和命令模式:
15、模板方法模式(Template Method)
- 两个模板登录:重复代码或相似代码太多,扩展起来很不方便;
- 模板方法的定义:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某=某些特定步骤;
模式讲解
- 1、认识模板方法
- 模板方法模式的功能在于固定算法骨架,而让具体算法实现可扩展;
- 模板方法模式还额外提供了一个好处,可以控制子类的扩展;
- 为什么不用接口?
- 接口是一种特殊的抽象类,所有接口的属性是常量,,所有接口中的方法必须是抽象的;
- 抽象类不一定包含抽象方法;有抽象方法的类一定是抽象类;
- 抽象类的方法可以有具体实现;
- 既有约束子类的行为,又要为子类提供公共功能的时候使用抽象类;
- 变与不变:分析程序中哪些功能是变化的,哪些功能是不变的?
- 好莱坞法则:由父类找子类,而不是让子类来找父类;
- 理论依据:Java的动态绑定采用的是后期绑定,对于出现子类覆盖父类方法的情况,在编译时是看数据类型,运行时则看实际的对象类型(new 谁就调用谁的方法);
- 2、模板的写法
- 模板方法、具体的操作、具体的AbstractClass操作、原语操作、钩子操作、Factory Method;
/**
* 一个较为完整的模版定义示例
*/
public abstract class AbstractTemplate {
/**
* 模板方法,定义算法骨架
*/
public final void templateMethod(){
//第一步
this.operation1();
//第二步
this.operation2();
//第三步
this.doPrimitiveOperation1();
//第四步
this.doPrimitiveOperation2();
//第五步
this.hookOperation1();
}
/**
* 具体操作1,算法中的步骤,固定实现,而且子类不需要访问
*/
private void operation1(){
//在这里具体的实现
}
/**
* 具体操作2,算法中的步骤,固定实现,子类可能需要访问,
* 当然也可以定义成public的,不可以被覆盖,因此是final的
*/
protected final void operation2(){
//在这里具体的实现
}
/**
* 具体的AbstractClass操作,子类的公共功能,
* 但通常不是具体的算法步骤
*/
protected void commonOperation(){
//在这里具体的实现
}
/**
* 原语操作1,算法中的必要步骤,父类无法确定如何真正实现,需要子类来实现
*/
protected abstract void doPrimitiveOperation1();
/**
* 原语操作2,算法中的必要步骤,父类无法确定如何真正实现,需要子类来实现
*/
protected abstract void doPrimitiveOperation2();
/**
* 钩子操作,算法中的步骤,不一定需要,提供缺省实现
* 由子类选择并具体实现
*/
protected void hookOperation1(){
//在这里提供缺省的实现
}
/**
* 工厂方法,创建某个对象,这里用Object代替了,在算法实现中可能需要
* @return 创建的某个算法实现需要的对象
*/
protected abstract Object createOneObject();
}
- Java回调技术:通过回调在接口中定义的方法,调用具体的实现类中的方法;
- 模板方法模式的优缺点
- 优点:实现代码复用
- 缺点:算法骨架不容易升级
- 模板方式模式的本质:固定算法骨架
- 何时选用模板方法模式
- 定义算法骨架,实现一个算法的不变的部分,并把可变的行为留给子类来实现
- 各个子类由公共行为,抽取,集中到一个公共类去实现
- 控制子类扩展的情况。模板方法模式会在特定的点来调用子类的方法,这样就允许这些点进行扩展;
- 相关模式
- 模板方法模式和工厂模式可配合使用
- 模板方法模式和策略模式:模板方法封装的时算法的骨架,策略模式是把某个步骤的具体实现封装起来;
16、策略模式(Strategy)
- 问题:(新老)顾客相同的商品不同的价钱,如何实现可维护,可扩展,又能动态地切换变化呢?
- 策略模式的定义:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化;
- 策略模式的核心不是如何实现算法,而是如何组织调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性;
- 策略算法是相同行为的不同实现;
- 客户端选择具体的策略算法;
- Strategy都是使用接口来定义的;如果多个算法具有公共功能的时候,,也可以把Strategy实现为抽象类,然后把多个算法的公共功能实现到Strategy中;
- 运行时策略的唯一性;
- 策略模式的优缺点
- 优点:
- 定义一系列算法,实现让这些算法可以相互替换;
- 避免多重条件语句;
- 更好的扩展性;
- 缺点:
- 客户必须了解每种策略的不同;
- 增加了对象数目;
- 只i适合扁平的算法接口;
- 优点:
- 思考策略模式:
- 策略模式的本质:分离算法,选择实现;
- 策略模式很好的体现了开闭原则;里氏替换原则;
- 何时选用策略模式:
- 出现由许多相关的类,仅仅是行为有差别的情况下,实现算法动态切换;
- 需要封装算法,可以使用策略模式避免暴露这些与算法相关的数据接口;
- 抽象一个定义了很多行为的类;
- 相关模式:
- 策略模式和状态模式:状态模式是根据状态的变化来选择相应的行为,不同的状态对应不同的类;策略模式中各个实现类是平等的,是可以相互替换的
- 策略模式可用于分离并封装算法实现,模板方法重在封装算法骨架;
- 策略模式和享元模式
17、状态模式(State)
- 抽象问题:在线投票,恶意投票行为;
- 策略模式的定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类;
模式讲解
- 认识状态模式(参考example4)
- 状态和行为:状态是指对象实例的属性的值,行为就是对象的功能;
- 状态决定行为;行为的平行性;
- 状态模式和策略模式一个很重要的区别:状态模式是平行性的,不可相互替换;策略模式的行为是平等性的,是可相互替换的;
- 上下文是持有状态的对象;
- 状态的维护和转换控制(example5):
- 一个是在上下文中; (如果状态换规则是一定的,适合在上下文中统一进行状态的维护)
- 另一个地方是在状态的处理类中;(如果状态的转换取决于前一个状态动态处理的结构或者依赖外部的数据,一般可选择在状态处理类中进行状态的维护)
- 维护状态 还可以使用数据库;
- 请假工作流程代码(example8)
- 状态模式优缺点
- 优点:
- 简化应用逻辑控制:使用单独的类封装一个状态的行为
- 分离状态和行为:定义所有状态类的公共接口,把所有与一个特定的状态相关的行为都放入到一个对象中;
- 更好的扩展性:新增加公共接口的实现类
- 显式化进行状态转换 :为不同的状态引入独立的对象;
- 优点:
- 状态模式的本质:根据状态来分离和选择行为;
- 何时选用状态模式:
- 一个对象的行为取决与它的状态
- 一个操作中含有庞大的分支语句
- 相关模式
- 状态模式和策略模式
- 状态模式和观察者模式:观察者是通知所有的观察者,状态模式是根据状态来选择不同的处理
- 状态模式和单例模式
- 状态模式和享元模式
18、备忘录模式(Memento)
- 抽象问题:仿真问题?
- 功能是:模拟运行针对某个具体的问题的多个解决方案,记录运行过程的各种数据,模拟运行完成之后,方便对这多个解决方案进行比较和评价,从而选定最优的解决方案;
- 备忘录模式定义:在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态;
- 一个备忘录是一个对象,它存储另一个对象在某个瞬间的内部状态,后者被称为备忘录的原发器;
- 原发器内部有需要保存的数据,内部定义一个私有静态内部类,此内部类实现备忘录接口,并用来保存原发器的内部的数据;
- 通过管理者管理这个备忘录接口,这个接口没有任何方法,仅仅起到了一个标识对象类型的作用,从而保证内部的数据不会被外部获取或是操作,保证了原发器对象的封装性,也不会暴露原发器对象的内部结构。
模式讲解
- 认识备忘录模式
- 备忘录模式的功能
- 在不破坏封装性的前提下(对象不能暴露他不应该暴露的细节);
- 捕获对象的内部状态,而且通常还是运行期间某个时刻对象的内部状态;
- 备忘录的真正目的:为了以后某个时候,将该对象的状态恢复到备忘录所保存的状态;
- 捕获的对象状态存储在备忘录对象中,而备忘录对象通常会被存储在原发器对象之外,通常是存放在管理者对象那里;
- 备忘录对象:记录原发器需要保存的状态的对象;
- 原发器对象:需要被保存状态或可能恢复状态的对象;
- 管理者对象:负责保存备忘录对象;
- 窄接口和宽接口:
- 备忘录模式的功能
- 备忘录模式是依靠缓存实现的,如果很频繁的创建备忘录,就不要选用备忘录模式;
- 增量存储
- 结合原型模式
- 离线存储:主要用来修改管理者对象,把它保存备忘录对象的方法实现为保存到文件中,而恢复备忘录对象实现成为读取文件,其他的相关对象,主要用来实现序列化,只有序列化的对象才能被存储到文件中;
- 备忘录模式的优缺点:
- 更好的封装性:使用备忘录对象,来封装原发器对象的内部状态
- 简化了原发器:备忘录对象被保存在原发器对象之外,让客户来管理它们请求的状态;
- 窄接口和宽接口:窄接口保证了只有原发器才可以访问备忘录对象的状态
- 思考备忘录模式: 保存和修复内部状态;
- 何时选用备忘录模式:
- 如果必须保存一个对象在某一个时刻的全部或者部分状态,方便在以后需要的时候,可以把该对象恢复到先前的状态;
- 如果需要保存一个对象的内部状态;
- 相关模式:
- 备忘录和命令模式
- 备忘录和原型模式
19、享元模式(Flyweight)
- 抽象问题(权限控制:授权(指把对某些安全实体的某些权限分配给某些人员的过程)和验证(指判断某个人员对某个安全实体是否拥有某个或某些权限的过程))
- 安全实体:被权限系统检测的对象,比如工资数据;
- 权限:需要被校验的权限对象,比如查看、修改;
- 享元模式的定义:运用共享技术有效地支持大量细粒度的对象;
- 享元模式
- 定义享元接口
- 定义享元接口实现类(封装授权数据中重复出现部分的数据)
- 提供享元工厂来负责享元对象的共享管理和对外提供访问享元的接口;
- 使用享元对象
模式讲解
-
享元模式设计的重点在于分离变与不变;把一个对象的状态分成内部状态和外部状态,内部状态是不变的,外部状态是可变的,然后通过共享不变的部分,达到减少对象数量并节约内存的目的;
-
享元模式的优缺点:
- 优点:减少对象数量,节省内存空间
- 缺点:维护共享对象,需要额外开销
-
享元模式本质:分离与共享
-
-
相关模式
- 享元模式与单列模式
- 享元模式与组合模式
- 享元模式与状态模式
- 享元模式与策略模式
20、解释器模式(Interpreter)
- 抽象问题(读取配置文件)
- 解释器模式的定义:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子;
- 解释器(XML文件)示例:
- 定义抽象的解释器接口;
- 定义上下文(封装解释器需要的全局数据);
- 定义元素作为非终结符对应的解释器;
- 定义元素作为终结符对应的解释器;
- 定义属性作为终结符对应的解释器;
- 使用解释器
- 解释器模式优缺点:
- 优点:易于实现语法,易于扩展新的语法 ;
- 缺点:不适合复杂的语法;
- 解释器模式的本质:分离实现,解释执行 ;
- 相关模式:
- 解释器模式和组合模式
- 解释器和迭代器模式
- 解释器模式和享元模式
- 解释器模式和访问者模式
21、装饰器模式(Decorator)
- 抽象问题:如何实现灵活的奖金计算?
- 装饰模式的定义:动态地给一个对象添加一些额外的职责。就增加功能来时,装饰模式比生成子类更为灵活;
- 透明地给一个对象增加功能:要给一个对象增加功能,但是不能让这个对象知道,也就是不能去改动这个对象。
模式讲解
- 各个装饰器之间最好是完全独立的功能,不要有依赖,这样在进行装饰组合的时候,才没有先后顺序的限制;
- Java中的装饰器应用模式-IO流
- AOP模式(aop的实现方法不同,aop主要是主从换位)
- 装饰器模式
- 优点:比继承灵活、更容易被复用、简化高层定义;
- 缺点:会产生还跟多细粒度对象;
- 装饰模式的本质:动态组合;
- 何时选用装饰模式:
- 如果在不影响其他对象的情况下,以动态、透明的方式给对象添加职责,可以使用装饰模式
- 如果不适合使用子类来进行扩展的时候,可以考虑使用装饰模式
- 相关模式
- 装饰模式是改变对象功能的,适配器模式用来改变对象功能的;
- 装饰模式和组合模式:装饰模式要动态地给对象增加功能,组合模式是想要管理组和对象和叶子对象,为它们提供一个一致的操作接口给客户端;
- 装饰模式与策略模式:策略模式也可以实现动态地改变对象的功能,策略模式只是一种选择,装饰模式是递归调用,无数层都可以;
- 装饰模式和模板方法模式:模板主要用于算法骨架固定;
装饰器模式
问题对一组图形画画新增红笔画画的功能?
1、创建 形状接口Shape 实现类方形,圆形
2、创建抽象类抽象装饰者A,持有Shape对象,并实现画画功能;
3、创建A的装饰器的实现类B,增强其画画功能;
22、职责链模式(Chain of Responsibility)
- 抽象问题:申请聚餐费用的管理?
- 职责链模式定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条先,并沿着这条链传递该请求,知道有一个对象处理它为止;
- 责任链模式优缺点:
- 优点:请求者和接收者松散耦合、动态组合职责;
- 缺点:产生很多细粒度对象、不一定能被处理到;
- 职责链模式的本质:分离职责、动态组合;
- 何时选用职责链模式:
- 如果多个对象可以处理同一个请求,但具体由哪个对象来处理请求,是运行时刻动态确定的;
- 如果不明确指定接收者的情况下,向多个对象中的其中一个提交请求的话;
- 如果想要动态指定处理一个请求的对象集合;
- 相关模式
- 职责链模式和组合模式
- 职责链模式和装饰模式
- 职责链模式和策略模式
23、桥接模式(Bridge)
-
抽象问题:发送提示消息
-
桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化;
-
-
桥接模式:桥接模式是单向的,也就是只能是抽象部分的对象去使用具体实现部分的对象;
-
桥接模式:
- 优点:分离抽象和实现部分、更好的扩展性、可动态地切换实现、减少子类的个数;
-
桥接模式的本质:分离抽象和实现;
-
何时选用桥接模式:
- 不希望抽象部分和实现部分采用固定的绑定关系;
- 如果出现抽象部分和实现部分都能扩展的情况;
- 希望实现部分的修改不会对客户产生影响;
桥接模式:
问题:模拟【正常、加急】发送【邮件、手机】消息
0、发送消息类型接口A 【实现类 发送邮件,发送手机】、 抽象类B(调用消息接口,做出发消息操作)【实现类 正常发,加急发】,
1、定义发送消息类型接口A,和 实现发送邮件消息,手机消息的实现类;
2、定义抽象类B,内部定义一个发送消息的类型接口A的字段,并有一个发送消息的抽象方法;
3、继承抽象类B,重写发送消息,实现正常发、加急发的内容,并调用A接口发送;
24、访问者模式(Visitor)
- 抽象问题:扩展客户管理的功能;
- 访问者模式的定义:表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变个元素的类的前提下定义作用于这些元素的新操作。
模式讲解
- 访问者模式的本质:预留通路,回调实现;
举例
拜访者举例:
问题1:【校长和家长】分别查询【学生和老师的信息】?
0、对象:校长、家长、校长家长基类、拜访者接口、校长拜访者、家长拜访者、客体处理对象
1、学生和老师同属客体,校长和家长同属拜访者;
2、学生和老师继承客体基类,并实现基类【接收拜访者】的抽象方法
3、拜访者接口:拜访老师方法、拜访学生方法
4、校长拜访者 和 学生拜访者 分别实现上诉拜访者接口方法
5、客体处理对象:
1、存放校长、家长对象集合,并提供添加对象方法
2、接收拜访者对象,遍历集合并调用接收拜访者方法
常见的面向对设计原则
- 单一职责原则(SRP):一个类应该仅有一个引起它变化的原因;
- 开放-关闭原则(COP):一个类应该对扩展开放,对修改关闭; 是非常核心的一个原则;关键在于合理地抽象,分离变与不变,为变化的部分预留下可扩展的方式;
- 里式替换原则(LSP):子类型必须能够替换掉它们的父类型,(当子类覆盖了父类的某些方法,或者修改了父类型某些属性的值)
- 依赖倒置原则(DIP):要依赖于抽象,不要依赖于具体类;(好莱坞原则:不要联系我们,我们会找你)
- 高层模块不应该依赖于底层模块,二者应该依赖于抽象;
- 抽象不应该依赖于具体实现,具体实现应该依赖于抽象;
- 接口隔离原则(ISP):不应该强迫客户依赖于他们不用的方法;(主要用来处理那些比较“庞大”的接口)
- 最少知识原则(LKP):尽量减少对象之间的交互,松散之间的耦合
UML简介:图形化建模语言
类图的基本表达
- 属性的定义语句: 可见性 属性名: 类型名=初值
- 可见性 +(public) 、- (private)、# (protected)、没有符号就是默认的可见性
- 方法定义的基本语法 可见性 操作名(参数列表): 返回值类型
- 可见性 +(public) 、- (private)、# (protected)、没有符号就是默认的可见性
- static的表示 属性或方法如果是静态的,那么属性或者方法的定义下边有一个下划线;
-
关系
-
-
关联 (普通关联、递归关联、限定关联、或关联、有序关联、三元关联和聚合)
-
普通关联
-
递归关联
-
聚合关联
-
泛化关系(通用化或继承)
-
实现关系(描述类实现接口)
-
依赖关系(使用,A使用了B,就是A依赖于B)
顺序图
参考书籍:《研磨设计模式》