设计模式


第一部分 面向对象设计原则---支持可维护性复用

设计原则名称 使用频率
单一职责原则(Single Responsibility Principle, SRP) 一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类 ★★★★☆
开闭原则(Open-Closed Principle, OCP) 软件实体应当对扩展开放,对修改关闭 ★★★★★
里氏代换原则(Liskov Substitution Principle, LSP) 所有引用基类的地方必须能透明地使用其子类的对象 ★★★★★
依赖倒转原则(Dependence Inversion Principle, DIP) 高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象 ★★★★★
接口隔离原则(Interface Segregation Principle, ISP) 客户端不应该依赖那些它不需要的接口 ★★☆☆☆
合成复用原则(Composite Reuse Principle, CRP) 优先使用对象组合,而不是继承来达到复用的目的 ★★★★☆
迪米特法则(Law of Demeter, LoD) 每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位 ★★★☆☆

1.单一职责原则:一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中(就一个类而言,应该仅有一个引起它变化的原因)

2.开闭原则:软件实体应当对扩展开放,对修改关闭

  • 软件实体:可以是一个软件模块、一个由多个类组成的局部结构或一个独立的类
  • 拓展:软件实体应尽量在不修改原有代码的情况下进行扩展

3.里氏代换原则:所有引用基类的地方必须能透明地使用其子类的对象

  • 将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常
  • 尽量使用基类类型来对对象进行定义

4.依赖倒转原则:高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象

  • 要针对接口编程,不要针对实现编程
  • 尽量引用层次高的抽象层类
  • 在程序中尽量使用抽象层进行编程,而将具体类写在配置文件中
  • 依赖注入
    • 构造注入
    • 设值注入(Setter注入)
    • 接口注入

5.接口隔离原则:客户端不应该依赖那些它不需要的接口

  • 分割大接口为细小的接口
  • 客户端仅需知道与之相关的方法
  • 每一个接口应该承担一种相对独立的角色
  • 接口定义
    • 一个类型所提供的所有方法特征的集合;代表一个角色
    • 狭义的特定语言的接口;定制服务

6.合成复用原则:优先使用对象组合,而不是继承来达到复用的目的

  • 关联关系使用已有对象使之成为新对象的一部分
  • 委派已有对象的方法
  • 少用继承,多用组合/聚合
    • 继承缺点太大:三大缺点
      • 会继承父类中不需要的方法
      • 要继承的类是final类,无法继承
      • 修改不方便

7.迪米特法则:一个软件实体应当尽可能少地与其他实体发生相互作用

  • 尽量减少对象之间的交互
  • 引入一个合理的“第三者”来降低现有对象之间的耦合度
  • 举例:net->star(QQ聊天群)

第二部分 设计模式

一、简单工厂模式(静态工厂方法模式)

  1. 概念:定义一个工厂类,他可以根据参数的不同返回不同的类的实例,被创建的实例通常都具有相同的父类。
  2. 结构:
    • Factory(工厂角色):工厂类,负责实现创建所有产品实例的内部逻辑。提供静态工厂方法factoryMethod(),返回抽象产品类型
    • Product(抽象产品角色):所有对象的父类,封装公有方法。
    • ConcreteProduct(具体产品角色):工厂创建的目标,需要实现抽象方法
  3. 关于创建对象与使用对象:new;反射机制;reflection;克隆方法clone/copy;Factory反序列化

简单工厂模式

二、工厂方法模式

  1. 定义:定义一个用于创建对象的接口,但是让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类

  2. 结构:

    • Product:抽象产品
    • ConcreteProduct:具体产品
    • Factory:抽象工厂
    • ConcreteFactory:具体工厂
  3. 反射机制与配置文件

三、抽象工厂模式

1.产品等级结构:产品的继承结构,抽象电视机与具体品牌的电视机

2.产品族:同一工厂的一组产品

3.定义:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类

4.结构:至少需要四个类

  • AbstractFactory:抽象工厂
  • ConcreteFactory:具体工厂
  • AbstractProduct:抽象产品
  • ConcreteProduct:具体产品

5.开闭原则的倾斜性

  • 增加产品族时:符合开闭原则
  • 增加产品等级结构时:不符合开闭原则

抽象工厂模式

四、原型模式

1.定义:使用原型实例指定待创建对象的类型,且通过复制这个原型来创建新的对象

