揭开设计模式面纱-初识Java设计模式

613 阅读13分钟

1 设计模式的目的

  • 代码重用性,不写重复代码
  • 可读性,其他程序员也能读懂
  • 可扩展性,增加功能时非常方便,易维护
  • 可靠性,增加新功能对原功能没影响
  • 使程序具备【高内聚低耦合】的特性

2 设计模式的七大原则

  • 单一职责原则
  • 接口隔离原则
  • 依赖倒置原则
  • 里氏替换原则
  • 开闭原则
  • 迪米特法则
  • 合成复用原则

2.1 单一职责原则

对类来说,一个类只负责一项职责

2.2 接口隔离原则

客户端不应该依赖他不需要的接口, 即一个类对另一个类的依赖应该建立在最小的接口上,用不到的方法进行隔离

2.3 依赖倒置原则

  • 高层模块不应该依赖低层模块,二者都应该依赖于抽象
  • 抽象不应该依赖细节,细节应该依赖抽象
  • 依赖倒置的中心思想是面向接口编程
  • 变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间就存在一个缓冲层,利于程序扩展和优化

2.4 里氏替换原则

  • 使用继承时,在子类中尽量不要重写父类的方法
  • 所有引用基类的地方必须能透明的使用其子类的对象
  • 继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合、组合、依赖来解决问题

2.5 开闭原则

  • 一个软件实体如类、模块、函数应该对扩展开放、对修改关闭,用抽象构建框架,用实现扩展细节
  • 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化

2.6 迪米特法则

  • 一个对象应该对其他对象保持最少的了解
  • 又叫最少知道原则,即一个类对自己依赖的类知道的越少越好,也就是说,对于被依赖的类不管都么复杂,都尽量将逻辑封装在类的内部,对外除了提供的public方法,不对外泄露任何信息(只和直接的朋友通信)

2.7 合成复用原则

  • 尽量使用合成/聚合的方式,而不是使用继承

3. 设计模式分类

3.1 创建型设计模式

3.1.1 单例模式

描述

采取一定的方法保证在整个软件系统中,对某个类智能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)

使用场景

需要频繁的创建和销毁的对象、创建对象时耗时过多或耗费资源过多,但又经常用到的对象,工具类对象、频繁访问数据库或文件的对象(数据源、session工厂)

单例模式的写法

  • 饿汉式(静态变量)

    • 构造器私有化(防止new)

    • 类的内部创建对象

    • 向外暴露一个静态的公共方法

      class Singleton {
          private Singleton{}
          
          private final static Singleton instance = new Singleton();
          
          public static Singleton getInstance() {
              return instance;
          }
      }
      
    • 优点:类装载就完成实例化,避免了线程安全问题

    • 缺点:如果从未使用过这个实例,会造成内存浪费

  • 饿汉式(静态代码块)

    • 优点:类装载就完成实例化,避免了线程安全问题
    • 缺点:如果从未使用过这个实例,会造成内存浪费
    • 代码实现
    class Singleton {
        private Singleton{}
        
        private static Singleton instance;
        
        static {
            instance = new Singleton();
        }
        
        public static Singleton getInstance() {
            return instance;
        }
    }
    
  • 懒汉式(线程不安全)

    • 优点: 起到了懒加载的效果,避免资源浪费,但是只能在单线程下使用
    • 缺点:多线程环境下会产生多个实例,无法保证完全单例
    • 代码实现
    class Singleton {
        private Singleton{}
        
        private static Singleton instance;
        
        public static Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    
  • 懒汉式(线程安全,同步方法)

    • 优点: 解决了线程安全问题

    • 缺点:效率太低,每个线程在想获取实例的时候都要进行同步

    • 代码实现

      class Singleton {
          private Singleton{}
          
          private static Singleton instance;
          
          public static synchronized Singleton getInstance() {
              if (instance == null) {
                  instance = new Singleton();
              }
              return instance;
          }
      }
      
  • 懒汉式(线程安全,同步代码块)

    • 优点:提高了执行效率
    • 缺点:不能起到线程同步的作用。多线程环境下会产生多个实例,无法保证完全单例
    • 代码实现
    class Singleton {
        private Singleton{}
        
        private static Singleton instance;
        
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized(Singleton.class) {
                    instance = new Singleton();
                }
            }
            return instance;
        }
    }
    
  • 懒汉式(双重检查锁)

    • 优点: 实现懒加载,同时解决了线程安全问题,同时保证了效率(避免反复同步)

    • 缺点:无

    • 代码实现

      class Singleton {
          private Singleton{}
          
          private static volatile Singleton instance;
          
          public static Singleton getInstance() {
              if (instance == null) {
                  synchronized(Singleton.class) {
                      if (instance == null) {
                          instance = new Singleton();
                      }
                  }
              }
              return instance;
          }
      }
      
  • 静态内部类

    • 特点
      • 外部类装载时,内部类不会装载
      • 静态内部类使用时才装载,且只会装载一次,不存在线程安全问题
    • 优点:采用类装载机制来保证初始化实例时只有一个线程,避免了线程不安全
    • 缺点:无
    • 代码实现
    class Singleton {
        private Singleton{}
        
        private static class SingletonInstance {
            private static final Singleton INSTANCE = new Singleton();
        }
        
        public static Singleton getInstance() {
            return SingletonInstance.INSTANCE;
        }
    }
    
  • 枚举

    • 特点: 借助枚举实现单例模式

    • 优点: 避免多线程同步问题,而且还能防止反序列化重新创建新的对象

    • 缺点:不适用于类实例私有化场景

    • 代码实现

      enum Singleton {
          INSTANCE;
          
          public void a() {
              
          }
      }
      

