设计模式
零、设计原则
0.1 单一职责
0.2 接口隔离
0.3 开闭原则
0.4 依赖倒置
0.5 迪米特法则,最小知道原则
用户关机
只和朋友通信
朋友条件:
1)当前对象本身(this)
2)以参量形式传入到当前对象方法中的对象
3)当前对象的实例变量直接引用的对象
4)当前对象的实例变量如果是一个聚集,那么聚集中的元素也都是朋友
5)当前对象所创建的对象
0.6 里氏替换原则
代码符合里氏替换原则
长方形继承正方形,就是反例
根据历史替换原则,正方形不是长方形
0.7 组合由于继承
一丶单例模式
类加载创建对象,是饿汉式,调用方法创建对象是懒汉式
二丶简单工厂模式(静态工厂模式)
2.1 UML类图
2.2 说明
封装实例化代码的行为
2.2 三种工厂方法区别
产品的种类很多,可以用抽象工厂模式,如果就那几个产品可以用简单工厂
工厂模式意义:将实例化对象的代码提取出来,放到一个类中,统一管理和维护,达到这主项目的一来关系解耦,从而提供项目的扩展和维护性。
三丶工厂方法模式
3.1 UML类图
3.2 说明
- 工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
四丶抽象工厂模式
4.1 UML类图
4.2 说明
- 将工厂抽象成两层,AbsFactory(抽象工厂) 和 具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。
五丶原型模式(克隆羊)
5.1 UML类图
5.2 说明
- 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
- 不用重新初始化对象,而是动态地获得对象运行时的状态
- 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
- 在实现深克隆的时候可能需要比较复杂的代码
- 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则,这点请同学们注意.
比如Spring bean的的类型为prototype时会用到
深拷贝: 实现clone方法
实现序列化接口 输出对象,再读取对象
浅拷贝: 简单实现
六丶建造者模式
6.1 UML类图(传统)
6.2 说明
建造者模式的注意事项和细节
- 客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
- 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象
- 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程
- 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合 “开闭原则”
6.3 UML类图(改进版)
七丶适配器(手机充电)
将一个类的接口转换成另一种接口,让原本不兼容的类可以兼容。
7.1 UML类图(类适配器)
7.2 说明
类适配器有继承关系,不建议使用
根据合成复用原则,在系统中尽量使用关联关系代替继承关系。
7.3 UML类图(对象适配器)
7.4 说明
对象适配器模式介绍
- 基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承src类,而是持有src类的实例,以解决兼容性的问题。即:持有src类,实现dst类接口,完成src->dst的适配
- 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系。
- 对象适配器模式是适配器模式常用的一种
7.5 UML类图 (接口适配器)
public class Client {public static void main(String[] args) {AbsAdapter absAdapter = new AbsAdapter() {@Overridepublic void operation1() {System.out.println("调用operation1方法");}};absAdapter.operation1();}
}
7.6 说明
接口适配器模式介绍
- 一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式。
- 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
- 适用于一个接口不想使用其所有的方法的情况。
7.6 源码应用
说明:
• Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类
• 适配器代替controller执行相应的方法
• 扩展Controller 时,只需要增加一个适配器类就完成了SpringMVC的扩展了,
• 这就是设计模式的力量
7.6 手动实现Springmvc
7.6.1 类图
7.6.2 适配器模式的注意事项和细节
- 三种命名方式,是根据 src是以怎样的形式给到Adapter(在Adapter里的形式)来
命名的。 - 类适配器:以类给到,在Adapter里,就是将src当做类,继承
对象适配器:以对象给到,在Adapter里,将src作为一个对象,持有
接口适配器:以接口给到,在Adapter里,将src作为一个接口,实现 - Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作。
- 实际开发中,实现起来不拘泥于我们讲解的三种经典形式
八、桥接模式(手机分类)
8.1 UML类图
解决类爆炸
8.2 说明
桥接模式(Bridge)-基本介绍
基本介绍
- 桥接模式(Bridge模式)是指:将实现与抽象放在两个不同的类层次中,使两个层
次可以独立改变。 - 是一种结构型设计模式
- Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同
的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现
(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能
扩展
8.3 桥接模式在JDBC的源码剖析
- Jdbc的Driver接口,如果从桥接模式来看,Driver就是一个接口,下面可以有MySQL的Driver,Oracle的Driver,这些就可以当做实现接口类
8.4 桥接模式的注意事项和细节
- 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
- 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成。
- 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
- 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性,即需要有这样的应用场景。
8.5 桥接模式其它应用场景
- 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用.
- 常见的应用场景:
-JDBC驱动程序
-银行转账系统
转账分类:网上转账,柜台转账,AMT转账
转账用户类型:普通用户,银卡用户,金卡用户…
-消息管理
消息类型:即时消息,延时消息
消息分类:手机短信,邮件消息,QQ消息…
九、装饰者模式(购买咖啡)
9.1 UML类图
9.1.1 最差方案
9.1.2 第二种解决方案
9.1.3 装饰者方案
9.1.2 说明
装饰者模式定义
- 装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)
- 这里提到的动态的将新功能附加到对象和ocp原则,在后面的应用实例上会以代码的形式体现,请同学们注意体会。
9.2 源码
public abstract class InputStream implements Closeable{} //是一个抽象类,即Component
public class FilterInputStream extends InputStream { //是一个装饰者类Decorator
protected volatile InputStream in //被装饰的对象 }
class DataInputStream extends FilterInputStream implements DataInput { //FilterInputStream 子类
十、组合模式(输出院系信息)
10.1 UML类图
10.2 说明
- 组合模式的注意事项和细节
组合模式的注意事项和细节
- 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
- 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动.
- 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构
- 需要遍历组织机构,或者处理的对象具有树形结构时,非常适合使用组合模式.
- 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式
10.3 源码应用
十一、外观模式(家庭影院)
11.1 UML类图(传统方式)
11.2 传统方式问题分析
11.3 UML类图(外观模式)
11.4 说明
11.5 源码
11.6 外观模式的注意事项和细节
- 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复
杂性 - 外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易维护和扩展
- 通过合理的使用外观模式,可以帮我们更好的划分访问的层次
- 当系统需要进行分层设计时,可以考虑使用Facade模式
- 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时
可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口,
让新系统与Facade类交互,提高复用性 - 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。
要以让系统有层次,利于维护为目的。
十二、享元模式(同一网站多种形式发布)
12.1 UML类图(传统)
12.2 问题分析
12.3 UML类图(享元模式)
12.4 说明
12.5 源码应用
public class FlyweightInInteger {public static void main(String[] args) {Integer x = Integer.valueOf(127);Integer y = new Integer(127);Integer z = Integer.valueOf(127);Integer w = new Integer(127);System.out.println(x.equals(y)); // trueSystem.out.println(x == y); // falseSystem.out.println(x == z); // trueSystem.out.println(w == x); // falseSystem.out.println(w == y); // false}
}
十三、代理模式(代理老师授课)
13.1 UML类图
13.2 说明 (静态代理)
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类
13.3 UML类图
13.4 说明(动态代理)
13.4 Cglib代理
代理不能是final
13.5 UML类图(Cglib)
13.6 两者区别
jdk代理
Proxy.newProxyInstance获取代理对象,重写invoke方法
Cclib代理
实现MethodInterceptor接口,重写Interceptor方法。用Enhancer获取代理对象,在内存中生成子对象。
// 1、创建工具类Enhancer enhancer = new Enhancer();// 2、设置父类enhancer.setSuperclass(target.getClass());// 3、设置回调函数enhancer.setCallback(this);// 4、创建子类对象,即代理对象return enhancer.create();
十四、模板方法模式(制作豆浆)
制作豆浆
14.1 UML类图
14.2 说明
14.3 UML类图
14.4 钩子方法
14.5 源码分析
14.6 源码类图
14.7 注意事项和细节
十五、命令模式(稍微难)(遥控器控制开灯关灯)
15.1 UML类图
15.2 说明
15.3 源码使用
15.4 说明
十六、访问者模式(有点难)(访问动物园,需要买票)
16.1 UML类图
16.2 说明
16.3 注意事项和细节
十七、迭代器模式(遍历学校院系结构)ArrayList遍历,没仔细看
17.1 UML类图
17.2 说明
17.3 源码分析
17.3 注意事项和细节
十八、观察者模式(天气预报和气象站)
18.1 UML类图(传统)
18.2 说明(传统)
18.3 UML类图(观察者)
18.4 说明
18.5 源码应用
十九、中介者模式(家庭影院各个设备协调工作)
19.1 UML类图
19.2 说明
中介者模式(Mediator Pattern),用一个中介对象来封装一系列的对象交互。
- 中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立
地改变它们之间的交互 - 中介者模式属于行为型模式,使代码易于维护
- 比如MVC模式,C(Controller控制器)是M(Model模型)和V(View视图)的中
介者,在前后端交互时起到了中间人的作用
19.3 UML类图
19.4 注意事项
二十、备忘录模式(游戏角色死后恢复)
20.1 UML类图
20.2 说明
1)备忘录模式(Memento Pattern)在不破坏封装性的前提下,捕获一个对象的内
部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保
存的状态
2) 可以这里理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情,
或者是记录已经达成的共同意见的事情,以防忘记了。而在软件层面,备忘录
模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某
些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作
3) 备忘录模式属于行为型模式
20.3 UML类图(游戏角色)
20.4 注意事项
二十一、解释器模式(计算器求值)
21.1 UML类图
21.2 说明
21.3 源码应用
21.4 说明
解释器模式的注意事项和细节
- 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以
考虑使用解释器模式,让程序具有良好的扩展性 - 应用场景:编译器、运算表达式计算、正则表达式、机器人等
- 使用解释器可能带来的问题:解释器模式会引起类膨胀、解释器模式采用递归调用
方法,将会导致调试非常复杂、效率可能降低.
二十二、状态模式(用户抽奖)
22.1 UML类图
22.2 说明
- 状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外
输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换 - 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了
其类
22.3 订单模式
22.4 注意事项和细节
- 代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中
- 方便维护。将容易产生问题的if-else语句删除了,如果把每个状态的行为都放到一
个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多if-else语句,
而且容易出错 - 符合“开闭原则”。容易增删状态
- 会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维
护难度 - 应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状
态要求有不同的行为的时候,可以考虑使用状态模式
二十三、策略模式(鸭子问题)
23.1 UML类图
23.2 说明
传统的方式实现的问题分析和解决方案
- 其它鸭子,都继承了Duck类,所以fly让所有子类都会飞了,这是不正确的
- 上面说的1 的问题,其实是继承带来的问题:对类的局部改动,尤其超类的局部改
动,会影响其他部分。会有溢出效应 - 为了改进1问题,我们可以通过覆盖fly 方法来解决 => 覆盖解决
- 问题又来了,如果我们有一个玩具鸭子ToyDuck, 这样就需要ToyDuck去覆盖Duck
的所有实现的方法 => 解决思路 策略模式 (strategy pattern)
23.3 UML类图(策略模式)
23.4 源码应用
23.5 注意事项
- 策略模式的关键是:分析项目中变化部分与不变部分
- 策略模式的核心思想是:多用组合/聚合 少用继承;用行为类组合,而不是行为的
继承。更有弹性 - 体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只
要添加一种策略(或者行为)即可,避免了使用多重转移语句(if…else if…else) - 提供了可以替换继承关系的办法: 策略模式将算法封装在独立的Strategy类中使得
你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展 - 需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大
二十四、责任链模式(OA系统审批)
24.1 UML类图
24.2 说明
- 职责链模式(Chain of Responsibility Pattern),
又叫 责任链模式,为请求创建了一个接收者
对象的链(简单示意图)。这种模式对请求的
发送者和接收者进行解耦。 - 职责链模式通常每个接收者都包含对另一个接
收者的引用。如果一个对象不能处理该请求,
那么它会把相同的请求传给下一个接收者,依
此类推。 - 这种类型的设计模式属于行为型模式