原创

设计模式

  • 设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。
  • 设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
  • 设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
  • 设计模式不是一种方法和技术,而是一种思想。设计模式和具体的语言无关,学习设计模式就是要建立面向对象的思想,尽可能的面向接口编程,低耦合,高内聚,使设计的程序可复用。
  • 学习设计模式能够促进对面向对象思想的理解,反之亦然。它们相辅相成。

设计模式分类

设计模式按照功能分为三类23种:

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

设计模式注意事项:

每个设计模式都有自己的优缺点,要根据实际情况,去选择合适的设计模式

创建型

为什么要使用创建型设计模式?

  • 有些时候开发人员不想要知道对象的创建细节,只是想要创建一个可以使用的对象。所以可以将创建的工作给到专业的类。
  • 由于要使用的类是第三方包提供的,我们基本上不了解如何去构造该对象。

简单工厂

一个工厂类做所有的事情,可以生产所有需要的对象(万能工厂)

// 简单工厂设计模式(负担太重、不符合开闭原则)
public static Animal createAnimal(String name) {
    if ("cat".equals(name)) {
        return new Cat();
    } else if ("dog".equals(name)) {
        return new Dog();
    } else if ("cow".equals(name)) {
        return new Dog();
    } else {
        return null;
    }
}

优缺点

优点:

  • 很明显,简单工厂的特点就是“简单粗暴”,通过一个含参的工厂方法,我们可以实例化任何产品类。
    缺点:
  • 任何”东西“的子类都可以被生产,负担太重。当所要生产产品种类非常多时,工厂方法的代码量可能会很庞大。
  • 违反了开闭原则。

改造方案:

  1. spring改造
    // 通过配置文件和反射方式优化
    public static Object getBean(String name) {
         // 给对象起个名,在xml配置文件中,建立名称和对象的映射关系
         Map<String, Object> map = new HashMap<>();// Map中的数据怎么来?
         Object object = map.get(name);
         return object;
     }
    
  2. 工厂方法

工厂方法

⼯⼚⽅法是针对每⼀种产品提供⼀个⼯⼚类。
通过不同的⼯⼚实例来创建不同的产品实例。

工厂方法

// 抽象出来的动物工厂----它只负责生产一种动物
public abstract class AnimalFactory {
    // 工厂方法
    public abstract Animal createAnimal();
}

// 具体的工厂实现类
public class CatFactory extends AnimalFactory {
    @Override
    public Animal createAnimal() {
        return new Cat();
    }
}

//具体的工厂实现类
public class DogFactory extends AnimalFactory {

    @Override
    public Animal createAnimal() {
        return new Dog();
    }
}

优缺点

优点:

  • 工厂方法模式就很好的减轻了工厂类的负担,把某一类/某一种东西交由一个工厂生产;(对应简单工厂的缺点1)
  • 同时增加某一类”东西“并不需要修改工厂类,只需要添加生产这类”东西“的工厂即可,使得工厂类符合开闭原则。
    缺点:
  • 对于某些可以形成产品族(一个系列产品,泛指,比如说汽车、家电等)的情况处理⽐较复杂。

抽象工厂

工厂会是多个,生产多个产品(抽象概念),这多个产品属于一个产品族。

抽象工厂

示例

  1. 先把多类产品设计出来
    ```java
    public abstract class Car {

    public abstract void name();

}

public abstract class Computer {

public abstract void name();

}

public abstract class Phone {

public abstract void name();

}


2. 每一类产品有自己的具体实现,比如手机
```java
public class Huawei1 extends Phone {
    @Override
    public void name() {
        System.out.println("this is a huawei1");
    }
}

public class Xiaomi1 extends Phone {
    @Override
    public void name() {
        System.out.println("this is a xiaomi1");
    }
}
  1. 设计抽象工厂类,加入所有工厂能够生产的产品种类
    ```java
    public abstract class ProductFactory {

    public abstract Computer produceComputer();

    public abstract Phone producePhone();

    public abstract Car produceCar();

}

3. 工厂类
```java
public class HuaweiFactory extends ProductFactory {
    @Override
    public Computer produceComputer() {
        return new Matebook();
    }

    @Override
    public Phone producePhone() {
        return new Huawei1();
    }

    @Override
    public Car produceCar() {
        return new SUV();
    }
}

public class XiaomiFactory extends ProductFactory {
    @Override
    public Computer produceComputer() {
        return new Macbook();
    }

    @Override
    public Phone producePhone() {
        return new Xiaomi1();
    }

    @Override
    public Car produceCar() {
        return new MPV();
    }
}

三总工厂模式总结

⼯⼚模式区别:

  • 简单⼯⼚: 使⽤⼀个⼯⼚对象⽤来⽣产同⼀等级结构中的任意产品。(不⽀持拓展增加产品)
  • ⼯⼚⽅法: 使⽤多个⼯⼚对象⽤来⽣产同⼀等级结构中对应的固定产品。(⽀持拓展增加产品)
  • 抽象⼯⼚: 使⽤多个⼯⼚对象⽤来⽣产不同产品族的全部产品。(不⽀持拓展增加产品;⽀持增加产品族)

构建者

将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。

构建者模式和工厂模式很类似,区别在于构建者模式是一种个性化产品的创建。而工厂模式是一种标准化的产品创建。

构建者模式角色

  1. 产品:要生产的对象
  2. 导演类:如何去订制一个对象
  3. 构建者类:完成私人订制功能的具体类

构建者模式与常用的set模式区别:使用builder模式(一般是使用内部builder类实现)时,尚未产生对象,还不能被使用。所以,不用担心对象污染。

原型模式

原型模式虽然是创建型的模式,但是与工厂模式没有关系,从名字即可看出,该模式的思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。

就是说不用new的方式创建对象,而是直接复制对象,分为浅复制和深复制(用的底层C语言的clone和serialize方式)。