2.三个角色

  • Prototype(抽象原型类):声明克隆方法的接口,具体原型类的公共父类
  • ConcretePrototype(具体原型类):实现clone方法
  • Client(客户类)

3.浅clone与深clone:是否复制包含在原型对象中引用类型的成员变量

  • 浅clone不复制,深clone复制

4.原型模式实现

  • 通用实现方法:在clone方法中实例化一个自身类型相同的对象并且将其返回,同时将相关参数传入新创建的对象中,保证成员变量相同
    • 对于引用类型的对象,使用赋值来实现复制,是浅clone
    • 使用创建一个全新的成员对象来实现复制,是深clone
  • Java语言中的clone()方法和Cloneable接口
    • Java中clone方法满足以下几点
      • x.clone()!=x
      • x.clone().getClass()==x.getClass()
      • 若equals()方法定义恰当,x.clone.equals(x)

5.深clone解决方案

  • 通过序列化,将对象写到流中
  • 需要实现Serializable接口

6.原型管理器

  • 将多个原型对象储存在一个集合中供客户端使用,专门负责克隆对象的工厂

7.优点+缺点+使用环境:P102

五、单例模式

  1. 定义:确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例

  2. 实现:一个私有构造函数 (确保只能单例类自己创建实例)、一个私有静态变量 (确保只有一个实例)、一个公有静态函数 (给使用者提供调用方法)

  3. 应用场景:网站计数器、应用程序的日志应用、Web项目中的配置对象的读取、数据库连接池、多线程池。

  4. 解决方案

    • 饿汉式单例:在定义静态变量的时候实例化单例类
      1. 类被加载的时候,成员变量被初始化,唯一实例被创建
      2. 优点: 提前实例化好了一个实例,避免了线程不安全问题的出现。
      3. 缺点: 直接实例化好了实例,不再延迟实例化;若系统没有使用这个实例,或者系统运行很久之后才需要使用这个实例,都会操作系统的资源浪费。
public class Singleton {
    private static Singleton uniqueInstance = new Singleton();
    private Singleton() { }
    
    public static Singleton getUniqueInstance() {
        return uniqueInstance;
    }
}
  • 懒汉式单例:在第一次被引用的时候将自己实例化(延迟加载)

    • 线程不安全懒汉式
      1. 优点: 延迟了实例化,如果不需要使用该类,就不会被实例化,节约了系统资源。
      2. 缺点: 线程不安全,多线程环境下,如果多个线程同时进入了 if (uniqueInstance == null) ,若此时还未实例化,也就是uniqueInstance == null,那么就会有多个线程执行 uniqueInstance = new Singleton(); ,就会实例化多个实例;
    public class Singleton {
         private static Singleton uniqueInstance;
         private Singleton() { }
       
        public static Singleton getUniqueInstance() {
            if (uniqueInstance == null) {
                uniqueInstance = new Singleton();
            }
            return uniqueInstance;
        }
    }
    
    • 线程安全懒汉式:使用synchronized关键字对getInstance()方法上锁(高并发下会导致系统性能降低)
      1. 优点: 延迟实例化,节约了资源,并且是线程安全的。
      2. 缺点: 虽然解决了线程安全问题,但是性能降低了。因为,即使实例已经实例化了,既后续不会再出现线程安全问题了,但是锁还在,每次还是只能拿到锁的线程进入该方法,会使线程阻塞,等待时间过长。
    public class Singleton {
        private static Singleton uniqueInstance;
        private static singleton() {}
       
        private static synchronized Singleton getUinqueInstance() {
            if (uniqueInstance == null) {
                uniqueInstance = new Singleton();
            }
            return uniqueInstance;
        }
    }
    
    • 双重检查锁定:对创建实例代码锁定的同时,再加一行检查代码(需要在静态成员变量的前面加上volatile关键词,取消虚拟机对于代码的优化--重复检查代码)
      1. 使用 volatile 关键字修饰了 uniqueInstance 实例变量 :uniqueInstance = new Singleton(); 这段代码执行时分为三步:(1)为 uniqueInstance 分配内存空间;(2)初始化 uniqueInstance;(3)将 uniqueInstance 指向分配的内存地址。正常的执行顺序当然是 1>2>3 ,但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。 单线程环境时,指令重排并没有什么问题;多线程环境时,会导致有些线程可能会获取到还没初始化的实例。 例如:线程A 只执行了 1 和 3 ,此时线程B来调用 getUniqueInstance(),发现 uniqueInstance 不为空,便获取 uniqueInstance 实例,但是其实此时的 uniqueInstance 还没有初始化
      2. 优点: 延迟实例化,节约了资源;线程安全;并且相对于 线程安全的懒汉式,性能提高了。
      3. 缺点: volatile 关键字,对性能也有一些影响。
    public class Singleton {
        private volatile static Singleton uniqueInstance;
        private Singleton() { }
        
        public static Singleton getUniqueInstance() {
            if (uniqueInstance == null) {
                synchronized (Singleton.class) {
                    if (uniqueInstance == null) {
                        uniqueInstance = new Singleton();
                    }
                }
            }
            return uniqueInstance;
        }  
    }
    
  • 静态内部类实现(线程安全):首先,当外部类 Singleton 被加载时,静态内部类 SingletonHolder 并没有被加载进内存。当调用 getUniqueInstance() 方法时,会运行 return SingletonHolder.INSTANCE; ,触发了 SingletonHolder.INSTANCE ,此时静态内部类 SingletonHolder 才会被加载进内存,并且初始化 INSTANCE 实例,而且 JVM 会确保 INSTANCE 只被实例化一次。

    1. 优点: 延迟实例化,节约了资源;且线程安全;性能也提高了。