3.1.2 抽象工厂模式

3.1.2.1 简单工厂模式

描述

由一个工厂对象决定创建出哪一种产品类的实例,定义一个创建对象的类,由这个类来封装实例化对象的行为

使用场景

当我们会用到大量的创建某种、某类或某批对象时,就会使用到工厂模式

简单工厂.png

3.1.2.2 工厂方法模式

描述

定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类

使用场景

当我们需要创建满足多种特征的对象时,如北京的胡椒披萨、法国的草莓披萨,对创建对象的方法进行抽象,由不同的子类实现,将对象的实例化推迟到子类。

工厂方法.png

3.1.2.3 抽象工厂模式

描述

定义了一个接口用于创建相关或有依赖关系的对象簇,而无需指明具体的类,可以将简单工厂模式和工厂方法模式进行整合。

从设计层面看,抽象工厂模式就是对简单工厂模式的进一步抽象。

将工厂抽象为两层,抽象工厂和具体实现的工厂子类,程序员可以根据对象类型使用对应的工厂子类。

抽象工厂.png

3.1.3 原型模式

描述

用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象

工作原理

通过将一个原型对象传给那个要发起创建的对象,这个要发起创建的对象通过请求原型对象拷贝他们自己来实施创建,即对象.clone()

原型模式的实现

  • 实现Cloneable接口
  • 重写clone方法

原型模式在spring源码的应用

配置bean时指定scope为prototype即可将bean创建设置为原型模式,具体可查看ApplicationContext.getBean()的源码实现

成员变量浅拷贝的处理方式

  • 浅拷贝是使用默认的clone()方法来实现。

  • 对于基本数据类型,浅拷贝会直接进行值传递,也就是将该属性的值复制一份给新的对象。

  • 对于引用数据类型,浅拷贝会进行引用传递,也就是只是将该成员变量的引用(内存地址)复制一份给新的对象,因为实际上两个对象的该成员变量都是指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

成员变量深拷贝的处理方式

  • 对于基本数据类型,复制对象的成员变量值
  • 对于引用数据类型,为其申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象,也就是说,对象进行深拷贝要对整个对象进行拷贝
  • 实现方式
    • 重写clone()方法实现深拷贝
    • 通过对象序列化实现深拷贝(使用ObjectInputStream)

3.1.4 建造者模式

描述

建造者模式又叫生成器模式,是一种对象构建模式,他可以将复杂对象的建造过程抽象出来,使这个抽象过程的不同实现方法可以构造出不同表现的对象。

建造者模式是一步一步创建一个复杂的对象,他允许用户只通过指定复杂对象的类型和内容就可以构建他们,用户不需要知道内部·的具体构建细节,符合开闭原则。

建造者模式的角色

  • Product(产品角色):一个具体的产品对象
  • Builder(抽象建造者):创建一个product对象的各个部件指定的接口/抽象类
  • ConcreateBuilder(具体建造者):实现接口,构建和装配各个部件
  • Director(指挥者):构建一个使用Builder接口的对象

建造者模式.png

StringBuilder中建造者角色分析

  • Builder(抽象建造者):Appendable接口定义了多个append方法(抽象方法)
  • ConcreateBuilder(具体建造者):AbstractStringBuilder实现了Appendable接口方法;StringBuilder也充当了具体建造者,因为继承了AbstractStringBuilder
  • Director(指挥者):StringBuilder也是指挥者