浅拷贝

只有对象的基本类型和String类型进行复制,而引用类型只是复制了引用,没有将引用类型对应的对象进行复制。
需要实现clonable
/* 浅复制 */
public Object clone() throws CloneNotSupportedException {
    Prototype proto = (Prototype) super.clone();
    return proto;
}

深拷贝

引用类型对应的对象,也在内存中复制一份。
需要实现serializable
/* 深复制 */
public Object deepClone() throws IOException, ClassNotFoundException {
    /* 写入当前对象的二进制流 */
    ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
    ObjectOutputStream oos = new ObjectOutputStream(bos); 
    oos.writeObject(this);
    /* 读出二进制流产生的新对象 */
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    return ois.readObject();
}

优缺点

优点:

  1. 根据客户端要求实现动态创建对象,客户端不需要知道对象的创建细节,便于代码的维护和扩展。
  2. 使⽤原型模式创建对象⽐直接new⼀个对象在性能上要好的多,因为Object类的clone⽅法是⼀个本地⽅法,它直接操作内存中的⼆进制流,特别是复制⼤对象时,性能的差别⾮常明显。所以在需要重复地创建相似对象时可以考虑使⽤原型模式。⽐如需要在⼀个循环体内创建对象,假如对象创建过程⽐较复杂或者循环次数很多的话,使⽤原型模式不但可以简化创建过程,⽽且可以使系统的整体性能提⾼很多。

需要注意的问题:

  1. 使⽤原型模式复制对象不会调⽤类的构造⽅法。因为对象的复制是通过调⽤Object类的clone⽅法来完成的,它直接在内存中复制数据,因此不会调⽤到类的构造⽅法。不但构造⽅法中的代码不会执⾏,甚⾄连访问权限都对原型模式⽆效。还记得单例模式吗?单例模式中,只要将构造⽅法的访问权限设置为private型,就可以实现单例。但是clone⽅法直接⽆视构造⽅法的权限,所以,单例模式与原型模式是冲突的。
  2. 在使⽤时要注意深拷⻉与浅拷⻉的问题。clone⽅法只会拷⻉对象中的基本的数据类型,对于数组、容器对象、引⽤对象等都不会拷⻉,这就是浅拷⻉。如果要实现深拷⻉,必须将原型模式中的数组、容器对象、引⽤对象等另⾏拷⻉。

单例模式

单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:
1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。

单例模式分成两种实现方式:懒汉式和饿汉式

饿汉式

在类初始化时就将对象创建出来

public class Student1 {

    // 2:成员变量初始化本身对象
    // 静态类在进行类加载(静态变量初始化)的时候,在JVM内部是保证线程安全的
    private static Student1 student = new Student1();

    // 1.构造私有
    private Student1() {}

    // 3:对外提供公共方法获取对象
    public static Student1 getSingletonInstance() {
        return student;
    }

    public void sayHello(String name) {
        System.out.println("hello," + name);
    }
}

饿汉式不存在线程安全问题,但是会浪费内存资源

懒汉式

在第一次使用该类的时候

public class Student2 {

    //1:构造私有
    private Student2(){}

    //2:定义私有静态成员变量,先不初始化
    private static Student2 student = null;

    //3:定义公开静态方法,获取本身对象
    public static Student2 getSingletonInstance(){
        //没有对象,再去创建
        if (student == null) {
            student = new Student2();
        }
        //有对象就返回已有对象
        return student;
    }
}

节省内存资源,但是存在线程安全问题

懒汉式三种线程安全写法

  1. 双重检查锁
  2. 静态内部类
  3. 枚举

并发编程三大特性

  1. 原子性: CPU操作指令必须是原子的;广义上是字节码指令是原子的
  2. 有序性: 狭义上指的是CPU操作指令是有序执行的;广义上指的是字节码指令是有序执行的
  3. 可见性: 在多核CPU下,不同的CPU缓存之间是相互不可见的

双重检查锁

首先来看几个问题:

  1. 如何保证原子性?
    加锁(synchronize、Lock)

  2. 指令重排序?
    JIT即时编译器的优化策略,happend-before六大原则。
    两行代码之后的操作,执行结果不存在影响,就可以发生指令重排序。

  3. 对象在JVM中的创建步骤Student student = new Student();过程中

    1. new:开辟JVM堆中的内存空间
    2. 将内存空间初始化(指的就是对象的成员变量初始化为0值)
    3. 将内存空间地址(引用地址)赋值给引用类型的变量

结论:在new对象的时候,JIT即时编译器会根据运行情况,对对象创建的过程进行指令重排序(132)

  1. 线程执行需要竞争CPU时间片来执行

由以上几点,当线程A双重检查锁单例中进行对象创建的时候释放时间片,线程B进入单例并识别对象不为空,并调用单例中的属性时,会有NullPointer风险。

所以在双重检查锁中,使用volatile关键字。该关键字有几个作用:

  1. 禁止被它修饰的变量发生指令重排序。是通过内存屏障实现的。
  2. 禁止使用CPU缓存(保证可见性),内部实现是被volatile关键词修饰的变量,在修改之前都需要将CPU缓存中的数据刷新到主内存中。
public class Student5 {

    private volatile static Student5 student;

    private Student5() {
    }

    public static Student5 getSingletonInstance() {
        if (student == null) {
            // B线程检测到student不为空
            synchronized (Student5.class) {
                if (student == null) {
                    student = new Student5();
                    // A线程被指令重排了,刚好先赋值了;但还没执行完构造函数。
                }
            }
        }
        return student;// 后面B线程执行时将引发:对象尚未初始化错误。
    }

}

静态内部类

类似于饿汉式。

public class Student {

    private Student() {}

    /*
     * 此处使用一个内部类来维护单例 JVM在类加载的时候,是互斥的,所以可以由此保证线程安全问题
     */
    private static class SingletonFactory {
        private static Student student = new Student();
    }