public class Singleton {
    private Singleton() { }
    
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getUniqueInstance() {
        return SingletonHolder.INSTANCE;
    } 
}
  • 枚举类实现(线程安全):默认枚举实例的创建就是线程安全的,且在任何情况下都是单例。
    1. 优点: 写法简单,线程安全,枚举天然防止反射和反序列化调用。
public enum Singleton {
    INSTANCE;
    
    //添加自己需要的操作
    public void doSomeThing() { }
}

六、适配器模式

1.定义:将一个类的接口转换成客户希望的另一个接口,适配器模式让那些接口不兼容的类可以一起工作

2.结构

  • Target-目标抽象类:客户所需的接口
  • Adapter-适配器类:转换器,产生联系
  • Adaptee-适配者类:希望使用的接口

3.实现

  • 类适配器:适配器实现Target接口,与适配者是继承关系
  • 对象适配器:适配器继承Target接口,与适配者是关联关系

4.缺省适配器模式

  • 定义:当不需要实现一个接口中提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中的每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性的覆盖父类的某些方法来实现需求。

  • 适用于不想使用一个接口中所有方法的情况,又称为单接口适配器模式

  • 结构:

    • ServiceInterface-适配者接口:接口,有大量方法
    • AbstractServiceClass-缺省适配器类:使用空方法实现
    • ConcreteServiceClass-具体业务类:子类,选择覆盖方法

5.双向适配器:Target和Adaptee可以互换角色

6.优点+缺点+适用环境:P129

P.S.判断类模式和对象模式:关联关系还是继承关系

七、桥接模式(毛笔和蜡笔)

1.定义:将抽象部分(粒度大)与他的实现部分(粒度小)解耦,使得两者都能够独立变化

2.结构:

  • Abstraction-抽象类:
  • RefinedAbstraction-扩充抽象类:
  • Implementor-实现类接口:
  • ConcreteImplementor-具体实现类:

3.实现:毛笔和蜡笔

4.桥接模式和适配器模式的联用

5.优点+缺点+适用环境:P144

八、外观模式(茶馆喝茶)

1.定义:为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易被使用。(构建了一个茶馆服务员)

2.结构

  • Facade-外观角色:茶馆服务员
  • SubSystem-子系统角色:需要的茶具,开水之类

3.实现

  • 普通外观模式在实现过程中由于没有抽象层,在需要调用新的子系统的时候,需要修改源代码,违背开闭原则
  • 解决---抽象外观类
  • 可以与单例模式联用:控制系统资源

4.优点+缺点+适用环境:P185

九、代理模式(代购)

1.定义:给某一个对象提供一个代理或者占位符,并由代理对象来控制对源对象的访问

2.结构

  • Subject-抽象主题角色:真实主题和代理主题的共同接口,Client对于它进行编程,代购行业
  • Proxy-代理主题角色:代购人
  • RealSubject-真实主题角色:代购商品