使用场景

要求按照指定的蓝图建造产品,他的主要目的是通过组装零配件而产生一个新产品。

建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式

3.1.5 工厂模式

工厂模式的意义

将实例化的代码提取出来,放到一个类中统一管理和维护,达到依赖关系的解耦,从而提高项目的扩展和可维护性

工厂模式设计原则

  • 创建对象实例时,不要直接new类,而是把这个new类的动作放在一个工厂的方法中,并返回,变量不要直接持有具体类的引用
  • 不要让类继承具体类,而是继承抽象类或者是实现接口
  • 不要覆盖基类中已经实现的方法

3.2 结构型设计模式

3.2.1 适配器模式

描述

适配器模式将某个类的接口转换成客户端期望的另一个接口表示,主要目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作,其别名为包装器。

适配器模式属于结构性模式

分类

  • 类适配器模式
  • 对象适配器模式
  • 接口适配器模式

工作原理

  • 将一个类的接口转换成另一种接口,让原本接口不兼容的类可以兼容
  • 从用户的监督看不到被适配者,是解耦的
  • 用户调用适配器转换出来的目标接口方法,适配器在调用被适配者的相关接口方法
  • 用户收到反馈结果,感觉只是和目标接口交互
3.2.1.1 类适配器

要点

  • Java是单继承机制,所以类适配器需要继承源类这一点算是一个缺点,因为这要求目标类必须是接口,有一定局限性
  • 源类的方法在Adapter中都会暴露出来,也增加了使用的成本
  • 由于其继承了源类,所以他可以根据需求重写源类的方法,使得adapter的灵活性增强了

类适配器.png

3.2.1.2 对象适配器

介绍

  • 基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承源类,而是持有源类的实例,以解决兼容性的问题,即:持有源类,实现目标类接口,完成src->dist的适配
  • 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系
  • 对象适配器模式是适配器模式常用的一种

对象适配器.png

3.2.1.3 接口适配器

介绍

  • 当不需要全部实现接口提供的方法时, 可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
  • 适用于一个接口不想使用其所有的方法的情况
  • 根据“接口隔离原则”, 非必要不实现无关方法

接口适配器.png

三种适配器的区别

三种命名方式,是根据src是以怎样的形式给到adapter来命名的

  • 类适配器:以类给到,在adapter里,就是将src当做类,继承
  • 对象适配器:以对象给到,在adapter里,就是将src作为一个对象,持有
  • 接口适配器:以接口给到,在adapter里,就是将src作为一个接口,实现
3.2.1.4 springmvc适配器源码分析
  • SpringMVC中的HandlerAdapter,就使用到了适配器模式

  • SpringMVC处理流程

    • 通过HandlerMapping来映射Controller

      MappedHandler mappedHandler = getHandler(processRequest);

    • 获取适配器,遍历HandlerAdapters,执行ha.supports(handler),为true则返回。

      HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

    • 通过适配器调用Controller的方法返回ModelAndView

      mv = ha.handle(processRequest, response, mappedHandler.getHandler());

  • 使用HandlerAdapter的原因分析

    可以看到处理器的类型不同,由多重实现方式,那么调用方式就不是确定的,如果需要直接调用Controller方法,需要调用的时候就得不断使用if else来判断是哪一种子类然后执行。那么如果后面要扩展Controller,就得修改原来的代码,这样就违背了开闭原则

springmvc适配器.png

3.2.2 桥接模式

描述

  • 将实现与抽象放在两个不同的层次中,使两个层次可以独立改变。
  • 是一种结构型设计模式
  • 桥接模式基于类的单一职责原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。他的主要特点是把抽象与行为实现分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展。

原理类图

桥接模式.png

桥接模式在JDBC源码的应用

  • JDBC的Driver接口,如果从桥接模式看,Driver就是一个接口,下面可以有MySQL的Driver,Oracle的Driver,这些就可以当做实现接口类

jdbc桥接模式.png

3.2.3 装饰器模式

3.2.4 组合模式

3.2.5 外观模式

3.2.6 享元模式

3.2.7 代理模式

3.3 行为型设计模式

3.3.1 模板方法模式

3.3.2 命令模式

3.3.3 访问者模式

3.3.4 迭代器模式

3.3.5 观察者模式

3.3.6 中介者模式

3.3.7 备忘录模式

3.3.8 解释器模式

3.3.9 状态模式

3.3.10 策略模式

3.3.11 责任链模式