    /* 获取实例 */
    public static Student getSingletonInstance() {
        return SingletonFactory.student;
    }

}

原理:当使用Student.getSingletonInstance()时,相当于SingletonFactory的静态初始化,而SingletonFactory使用饿汉式,所以线程安全。

破坏单例的方式

  1. 反射攻击
    public class SingletonAttack {
     public static void main(String[] args) throws Exception {
         reflectionAttack();
     }
     public static void reflectionAttack() throws Exception { 
         //通过反射,获取单例类的私有构造器
         Constructor constructor = DoubleCheckLockSingleton.class.getDeclaredConstructor(); //设置私有成员的暴力破解
         constructor.setAccessible(true); // 通过反射去创建单例类的多个不同的实例 
         DoubleCheckLockSingleton s1 = (DoubleCheckLockSingleton)constructor.newInstance(); // 通过反射去创建单例类的多个不同的实例 DoubleCheckLockSingleton s2 = (DoubleCheckLockSingleton)constructor.newInstance();
         s1.tellEveryone();
         s2.tellEveryone();
         System.out.println(s1 == s2);
     }
    }
    
  2. 序列化攻击
    就是深复制
    public class SingletonAttack {
     public static void main(String[] args) throws Exception {
         serializationAttack();
     }
     public static void serializationAttack() throws Exception { // 对象序列化流去对对象进行操作
         ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("serFile"));
         //通过单例代码获取一个对象
         DoubleCheckLockSingleton s1 = DoubleCheckLockSingleton.getInstance(); //将单例对象,通过序列化流,序列化到文件中 outputStream.writeObject(s1); // 通过序列化流,将文件中序列化的对象信息读取到内存中
         ObjectInputStream inputStream = new ObjectInputStream(new
         FileInputStream(new File("serFile"))); //通过序列化流,去创建对象
         DoubleCheckLockSingleton s2 = (DoubleCheckLockSingleton)inputStream.readObject();
         s1.tellEveryone();
         s2.tellEveryone();
         System.out.println(s1 == s2);
     }
    }
    
    为什么会这样呢?⻓话短说,在ObjectInputStream.readObject()⽅法执⾏时,其内部⽅法readOrdinaryObject()中有这样⼀句话:
    //其中desc是类描述符
    obj = desc.isInstantiable() ? desc.newInstance() : null;
    
    也就是说,如果⼀个实现了Serializable/Externalizable接⼝的类可以在运⾏时实例化,那么就调⽤newInstance()⽅法,使⽤其默认构造⽅法反射创建新的对象实例,⾃然也就破坏了单例性。

破坏单例问题的解决方案

要防御序列化攻击,就得将instance声明为transient,并且在单例中加⼊以下语句:

private Object readResolve() {
    return instance; 
}

这是因为在上述readOrdinaryObject()⽅法中,会通过卫语句desc.hasReadResolveMethod()检查类中是否存在名为readResolve()的⽅法,如果有,就执⾏desc.invokeReadResolve(obj)调⽤该⽅法。
readResolve()会⽤⾃定义的反序列化逻辑覆盖默认实现,因此强制它返回instance本身,就可以防⽌产⽣新的实例。

枚举单例

public enum EnumSingleton {
    INSTANCE;
    public void tellEveryone() {
        System.out.println("This is an EnumSingleton " + this.hashCode());
    } 
}
  1. 枚举单例预防反射攻击

java枚举类都隐式继承Enum抽象类,而Enum抽象类没有无参构造方法,只有一个

protected Enum(String name, int ordinal) {
    this.name = name;
    this.ordinal = ordinal;
}

而在暴力破解时使用反射Constructor时

if ((clazz.getModifiers() & Modifier.ENUM) != 0)
    throw new IllegalArgumentException("Cannot reflectively create enumobjects");

又不允许Enum类进行实例化。

  1. 枚举单例防御序列化攻击

在使用ObjectInputStream类时,对枚举类型有一个专门的readEnum()方法来处理,其简要流程如下:

  • 通过类描述符取得枚举单例的类型EnumSingleton;
  • 取得枚举单例中的枚举值的名字(这里是INSTANCE);
  • 调用Enum.valueOf()方法,根据枚举类型和枚举值的名字,获得最终的单例。

这种处理方法与readResolve()方法大同小异,都是以绕过反射直接获取单例为目标。不同的是,枚举对序列化的防御仍然是JDK内部实现的。

综上所述,枚举单例确实是目前最好的单例实现了,不仅写法非常简单,并且JDK能够保证其安全性,不需要我们做额外的工作。

行为型

模板⽅法模式

所谓模板方法其实就是父子类。

⽗类去抽取共性的⽅法操作,⼦类去实现复杂的特性的功能。

示例

⼀般⽗类去制定⽅法的操作步骤,⽐如说把⼤象装冰箱分⼏步

  1. 打开冰箱
  2. 将大象塞进冰箱
  3. 关上冰箱
public abstract class Father {

    public void open() {
        System.out.println("open the refrigerator");
    }

    public abstract void fill();

    public void close() {
        System.out.println("close the refrigerator");
    }

}

其中第2步非常麻烦,⼦类去实现复杂的特性的功能。

public class Son extends Father {
    @Override
    public void fill() {
        // 这些都是不同的子类各自实现的
        first();
        second();
        last();
    }

    private void first() {

    }

    private void second() {

    }

    private void last() {

    }
}

策略模式

在策略模式(Strategy Pattern)中,⼀个类的⾏为或其算法可以在运⾏时更改。这种类型的设计模式属于⾏为型模式。

在策略模式中,我们创建表示各种策略的对象和⼀个⾏为随着策略对象改变⽽改变的context对象。策略对象改变context对象的执⾏算法。

