秋招-设计模式概述


本文介绍一下设计模式,(暂时介绍常见的设计模式)。

1. 概述

什么是设计模式呢?为什么要采用设计模式呢?

在软件工程中,设计模式是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案,就像类是由万物抽象出来类似,设计模式也是从各种经典的解决方案中抽象出来的,就是某类问题的通用解决方案,这些方案是经过相当长时间的试验和错误总结出来的。比如软件经常新增附加的功能,比如某个系统只能创建一个主体对象等。

框架中经常采用各种各样的设计模式。一共有23种设计模式。23种设计模式可分为三种类型:

  1. 创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式。
  2. 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
  3. 行为型模式:模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)。

2. 七大原则

设计模式中常用的七大原则有:

  1. 单一职责原则
  2. 接口隔离原则
  3. 依赖倒转原则
  4. 里氏替换原则
  5. 开闭原则
  6. 迪米特原则
  7. 合成复用原则

3. 单例模式

单例模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法

  1. 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提供系统性能
  2. 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new。
  3. 单例模式使用场景:需要频繁使用的进行创建和销毁的对象、创建对象消耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)

单例模式在开发中有八种方式:

  1. 饿汉式(静态常量)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Mouse {

    // 私有化构造方法
    private Mouse(){}

    // 定义常量,创建实例
    public static final Mouse instance = new Mouse();

    // 提供静态方法,返回实例。
    public static Mouse getInstance(){
    return instance;
    }
    }

    优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。

    缺点:在类装载的时候就完成实例化,没有达到Lazy Loading(懒加载)的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

    这种方式基于类加载机制避免了多线程的同步问题,不过,instance在类装载时就实例化。因为导致类装载的方式有很多,在程序中显式调用只是其中一种,有时候不能确定是否有其他方式导致类装载,从而导致无故被装载,导致实例化对象,造成内存浪费

    因为类加载就创建实例,这种方式即需要的时候,保证肯定已经创建好了,成为饿汉式。

  2. 饿汉式(静态代码块)

    和上面类似,只不过是在静态代码块中创建实例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Mouse2 {

    // 私有化构造方法
    private Mouse2(){}

    // 定义常量,创建实例
    public static Mouse2 instance;

    // 静态代码块
    static {
    instance = new Mouse2();
    }

    // 提供静态方法,返回实例。
    public static Mouse2 getInstance(){
    return instance;
    }
    }

    优缺点和上面类似。注意,饿汉式是线程安全的,缺点是会造成内存浪费。

  3. 懒汉式(线程不安全)

    懒汉式,就是需要的时候,才创建对象,而不是类加载的时候就创建了。但是显然线程不安全,对同一个变量赋值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Dog {

    private static Dog instance;

    private Dog(){}

    // 提供一个静态的共有方法,当使用到该方法时,才会创建instance
    // 即懒汉式,先判断是否为空,如果为空,则创建,不为空则返回已有的对象。
    public static Dog getInstance() {

    if(instance == null) {
    instance = new Dog();
    }

    return instance;
    }
    }

    优点:达到了懒加载的效果。

    缺点:在多线程下,如果有一个线程进入到了if判断里面,还没有来得及创建对象并赋值,而此时另一个线程也有可能进入,导致创建多个对象。显然不满足要求,这种方式不能使用

  4. 懒汉式(线程安全,同步方法)

    既然线程不安全,加入同步处理机制即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Dog2 {

    private static Dog2 instance;

    private Dog2(){}

    // 提供一个静态的共有方法,当使用到该方法时,才会创建instance
    // 即懒汉式,先判断是否为空,如果为空,则创建,不为空则返回已有的对象。
    // 加入synchronized同步,保证线程安全
    public static synchronized Dog2 getInstance() {

    if(instance == null) {
    instance = new Dog2();
    }

    return instance;
    }
    }

    虽然此方法解决了线程安全,但是后续如果有多个线程进入同步方法,其实已经创建好对象了,没必要同步串行进行该方法,直接返回return即可。这种方式效率太低,在实际开发中,不建议使用这种方式

  5. 懒汉式(线程安全,同步代码块)

    同步代码块在方法里面,if外面进行synchronized包装。

  6. 双重检查(Double-Check)

    既然上面的懒汉式效率太低,此时可以添加一层非同步代码块if判断,这样,多线程达到的时候就可以并行判断了。但是核心创建对象仍然需要synchronized同步。但是要注意,因为外层非同步代码块用到了共享变量,所以为了保证及时可见,要加volatile修饰。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class Dog3 {

    private static volatile Dog3 instance;

    private Dog3(){}

    public static Dog3 getInstance() {

    // 首次判断,非同步
    if(instance == null) {

    // 同步代码块
    synchronized (Dog3.class) {
    if(instance == null) {
    instance = new Dog3();
    }
    }

    }

    return instance;
    }
    }

    既实现了懒加载,也解决了线程安全问题,并且也保证了效率,推荐使用

  7. 静态内部类

    静态内部类在外部类被状态的时候,并不会被装载。只有显式需要该内部类的时候才会被装载,而且只会被装载一次。另外,在类装载的时候是线程安全的。所以静态内部类既实现了懒加载,也解决了线程安全问题,同时也保证了效率。推荐使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Cat {

    private Cat(){}

    // 静态内部类
    // 只有在显式调用内部类的时候,才会被加载,即实现了懒加载
    // 同时类加载的时候是线程安全的
    // 保证了效率。
    public static class CreateCat{
    // 常量
    private static final Cat instance = new Cat();
    }

    public static Cat getInstance() {
    return CreateCat.instance;
    }
    }
  8. 枚举

    不太了解枚举,通过枚举,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,推荐使用。代码如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public class SingletonTest04 {

    public static void main(String[] args) {
    System.out.println();

    Animal animal1 = Animal.INSTANCE;
    Animal animal2 = Animal.INSTANCE;

    System.out.println(animal1.hashCode());
    System.out.println(animal2.hashCode());

    // 通过枚举对象,也可调用实例方法
    animal1.sayOK();
    }
    }


    enum Animal {

    // 属性,只有一个,保证了单例
    INSTANCE;

    public void sayOK() {
    System.out.println("ok~");
    }
    }

总体来看,饿汉式的双重检查、静态内部类以及枚举方式都推荐使用。另外,饿汉式是可以使用的。

在java.lang.Runtime这个类中,就出现了单例模式,是饿汉式。源码如下所示:

1
2
3
4
5
6
7
8
9
public class Runtime {
private static final Runtime currentRuntime = new Runtime();

public static Runtime getRuntime() {
return currentRuntime;
}

private Runtime() {}
}

4. 工厂模式

工厂模式分为简单工厂模式、工厂方法模式和抽象工厂模式三种。

5. 适配器模式

6. 装饰者模式

7. 代理模式

8. 观察者模式

9. 模板模式


文章作者: 浮云
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 浮云 !
  目录