设计模式
一、创建型模式概述
1. 单例模式
(1)保证一个类仅有一个实例,并提供一个访问它的全局访问点。
(2)使用单例模式的目的
a. 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
b. 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间。
(3)单例模式的实现方式
a. 饿汉式加载:编译时创建实例对象
b. 懒汉式加载:运行时创建实例对象
(4)无论是饿汉式加载还是懒汉式加载,单例模式中都需要一个被修饰成private的构造函数,以及一个可以获得实例的public方法。
2. 工厂模式
(1)分类
a. 简单工厂模式:静态工厂方法
b. 工厂方法模式:针对不同的对象提供不同的工厂
c. 抽象工厂模式:工具箱模式
(2)目的
a. 解耦 :把对象的创建和使用的过程分开
b. 降低代码重复: 如果创建某个对象的过程都很复杂,需要一定的代码量,而且很多地方都要用到,那么就会有很多的重复代码;
c. 降低维护成本 :由于创建过程都由工厂统一管理,所以发生业务逻辑变化,不需要找到所有需要创建对象B的地方去逐个修正,只需要在工厂里修改即可,降低维护成本。
(3)抽象工厂实现流程
a. 创建产品接口
b. 创建产品接口实现类
c. 创建抽象工厂接口,实例化产品对象
d. 创建具体工厂
3. 建造者模式
(1)定义:它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
(2)优点:
a. 产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
b. 用户使用不同的具体建造者即可得到不同的产品对象
c. 可以更加精细地控制产品的创建过程
d. 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合 “开闭原则”
(3)建造者模式主要包含四个角色
a. Product(产品角色):一个具体的产品对象。
b. Builder(抽象建造者):创建一个Product对象的各个部件指定的抽象接口。
c. ConcreteBuilder(具体建造者):实现抽象接口,构建和装配各个部件。
d. Director(指挥者):构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。
4. 原型模式
(1)目的:在软件系统中,有些对象的创建过程较为复杂,而且有时候需要频繁创建,原型模式通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象,这就是原型模式的意图所在。在面向对象系统中,使用原型模式来复制一个对象自身,从而克隆出多个与原型对象一模一样的对象。
(2)模式分析
在原型模式结构中定义了一个抽象原型类,所有的Java类都继承自java.lang.Object,而Object类提供一个clone()方法,可以将一个Java对象复制一份。因此在Java中可以直接使用Object提供的clone()方法来实现对象的克隆
(3)原型模式的角色分析
a. Prototype(抽象原型类):负责定义用于复制现有实例来生成新实例的方法,一定继承Cloneable类;
b. ConcretePrototype(具体原型类):ConcretePrototype角色负责实现复制现有实例并生成新实例的方法;
c. Client角色负责使用复制实例的方法生成新的实例
二、结构型模式
1. 适配器模式
(1)适配器模式(Adapter Pattern)属于结构型模式的一种,把一个类的接口变成客户端所期待的另一种接口,从而使原本接口不匹配而无法一起工作的两个类能够在一起工作。类似一个转接器。(Spring中的通知Advice)
(2)类适配器与对象适配器的区别
a. 类适配器:对象继承的方式,静态的定义。适配器类继承被适配类,同时实现标准接口;
b. 对象适配器:依赖于对象的组合,都是采用对象组合的方式,也就是对象适配器实现的方式。创建适配器类,实现标准接口,将被适配类以对象的形式传入。
2. 装饰者模式
(1)目的:动态地给一个对象添加一些额外的职责。
(2)角色
a. Component:定义一个对象接口,可以给这些对象动态地添加职责;
b. ConcreteComponent:对Component的是实现类;
c. Decorator:这是一个抽象类,维持一个指向Component实例的引用,并定义一个与Component接口一致的接口。
d. ConcreteDecorator:具体的装饰对象,给内部持有的具体被装饰对象,增加具体的职责。抽象类的实现类。
3. 代理模式:代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
(1)静态代理:代理类在编译时创建
(2)动态代理:代理类在运行时创建,利用反射机制
a. 被代理接口
b. 被代理接口实现类
c. 代理对象生产执行器:需要实现InvocationHandler接口,重写invoke(Object proxy,Method method, Object[] args)方法。其中proxy表示代理对象,method 代理对象调用的方法,args 调用的方法中的参数。
d. 最后通过Proxy类的静态方法newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)。其中loader为被代理对象的类加载器,interface表示被代理对象实现的接口,h表示代理生产执行器。
三、 行为型模式
1. 策略模式
(1)定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化。
(2)模式结构
a. Context: 环境类
b. Strategy: 抽象策略类
c. ConcreteStrategy: 具体策略类
(3)策略模式让算法独立于使用它的客户而独立变化。策略模式重点是封装不同的算法和行为,不同的场景下可以相互替换。策略模式是开闭原则的体现,开闭原则讲的是一个软件实体应该对拓展开放对修改关闭。因为策略模式在加入新的策略时,不会影响其他类的修改,增加了拓展性,也就是对拓展是开放的;对于调用场景来说,只依赖于抽象,而不依赖于具体实现,所以对修改是关闭的。
2. 观察者模式(发布-订阅模式)
(1)动机:建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。在此,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展,这就是观察者模式的模式动机。
(2)模式结构
a. Subject: 抽象目标。一般用一个抽象类或一个接口实现,它把所有对观察者对象的引用保存在一个集合list里,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
b. ConcreteSubject: 具体目标。将有关状态存入具体观察者对象;在具体目标的内部状态改变时,给所有登记过的观察者发出通知。具体目标角色通常用一个具体子类实现。
c. Observer: 抽象观察者。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个Upadate()方法,这个方法叫做更新方法。
d. ConcreteObserver: 具体观察者。实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。
(3)使用场景
a. 当一个对象的改变需要同时改变其他对象,而且,它不知道具体有多少对象有待改变时,应该考虑使用观察者模式。
b. 当一个抽象模型有两个方面,其中一方面依赖于另一方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。
c. 观察者模式所做的工作其实就是在解除耦合。让耦合的双方都依赖于抽象,而不是依赖于具体,从而使得各自的变化都不会影响另一边的变化。是依赖倒转原则的最佳体现。
(4)Spring中Observer模式常用的地方是listener的实现。如ApplicationListener。
3. 模板方法模式
(1)要点
a. 模版方法定义了算法的步骤,把这些步骤的实现延迟到了子类。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
b. 模版方法模式为我们提供了一种代码复用的重要技巧。
c. 模版方法的抽象类可以定义具体方法、抽象方法和钩子。
d. 抽象方法由子类实现。
e. 为了防止子类改变模版方法中的算法,可以将模版方法声明为final
f. 模版方法和策略模式都封装了算法,一个用组合(策略模式),一个用继承(模版方法)。
(2)钩子hook
在模板方法模式中,钩子函数作为一个基本操作在模板方法之中被调用,在抽象基类中则为其提供一个空的或缺省的实现hook()。这样一来子类可以在必要时按需进行扩展,利用isHook()方法判断是否需要重写hook()函数来扩展应用。提高了子类代码的灵活性。有些模板可写可不写时,就需要加钩子。
设计模式例子
一、 单例模式
1. 饿汉式加载(立即加载)
2. 懒汉式加载(延迟加载)
3. 同步延时加载
4. 同步延迟加载 — 使用内部类实现延迟加载
5. 双重检测
6. ThreadLocal