策略模式实现与工厂方法类似,主旨,都是解决开闭原则的问题,都是提取公共的部分为抽象类(工厂方法)或接口(策略模式),区别在于工厂方法是针对于生产对象(名词),而策略模式抽象的是动作(动词)。

主要解决:在有多种算法相似的情况下,使⽤if...else所带来的复杂和难以维护。

何时使⽤:⼀个系统有许多许多类,⽽区分它们的只是他们直接的⾏为。

如何解决:将这些算法封装成⼀个⼀个的类,任意地替换。

优缺点

优点

  1. 算法可以⾃由切换
  2. 避免使⽤多重条件判断
  3. 扩展性良好

缺点

  1. 策略类会增多
  2. 所有策略类都需要对外暴露

针对这些缺点,可以考虑在策略多于四个时,使用混合模式,解决策略类过多的问题。

使⽤场景

比如假期出去旅游,可以有很多种方式,骑⾃⾏⻋、坐汽⻋,每⼀种旅⾏⽅式都是⼀个策略。

  1. 如果在⼀个系统⾥⾯有许多类,它们之间的区别仅在于它们的⾏为,那么使⽤策略模式可以动态地让⼀个对象在许多⾏为中选择⼀种⾏为。
  2. ⼀个系统需要动态地在⼏种算法中选择⼀种。
  3. 如果⼀个对象有很多的⾏为,如果不⽤恰当的模式,这些⾏为就只好使⽤多重的条件选择语句来实现。
具体实现

我们将创建⼀个定义活动的Strategy接⼝和实现了Strategy接⼝的实体策略类。Context是⼀个使⽤了某种策略的类。
策略模式

原始做法:

@Test
public void test3() {
    String flag = "+";
    int a = 10;
    int b = 1;
    if ("+".equals(flag)) {
        System.out.println(a + b);
    } else if ("-".equals(flag)) {
        System.out.println(a - b);
    } else if ("*".equals(flag)) {
        System.out.println(a * b);
    } else {
        System.out.println(a / b);
    }
}
  1. 定义Strategy接口
/**
 * + - * /
 */
public interface CalStrategy {

    int doOperation(int a, int b);

}
  1. 定义实现类
public class OperationAdd implements CalculateStrategy {
    @Override
    public int doOperation(int a, int b) {
        return a + b;
    }
}

public class OperationSubtract implements CalculateStrategy {
    @Override
    public int doOperation(int a, int b) {
        return a - b;
    }
}

public class OperationMultiply implements CalculateStrategy {
    @Override
    public int doOperation(int a, int b) {
        return a * b;
    }
}

public class OperationDivide implements CalculateStrategy {
    @Override
    public int doOperation(int a, int b) {
        return a / b;
    }
}
  1. 创建Context
public class CalculateContext {

    private CalculateStrategy calculateStrategy;

    public CalculateContext(CalculateStrategy calculateStrategy) {
        this.calculateStrategy = calculateStrategy;
    }

    public int calculate(int a, int b) {
        return calculateStrategy.doOperation(a, b);
    }

}
  1. 测试类StrategyPatternDemo
public static void test4() {
    int a = 10;
    int b = 2;
    CalculateContext calculateContext1 = new CalculateContext(new OperationAdd());
    System.out.println("10 + 2 = " + calculateContext1.calculate(a, b));

    CalculateContext calculateContext2 = new CalculateContext(new OperationSubtract());
    System.out.println("10 - 2 = " + calculateContext2.calculate(a, b));

    CalculateContext calculateContext3 = new CalculateContext(new OperationMultiply());
    System.out.println("10 * 2 = " + calculateContext3.calculate(a, b));

    CalculateContext calculateContext4 = new CalculateContext(new OperationDivide());
    System.out.println("10 / 2 = " + calculateContext4.calculate(a, b));
}
  1. 第二类实现,由Strategy自己维护自己的flag,这样在Context中不需要传入具体的strategy,直接输入原始值。
/**
 * + - * /
 */
public interface CalculateStrategy {

    int doOperation(int a, int b);

    boolean match(String flag);

}

public class OperationAdd implements CalculateStrategy {
    @Override
    public int doOperation(int a, int b) {
        return a + b;
    }

    @Override
    public boolean match(String flag) {
        return "+".equals(flag);
    }
}
...

public class CalculateContext {

    private List<CalculateStrategy> calculateStrategyList;

    public CalculateContext() {
        calculateStrategyList = new ArrayList<>();
        calculateStrategyList.add(new OperationAdd());
        calculateStrategyList.add(new OperationSubtract());
        calculateStrategyList.add(new OperationMultiply());
        calculateStrategyList.add(new OperationDivide());
    }

    public int calculate(int a, int b, String flag) {
        for (CalculateStrategy strategy : calculateStrategyList) {
            if (strategy.match(flag)) {
                return strategy.doOperation(a, b);
            }
        }
        return 0;
    }
}

public static void test5() {
    int a = 10;
    int b = 2;
    CalculateContext calculateContext1 = new CalculateContext();
    System.out.println("10 + 2 = " + calculateContext1.calculate(a, b, "+"));
    System.out.println("10 - 2 = " + calculateContext1.calculate(a, b, "-"));
    System.out.println("10 * 2 = " + calculateContext1.calculate(a, b, "*"));
    System.out.println("10 / 2 = " + calculateContext1.calculate(a, b, "/"));
}

结构型

适配器模式

类的适配器模式

核⼼思想就是:有⼀个Source类,拥有⼀个⽅法,待适配,⽬标接⼝是Targetable,通过Adapter类,将Source的功能扩展到Targetable⾥。

类的适配器模式

public class Source {

    public void method1() {
        System.out.println("这是Source类的method1");
    }

}

public interface Target {

    void method1();

    void method2();

}

public class TargetAdapter extends Source implements Target {

    @Override
    public void method2() {
        System.out.println("这是TargetAdapter类的method2");
    }
}