3.实现

  • 远程代理:为一个位于不同地址空间中的对象提供一个本地的代理(大使)
  • 虚拟代理:使用资源消耗消耗相对较小的对象表示资源消耗较大的对象(桌面快捷方式)
  • 保护代理:控制对一个对象的访问,提供不同级别的使用使用权限
  • 缓冲代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果(内存->硬盘 空间换时间)
  • 智能引用代理:提供一些额外的操作
  • Java动态代理:在系统运行时根据需要动态创建代理类

4.优点+缺点+适用环境:P217

十、装饰模式

1.定义:动态的给一个对象增加一些额外的职责,就扩展功能而言,装饰模式提供了一种比较用子类更加灵活的代替方案

2.实例:awt中的JList与JTable没有滚动条,所以需要作为参数传递给JScrollPane构造函数,来实现滚动条的效果

3.结构

  • Component-抽象构件:共同父类
  • ConcreteComponent-具体构件:需要使用的构件
  • Decorator-抽象装饰类:装饰类的抽象层
  • ConcreteDecorator-具体装饰类:负责给构件添加新的职责

十一、职责链模式(向学校请假)

  1. 定义:避免将一个请求的发送者与接受者耦合在一起,让多个对象独有机会处理请求。将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止

  2. 结构

    • Handler-抽象处理者:处理者的抽象层,面向该类编程,定义一个抽象处理者类型的对象,声明处理方法
    • ConcreteHandler-具体处理者:实现处理方法
    • 例子:创建抽象类 AbstractLogger,带有详细的日志记录级别。然后创建三种类型的记录器,都扩展了 AbstractLogger。每个记录器消息的级别是否属于自己的级别,如果是则相应地打印出来,否则将不打印并把消息传给下一个记录器。
  3. 实现

    • 纯职责链模式:每个请求一定被处理,且只被一个处理者处理
    • 不纯职责链模式:每个处理者可以只处理请求的一部分,请求也可以不被处理
    • 权限小的处理者要在链的前面
    • 链在客户端中定义----建链

十二、命令模式(开关)

1.定义:将一个请求封装为有一个对象,从而可用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作

2.结构

  • Command-抽象命令类:声明execute()方法,可用调用请求接收者的方法
  • ConcreteCommand-具体命令类:实现方法,调用具体请求接收者
  • Invoker-调用者:请求发送者
  • Receiver-接收者:执行与请求相关的操作

3.实现

  • 命令队列
  • 记录请求日志
  • 撤销操作
  • 宏命令

十三、观察者模式

1.定义:定义对象之间的一种一对多的依赖关系,使得每当一个对象状态发生改变时其相关依赖对象皆得到通知并被自动更新

2.结构:

  • Subject-目标:被观察的对象
  • ConcreteSubject-具体目标:
  • Observer-观察者:观察目标的改变作出反应
  • ConcreteObserver-具体观察者:

3.实现

4.优点、缺点、使用环境

十四、模板方法模式---类行为型模式(流程)

1.定义:定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤

2.结构:

  • AbstractClass-抽象类:定义一系列基本操作、步骤,实现相同方法
  • ConcreteClass-具体子类:特定实现不同的步骤

3.实现

  • 模板方法:组合一系列步骤
  • 基本方法:实现各个步骤
    • 抽象方法
    • 具体方法
    • 钩子方法:一般返回bool类型,判断是否执行某个步骤

十五、策略模式

  1. 定概念

    • 定义一系列算法,将每一个算法封装起来,并让他们可以相互替换。策略模式让算法可以独立于使用它的客户而变化
    • 优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
    • 缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
  2. 结构:

    • Context-环境类:使用算法的角色
    • Strategy-抽象策略类:声明算法
    • ConcreteStrategy-具体策略类:实现算法

    策略模式

十六、组合模式(文件与文件夹)

1.定义:组合多个对象形成树形结构以表示具有部分-整体关系的层次结构。组合模式让客户端可以统一对待单个对象和组合对象

2.结构

  • Component-抽象构件:接口或者抽象类
  • Leaf-叶子构件:相当于文件
  • Composite-容器构件:相当于文件夹

3.实现

  • 透明组合模式:Component中声明所有管理成员对象的方法,不安全(文件不能拥有文件夹的方法)
  • 安全组合模式:Component中不声明管理成员对象的方法,在Composite中声明并实现

4.优点+缺点+适用环境:P158

面试题

1.单例模式为什么要使用双段锁呢?

posted @ 2021-12-22 15:40  汤十五  阅读(42)  评论(0)    收藏  举报