设计模式笔记
一、设计模式的目的
- 代码重用性**(相同功能的代码,不用多次编写)**
- 可读性 (编码规范性,便于其他程序员阅读和理解)
- 可扩展性**(当需要增加功能时,能方便的扩展)**
- 可靠性**(新增功能后,对原来的功能没有任何影响)**
- 使程序呈现高内聚,低耦合
二、设计模式的七大原则
-
单一职责原则
- 降低类的复杂度,一个类应该只负责一项职责或者一个任务(如:用户类只处理用户的业务)
- 提高类的可读性,可维护性
- 降低变更引起的风险
- 通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级别违反单一职责原则;只有类中的方法数足够的少,才可以在方法级别保持单一职责原则
-
接口隔离原则
- 一个类应该依赖接口时,不应该实现它不需要的接口方法,就是用不到的方法就把它拆分成多个小的接口,依赖小的接口就可以了
-
依赖倒转(倒置)原则
-
定义
- 高层模块不依赖与低层模块,二者应该依赖于共同的抽象类或者接口
- 抽象不应该依赖细节,细节应依赖抽象
- 依赖倒转原则的中心思想是面向接口编程
- 实现理念:抽象指的是抽象类或者接口;细节指的是接口或者抽象类的具体实现类;抽象相对于实现来比要稳定的多,没有那么多变
- 使用抽象类或者接口的目的是为了制定好规范,这样不用涉及具体的操作,具体的操作交给他们的实现类去完成
-
依赖关系传递的3种方式:
- 接口传递
- 构造函数传递
- set传递
-
注意事项和细节
-
低层模块尽量都要有抽象类和接口,或者两者都有,程序的稳定性才好
-
变量的声明类型尽量是抽象类或者接口,这样变量引用和实际对象之间就存在一个缓冲层,便于程序的扩展性和优化
-
继承时遵循里氏替换原则
-
-
-
里氏替换原则
- 继承给程序设计带来便利(继承是为了代码复用,避免重复代码)的同时也带来了弊端,会增加对象间的耦合性,如 修改父类的方法时要考虑到所有子类,修改后可能子类的功能会产生影响
- 所有引用基类的地方必须能透明的使用其子类的对象
- 在使用继承时,遵循里氏替换原则,子类中尽量不要重写父类的方法
- 继承使两个类的耦合性增强了,在适当的情况下,可以通过聚合、组合、依赖的方法来解决问题
- 通用的做法,原来的父类和子类都继承一个通用的基类,原有的继承关系就去掉了,采用依赖、组合、聚合等关系代替
-
开闭原则(ocp)
- 模块和函数对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节
- 在软件需求变化的时候,尽量通过扩展软件方法或者类来实现,而不是通过修改原有的代码来实现
-
迪米特法则
- 迪米特法则又称 最少知道原则 在一个类中调用另一个类的方法,不需要知道它怎么实现的,也就是说,就只需要调用它的方法就完成功能,具体怎么实现的写在被调用方法里
-
合成复用原则
- 原则时尽量使用合成/聚合的方式,而不是继承,因为继承耦合性太强了
三、设计模式原则的核心思想
-
找出应用中可能需要变化的地方,将它们独立出来,不要和哪些不需要变化的代码混在一起。
-
针对接口编程,而不是针对实现编程。
-
为了交互对象之间的松耦合设计而努力。
四、UML类图
(一) 类之间的关系
-
依赖 (只要在类中用到对方,那么他们就存在依赖关系)
(1) 在类中用到对方就是依赖关系
(2)是类的成员属性
(3)是方法的返回值类型
(4)是方法接受的参数类型
(5)方法中使用到
-
泛化(实际上就是继承关系,他是依赖关系的一种特例)
-
实现(就是实现关系,也是依赖关系的一种特例)
-
关联(就是类与类之间的联系,分为单向一对一关系,双向一对一关系,具有导航性(谁继承谁)、还有多重性)
-
聚合(聚合关系表示的是整体和部分的关系,整体与部分可以分开。聚合关系是关联关系的特例)
-
组合(也是整体和部分的关系,强依赖 如 在类中new 对象了 这个对象就不可分离,伴随着类的生命周期 (而不是像聚合关系那样 引用的类定义在成员属性上 通过set方式传入) )
(二)用于描述系统中 类本身的组成结构 和 类(对象)之间的各种静态关系
五、 设计模式的类型
设计模式分为三种类型
- 创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式;
- ***结构型模式:***适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式;
- ***行为型模式:***模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、责任链模式(职责链模式)
六、 单例模式
单例模式的实现方式有八种:
饿汉式(静态常量)
饿汉式(静态代码块)
懒汉式(线程不安全)
懒汉式(线程安全,同步方法)
懒汉式(线程安全、同步代码块)
双重检查
静态内部类
枚举
一、 饿汉式(静态常量)
步骤:
-
构造器私有化(防止 new)
-
类的内部创建对象
-
向外暴露一个静态的公共方法(getInstance)
这种写法的优缺点:
**优点:**写法简单,在类装载的时候就能完成实例化。避免了线程同步的问题。
**缺点:**在类装载的时候就完成了实例化,没有达到 懒加载 的效果,如果程序从始至终都没用过,就造成了内 存的浪费。
结论:这种单例模式可用,但是可能造成内存的浪费。
二、 饿汉式(静态代码块)
优缺点都跟 上面的一样,只是写法改变了,将实例化操作放到了静态代码块里面。
三、 懒汉式(线程不安全)
优缺点:
优点***:***起到了 懒加载 的效果 ,但是只能在单线程下使用
***缺点:***如果在多线程下使用,第一个线程进入 if(instance==null) 时, 第二个线程也紧接着进来,第一个线程还未创建出对象,所以第 二个判断 if(instance==null) 时还是为true 就会造成创建多个 对象,违背了单例模式
结论:在实际开发中,不要使用这种方式
四、懒汉式(线程安全、同步方法)
在上面的基础上 在 getInstance() 方法上 加上 synchronized 同步
优缺点:
优点:解决了线程不安全问题
缺点:效率太低,每次想要获得实例的时候,执行getInstance()都会进 行同步,效率低
五、懒汉式(线程安全、同步代码块)
加入了同步代码块 在 判断 null 的下面 加入 synchronized(Singleton.class){ 实例化对象代码 }
优缺点:存在线程安全问题,不能使用
六、双重检查
-
双重验证概念时多线程开发中常用到的,如上图,进行2次null的检查,这样就保证了 线程的安全
-
实例化代码只用执行一次,后面在访问时,判断单例对象是否为空时,直接return 实例化对象,避免反复进行方法同步。
-
线程安全;延迟加载(懒加载);效率较高
结论:在实际开发中,推荐使用这种单例设计模式
七、静态内部类
优缺点:
- 这种方式采用了类装载机制来保证初始化实例时只有一个线程
- 静态的内部类只有在调用getInstance()的时候才会去装载SingletonInstance类
- 类的静态属性只会在第一次加载类的时候初始化,JVM保证了线程的安全性,因为在类进行初始化时,别的线程时无法进入的。
- 避免了线程不安全,利用静态内部类的特点实现延迟加载,效率高
八、枚举方式
借助JDK1.5中添加枚举来实现单例模式,不仅能避免多线程同步问题,还能防止反序列化重新创建新的对象。
七、单例模式的注意事项和使用场景
-
单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使 用单例模式可以提高系统性能
-
实例化一个单例类时,应该是 调用类的获取实例方法,而不是去new
-
使用场景:需要频繁的进行创建和销毁的对象,或者创建对象耗时过多或者耗费资源过多1) (即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)
八、工厂模式
一、 工厂模式的意义
将实例化的代码提取出来,放到一个类中统一管理和维护,这个类就是工厂类,可以达到和主项目依赖关系的解耦,提高项目的拓展和维护性。
二、 工厂模式分类:
代码详见工程 G:\Test_Project\DesignMode\src\main\java\com\lizihao\design\factory
*简单工厂模式*
*工厂方法模式*
*抽象工厂模式*
三、 设计模式的依赖抽象原则
-
创建对象实例时,不要直接 new 类, 而是把这个 new 类的动作放在一个工厂的方法中,并返回。有的书上说, 变量不要直接持有具体类的引用。
-
不要让类继承具体类,而是继承抽象类或者是实现 interface(接口)
-
不要覆盖基类中已经实现的方法。
九、原型模式
原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。Prototype类需要具备以下两个条件:
实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。 重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。
原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。
十、建造者模式
一、基本介绍
1)建造者模式(Builder Pattern) 又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
2)建造者模式 是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们, 用户不需要知道内部的具体构建细节。
二、建造者模式的四个角色
-
Product(产品角色): 一个具体的产品对象。
-
Builder(抽象建造者): 创建一个 Product 对象的各个部件指定的 接口**/**抽象类。
-
ConcreteBuilder(具体建造者): 实现接口,构建和装配各个部件。
-
Director(指挥者): 构建一个使用 Builder 接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。
三、建造者模式解决盖房的应用案例
-
需要建房子:这一过程为打桩、砌墙、封顶。不管是普通房子也好,别墅也好都需要经历这些过程,下面我们使用建造者模式(Builder Pattern)来完成
-
思路分析图解(类图)
四、建造者模式注意事项和细节
-
客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
-
每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象
-
可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰, 也更方便使用程序来控制创建过程
-
增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”
-
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
-
如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式
-
抽象工厂模式 VS 建造者模式 :抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品
十一、适配器模式
一、基本介绍
-
适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
-
适配器模式属于结构型模式
-
主要分为三类:类适配器模式、对象适配器模式、接口适配器模式
二、工作原理
-
适配器模式:将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼容
-
从用户的角度看不到被适配者,是解耦的
-
用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
用户收到反馈结果,感觉只是和目标接口交互
三、类适配器模式
1) 应用实例说明
以生活中充电器的例子来讲解适配器,充电器本身相当于 Adapter,220V 交流电相当于 src (即被适配者),我们的目 dst(即 目标)是 5V 直流电
2) 思路分析(类图)
3)类适配器模式的注意细节
1) Java 是单继承机制,所以类适配器需要继承 src 类这一点算是一个缺点, 因为这要求 dst 必须是接口,有 一定局限性;
2) src 类的方法在 Adapter 中都会暴露出来,也增加了使用的成本。
3) 由于其继承了 src 类,所以它可以根据需求重写 src 类的方法,使得 Adapter 的灵活性增强了。
四、对象适配器模式
- 基本介绍
1) 基本思路和类的适配器模式相同,只是将 Adapter 类作修改,不是继承 src 类,而是持有 src 类的实 例,以解决兼容性的问题。 即:持有 src 类,实现 dst 类接口,完成 src->dst 的适配
2) 根据“合成复用原则”,在系统中尽量使用关联关系(聚合)来替代继承关系。
3) 对象适配器模式是适配器模式常用的一种
-
应用实例
以生活中充电器的例子来讲解适配器,充电器本身相当于 Adapter,220V 交流电相当于 src (即被适配者),我们的目 dst(即目标)是 5V 直流电,使用对象适配器模式完成。
-
思路分析(类图):只需修改适配器即可, 如下:
五、接口适配器模式
-
基本介绍
-
一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式。
-
核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
-
适用于一个接口不想使用其所有的方法的情况。
-
十一、桥接模式
一、 基本介绍
1) 桥接模式(Bridge 模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
2) 是一种结构型设计模式
3) Bridge 模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职 责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分 的独立性以及应对他们的功能扩展
二、 案例介绍(传统方案解决手机操作问题)
传统方式实现
使用桥接模式改进传统方式,让程序具有搞好的扩展性,利用程序维护
1) 使用桥接模式对应的类图
三、桥接模式的注意事项和细节
1)实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来, 这有助于系统进行分层设计,从而产生更好的结构化系统。
2)对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完 成。 3)桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
4)桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽 象进行设计和编程 5)桥接模式要求正确识别出系统中两个独立变化的维度(抽象、和实现),因此其使用范围有一定的局限 性,即需要有这样的应用场景。
桥接模式其它应用场景
对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用.
十二、 代理模式
一、代理模式介绍
1) 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这 样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
2) 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
3) 代理模式有不同的形式, 主要有三种 静态代理、动态代理 (JDK 代理、接口代理)和 Cglib 代理 (可以 在内存动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴) 。
4) 代理模式示意图:
二、静态代理
**介绍:**静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接 口或者是继承相同父类
-
静态代理模式的优缺点
-
优点:在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展
-
缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类、
一旦接口增加方法,目标对象与代理对象都要维护
-
三、动态代理
**介绍:**1) 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
2) 代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象
3) 动态代理也叫做:JDK 代理、接口代理
**用法:**简单来说就是用Proxy类代理使用 如下图:
四、Cglib代理
介绍:
-
静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是 Cglib 代理
-
Cglib 代理也叫作子类代理**,**它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将 Cglib 代理归属到动态代理。
-
Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口.它广泛的被许多 AOP 的框架使用,例如 Spring AOP,实现方法拦截
-
在 AOP 编程中如何选择代理模式:
-
目标对象需要实现接口,用 JDK 代理
-
目标对象不需要实现接口,用 Cglib 代理
Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类、
十三、模板方法模式
一、基本介绍
-
模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern), 在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
-
简单说,模板方法模式 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤
-
这种类型的设计模式属于行为型模式。
Ø 对原理类图的说明-即(模板方法模式的角色及职责)
-
AbstractClass 抽象类, 类中实现了模板方法(template),定义了算法的骨架,具体子类需要去实现 其它的抽象方法 operationr2,3,4
-
ConcreteClass 实现抽象方法 operationr2,3,4, 以完成算法中特点子类的步骤
二、案例(豆浆问题)
三、模板方法的注意事项和细节
-
基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
-
实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。
-
既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。
-
该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
-
一般模板方法都加上 final 关键字, 防止子类重写模板方法.
-
模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤 ,这一系列的步骤基本相同,但其个别步骤在实现时 可能不同,通常考虑用模板方法模式来处理
十四、观察者模式
一、基本介绍
-
观察者模式类似订牛奶业务
-
奶站/气象局:Subject
-
用户/第三方网站:Observer
**Subject:**登记注册、移除和通知
-
registerObserver 注 册
-
removeObserver 移 除
-
notifyObservers() 通知所有的注册的用户,根据不同需求,可以是更新数据,让用户来取,也可能是实施推送, 看具体需求定
**Observer:**接收输入
**观察者模式:**对象之间多对一依赖的一种设计方案,被依赖的对象为 Subject,依赖的对象为 Observer,Subject
通知 Observer 变化,比如这里的奶站是 Subject,是 1 的一方。用户时 Observer,是多的一方。
下图中 WeatherDatat(天气数据) 是 单一的一方 ,Baidu 和 CurrentCondition 是多 的一方
十五、策略模式
一、基本介绍
-
策略模式(Strategy Pattern)中,定义算法族(策略组),分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
-
这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)。
在软件开发中,我们也常常会遇到类似的情况,实现某一个功能有多条途径,每一条途径对应一种算法,此时我们可以使用一种设计模式来实现灵活地选择解决途径,也能够方便地增加新的解决途径。
譬如商场购物场景中,有些商品按原价卖,商场可能为了促销而推出优惠活动,有些商品打九折,有些打八折,有些则是返现10元等。
而优惠活动并不影响结算之外的其他过程,只是在结算的时候需要根据优惠方案结算
角色:
Context(环境类):环境类是使用算法的角色,它在解决某个问题(即实现某个方法)时可以采用多种策略。在环境类中维持一个对抽象策略类的引用实例,用于定义所采用的策略。
Strategy(抽象策略类):它为所支持的算法声明了抽象方法,是所有策略类的父类,它可以是抽象类或具体类,也可以是接口。环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。
ConcreteStrategy(具体策略类):它实现了在抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理。
十六、责任链模式
一、基本介绍
责任链模式(Iterator Pattern), 是行为型设计模式之一。这种模型结构有点类似现实生活中铁链,由一个个铁环首尾相接构成一条链,如果这种结构用在编程领域,则每个节点可以看做一个对象,每个对象有不同的处理逻辑,将一个请求从链的首端发出,沿着链的路径依次传递每个节点对象,直到有对象处理这个请求为止,我们将这样一种模式称为责任链模式。
二、责任链模式使用场景
- 多个对象可以处理同一个请求,但具体由哪个对象处理则在运行时动态决定。
- 在请求处理者不明确的情况下向对个对象中的一个提交一个请求。
- 需要动态处理一组对象处理请求。
UML类图
调用过程