public class ClazzAdapter {

    public static void main(String[] args) {
        Target target = new TargetAdapter();
        target.method1();
        target.method2();
    }

}

这种用法相对少见,本质是嫁接source类的功能到target中,但是因为是继承实现的adapter,所以这种方式要求sourcetarget中的方法名称必须相同。

对象适配器模式

基本思路和类的适配器模式相同,只是将Adapter类作修改,这次不继承Source类,⽽是持有Source类的实例,以达到解决兼容性的问题。

对于关系来说就是从继承关系下降到关联关系,这样降低了耦合度和实现的复杂度。

对象适配器模式

public class Source {

    public void method1() {
        System.out.println("调用Source的method1");
    }

}

public interface Target {

    void method1();

    void method2();

}

public class TargetWrapper implements Target {

    private Source source;

    public TargetWrapper(Source source) {
        this.source = source;
    }

    @Override
    public void method1() {
        System.out.println("这是targetWrapper的method1");
        source.method1();
    }

    @Override
    public void method2() {
        System.out.println("这是targetWrapper的method2");
    }
}

public class ObjectAdapter {

    public static void main(String[] args) {
        Source source = new Source();
        Target target = new TargetWrapper(source);
        target.method1();
        target.method2();
    }

}

接口的适配器模式

有时我们写的⼀个接⼝中有多个抽象⽅法,当我们写该接⼝的实现类时,必须实现该接⼝的所有⽅法,这明显有时⽐较浪费,因为并不是所有的⽅法都是我们需要的,有时只需要某⼀些,此处为了解决这个问题,我们引⼊了接⼝的适配器模式,借助于⼀个抽象类,该抽象类实现了该接⼝,实现了所有的⽅法,⽽我们不和原始的接⼝打交道,只和该抽象类取得联系,所以我们写⼀个类,继承该抽象类,重写我们需要的⽅法就⾏。

SpringMVC中我们经常会用到这种模式。比如说:
接口的适配器举例
WebMvcConfigurer中定义了很多方法,其中很多我们不需要实现的,WebMvcConfigurerAdapter抽象方法中罗列了所有接口,而我们只需要继承并重写我们需要用到的即可。

public interface Sourceable {
    public void method1(); 
    public void method2(); 
}

public abstract class Wrapper implements Sourceable { 
    public void method1(){}
    public void method2(){}
 }

public class SourceSub1 extends Wrapper { 
    public void method1() { 
        System.out.println("the sourceable interface's first Sub1!"); 
    } 
}

public class SourceSub2 extends Wrapper { 
    public void method2(){ 
        System.out.println("the sourceable interface's second Sub2!"); 
    } 
}

public class WrapperTest { 
    public static void main(String[] args) { 
        Sourceable source1 = new SourceSub1(); 
        Sourceable source2 = new SourceSub2(); 
        source1.method1(); 
        source1.method2(); 
        source2.method1(); 
        source2.method2(); 
    } 
}

总结

  • 类的适配器模式:当希望将⼀个类转换成满⾜另⼀个新接⼝的类时,可以使⽤类的适配器模式,创建⼀个Adapter类,继承原有的类,实现新的接⼝即可。
  • 对象的适配器模式:当希望将⼀个对象转换成满⾜另⼀个新接⼝的对象时,可以创建⼀个Wrapper类,持有原类的⼀个实例,在Wrapper类的⽅法中,调⽤实例的⽅法就⾏。
  • 接⼝的适配器模式:当不希望实现⼀个接⼝中所有的⽅法时,可以创建⼀个抽象类Wrapper,实现所有⽅法,我们写别的类的时候,继承抽象类即可。

装饰模式

装饰模式就是给⼀个对象增加(装饰)⼀些新的功能,⽽且是动态的,要求装饰对象和被装饰对象实现同⼀个接⼝,装饰对象持有被装饰对象的实例,关系图如下:
装饰模式

示例

/**
 * 手机接口
 */
public interface PhoneInterface {
    void call();
}

/**
 * 目标类
 */
public class HuaWeiPhone implements PhoneInterface {
    @Override
    public void call() {
        System.out.println("使用华为手机打电话");
    }
}

// 使用装饰者增强目标类的实现方法
/**
 * 装饰模式
 * 
 * 1:装饰类,需要去实现被装饰类接口 2:装饰类的本质是对已有的类进行功能增强
 * 
 * 特点: 1:外表看起来是被装饰类的接口表示形式 2:内在其实使用的是被装饰类本身的功能,只是在此基础之上进行增强。
 */
public class HuaWeiPhoneDecorate implements PhoneInterface {

    // 被装饰的类的实例,该实例由外部传入
    private PhoneInterface phone;

    // 通过构造方法传入被装饰类的实例
    public HuaWeiPhoneDecorate(PhoneInterface phone) {
        this.phone = phone;
    }

    // 对被装饰类的打电话功能进行装饰,使其功能增强
    @Override
    public void call() {
        // 依然使用的是被装饰类的手机去打电话
        phone.call();
        // 通过装饰扩展的新功能
        System.out.println("拍照贼NB");
    }
}

上面代码,装饰者HuaWeiPhoneDecorate将目标类HuaWeiPhone中的call()方法增强了。

使用场景

  1. 需要扩展⼀个类的功能。
  2. 动态的为⼀个对象增加功能,⽽且还能动态撤销。(继承不能做到这⼀点,继承的功能是静态的,不能动态增删。)

缺点

产⽣过多相似的对象,不易排错!

委托模式(*)

这里新增一个委托模式,不在上面提到的23种设计模式中,但也是经常使用的一个设计模式。

delegate模式的表现:看似是一个对象完成的功能,但是实质上通过两个对象去实现一个功能。

比如说项目经理和开发人员的关系,客户只告诉项目经理说做什么需求,但是项目经理没有真正自己去实现该需求,是安排开发人员去实现需求。那这时的项目经理就相当于开发人员的委托人。

/**
 * 委托人
 */
public class ProjectManager {

    // 受托人
    private Developer delegate;

    public ProjectManager(Developer delegate) {
        this.delegate = delegate;
    }

    public void implementDemands() {
        //根据需求,将实际工作派发给开发人员或者其他人员
        delegate.development();
        review();
    }

    private void review() {
        System.out.println("review");
    }
}

public class Developer {

    public void development() {
        System.out.println("又来新需求了,开干");
    }
}

委派模式中两个对象之间没有从属关系,只有关联关系。

代理模式

其实每个模式名称就表明了该模式的作⽤,代理模式就是多⼀个代理类出来,替原对象进⾏⼀些操作。

代理⼜分为动态代理和静态代理

静态代理

⽐如我们在租房⼦的时候回去找中介,为什么呢?因为你对该地区房屋的信息掌握的不够全⾯,希望找⼀个更熟悉的⼈去帮你做,此处的代理就是这个意思。再如我们有的时候打官司,我们需要请律师,因为律师在法律⽅⾯有专⻓,可以替我们进⾏操作,表达我们的想法。先来看看关系图:

静态代理

public interface Sourceable { 
    public void method(); 
}

public class Source implements Sourceable { 
    @Override
    public void method() { 
        System.out.println("the original method!"); 
    } 
}

public class Proxy implements Sourceable { 
    private Source source; 
    public Proxy(){ 
        super(); 
        this.source = new Source(); 
    } 
    @Override
    public void method() { 
        before(); 
        source.method(); 
        atfer(); 
    } 
    private void atfer() { 
        System.out.println("after proxy!"); 
    } 
    private void before() { 
        System.out.println("before proxy!"); 
    } 
}

从这儿可以看到静态代理和装饰模式代码逻辑格式相同,但他们的逻辑不同,装饰模式目的是为了增强原类的功能,而静态代理主要是为了实现原类能实现而不擅长的功能,比如说本例中,其实就是method执行前后的一些操作,又不想修改原类(违背开闭原则),所以采用代理来操作。

动态代理

动态代理在编译期间,不需要为源类去⼿动编写⼀个代理类。
只会再运⾏期间,去为源对象产⽣⼀个代理对象。

一般实现动态代理有两种:

  1. JDK
  2. CGLib

两种方式区别:

  1. JDK动态代理是Java⾃带的,cglib动态代理是第三⽅jar包提供的。
  2. JDK动态代理是针对【拥有接⼝的⽬标类】进⾏动态代理的,⽽Cglib是【⾮final类】都可以进⾏动态代理。但是Spring优先使⽤JDK动态代理。
  3. JDK动态代理实现的逻辑是【⽬标类】和【代理类】都【实现同⼀个接⼝】,⽬标类和代理类【是兄弟关系】。⽽Cglib动态代理实现的逻辑是给【⽬标类】⽣个孩⼦(⼦类,也就是【代理类】),【⽬标类】和【代理类】是⽗⼦继承关系。
  4. JDK动态代理在早期的JDK1.6左右性能⽐cglib差,但是在JDK1.8以后cglib和jdk的动态代理性能基本上差不多。反⽽jdk动态代理性能更加的优越。
JDK动态代理

JDK动态代理是基于接口和实现类的。

在这里我们首先需要了解的是动态代理实现逻辑与静态代理实际上是相同的,执行任务的是目标对象,而代理对象只做增强,不同的是静态代理是程序猿编写代码完成增强的功能;而动态代理是在运行时JVM自己生成代理对象完成该功能。

  1. 准备工作

首先我们创建接口,比如用户相关接口:

public interface UserService {
    void saveUser();
}

创建实现类:

public class UserServiceImpl implements UserService {
    @Override
    public void saveUser() {
        System.out.println("添加用户");
    }
}
  1. 实现InvocationHandler

我们先来看看InvocationHandler

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

通过代码我们可以看到方法invoke有三个参数:

  • proxy:代理对象本身
  • method:目标对象的方法对象(Method对象),一个方法针对一个Method对象
  • args:目标对象的方法参数

上面我们说过动态代理与静态代理方式相同,方法执行方仍然是目标对象本身,那么这里我们实现InvocationHandler时,可以这样写:

/**
 * JDK动态代理使用的动态增强的类
 */
public class MyInvocationHandler implements InvocationHandler {

    // 目标对象的引用
    private Object target;

    // 通过构造方法将目标对象注入到代理对象中
    public MyInvocationHandler(Object target) {
        super();
        this.target = target;
    }

    /**
     * 代理对象会执行的方法
     * 第一个参数:代理对象本身
     * 第二个参数:目标对象的方法对象(Method对象),一个方法针对一个Method对象
     * 第三个参数:目标对象的方法参数
     * 
     * 代理对象执行的逻辑:
     *         需要执行目标对象的原方法?
     *             如何执行目标对象的原方法呢?
     *             该处使用的是反射
     *             【要调用方法的Method对象.invoke(要调用方法的对象,要调用方法的参数)】
     *         只是在调用目标对象的原方法前边和后边可能要加上一些增强功能的代码
     * 
     * 增强代码比如:
     *         在原方法调用之前,开启事务,源方法结束之后,提交和回滚事务
     * 
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("这是jdk的代理方法");
        // 下面的代码,是反射中的API用法
        // 该行代码,实际调用的是[目标对象]的方法
        // 利用反射,调用[目标对象]的方法
        Object returnValue = method.invoke(target, args);

        // 增强的部分
        return returnValue;
    }
}

这里我们可以看到实际上代理对象在真正方法执行过程中并不需要做任何操作。

  1. 生成代理类
public class JDKProxyFactory {

    public Object getProxy(Object target) {
        Object proxy = Proxy.newProxyInstance(clazz.getClassLoader(),
                target.getClass().getInterfaces(),
                new MyInvocationHandler(target));
        return proxy;
    }
}

最后我们通过调用Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);生成代理类。

优化实现

上面我们看到JDKProxyFactory生成代理类时调用了MyInvocationHandler,实际上我们可以将两个类简化成一个类:

public class JDKProxyFactory implements InvocationHandler {

    private Object target;

    public JDKProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxy() {
        Object proxy = Proxy.newProxyInstance(clazz.getClassLoader(), target.getClass().getInterfaces(), new MyInvocationHandler(target));
        return proxy;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object returnValue = method.invoke(target, args);
        return returnValue;
    }
}

JDKProxyFactory既作为InvocationHandler又作为代理对象的生成类。

实现原理

根据Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);我们可以利用反射等方式推导出来。

  1. 代理类同样实现了interfaces集合。
  2. 我们可以根据interfaces接口集合创建出来一个目标类的方法集合,那么对象类里面应该维护了这些个方法。因为这些方法都是实现了这些接口的覆写方法,所以可以通过interfaces获得,通过这一点我们可以得出,JDK形式的动态代理我们没有办法复原目标类,因为我们没有办法获得所有目标类的方法。
  3. 并且我们可以通过InvocationHandler实现调用代理类的方法时实际执行的是目标类的方法,在代理类中只是做了增强,那么生成的代理类中也维护了InvocationHandler

除此之外,因为这种方式代理类没有办法获得目标类的的类路径,所以生成的代理类一般是在根目录,但是由于是动态代理,所以不会产生实际的.java.class文件,代理类的命名规则一般是$ProxyXX,其中XX是数字。

由以上几点,我们可以推导出代理类如下:

/**
 * 动态代理实现原理与静态代理相同,都是实现目标类接口集合
 * 然后再代理类执行相同的方法时,对目标类进行增强,但是执行者仍是目标类
 */
public class $Proxy20 implements UserService {

    /**
     * 这里动态代理与静态代理不同的是:
     * 静态代理在这里是直接持有目标类对象
     * 动态代理在这里并没有直接持有目标类对象,而是持有与实现方法数相同的Method对象和一个InvocationHandler
     */
    private static Method method1;

    private InvocationHandler handler;

    public $Proxy20(InvocationHandler handler) {
        this.handler = handler;
    }

    /**
     * 动态代理在这里通过反射的方法,利用目标类的Class对象将method对象创建出来
     */
    static {
        // 通过 Class.forName("目标类实现的接口路径").getDeclaredMethod("目标类实现的接口方法名", 目标类实现的接口方法的参数类型集合) 获得
        try {
            method1 = Class.forName("proxy.target.UserService").getDeclaredMethod("saveUser");
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void saveUser() {
        try {
            this.handler.invoke(this, method1, null);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
}

现在我们仿写一遍生成代理对象的方法:

/**
 * 演示JDK动态代理产生代理对象的底层原理
 */
public Object newProxyInstance(ClassLoader classLoader,
                               @NotNull Class<?>[] interfaces,
                               @NotNull InvocationHandler h) {
    try {
        // 1.根据目标类的接口信息,去动态编写代理对象的源代码(java代码---字符串)
        int flag = 19;
        String sourceCode = generateSourceCode(interfaces, flag);
        String path = URLDecoder.decode(JDKProxyFactory.class.getResource("").getPath(), "UTF-8");
        File f = new File(path + "$Proxy" + flag + ".java");
        if (f.exists()) {
            f.delete();
        }
        f.createNewFile();
        FileWriter fw = new FileWriter(f);
        fw.write(sourceCode);
        fw.close();

        // 2.使用JDK自带的API完成javac编译功能,将java源代码编译成class文件
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
        Iterable iterable = manager.getJavaFileObjects(f);

        JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
        task.call();
        manager.close();

        // 3.使用目标类对应的类加载器完成代理类的class文件的类加载
        //ClassLoader classLoader = target.getClass().getClassLoader();
        Class<?> clazz = classLoader.loadClass("$Proxy" + flag);
        // 4.而JVM就会根据代理类的class信息创建代理对象。
        Constructor<?> constructor = clazz.getDeclaredConstructor(InvocationHandler.class);
        // newInstance第三个参数也有用了
        Object proxy = constructor.newInstance(h);

        return proxy;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

private String generateSourceCode(Class<?>[] interfaces, int flag) {
    StringBuffer sb = new StringBuffer();
    sb.append("import java.lang.reflect.InvocationHandler;" + ln);
    sb.append("import java.lang.reflect.Method;" + ln);

    sb.append("public class $Proxy" + flag + " implements ");

    String infs = "";
    for (int i = 0; i < interfaces.length; i++) {
        infs += interfaces[0].getCanonicalName() + ",";
    }
    infs = infs.substring(0, infs.length() - 1);
    // 去掉最后的,
    sb.append(infs + " { " + ln);

    Map<Class, List<Method>> methodMap = new HashMap<>();
    for (Class inf : interfaces) {
        methodMap.put(inf, Arrays.asList(inf.getDeclaredMethods()));
    }

    sb.append("private InvocationHandler handler;" + ln);

    // 遍历接口中的方法
    int methodCount = 1;
    for (Map.Entry<Class, List<Method>> entry : methodMap.entrySet()) {
        for (Method method : entry.getValue()) {
            sb.append("private static Method m" + methodCount++ + ";" + ln);
        }
    }

    sb.append("public $Proxy" + flag + "(InvocationHandler handler) {" + ln);
    sb.append("this.handler = handler;" + ln);
    sb.append("}" + ln);

    //遍历接口中的方法
    for (Method method : methodMap.values().stream().reduce((methods, methods2) -> {
        methods.addAll(methods2);
        return methods;
    }).get()) {
        String modifier = "";
        int mod = method.getModifiers() & Modifier.methodModifiers();
        if ((mod & Modifier.PUBLIC) != 0) {
            modifier = "public ";
        } else if ((mod & Modifier.PROTECTED) != 0) {
            modifier = "protected ";
        } else if ((mod & Modifier.PRIVATE) != 0) {
            modifier = "private ";
        }
        sb.append(modifier + method.getReturnType().getCanonicalName() + " " + method.getName() + "(");
        String parameters = "";
        String inner = "(Object[])null";
        if (method.getParameters() != null && method.getParameters().length > 0) {
            inner = "new Object[]{";
            for (int i = 1; i <= method.getParameters().length; i++) {
                parameters += method.getParameters()[i - 1].getType().getCanonicalName() + " var" + i + ",";
                inner += "var" + i + ",";
            }
            inner = inner.substring(0, inner.length() - 1);
            inner += "}";
            parameters = parameters.substring(0, parameters.length() - 1);
        }
        sb.append(parameters + ") {" + ln);
        sb.append("try {" + ln);
        sb.append("handler.invoke(this, m1, " + inner + ");" + ln);
        sb.append("} catch (Throwable e) {" + ln);
        sb.append("e.printStackTrace();" + ln);
        sb.append("}" + ln);
        sb.append("}" + ln);
    }

    sb.append("static {" + ln);
    sb.append("try {" + ln);

    //遍历接口中的方法
    for (Map.Entry<Class, List<Method>> entry : methodMap.entrySet()) {
        Class inf = entry.getKey();
        for (Method method : entry.getValue()) {
            sb.append("m1 = Class.forName(\"" + inf.getCanonicalName() + "\").getDeclaredMethod(\"" + method.getName() + "\");" + ln);
        }
    }

    sb.append("} catch (Exception e) {" + ln);
    sb.append("e.printStackTrace();" + ln);
    sb.append("}" + ln);
    sb.append("}" + ln);
    sb.append("}" + ln);

    return sb.toString();
}

通过这种方式我们生成了$Proxy19代理对象,那么我们推理是否正确呢,其实我们也可以通过代理JDK生成的代理对象,生成class文件,然后反编译查看class文件是否和我们推导出的$Proxy20是否相似:

@Test
public void testJDKProxy() {

    // 1. 创建目标对象
    UserService service = new UserServiceImpl();
    // 2. 生成代理对象
    JDKProxyFactory proxyFactory = new JDKProxyFactory(service);
    // 3. 得到代理对象
    UserService proxy = (UserService) proxyFactory.getProxy();
    // 4. 生成class文件
    generatorClass(proxy);
}

private void generatorClass(Object proxy) {
    FileOutputStream out = null;
    try {
        // byte[] generateProxyClass =
        // ProxyGenerator.generateProxyClass(proxy.getClass().getName(), new Class[]
        // {proxy.getClass()});
        byte[] generateProxyClass = ProxyGenerator.generateProxyClass(proxy.getClass().getSimpleName(),
                new Class[]{proxy.getClass()});
        out = new FileOutputStream(proxy.getClass().getSimpleName() + ".class");
        out.write(generateProxyClass);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (out != null) {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

生成出的class反编译:

public final class $Proxy0 extends Proxy implements Proxy0 {
    private static Method m3;
    // ...Object类、Proxy类方法对应对象

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    // ...Object类、Proxy类方法

    public final void saveUser() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    // ...Object类、Proxy类方法

    static {
        try {
            // ...Object类、Proxy类方法
            m3 = Class.forName("com.sun.proxy.$Proxy0").getMethod("saveUser");
            // ...Object类、Proxy类方法
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

这里与我们预测大致相同,但是该代理类是继承Proxy

CGlib动态代理

CGlib动态代理通过⼦类继承⽗类的⽅式去实现的动态代理,不需要接⼝。底层是使用ASM技术对字节码进行了修改,生成了代理类。

CGlib动态代理

  1. 和JDK动态代理的InvocationHandler相同,CGlib动态代理也需要一个回调类MethodInterceptor
public interface MethodInterceptor extends Callback {
    /***
     * Object proxy:这是代理对象,也就是[目标对象]的子类
     * Method method:[目标对象]的方法 
     * Object[] arg:参数
     * MethodProxy methodProxy:代理对象的方法
     */
    Object intercept(Object proxy, Method method, Object[] arg, MethodProxy methodProxy) throws Throwable;
}

我们创建一个自定义的MethodInterceptor实现该接口:

public class MyMethodInterceptor implements MethodInterceptor {

    /***
     * Object proxy:这是代理对象,也就是[目标对象]的子类
     * Method method:[目标对象]的方法 
     * Object[] arg:参数
     * MethodProxy methodProxy:代理对象的方法
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] arg, MethodProxy methodProxy) throws Throwable {
        System.out.println("这是cglib的代理方法");

        // 通过调用子类[代理类]的invokeSuper方法,去实际调用[目标对象]的方法
        // 这里的作用等同于 Object returnValue = method.invoke(target, arg);
        // 这样做有个好处就是不需要在MethodInterceptor中维护目标对象
        Object returnValue = methodProxy.invokeSuper(proxy, arg);

        // 代理对象调用代理对象的invokeSuper方法,而invokeSuper方法会去调用目标类的invoke方法完成目标对象的调用

        return returnValue;
    }
}
  1. 在生成代理对象这里,实现有些不同。

    public class CgLibProxyFactory {
    
     public Object getProxyByCgLib(Class<?> clazz) {
         // 创建增强器
         Enhancer enhancer = new Enhancer();
         // 设置需要增强的类的类对象
         enhancer.setSuperclass(clazz);
         // 设置回调函数
         enhancer.setCallback(new MyMethodInterceptor());
         // 获取增强之后的代理对象
         Object object = enhancer.create();
    
         return object;
     }
    }
    
正文到此结束