史上最全23种设计模式


文章目录

前言


从0到1开始学习,一点点的进大厂。现在我们来学设计模式

一、什么是设计模式

反复使用、多人知晓、经过分类编目、代码设计经验的总结。在程序员世界中,本没有设计模式,写代码的人多了,他们便总结出来一套能够提高开发和维护效率的套路。

1. 为什么学习设计模式呢?

好处:
1) 提升自己应对面试
2)不再编写bullshit-code
3)提升复杂逻辑的代码的设计和开发能力
4)有助于学习源码,学习框架事半功倍

二、设计模式分类

1.创建型模式(Structural Pattern)

提供创建对象的机制,提升已有代码的灵活性和可复用性。

1.1 单例(Singleton Pattern)

1.1.1 什么是单例呢?

单例是最简单的设计模式之一,此模式保证某个类在运行期间,只有一个实例对外提供服务。使用单例要保证,一个类只有一个实例,并为该类实例提供一个全局访问点。

1.1.2 我们来写写单例模式吧
1.1.2.1 懒汉式

特点:
线程不安全

public class Singleton2 {

  private static Singleton2 singleton2;

  private Singleton2() { }

  public static Singleton2 getInstance() {
      singleton2 = new Singleton2();
      return singleton2;
  }
}
1.1.2.2 懒汉式-线程安全

特点:
使用synchronized锁,锁住创建爱你单例的方法,防止多个线程同时调用
但是,因为我们加了synchronized锁,导致我们的这个函数,并发度非常低。

public class Singleton4 {

    private static Singleton4 singleton4;

    private Singleton4() {}

    public static synchronized Singleton4 getInstance() {
        if (singleton4 != null) {
            singleton4 = new Singleton4();
        }
        return singleton4;
    }
}
1.1.2.3 懒汉式-双重校验

保证可见性,并且可以实现指令重排

public class Singleton3 {

    private static volatile Singleton3 singleton3;

    private Singleton3() {}

    public static Singleton3 getInstance() {
        if (singleton3 != null) {
            synchronized (Singleton3.class) {
                if (singleton3 != null) {
                    singleton3 = new Singleton3();
                }
            }
        }
        return singleton3;
    }
}
1.1.2.4 饿汉式:

特点 :
线程安全的,因为在类加载期间初始化私有的静态实例,保证instance实例创建过程中是线程安全的。
不支持延时加载,获取实例速度比较快,但是如果对象比较大,而且一直没有使用的时候,会造成资源浪费

public class Singleton1 {

    private static Singleton1 SINGLETON_1 = new Singleton1();

    private Singleton1() {}

    public static Singleton1 getInstance() {
        return SINGLETON_1;
    }
}
1.1.2.5 静态内部类

特点 :
支持懒加载,在静态内部类中创建单例,在装载内部类的时候,才会创建单例对象

public class Singleton5 {

    private Singleton5() {}

    private static class Singleton05Handler{
        private static Singleton5 singleton5 = new Singleton5();
    }

    public static  Singleton5 getInstance(){
        return Singleton05Handler.singleton5;
    }
}
1.1.2.6 反射和序列化都能对我们的单例进行破坏
public class DestroySingleton {
    
    public static void main(String[] args) throws Exception {
        Class<Singleton5> singleton5Class = Singleton5.class;
        Constructor<Singleton5> singleton5Constructor = singleton5Class.getDeclaredConstructor();
        //暴利反射
        singleton5Constructor.setAccessible(true);
        Singleton5 singleton5 = singleton5Constructor.newInstance();
        System.out.println(singleton5 == Singleton5.getInstance());
    }
}
结果:false

那么我们怎么优化呢?

public class Singleton5 {

    private Singleton5() {
        if (Singleton05Handler.singleton5 != null){
            throw new RuntimeException("不允许反射");
        }
    }

    private static class Singleton05Handler {
        private static Singleton5 singleton5 = new Singleton5();
    }

    public static Singleton5 getInstance() {
        return Singleton05Handler.singleton5;
    }
}

最终结果:

Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
	at com.plan.dream.pattern.singleton.DestroySingleton.main(DestroySingleton.java:21)
Caused by: java.lang.RuntimeException: 不允许反射
	at com.plan.dream.pattern.singleton.Singleton5.<init>(Singleton5.java:16)
	... 5 more

序列化与反序列化也能破坏我们的单例

public class SerializableSingleton {

    public static void main(String[] args) throws Exception {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("temp.obj"));
        objectOutputStream.writeObject(Singleton5.getInstance());

        File file = new File("temp.obj");
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
        Singleton5 singleton5 = (Singleton5) objectInputStream.readObject();

        System.out.println(singleton5 == Singleton5.getInstance());
    }
}

结果 false

这个我们怎么解决呢?

public class Singleton5 implements Serializable {

    private Singleton5() {
        if (Singleton05Handler.singleton5 != null){
            throw new RuntimeException("不允许反射");
        }
    }

    private static class Singleton05Handler {
        private static Singleton5 singleton5 = new Singleton5();
    }

    public static Singleton5 getInstance() {
        return Singleton05Handler.singleton5;
    }

    private Object readResolve(){
       return  Singleton05Handler.singleton5;
    }
}

**再执行上一个序列化-反序列化的方法** 
结果:true

为什么反序列化就能破坏我们的单例呢?为什么加了一个方法就解决了呢?
在这里插入图片描述主要是这个方法导致了我们反序列化破坏了单例。那么我们一层层去看。
在这里插入图片描述
这里我们追到了这个方法,这个方法就是反序列化我们的Obj,我们点进去看看仔细看看这个方法。
在这里插入图片描述
仔细看了这个方法后,我们能看到这个switch,如果是对象,就调用checkResolve方法,那么我们继续点readOrdinaryObject进去看一下。
在这里插入图片描述
第一个框,三目运算 表达的含义就是:判断一个实现序列化的接口的类,可以在运行时,被实例化,就返回true。如果为true,就通过反射调用无参构造,创建一个新对象。
第二个框就是判断,如果实现了序列化接口的类中如果有readResolve方法,就返回true。
第三个框通过反射的方式,调用被反序列化的类的readResolve方法。

1.1.2.7 枚举类型

特点:
阻止反射对单例的破坏,在反射方法中不允许使用反射来创建枚举对象
阻止序列化对单例的破坏,在序列化中,只是将枚举对象的name属性输出到了结果中,返序列化的时候,就会通过Enum的valueOf方法,来根据名字去查找对应的枚举对象,因此反序列化后,他们的对象是同一个。

public enum SingletonEnum {
    
    /**
     * 注释
     */
    INSTANCE;

    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public static SingletonEnum getInstance() {
        return INSTANCE;
    }

}

1.2 工厂模式(Factory Pattern)

1.2.1 简单工厂
1.2.1.1 实现方式

定义一个工程类,根据传入的参数不同返回不同的实力,被创建的实例具有共同的父类或接口(多态)。
在这里插入图片描述

1.2.1.2 使用场景

1)需要创建的对象比较少
2)客户端不关心对象的创建

1.2.1.3 简单工厂结构

1)抽象产品:定义产品规范,描述了产品的特性和功能
2)具体产品:实现或集成抽象产品的子类
3)具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品

1.2.1.4 简单工厂实现

场景:我要给我们的会员提供奖品,分别是京东卡和微信红包,下面就是简单工厂的实现。
在这里插入图片描述

好处:
1)封装了创建对象的过程,可以通过参数直接获取对象,把对象的创建和业务逻辑分开,避免了可能会修改客户端代码的问题。
2)如果要实现新产品,直接修改工厂类,不需要再在源码中进行修改,降低了客户端修改代码的可能性,更加容易扩展。
缺点:
再增加新产品的时候,还是需要修改工厂的代码。违背了开闭原则。

1.2.2 工厂方法模式

概念:
定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法是一个产品类的实力化延迟到其工厂 的子类。
在这里插入图片描述

1.2.2.1 原理

工厂方法就是封装对象的创建过程,提升创建对象方法的可复用性
工厂方法中的主要角色
抽象工厂:提供创建产品的接口,调用者通过它访问具体工厂方法来创建产品。
具体工厂:主要实现抽象工厂中的抽象方法,完成具体产品的创建。
抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
具体产品: 实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是一一对应的。

1.2.2.2 实现

在这里插入图片描述
因为需要controller去调用,调用的时候,也要关心像简单工厂那样,要按输入类型分别创建对象。那么我又做了一个优化:
在这里插入图片描述
优点:
用户只需要知道具体工厂,就可以获取到想要的产品,而不需要关注产品创建的过程。
在系统新增产品时,只需要添加具体的产品和对应的工厂就可以了,而不需要对原工厂进行修改
缺点:
增加系统的复杂度

1.3 抽象工厂模式(Abstract Factory Pattern)

1.3.1 概念

抽象工厂模式比工厂方法模式的抽象程度更高,在工厂方法模式中每一个具体的工厂只需生产一种具体的产品,但是抽象工厂模式中一个具体工厂可以生产一组相关的具体产品,这样一组产品被称为产品族。产品族中的每一个产品,都分属于某一个产品继承等级结构。抽象工厂模式提供一个,创建一系列相关或相互依赖对象的接口,而无须指定他们具体的类。抽象工厂模式中的具体工厂,不只创建一种产品,而是负责创建一个产品族
在这里插入图片描述

1.3.2 产品等级结构和产品族
1.3.2.1 产品等级结构

产品等级结构即继承结构,如一个抽象类是手机,其子类有苹果手机、华为手机,则抽象手机与具体品牌的手机之间,构成了一个产品等级结构。如手机是父类,而具体品牌的手机是子类。

1.3.2.2 产品族

在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如苹果手机,苹果笔记本。苹果手机位于手机产品等级结构中,苹果笔记本位于笔记本产品等级结构中。

1.3.3 UML类图

在这里插入图片描述

1.3.4 代码实现

产品等级结构:手机

public interface AbstractPhone {
}

public class ApplePhone implements AbstractPhone {
}

public class HwPhone implements AbstractPhone {
}

产品等级结构:笔记本

public interface AbstractNoteBook {
}

public class AppleNoteBook implements AbstractNoteBook {
}

public class HwNoteBook implements AbstractNoteBook {
}

抽象工厂:

public interface ElectronicsAbstractFactory {

    /**
     * 生产phone
     *
     * @param
     * @return com.plan.dream.pattern.abstract_factory.product.AbstractPhone
     * @throws
     * @method createPhone
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/10/27 12:24
     */
    AbstractPhone createPhone();

    /**
     * 生产notebook
     *
     * @param
     * @return com.plan.dream.pattern.abstract_factory.product.AbstractNoteBook
     * @throws
     * @method createNoteBook
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/10/27 12:25
     */
    AbstractNoteBook createNoteBook();
}

工程厂实现类

public class AppleElectronicsFactory implements ElectronicsAbstractFactory {
    @Override
    public AbstractPhone createPhone() {
        return new ApplePhone();
    }

    @Override
    public AbstractNoteBook createNoteBook() {
        return new AppleNoteBook();
    }
}

工厂实现类

public class HwElectronicsFactory implements ElectronicsAbstractFactory {
    @Override
    public AbstractPhone createPhone() {
        return new HwPhone();
    }

    @Override
    public AbstractNoteBook createNoteBook() {
        return new HwNoteBook();
    }
}

调用方

public class Client {

    private AbstractPhone phone;
    private AbstractNoteBook noteBook;

    public Client(ElectronicsAbstractFactory factory) {
        this.phone = factory.createPhone();
        this.noteBook = factory.createNoteBook();
    }

    public AbstractPhone getPhone() {
        return phone;
    }

    public void setPhone(AbstractPhone phone) {
        this.phone = phone;
    }

    public AbstractNoteBook getNoteBook() {
        return noteBook;
    }

    public void setNoteBook(AbstractNoteBook noteBook) {
        this.noteBook = noteBook;
    }

    public static void main(String[] args) {
        Client client = new Client(new AppleElectronicsFactory());
        final AbstractPhone phone = client.getPhone();
        final AbstractNoteBook noteBook = client.getNoteBook();
        System.out.println(phone);
        System.out.println(noteBook);
    }
}

输出结果:
com.plan.dream.pattern.abstract_factory.product.impl.ApplePhone@f39991
com.plan.dream.pattern.abstract_factory.product.impl.AppleNoteBook@12b3a41
1.3.5 总结:

软件使用者,更关心共性功能,软件创建者,需要找到共性功能,并且隐藏细节。
优点:
对于不同产品系列有比较多共性特征时,可以使用抽象工厂模式,有助于提升组件的复用性。
当需要提升代码的扩展性,并降低维护成本时,把对象的创建和使用过程分开,能有效地将代码统一到一个级别上
解决跨平台带来的兼容性问题
缺点:
增加新的产品等级结构会增大难度,需要对原有结构进行较大的修改,违背了开闭原则。

1.4 建造者模式(Builder Pattern)

1.4.1 概念

也被称为生成器模式,就是将一个复杂对象的构建与表示分离,使得同样的构建过程,可以创建不同的表示。
建造者模式要解决什么问题呢
建造者模式可以将部件和其组装过程分开,一步步创建一个复杂的对象,用户只需要指定复杂对象的类型,就可以得到该对象。比如:一台电脑,由CPU、内存、主板、显卡、电源、磁盘等组成,对于大多数用户而言,并不需要知道这些部件的装配细节,并且几乎不会使用单独某个部件,而是使用一台完整的电脑。而建造者模式就是负责将这些部件进行组装,使其变成一台完整的电脑,供用户使用。

都有哪些角色呢?
抽象建造者(Builder):声明了所有类型生成器,通用的创建对象的步骤。
具体建造者(ConcreteBuilder):实现了具体的构造过程
产品(Product):最终由具体建造者生成的对象
指挥者(Director):负责安排复杂对象的构造顺序

1.4.2 UML

在这里插入图片描述

1.4.3 实现方式1

抽象建造者

/**
 * @ProjectName: dream
 * @Package: com.plan.dream.pattern.builder.service
 * @ClassName: Builder
 * @author: Rocky Qian
 * @description: 构造者类
 * @date: 2023/10/27 14:12
 * @version: 1.0
 */
public abstract class Builder {

    /**
     * 让具体构建者构建
     */
    protected Computer computer = new Computer();

    /**
     * 建造cpu
     *
     * @param
     * @return void
     * @throws
     * @method buildCpu
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/10/27 14:15
     */
    public abstract void buildCpu();

    /**
     * 建造主板
     *
     * @param
     * @return void
     * @throws
     * @method builderMotherBoard
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/10/27 14:15
     */
    public abstract void builderMotherBoard();

    /**
     * 生产电脑
     *
     * @param
     * @return com.plan.dream.pattern.builder.service.entity.Computer
     * @throws
     * @method createComputer
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/10/27 14:15
     */
    public abstract Computer createComputer();

}

具体建造者1

/**
 * @ProjectName: dream
 * @Package: com.plan.dream.pattern.builder.service.impl
 * @ClassName: AppleBuilder
 * @author: Rocky Qian
 * @description: 具体构造者
 * @date: 2023/10/27 14:16
 * @version: 1.0
 */
public class AppleBuilder extends Builder {

    @Override
    public void buildCpu() {
        System.out.println("制作苹果Cpu");
        computer.setCpu("苹果CPU");
    }

    @Override
    public void builderMotherBoard() {
        System.out.println("制作苹果主板");
        computer.setMotherBoard("苹果主板");
    }

    @Override
    public Computer createComputer() {
        return computer;
    }

具体建造者2

/**
 * @ProjectName: dream
 * @Package: com.plan.dream.pattern.builder.service.impl
 * @ClassName: LenovoBuilder
 * @author: Rocky Qian
 * @description: 具体构造者
 * @date: 2023/10/27 14:17
 * @version: 1.0
 */
public class LenovoBuilder extends Builder {

    @Override
    public void buildCpu() {
        System.out.println("制作联想Cpu");
        computer.setCpu("联想CPU");
    }

    @Override
    public void builderMotherBoard() {
        System.out.println("联想苹果主板");
        computer.setMotherBoard("联想主板");
    }

    @Override
    public Computer createComputer() {
        return computer;
    }

产品

/**
 * @ProjectName: dream
 * @Package: com.plan.dream.pattern.builder.service.entity
 * @ClassName: Computer
 * @author: Rocky Qian
 * @description: 产品
 * @date: 2023/10/27 14:14
 * @version: 1.0
 */
@Data
public class Computer {
    private String cpu;
    private String motherBoard;
}

指挥者

/**
 * @ProjectName: dream
 * @Package: com.plan.dream.pattern.builder.service.director
 * @ClassName: Director
 * @author: Rocky Qian
 * @description: 指挥者类
 * @date: 2023/10/27 14:18
 * @version: 1.0
 */
public class Director {

    private final Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }

    public Computer construct(){
        builder.buildCpu();
        builder.builderMotherBoard();
        return builder.createComputer();
    }
}

调用者

/**
 * @ProjectName: dream
 * @Package: com.plan.dream.pattern.builder.service.client
 * @ClassName: Client
 * @author: Rocky Qian
 * @description:
 * @date: 2023/10/27 14:17
 * @version: 1.0
 */
public class Client {

    public static void main(String[] args) {

        //创建一个指挥者,传参要具体的构造者
        final Director director = new Director(new AppleBuilder());

        //获取电脑
        System.out.println(director.construct());

    }

}

执行结果:
制作苹果Cpu
制作苹果主板
Computer(cpu=苹果CPU, motherBoard=苹果主板)
1.4.4 实现方式2

场景
我们原来系统会生成一些待办事项,来督促用户进行一些操作。每次创建这个对象的时候,因为这个对象属性很多,而且还要嵌套一些业务(一些判断代码去掉了)。那么我们在新增这个对象的时候,就十分麻烦(我们用lombok的注解@Builder能搞定哈,现在我们是学习)。那么我们怎么能更优雅的使用建造者模式,来创建我们的对象呢?

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ToDoWorkVo {

    @ApiModelProperty("主键")
    private Long id;

    @ApiModelProperty("类型")
    private Integer type;

    @ApiModelProperty("待办事项id")
    private Long eventId;

    @ApiModelProperty("广场id")
    private Integer plazaId;

    @ApiModelProperty("店铺id")
    private Integer storeId;

    @ApiModelProperty("绑定类型 1.店铺  2.角色  3.用户")
    private Integer bindType = 1;

    @ApiModelProperty("角色类型(bind_type=2时) 1.店长,2.区经,3.店员,4.财务")
    private Integer roleType;

    @ApiModelProperty("用户id(bind_type=3时)")
    private Long userId;

    @ApiModelProperty("待办备注")
    private String content;

    @ApiModelProperty("开始时间")
    private String startDate;

    @ApiModelProperty("创建时间")
    private Date createTime;
}

使用步骤:
1)目标类的构造方法一定要传入一个Builder对象
2)builder类一定位于目标类的内部,并且使用static修饰
3)builder类对象一共内置各种set方法,并且set的返回值一定是builder本身
4)builder类提供一个build()方法,实现目标对象的创建。

/**
 * @ProjectName: dream
 * @Package: com.plan.dream.pattern.builder.builder2
 * @ClassName: TodoWorkBuilder
 * @author: Rocky Qian
 * @description:
 * @date: 2023/10/27 15:58
 * @version: 1.0
 */
public class TodoWorkBuilder {

    private TodoWorkBuilder(Builder builder) {
    }

    public static class Builder {
        private Long id;

        private Integer type;

        private Long eventId;

        private Integer plazaId;

        private Integer storeId;

        private Integer bindType = 1;

        private Integer roleType;

        private Long userId;

        private String content;

        private String startDate;

        private Date createTime;

        public Builder setId(Long id) {
            this.id = id;
            return this;
        }

        public Builder setType(Integer type) {
            this.type = type;
            return this;
        }

        public Builder setEventId(Long eventId) {
            this.eventId = eventId;
            return this;
        }

        public Builder setPlazaId(Integer plazaId) {
            this.plazaId = plazaId;
            return this;
        }

        public Builder setStoreId(Integer storeId) {
            this.storeId = storeId;
            return this;
        }

        public Builder setBindType(Integer bindType) {
            this.bindType = bindType;
            return this;
        }

        public Builder setRoleType(Integer roleType) {
            this.roleType = roleType;
            return this;
        }

        public Builder setUserId(Long userId) {
            this.userId = userId;
            return this;
        }

        public Builder setContent(String content) {
            this.content = content;
            return this;
        }

        public Builder setStartDate(String startDate) {
            this.startDate = startDate;
            return this;
        }

        public Builder setCreateTime(Date createTime) {
            this.createTime = createTime;
            return this;
        }

        public TodoWorkBuilder build() {
            return new TodoWorkBuilder(this);
        }
    }

    public void send() {
        System.out.println("构造完成");
    }

}

调用


/**
 * @ProjectName: dream
 * @Package: com.plan.dream.pattern.builder.builder2
 * @ClassName: Client
 * @author: Rocky Qian
 * @description:
 * @date: 2023/10/27 16:04
 * @version: 1.0
 */
public class Client {

    public static void main(String[] args) {
        final TodoWorkBuilder build = new TodoWorkBuilder.Builder().setId(1L).setType(1).
                setEventId(2L).setPlazaId(1231).setStoreId(321).
                setBindType(1).setRoleType(32).setUserId(23L).
                setCreateTime(new Date()).setContent("321312312").build();
        build.send();
    }
}

结果:
构造完成
1.4.5 总结:
1.4.5.1 建造者区别与工厂模式的区别

工厂模式是用来创建不用但是通关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。
建造者模式是来创建一种类型的复杂对象,通过设置不同的可选参数,定制化地创建不同的对象。

1.4.5.2 建造者模式优缺点

优点:
1)建造者模式封装性很好,使用建造者模式可以有效地封装变化,在使用建造者模式场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中,对整理而言可以获得比较高的稳定性。
2)在建造者模式中,客户端可以不用知道产品的内部组成细节,将产品本身和产品创建过程进行解耦,使得相同的创建过程可以创建不同的产品对象。
3)可以更加精细地控制产品的创建过程,将复杂产品的创建步骤,分别在不同的方法中,使得创建过程更加清晰,也更方便使程序来控制创建过程。
4)创建者模式也很容易进行扩展,如果有新需求,通过实现一个新的建造者就可以完成,基本上不用修改,之前已经测试通过的代码,因此也不会对原有功能引入风险,符合开闭原则。
缺点:
建造者模式,所创建的产品一般具有很多相同点,其组成部分相似,如果产品之间产品差异性比较大,则不适用建造者模式,因此适用范围有一定的限制。

1.4.5.3 建造者模式使用场景

建造者模式创建的是复杂对象,其产品的各个部分经常面临剧烈的变化,但将他们组合在一起的算法却相对稳定。所以在以下场景是适用的:
1)创建的对象比较复杂,由多个部件构成,各部件面临着复杂的变化,但部件之间的建造顺序是稳定的;
2)创建复杂对象的算法独立于该对象的组成部分以及他们的装配方式,即产品的构建过程和最终表示是独立的。

1.5 原型模式(Prototype Design Pattern)

1.5.1 概念

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
原型模式主要解决什么问题呢?
如果创建对象的成本比较高,比如对象中的数据是经过复杂的计算得到的,或者需要从RPC接口或者数据库等比较慢的IO中获取到的。这种情况我们就可以用原型模式。从其他已有的对象中,进行拷贝。而不是每次都创建新对象,进行一些耗时的操作。
角色:
抽象原型类:主要是有clone抽象方法,这个方法返回的是自己本身。这个原型类可以是接口
具体原型类:实现了抽象类型类的视线方法, 返回了自己的原型类
客户端:调用者

1.5.2 UML

在这里插入图片描述

1.5.3 浅克隆
1.5.3.1 定义

克隆对象中所有变量的值与原型对象的值完全相同,包括基本数据类型和引用数据类型,引用数据类型变量存储的地址也是完全一样。

1.5.3.2 执行流程

在这里插入图片描述

1.5.3.3 代码实现

具体原型类:

/**
 * @ProjectName: dream
 * @Package: com.plan.dream.pattern.prototype
 * @ClassName: PrototypeClass
 * @author: Rocky Qian
 * @description: 原型类
 * @date: 2023/10/27 18:01
 * @version: 1.0
 */
public class ConcretePrototype implements Cloneable {

    public ConcretePrototype(){
        System.out.println("具体的原型对象创建成功了");
    }

    /**
     * clone 众所周知,object的clone方法是浅克隆,我们现在直接用就好了
     * 
     * @param 
     * @return com.plan.dream.pattern.prototype.shallow.ConcretePrototype
     * @throws 
     * @method clone
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/10/27 18:21
     */
    @Override
    protected ConcretePrototype clone() throws CloneNotSupportedException {
        System.out.println("克隆对象复制成功");
        return (ConcretePrototype) super.clone();
    }
}

测试结果:

/**
 * @ProjectName: dream
 * @Package: com.plan.dream.pattern.prototype.shallow
 * @ClassName: TestConcretePrototype
 * @author: Rocky Qian
 * @description:
 * @date: 2023/10/27 18:17
 * @version: 1.0
 */
public class TestConcretePrototype {

    public static void main(String[] args) throws CloneNotSupportedException {
        //创建一个新对象
        final ConcretePrototype c1 = new ConcretePrototype();
        //新对象clone出另外一个对象
        final ConcretePrototype c2 = c1.clone();
        //查看两个对象的地址值是否一致
        System.out.println(c1 == c2);
    }
}

输出结果:
具体的原型对象创建成功了
克隆对象复制成功
false
1.5.3.4 结论

浅克隆中,确实创建了一个新对象,但是构造方法只执行了一次。那么浅克隆肯定不是new操作,他的底层是用二进制的形式操作的。

1.5.4 深克隆
1.5.4.1 定义

克隆对象的所有基本类型变量含有的值与原型对象完全一致。但是引用数据类型不是,引用数据类型成员变量是创建了新的对象。

1.5.4.2 执行流程

在这里插入图片描述

1.5.4.3 代码实现

引用类型

/**
 * @ProjectName: dream
 * @Package: com.plan.dream.pattern.prototype.deep
 * @ClassName: Student
 * @author: Rocky Qian
 * @description: 引用类型
 * @date: 2023/10/27 18:26
 * @version: 1.0
 */
public class Student {

    private int age;
    private String name;

    public Student() {
    }

    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

基本数据类型

/**
 * @ProjectName: dream
 * @Package: com.plan.dream.pattern.prototype
 * @ClassName: PrototypeClass
 * @author: Rocky Qian
 * @description: 原型类
 * @date: 2023/10/27 18:01
 * @version: 1.0
 */
public class ConcretePrototype implements Cloneable {

    private Student student;

    public ConcretePrototype() {
        System.out.println("具体的原型对象创建成功了");
    }

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    public void getStudentInfo() {
        System.out.println("学生姓名:" + student.getName() + "   学生年龄:" + student.getAge());
    }

    /**
     * clone 众所周知,object的clone方法是浅克隆,我们现在直接用就好了
     *
     * @param
     * @return com.plan.dream.pattern.prototype.shallow.ConcretePrototype
     * @throws
     * @method clone
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/10/27 18:21
     */
    @Override
    protected ConcretePrototype clone() throws CloneNotSupportedException {
        System.out.println("克隆对象复制成功");
        return (ConcretePrototype) super.clone();
    }


}

新的浅克隆测试类

/**
 * @ProjectName: dream
 * @Package: com.plan.dream.pattern.prototype.shallow
 * @ClassName: TestConcretePrototype
 * @author: Rocky Qian
 * @description:
 * @date: 2023/10/27 18:17
 * @version: 1.0
 */
public class TestConcretePrototype {

    public static void main(String[] args) throws CloneNotSupportedException {
        //new 一个原型类
        ConcretePrototype c1 = new ConcretePrototype();
        //new 一个引用类
        Student s1 = new Student(1, "小明");
        //set 引用成员变量
        c1.setStudent(s1);
        //浅克隆
        ConcretePrototype c2 = c1.clone();
        //获取到浅克隆后的引用类
        Student s2 = c2.getStudent();
        //并把引用类赋值
        s2.setAge(2);
        s2.setName("小刚");
        //分别把原型类和浅克隆后的类信息打印
        c1.getStudentInfo();
        c2.getStudentInfo();
        //比较两个类中引用成员变量
        System.out.println(s1 == s2);
    }

}

输出结果:
具体的原型对象创建成功了
克隆对象复制成功
学生姓名:小刚   学生年龄:2
学生姓名:小刚   学生年龄:2
true

深克隆

/**
 * @ProjectName: dream
 * @Package: com.plan.dream.pattern.prototype.deep
 * @ClassName: Test1ConcretePrototype
 * @author: Rocky Qian
 * @description:
 * @date: 2023/10/27 19:26
 * @version: 1.0
 */
public class Test1ConcretePrototype {
    public static void main(String[] args) throws Exception {
     	//new 一个原型类 new 一个引用类型数据  set引用类型成员变量
        ConcretePrototype c1 = new ConcretePrototype();
        Student s1 = new Student(1, "小明");
        c1.setStudent(s1);
        
        //序列化原型类
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
        oos.writeObject(c1);
        oos.close();
        //反序列化成新对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
        ConcretePrototype c2 = (ConcretePrototype) ois.readObject();
        ois.close();
        //获取新对象中的引用成员变量
        Student s2 = c2.getStudent();
		//给引用成员变量赋新值
        s2.setAge(2);
        s2.setName("小刚");
		//分别获取原型类和新类的成员变量信息
        c1.getStudentInfo();
        c2.getStudentInfo();
        //比较两个成员变量是否为同一个对象
        System.out.println(s1 == s2);
    }
}

打印结果:
具体的原型对象创建成功了
学生姓名:小明   学生年龄:1
学生姓名:小刚   学生年龄:2
false
1.5.4.3 结论

最终结果,两个student对象s1、s2,经过clone发现,是同一个对象,即使重新赋值,也是同一个对象,并且s1、s2的值也是一致的。

1.5.5 原型模式的实际应用
1.5.5.1 需求

github消息系统会有发送邮件的功能,邮件内容都有一个模板,从数据库获取用户的信息,放到模板中生成一份完整的邮件。然后交给发送邮件接口。
在这里插入图片描述

1.5.5.2 UML

在这里插入图片描述

1.5.5.3 代码实现

Mail

@Data
public class Mail {

    /**
     * receiver:收件人
     * subject:邮件标题
     * appellation:称呼
     * context:邮件内容
     * tail:邮件结尾
     */
    private String receiver;
    private String subject;
    private String appellation;
    private String context;
    private String tail;

    public Mail (MsgTemplate msgTemplate) {
        this.subject = msgTemplate.getMsgSubject();
        this.context = msgTemplate.getMsgContext();

    }
}

Template

public class MsgTemplate {
    private final String  msgSubject = "[GitHub] A first-party GitHub OAuth application has been added to your account";
    private final String msgContext = "A first-party GitHub OAuth application (Git Credential Manager) with gist, repo";

    public String getMsgSubject() {
        return msgSubject;
    }

    public String getMsgContext() {
        return msgContext;
    }
}

发送邮件

public class Client {
    private final static int MAX_SEND_NUM = 5;

    public static void main(String[] args) {
        int sendNum = 0;
        Mail mail = new Mail(new MsgTemplate());
        mail.setTail("Thanks,\n" +
                "The GitHub Team");
        //发送邮件
        while (sendNum < MAX_SEND_NUM) {
            mail.setAppellation("Hay");
            mail.setReceiver(new Random().nextInt(99999999) + "@qq.com");
            sendMsg(mail);
            sendNum++;
        }


    }

    /**
     * 发送邮件
     * @param mail
     */
    public static void sendMsg(Mail mail) {
        System.out.print("标题:" + mail.getSubject() + " 收件人:" + mail.getReceiver());
        System.out.println("          ...发送成功");
    }
}

以上是我们平时的写法,但是呢,因为我们要频繁创建Mail对象。

我们把Mail做一下优化,并把调用方进行优化。

新Mail

public class Mail implements Cloneable {

    /**
     * receiver:收件人
     * subject:邮件标题
     * appellation:称呼
     * context:邮件内容
     * tail:邮件结尾
     */
    private String receiver;
    private String subject;
    private String appellation;
    private String context;
    private String tail;

    public Mail setReceiver(String receiver) {
        this.receiver = receiver;
        return this;

    }

    public Mail setSubject(String subject) {
        this.subject = subject;
        return this;
    }

    public Mail setAppellation(String appellation) {
        this.appellation = appellation;
        return this;
    }

    public Mail setContext(String context) {
        this.context = context;
        return this;
    }

    public Mail setTail(String tail) {
        this.tail = tail;
        return this;
    }


    public String getReceiver() {
        return receiver;
    }

    public String getSubject() {
        return subject;
    }

    public String getAppellation() {
        return appellation;
    }

    public String getContext() {
        return context;
    }

    public String getTail() {
        return tail;
    }

    @Override
    protected Mail clone() throws CloneNotSupportedException {
        return (Mail) super.clone();
    }

    public Mail(MsgTemplate msgTemplate) {
        this.subject = msgTemplate.getMsgSubject();
        this.context = msgTemplate.getMsgContext();

    }
}

新client

public class Client {
    private final static int MAX_SEND_NUM = 5;

    public static void main(String[] args) throws CloneNotSupportedException {
        int sendNum = 0;
        Mail mail = new Mail(new MsgTemplate());
        mail.setTail("Thanks,\n" +
                "The GitHub Team");
        //发送邮件
        while (sendNum < MAX_SEND_NUM) {
            //进行clone
            Mail cloneMail = mail.clone();

            cloneMail.setAppellation("Hay");
            cloneMail.setReceiver(new Random().nextInt(99999999) + "@qq.com");
            sendMsg(cloneMail);
            sendNum++;
        }


    }

    /**
     * 发送邮件
     * @param mail
     */
    public static void sendMsg(Mail mail) {
        System.out.print("标题:" + mail.getSubject() + " 收件人:" + mail.getReceiver());
        System.out.println("          ...发送成功");
    }
}
1.5.6 总结

优点:
1)创建新的对象实例比较复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。
2)原型模式提供了简化的创建结构。工厂方法模式常常需要一个与产品类等级结构相同的工厂等级接口。而原型模式不需要这样,原型模式的产品复制是通过封装在原型类中的克隆方法实现的,无需专门的工厂类来创建产品。
3)可以使用深克隆的方式保存对象状态。使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用。比如恢复到某一历史状态,可以辅助实现撤销操作。
缺点:
需要为每个类都装备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违反了开闭原则。

使用场景:

1)资源优化场景:
当对象初始化需要使用很多外部资源时,比如IO、数据文件、CPU、网络
2)复杂依赖场景
A对象创建依赖B,B又依赖C,C又依赖D…所以创建过程是一连串的get和set。
3)性能和安全要求的场景
同一个用户在一个会话周期内,可能会反复登录平台或使用一些受限功能,每一次访问请求都会访问授权服务器进行授权,但如果每次都过new产生一个对象会非常繁琐。
4)同一个对象可能被多个修改者使用的场景
一个商品需要提供给订单、物流、会员等多个服务,而且各个调用者可能需要修改数据时、
5)需要保存原始对象状态的场景
记录历史操作的场景。

2.结构型模式 (Structural Pattern)

主要总结了一些类与对象组合在一起的经典结构,这些经典结构可以解决对象的特定场景问题。

2.1 代理模式(Proxy Design Pattern)

2.1.1 概念

由于一些原因,客户端并不想或不能直接访问一个对象,此时可以通过一个称为“代理”的第三者来实现间接访问,该方案对应的设计模式被称为代理模式。代理模式让你能够提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许将请求提交给对象前后进行一些处理。现实生活中的场景:代购。
软件开发中的代理:
代理模式中引入了一个新的代理对象,代理对象在客户端对象和目标对象之间起到了中介的作用,它去掉客户端不能看到的内容和服务或者增加客户需要的额外的新服务。
代理模式的角色:
抽象主体类:声明了真是主题和代理主题的共同接口
真实主题类:实现了抽象主体中的具体业务,是代理对象代表的真实对象,也是最终要引用的对象。
代理类:也实现了抽象主体中的具体业务,其内部包含了对真实主体的引用。

在这里插入图片描述

2.1.2 UML

在这里插入图片描述

2.1.3 代码实现

场景:
我们要使用dao接口,给dao接口实现类,增加事务代理。

2.1.3.1 静态代理

抽象主体类

public interface MemberDao {

    /**
     * insert
     *
     * @param
     * @return void
     * @throws
     * @method insert
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/10/30 14:22
     */
    void insert();
}

真实类

public class MemberDaoImpl  implements MemberDao {

    /**
     * insert
     *
     * @param
     * @return void
     * @throws
     * @method insert
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/10/30 14:22
     */
    @Override
    public void insert() {
        System.out.println("新增成功");
    }
}

代理类

ublic class MemberDaoProxy implements MemberDao{

    private MemberDao target;


    public MemberDaoProxy(MemberDao target){
        this.target = target;
    }


    /**
     * 代理
     *
     * @param
     * @return void
     * @throws
     * @method insert
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/10/30 14:25
     */
    @Override
    public void insert() {
        System.out.println("开始事务");
        target.insert();
        System.out.println("关闭事务");
    }
}

测试类

public class Client {

    public static void main(String[] args) {
        //目标对象
        final MemberDaoImpl memberDao = new MemberDaoImpl();

        //代理对象
        final MemberDaoProxy memberDaoProxy = new MemberDaoProxy(memberDao);
        //执行代理方法
        memberDaoProxy.insert();
    }
}

输出结果:
开启事务
新增成功
关闭事务

静态代理:
优点:可以不在修改目标类的前提下,扩展目标类的功能
缺点
冗余由于代理对象要实现和目标对象一致的接口,会产生很多的代理。
不易维护,一旦接口中增加了方法,目标对象和代理对象都需要修改。

2.1.3.2 JDK动态代理

JDK动态代理的实现
动态代理利用了jdk api。动态地在内存对象中构建代理对象,从而实现对目标对象的代理功能,动态代理又称为JDK代理或接口代理。
静态代理和动态代理的区别
1)静态代理是在编译期就已经实现了,编译完之后代理类是一个实际的class文件
2)动态代理是运行时生成的,即编译完成后没有实际的class文件,而是在运行时,生成类字节码,并加载到jvm中。

JDK中生成代理对象主要涉及的类有:
1)主要用的方法:java.lang.reflect.Proxy类,newProxyInstance( )方法
在这里插入图片描述
在这里插入图片描述
2)java.lang.reflect.InvocationHandler 类 ,invoke方法
在这里插入图片描述
代码实现

public class ProxyFactory {

    private Object target;

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

    /**
     * 获取代理类
     *
     * @param
     * @return java.lang.Object
     * @throws
     * @method getProxyInstance
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/10/30 14:58
     */
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(
                //目标类使用的类加载器
                target.getClass().getClassLoader(),
                //目标对象实现的接口类型,
                target.getClass().getInterfaces(),
                //创建一个事件处理器
                new InvocationHandler() {
                    /**
                     * invoke
                     *
                     * @param proxy  代理对象
                     * @param method 对应于代理对象上调用的接口的实例
                     * @param args 对应了处理对象在调用接口方法是传递的实际参数
                     * @return java.lang.Object 返回目标对象方法的返回值,没有返回值就返回null
                     * @throws
                     * @method invoke
                     * @author Rocky Qian
                     * @version 1.0
                     * @date 2023/10/30 14:53
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //增强
                        System.out.println("开启事务");
                        method.invoke(target, args);
                        System.out.println("关闭事务");
                        return null;
                    }
                }
        );
    }
}

测试类

public class Client {

    public static void main(String[] args) {
        //获取真实类
        final MemberDaoImpl memberDaoImpl = new MemberDaoImpl();
        //使用代理类工厂
        final ProxyFactory proxyFactory = new ProxyFactory(memberDaoImpl);
        //创建代理类
        final MemberDao memberDao = (MemberDao) proxyFactory.getProxyInstance();
        //执行代理方法
        memberDao.insert();

    }
}

输出结果:
开启事务
新增成功
关闭事务
2.1.3.3 类是如何动态生成的

Java类虚拟机类加载过程主要分为三个阶段:装载链接初始化,其中链接阶段可分为三部分,验证准备解析。其中装载主要完成三件事:
1)通过一个类的全限定名获取定义此类的二级制字节流
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3)在内存中生成一个代表这个类的java.lang.class对象,作为方法区对这个类的各种数据访问入口

由于虚拟机规范对这3点要求并不具体,所以实际的实现是非常灵活的,关于第一点,获取类的二进制字节流就有很多途径:
在这里插入图片描述

1)从本地获取
2)从网络获取
3)从运行时计算生成,这种场景使用最多的就是动态代理,在java.lang.reflect,Proxy类中,就是用了ProxyGenerator.generatorProxyClass来为特定接口生成形式为"$Proxy"的代理类的二进制字节流。
在这里插入图片描述
所以,动态代理就是想办法,根据接口或者目标类对象,计算出代理类的字节码,然后加载到JVM中。

2.1.3.4 代理类的调用过程

由于我们无法获取到动态代理类的class文件,没办法知道调用过程。这里我们通过阿里的阿尔萨斯(arthas),帮助我们来看我们整个代理类的执行过程。
arthas 获取类信息步骤:
1.下载
2.在我们的测试类中,写一个死循环

public class Client {

    public static void main(String[] args) {
        //获取真是类
        final MemberDaoImpl memberDaoImpl = new MemberDaoImpl();
        //使用代理类工厂、创建代理类
        final MemberDao proxy = (MemberDao)new ProxyFactory(memberDaoImpl).getProxyInstance();

        System.out.println(proxy.getClass());
        // 执行代理方法
        proxy.insert();
        while (true){
        }
    }
}

3.cmd中 java -jar arthas-boot.jar
在这里插入图片描述

4.获取我们测试类的编号,等待一会
5.jad + 我们类的全量包名
6.得出以下代码

package com.sun.proxy;

import java.lang.annotation.Inherited;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Inherited {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals",Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("java.lang.annotation.Inherited").getMethod("annotationType", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            return;
        }
        catch (NoSuchMethodException noSuchMethodException) {
            throw new NoSuchMethodError(noSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException classNotFoundException) {
            throw new NoClassDefFoundError(classNotFoundException.getMessage());
        }
    }

    public final boolean equals(Object object) {
        try {
            return (Boolean)this.h.invoke(this, m1, new Object[]{object});
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String toString() {
        try {
            return (String)this.h.invoke(this, m2, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final int hashCode() {
        try {
            return (Integer)this.h.invoke(this, m0, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final Class annotationType() {
        try {
            return (Class)this.h.invoke(this, m3, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
}
2.1.3.3 GJLIB动态代理

**CGLib(code generator library)**是一个第三方代码生成类库,运行时在内存中生成一个子类对象从而实现对目标类对象功能的扩展,CGLib为没有实现接口的类实现代理,为JDK代理提供了补充。
在这里插入图片描述
1)最底层是字节码
2)ASM是操作字节码工具
3)CGLib基于ASM字节码工具操作字节码(即动态生成代理,对方法进行增强)
4)Sping AOP基于CGLib进行封装,实现CGLib方式的动态代理,如果使用CGLib需要引入CGLib的jar包,如果有了spring-core的jar包,则无需引入,因为Spring中包含了CGLib。

代码实现

1.获取代理类必须实现MethodInterceptor接口
2.Enhancer增强类
3.实现MethodInterceptor中intercept方法

所需要的实体

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Member {

    private String name;
    private Integer age;

}

无接口的类

public class MemberServiceImpl {

    /**
     * 获取会员
     *
     * @param
     * @return java.util.List<com.plan.dream.pattern.proxy.service.Member>
     * @throws
     * @method getMemberList
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/10/31 10:54
     */
    public void getMemberList() {
        final List<Member> list = Collections.singletonList(new Member("小明", 2));
        System.out.println(list);
    }
}

代理类

public class MemberTransactionProxy implements MethodInterceptor {

    /**
     * 生成CGLIB动态代理方法
     *
     * @param target 需要被代理的目标类
     * @return java.lang.Object 代理类对象
     * @throws
     * @method getTransactionProxy
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/10/31 10:56
     */
    public Object getTransactionProxy(Object target) {
        //增强类,用来创建动态代理类
        final Enhancer enhancer = new Enhancer();
        //设置代理类的父类字节码对象
        enhancer.setSuperclass(target.getClass());
        //设置回调
        enhancer.setCallback(this);
        //创建动态代理对象,并返回
        return enhancer.create();
    }

    /**
     * 实现回调方法
     *
     * @param o 代理对象
     * @param method 目标对象中的方法实例
     * @param objects 参数列表
     * @param methodProxy 代理对象中的方法的实例
     * @return java.lang.Object
     * @throws Throwable
     * @method intercept
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/10/31 11:03
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        System.out.println("开启事务");
        //执行代理方法
        final Object o1 = methodProxy.invokeSuper(o, objects);
        System.out.println("关闭事务");
        return o1;
    }
}

测试类

public class Client {

    public static void main(String[] args) {
        //创建目标类
        final MemberServiceImpl memberService = new MemberServiceImpl();
        System.out.println(memberService.getClass());
        //获取代理方法
        final MemberTransactionProxy memberTransactionProxy = new MemberTransactionProxy();
        //获取代理类
        MemberServiceImpl impl = (MemberServiceImpl) memberTransactionProxy.getTransactionProxy(memberService);
        System.out.println(impl.getClass());
        //代理类增强方法
        impl.getMemberList();
    }
}


结果:
class com.plan.dream.pattern.proxy.service.MemberServiceImpl
class com.plan.dream.pattern.proxy.service.MemberServiceImpl$$EnhancerByCGLIB$$bfef93f6
开启事务
[Member(name=小明, age=2)]
关闭事务

uml
在这里插入图片描述

流程图:
在这里插入图片描述

2.1.4 总结:
2.1.4.1 三种代理模式实现方式对比

1)JDK代理和CGLIB代理
使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使字节码生成代理类,在JDK1.6之前比使用java反射效率高点。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。
在JDK1.6、JDK1.7、JDK1.8逐步对JDK代理优化之后,在调用次数减少的情况下,JDK代理效率高于CGLib代理效率,只有当大量调用的时候,JDK1.6、JDK1.7比CGLib代理效率低一点,但是到了JDK1.8,JDK的代理效率高于CGLib代理,所以如果有接口,就使用JDK代理,如果没有接口就使用CGLib代理。
2)动态代理和静态代理
动态代理与静态代理相比较,最大的好处是接口中声明了的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke( )),这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理模式除了所有实现类都需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂性,而动态代理不会出现该问题。

2.1.4.2 代理模式的优缺点

优点:
代理模式在与客户端与目标之间起到一个中介的作用,保护目标对象的作用。
代理对象可以扩展目标对象的功能。
代理模式可能加客户端与目标对象分离,在一定程度上降低了系统的耦合度。

缺点:
增加了系统的复杂度

2.1.4.3 代理模式使用场景

1)功能增强
需要对一个对象的访问提供一些额外操作时,可以使用代理模式。
2)远程代理
RPC框架也可以看做一种代理模式,
3)防火墙
当浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网,当互联网返回值响应时,代理服务器再把它转给浏览器
4)保护代理
控制访问一个对象,如果需要,可以给不同的用户提供不同级别的使用权限。

2.2 桥接模式(Bridge Pattern)

2.2.1 概念

将抽象部分与他的实现部分分离,使他们都已独立地变化。桥接模式用一种巧妙的方式处理多层集成存在的问题,用抽象关系来取代传统的多层继承,将类之间的静态继承转系转变为动态地组合关系,使系统更加灵活,并易于扩展,有效的控制了系统中类的个数(避免了集成层级的指数级爆炸操作)。其实,桥接模式就是将两个独立变化的维度,进行了解耦,不是将两者耦合在一起,形成多层继承的结构。

2.2.2 UML
2.2.2.1 桥接模式中的角色

1)抽象化角色(Abstraction)
主要定义了这个角色的行为,并且包含了实现化对象的引用
2)实现化角色(Implementor)
主要定了这个角色必需的行为和属性,并提供了扩展抽象化角色调用
3)扩展抽象化角色(ReFinedAbstraction)
是抽象化角色的子类,继承了抽象化角色
4)具体实现化角色(ImplementorA、ImplementorD)
是对实现化角色的视线

2.2.2.2 UML

在这里插入图片描述

2.2.2 代码实现

场景:我有两种支付渠道微信、支付宝,支付模式有面容和指纹。我们将支付渠道和支付模式进行解耦。
在这里插入图片描述

抽象化角色

public interface PayMode  {

    /**
     * 对支付模式风控
     *
     * @param uid
     * @return boolean
     * @throws
     * @method security
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/1 10:59
     */
     boolean security(String uid);
}

实现化角色

在这里插入代码片

扩展抽象化角色A

public class WxPay extends Pay {

    public WxPay(PayMode payMode) {
        super(payMode);
    }

    /**
     * 微信支付
     *
     * @param uid     用户id
     * @param tradeId 商户id
     * @param amount  金额
     * @return java.lang.String
     * @throws
     * @method transfer
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/1 13:42
     */
    @Override
    public String transfer(String uid, String tradeId, BigDecimal amount) {
        System.out.println("微信支付开始");

        if (payMode.security(uid)) {
            System.out.println("微信支付成功");
            return "200";
        } else {
            System.out.println("微信支付失败");
        }
        return "500";
    }
}

扩展抽象化角色B

public class ZfbPay extends Pay {

    public ZfbPay(PayMode payMode) {
        super(payMode);
    }

    /**
     * 支付宝支付
     *
     * @param uid     用户id
     * @param tradeId 商户id
     * @param amount  金额
     * @return java.lang.String
     * @throws
     * @method transfer
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/1 13:42
     */
    @Override
    public String transfer(String uid, String tradeId, BigDecimal amount) {
        System.out.println("支付宝支付开始");

        if (payMode.security(uid)) {
            System.out.println("支付宝支付成功");
            return "200";
        } else {
            System.out.println("支付宝支付失败");
        }
        return "500";
    }
}

具体实现化角色A

public class PayFingerPrintMode implements PayMode {

    /**
     * 指纹支付
     *
     * @param uid
     * @return boolean
     * @throws
     * @method security
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/1 11:04
     */
    @Override
    public boolean security(String uid) {
        System.out.println("指纹风控通过");
        return true;
    }
}

具体实现化角色B

public class PayPaceMode  implements PayMode {

    /**
     * 刷脸支付
     *
     * @param uid
     * @return boolean
     * @throws
     * @method security
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/1 11:04
     */
    @Override
    public boolean security(String uid) {
        System.out.println("刷脸风控通过");
        return true;
    }
}

client

public class Client {
    public static void main(String[] args) {

        WxPay wxPay = new WxPay(new PayFingerPrintMode());
        wxPay.transfer("11", "11", new BigDecimal(11));
    }
}
输出结果:
微信支付开始
指纹风控通过
微信支付成功

2.2.3 总结
2.2.3.1 桥接模式的优缺点

优点:
1)分离抽象接口及其实现部分,桥接模式使用“对象之间的关联关系”解耦了抽象和实现之间固有绑定关系,使得抽象和实现可以沿着各自的维度来变化
2)在很多情况下,桥接模式可以取代多层继承方案,多层继承违背了单一职责原则,复用性差,类的数量多,桥接模式很好的解决了这些问题。
3)桥接模式提高了系统的扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合开闭原则。
缺点:
1)桥接模式的使用增加系统的理解和设计难度,由于关联关系建立在抽象层中,要求开发者一开始就要对抽象层,进行设计和编程
2)桥接模式要求正确识别出系统中的两个独立变化的维度,因此具有一定的局限性,并且如果正确的进行维度的划分,也需要相当丰富的经验

2.2.3.2 使用场景

1)需要平台独立性的应用程序时,比如,不同数据库的JDBC驱动程序,硬盘驱动程序等
2)需要在某种统一协议下增加更多组件,比如,在支付场景,我们同时支持微信、支付宝、各大银行支付组件等。这里的统一协议都是收款、支付、扣款,而组件就是支付宝、微信
3)基于信息驱动的场景,虽然消息的行为比较统一,主要包括发送、接收、处理和回执,但其实具体客户端的实现却通常各不一致,比如手机短信、邮件信息、QQ消息、微信消息等
4)拆分比较复杂的类对象时,当一个类中包含了大量对象和方法时,既不方便阅读,又不方便修改
5)希望从多个独立维度上扩展,比如系统功能性和非功能性的角度,业务或技术角度等

2.3 装饰器模式(Decorator Pattern)

2.3.1 概念

动态地给一个对象添加一些额外的职责,就扩展工鞥呢而言,装饰着模式提供了一种比使用子类更加灵活的替代方案。装饰者模式是一种用于继承的技术。他通过一种无需定义子类的方式,给对象动态的增加职责,用对象之间的关联关系,取代类之间的继承关系。

2.3.2 装饰器模式原理
2.3.2.1 装饰器模式中的角色

抽象构建角色(component):他是抽象构建角色和抽象装饰类的共同父类
具体构建角色(concreteComponent):他是抽象构建角色的子类,用于定义具体构建对象,被装饰的类
抽象装饰类角色(Decotator):是抽象构建角色的子类,是对具体构建角色增加职责
具体装饰类角色(concreteDecotator):他是抽象装饰类的子类,他负责向构建角色添加新的功能。

2.3.2.2 UML

在这里插入图片描述

2.3.3 代码实现

抽象构建类

public abstract class Component {

    /**
     * 抽象构建类-抽象方法
     *
     * @param
     * @return void
     * @throws
     * @method operation
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/1 16:01
     */
    public abstract void operation();
}

具体构建类

public class ConcreteComponent extends Component {

    /**
     * 具体构建类的基本方法
     *
     * @param
     * @return void
     * @throws
     * @method operation
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/1 16:02
     */
    @Override
    public void operation() {
        System.out.println("基本功能");
    }
}

抽象装饰类

public class Decorator extends Component {

    /**
     * 维持一个对抽象构建对象的引用
     */
    private Component component;

    public Decorator(Component component) {
        this.component = component;
    }


    /**
     * 装饰方法
     *
     * @param
     * @return void
     * @throws
     * @method operation
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/1 16:05
     */
    @Override
    public void operation() {
        //调用原有的业务方法,并没有真正的进行装饰,而且提供了一个统一的接口,将装饰的过程交给子类完成
        component.operation();
    }
}

具体装饰类

public class ConcreteDecorator extends Decorator {

    public ConcreteDecorator(Component component) {
        super(component);
    }


    @Override
    public void operation() {
        //调用原有的方法
        super.operation();
        print();
    }


    /**
     * 装饰方法
     *
     * @param
     * @return void
     * @throws
     * @method print
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/1 16:09
     */
    public void print(){
        System.out.println("进行装饰");
    }
}

调用

public class Client {
    public static void main(String[] args) {
        final ConcreteDecorator concreteComponent = new ConcreteDecorator(new ConcreteComponent());
        concreteComponent.operation();
    }
}

结果:
基本功能
进行装饰
2.3.4 总结
2.3.4.1优缺点

优点:
1)对于扩展一个对象的功能,装饰模式比继承更加铃铎,不会导致类的个数急剧增加
2)可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可在运行时选择不同的具体装饰类,从而实现不同的行为
3)可以对一个对象进行多次装饰,通过使用不同的具体装饰类,以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到更强大的对象。
4)具体构建类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构建类和具体装饰类,原有类库代码无需改变,符合开闭原则。

缺点:
1)使用装饰模式进行系统设计时将产生很多小对象。这些对象的区别在于他们之间相互连接的方式有所不同。而不是他们的类或者属性值不同,大量的小对象产生势必会占用更多的系统资源,在一定程度上影响程序的性能。
2)装饰器模式提供了一种比继承更加灵活、机动的解决方案,但同时也意味着比继承更容易出错。排错也更加困难,对于多次装饰的对象,在调试寻找错误时可能需要逐级排查,较为繁琐。

2.3.4.2 装饰器适用场景

1)快速动态扩展和撤销一个类的功能场景。比如,有的场景下对API接口安全性要求比较高,那么就可以使用装饰模式对传输的字符串进行加密解密或者压缩,如果安全性不高,则可以不使用
2)不支持继承扩展类的场景。比如,适用final关键字的类,或者系统中存在大量通过继承产生的子类

2.4 适配器模式(Adapter Pattern)

2.4.1 概念

将类的接口转换为客户期望的另一个接口,适配器可以让不兼容的两个类一起协同工作。适配器模式,是用来做适配的,他将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类,可以一起工作。适配器模式有两种方式实现:类适配器和对象适配器。其中,类适配器使用继承关系来实现,对象适配器使用组合关系来实现。类适配器的耦合度比对象适配器的高,且要求程序员了解现有组件库中相关组件的内部接口,所以应用相对较少。

2.4.2 UML
2.4.2.1 类适配器UML

在这里插入图片描述

2.4.2.2 对象适配器UML

在这里插入图片描述

2.4.3 代码实现
2.4.3.1 类适配器

目标抽象类

public interface Target {

    /**
     * 目标抽象类-请求接口
     *
     * @param
     * @return void
     * @throws
     * @method request
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 11:02
     */
    void request();
}

被适配的类

public class Adaptee {


    /**
     * 被适配的类-被适配的方法
     *
     * @param
     * @return void
     * @throws
     * @method adapteeRequest
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 11:04
     */
    public void adapteeRequest() {
        System.out.println("被适配的方法执行了");
    }

}

适配器类

public class Adapter extends Adaptee implements Target {


    /**
     * 适配器类-适配方法
     *
     * @param
     * @return void
     * @throws
     * @method request
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 11:05
     */
    @Override
    public void request() {
        System.out.println("开始调用类适配器");
        super.adapteeRequest();
        System.out.println("调用类适配器解释");
    }
}

调用者

public class Client {


    public static void main(String[] args) {
        //调用适配器
        Target target = new Adapter();
        //执行适配器方法
        target.request();
    }
}

结果:
开始调用类适配器
被适配的方法执行了
调用类适配器解释
2.4.3.2 对象适配器

目标抽象类

public interface Target {

    /**
     * 目标抽象类-请求接口
     *
     * @param
     * @return void
     * @throws
     * @method request
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 11:02
     */
    void request();
}

被适配的类

public class Adaptee {


    /**
     * 被适配的类-被适配的方法
     *
     * @param
     * @return void
     * @throws
     * @method adapteeRequest
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 11:04
     */
    public void adapteeRequest() {
        System.out.println("被适配的方法执行了");
    }

}

适配器类

public class Adapter implements Target {

    private Adaptee adaptee;

    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    /**
     * 适配器类-适配方法
     *
     * @param
     * @return void
     * @throws
     * @method request
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 11:05
     */
    @Override
    public void request() {
        System.out.println("开始调用类适配器");
        adaptee.adapteeRequest();
        System.out.println("调用类适配器解释");
    }
}

调用者

public class Client {


    public static void main(String[] args) {
        //调用适配器
        Target target = new Adapter(new Adaptee());
        //执行适配器方法
        target.request();
    }
}

结果:
开始调用类适配器
被适配的方法执行了
调用类适配器解释
2.4.3.3 具体场景实现

假设我们有一台电脑,目前只能读取SD卡信息,这时我们想使用电脑读取TF卡的内容,就需要将TF卡加上卡套,转换成SD卡,创建一个读卡器,将TF卡中的内容读取出来。

类适配器UML

在这里插入图片描述

对象适配器UML
在这里插入图片描述

代码实现
Computer

public class Computer {

    /**
     * 读取sd卡
     *
     * @param sdCard
     * @return java.lang.String
     * @throws
     * @method read
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 13:56
     */
    public String read(SdCard sdCard){

        return sdCard.readSd();
    }

}

**SdCard **

public interface SdCard {

    /**
     * 写入sd卡
     *
     * @param msg
     * @return void
     * @throws
     * @method writeSd
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 13:49
     */
    void writeSd(String msg);

    /**
     * 读取sd
     *
     * @param
     * @return java.lang.String
     * @throws
     * @method readSd
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 13:49
     */
    String readSd();
}

**SdCardImpl **

public class SdCardImpl implements SdCard {


    /**
     * 写入数据
     *
     * @param msg
     * @return void
     * @throws
     * @method writeSd
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 13:50
     */
    @Override
    public void writeSd(String msg) {
        System.out.println("成功写入Sd卡数据");
    }

    /**
     * 读取数据成功
     *
     * @param
     * @return java.lang.String
     * @throws
     * @method readSd
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 13:50
     */
    @Override
    public String readSd() {
        System.out.println("成功读取Sd卡数据");
        return "200";
    }
}

**TfCard **

public interface TfCard {


    /**
     * 写入Tf卡
     *
     * @param msg
     * @return void
     * @throws
     * @method writeSd
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 13:49
     */
    void writeTf(String msg);

    /**
     * 读取Tf
     *
     * @param
     * @return java.lang.String
     * @throws
     * @method readSd
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 13:49
     */
    String readTf();
}

TfCardImpl

public class TfCardImpl implements TfCard {

    /**
     *
     *
     * @param msg
     * @return void
     * @throws
     * @method writeTf
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 13:52
     */
    @Override
    public void writeTf(String msg) {
        System.out.println("成功写入Tf卡数据");
    }

    @Override
    public String readTf() {
        System.out.println("成功读取Tf卡数据");
        return "200";
    }
}

SdAdapterTf

public class SdAdapterTf extends TfCardImpl implements SdCard {

    /**
     * 写适配器
     *
     * @param msg
     * @return void
     * @throws
     * @method writeSd
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 13:58
     */
    @Override
    public void writeSd(String msg) {
        System.out.println("适配器写入Tf");
        writeTf(msg);
    }

    /**
     * 读适配器
     *
     * @param
     * @return java.lang.String
     * @throws
     * @method readSd
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 13:58
     */
    @Override
    public String readSd() {
        System.out.println("适配器读取Tf");
        return readTf();
    }
}

Client

public class Client {

    public static void main(String[] args) {
        System.out.println(new Computer().read(new SdAdapterTf()));
    }
}

对象适配器的代码就不实现了,有空可以自己尝试一下。

2.4.3 总结
2.4.3.1 优缺点

优点:
1)将目标类和适配者类解耦,通过引入一个适配器类,来重用现有的适配者类,无需修改原有结构
2)增加了类的透明性和复用性,将具体业务实现过程,封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性。同一个适配者类可以在多个不同的系统中复用。
3)灵活性和扩展性都非常好,通过使用配置文件可以很方便的更换适配器,也可以在不修改原有代码的基础上,增加新的适配器类,符合开闭原则。
缺点:
类适配器:
1)对于Java等不支持多重继承的语言,一次只能适配一个适配者类,不同同事适配多个适配者。
2)适配者类不是最终类

对象适配器:
1)与适配器模式比较,在该模式下要在适配器中置换适配者类的某些方法比较麻烦。

2.4.3.2 适用场景

1)统一过各类的接口设计时
某个功能的实现,需要依赖多个外部系统。通过适配器模式,将他们的接口适配为同一的接口定义
2)需要依赖外部系统时
当我们把项目中依赖的一个外部系统,替换成为另一个外部系统的时候,利用适配器模式,可以减少对代码的改动
3)原有接口无法修改时,或者原有接口功能太老但又需要兼容
jdk1.0 Enumeration到Iterator的替换,使用适配器模式保留Enumeration类,并将其,实现,替换为直接调用的Iterator。
4)适配不同的数据格式时:
Slf4j日志框架,定义打印日志的统一接口,提供针对不同日志框架的适配器。

2.4.4 代理、桥接、装饰器、适配器四种设计模式的区别

代理模式:在不改变原始类接口的条件下,为原始类定义一个代理类。主要是控制访问,这时他与装饰器最大的区别。
桥接模式:桥接模式的目的是将接口部分与实现部分分离,从而让他们较容易、也相对独立。
装饰器模式:在不改变原有接口的情况下,对原始类进行增强,并且支持多个装饰器嵌套使用。
适配器模式:将一类的接口转换成为,客户希望的另一个接口。适配器模式让那些不兼容的类可以一起工作。

2.5 外观模式(Facade Pattern)

2.5.1 概念

也叫门面模式,为了子系统中的一组接口,提供统一的接口,它定义了一个更级别的接口,使子系统更易于使用。外观模式,是一种通过为多个复杂的子系统提供一个一致的接口。而使这些子系统,更加容易被访问的模式。该模式对外,有一个统一的接口,外部应用程序不用关系把内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。门面模式基于迪米特法则和接口隔离原则,两个有交互的系统,只暴露有限的接口。
在这里插入图片描述
门面类,充当了系统中“服务员”的角色,他为多个业务类的调用提供了一个统一的入口,简化了类与类之间的交互。如果没有门面类,每个客户类,需要和多个子系统之间进行复杂的交互,系统的耦合度将会很大。

2.5.2 UML

在这里插入图片描述

2.5.2 代码实现

子系统A

public class SubSystemA {

    /**
     * A系统A功能
     *
     * @param
     * @return void
     * @throws
     * @method methodA
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 15:20
     */
    public void methodA() {
        System.out.println("A系统A功能");
    }
}

子系统B

public class SubSystemB {

    /**
     * B系统B功能
     *
     * @param
     * @return void
     * @throws
     * @method methodB
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 15:21
     */
    public void methodB() {
        System.out.println("B系统B功能");
    }
}

子系统C

public class SubSystemC {

    /**
     * C系统C功能
     *
     * @param
     * @return void
     * @throws
     * @method methodB
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 15:21
     */
    public void methodC() {
        System.out.println("C系统C功能");
    }
}

外观类

public class Facade {

    private SubSystemA subSystemA = new SubSystemA();
    private SubSystemB subSystemB = new SubSystemB();
    private SubSystemC subSystemC = new SubSystemC();

    /**
     * 外观类方法
     *
     * @param
     * @return void
     * @throws
     * @method method
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 15:22
     */
    public void method(){
        System.out.println("调用外观类,执行子系统方法");
        subSystemA.methodA();
        subSystemB.methodB();
        subSystemC.methodC();
        System.out.println("执行完成");
    }
}
结果:
调用外观类,执行子系统方法
A系统A功能
B系统B功能
C系统C功能
执行完成

调用

public class Client {

    public static void main(String[] args) {
        final Facade facade = new Facade();
        facade.method();
    }
}

2.5.3 总结
2.5.3.1 优缺点

优点:
1)他对客户端屏蔽了子系统组件,减少了客户端所需要处理的对象数据,并使子系统使用起来更加容易,通过引入外观模式,客户端代码将变得很简单,与之关联的对象也很少。
2)它实现了子系统与客户端之间的松耦合关系,这使子系统的变化不会影响到调用他的客户端,只需要调整外观类即可
3)一个子系统的修改对其他子系统没有任何影响,儿子系统内部变化也不会影响到外观对象。
缺点:
1)不能很好的控制客户端直接使用子系统,如果客户端访问子系统做太多的限制,则减少了灵活性和可变性。
2)如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则

2.5.3.2 适用场景

1)简化复杂系统
当我们开发了一整套的电商系统后(包括订单、商品、支付、会员等系统),我们不能让用户一次使用这些系统后,才能完成商品的购买,而是用户需要一个门户网站或者手机App这样的简化的门片系统来提供在线的购物功能。
2)减少客户端处理的系统数量
比如在web应用中,系统之间的调用可能需要处理DataBase数据库,Model业务对象等,其中DataBase对象就需要处理打开数据库、关闭连接等操作。然后转换为Model业务对象,实在太慢反,如果我们创建一个数据库的使用门面(Dao层),那么实现起来就很容易。
3)让多个系统(对象)为多个系统(对象)工作
线程池ThradPool其实就是一个门面模式,他为系统提供了统一的线程对象的创建、销毁、使用等。
4)联合更多的系统来扩展原有系统
当我们的电商系统中需要一些新功能时,比如人脸识别。我们可以不需要自行研发,而是购买其他公司提供的服务,这时我们通过门面系统就能方便快速地进行扩展。
5)作为一个简洁的中间层
门面模式还以用来隐藏或者封装系统中的分层结构,同时作为一个简化的中间层来使用。比如秒杀、库存、钱包等场景中,我们需要共享有状态的数据时(比如商品库存、账户里的钱),在不改变原有系统的前提下,通过一个中间的共享层(如秒杀活动的商品总数统一放在redis中),就能统一进行各种服务的调用。

2.6 组合模式(Composite Pattern)

2.6.1 概念

组合模式最初只是解决树形结构的场景,更多的是处理对象组织结构之间的问题。而组合关系则是,通过将不同对象封装起来,完成一个统一功能。不要搞混组合模式和组合关系。将对象组合成树形结构,以表示整个部分的层次结构。组合模式可以让用户,统一对待单个对象和对象的组合。

组合模式中的角色:
**Component:**抽象根节点,声明了所有子类共有的行为和属性
**Composite:**树枝节点,实现了根节点中定义的行为,其中还管理了集合,可以存储子节点
**Leaf:**叶子节点,存储在树枝节点的在最底层,叶子节点下没有其他分支,是系统遍历的最小单位

2.6.2 UML

在这里插入图片描述

2.6.3 代码实现

Component

public abstract class Component {

    /**
     * 增加节点
     *
     * @param c
     * @return void
     * @throws
     * @method add
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 17:43
     */
    public abstract void add(Component c);

    /**
     * 移除节点
     *
     * @param c
     * @return void
     * @throws
     * @method remove
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 17:44
     */
    public abstract void remove(Component c);

    /**
     * 获取子节点
     *
     * @param i
     * @return com.plan.dream.pattern.composite.demo1.Component
     * @throws
     * @method getChild
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/2 17:44
     */
    public abstract Component getChild(int i);


    /**
     * 具体业务方法
     *
     * @param
     * @return void
     * @throws
     * @method operation
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 9:54
     */
    public abstract void operation();

Composite

public class Composite extends Component {

    private List<Component> componentList = new ArrayList();


    /**
     * add
     *
     * @param c 对象
     * @return void
     * @throws
     * @method add
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 9:55
     */
    @Override
    public void add(Component c) {
        componentList.add(c);
    }

    /**
     * 移除 c
     *
     * @param c
     * @return void
     * @throws
     * @method remove
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 9:56
     */
    @Override
    public void remove(Component c) {
        componentList.remove(c);
    }

    /**
     * 获取子类
     *
     * @param i
     * @return com.plan.dream.pattern.composite.demo1.Component
     * @throws
     * @method getChild
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 9:56
     */
    @Override
    public Component getChild(int i) {
        return componentList.get(i);
    }

    /**
     * 具体业务方法
     *
     * @param
     * @return void
     * @throws
     * @method operation
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 9:56
     */
    @Override
    public void operation() {
        System.out.println(componentList);
    }

Leaf

public class Leaf extends Component {

    /**
     * 新增
     *
     * @param c
     * @return void
     * @throws
     * @method add
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 9:58
     */
    @Override
    public void add(Component c) {

    }

    /**
     * 删除
     *
     * @param c
     * @return void
     * @throws
     * @method remove
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 9:58
     */
    @Override
    public void remove(Component c) {

    }

    /**
     * 无子节点
     *
     * @param i
     * @return com.plan.dream.pattern.composite.demo1.Component
     * @throws
     * @method getChild
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 9:58
     */
    @Override
    public Component getChild(int i) {
        return null;
    }

    /**
     * 叶子节点的方法
     *
     * @param
     * @return void
     * @throws
     * @method operation
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 9:58
     */
    @Override
    public void operation() {
        System.out.println("叶子节点");
    }
}
2.6.4 具体业务实现

实现Windows,列出某一文件下的所有文件和文件夹

2.6.4.1 UML

在这里插入图片描述

2.6.4.1 具体业务的代码实现

抽象根节点

public abstract class Entry {

    /**
     * 获取名字
     *
     * @param
     * @return java.lang.String
     * @throws
     * @method getName
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 10:21
     */
    public abstract String getName();

    /**
     * 获取大小
     *
     * @param
     * @return int
     * @throws
     * @method getSize
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 10:21
     */
    public abstract int getSize();

    /**
     * 打印
     *
     * @param
     * @return void
     * @throws
     * @method print
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 10:22
     */
    public abstract void print();

    /**
     * 新增
     *
     * @param entry
     * @return com.plan.dream.pattern.composite.demo2.Entry
     * @throws
     * @method add
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 11:18
     */
    public abstract Entry add(Entry entry);


}

树枝节点

public class Directory extends Entry {

    /**
     * 文件夹和问价
     */
    private List<Entry> entries = new ArrayList<Entry>();

    /**
     * 文件名
     */
    private String name;

    public Directory(List<Entry> entries, String name) {
        this.entries = entries;
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getSize() {
        return 0;
    }

    @Override
    public void print() {
        System.out.println(this);
    }

    @Override
    public Entry add(Entry entry) {
        entries.add(entry);
        return this;
    }
}

叶子节点

public class File extends Entry {

    private String name;

    private int size;

    public File(String name ,int size){
        this.name = name;
        this.size = size;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public void print() {
        System.out.println(this);
    }

    @Override
    public Entry add(Entry entry) {
        return null;
    }

}
2.6.5 总结
2.6.5.1 组合模式分类

1)透明组合模式
透明组合模式中,抽象根节点角色中声明了,所有用于管理成员对象的方法,比如在示例中component声明了add、remove等方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准模式。透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其,提供了add(),remove()等方法是没有意义的,这在编译阶段不会出错。但是在运行阶段,如果调用这些方法可能会出错。
2)安全组合模式
在抽象根角色中,没有声明任何用于管理,成员对象的方法,而是在树枝节点类中声明了,并实现了这些方法。安全组合模式的缺点就是不够透明,因为叶子节点和容器节点具有不同的方法, 且容器节点中,那些用于管理成员对象的方法,没有在抽象根节点中定义,因此客户端不能完全针对抽象变成,必须有区别的对待叶子节点和叶子节点

2.6.5.2 优缺点

优点:
1)组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次。他让客户端忽略了层次的差异,方便对整个层次结构进行控制
2)客户端可以一致地使用一个组合接口或其中单个对象,不必关系处理的是单个对象,还是整个组合结构。简化了客户端代码
3)组合模式中,增加了新的树枝节点和叶子节点都很方便,无须对现有的类库进行任何修改,符合开闭原则
4)组合模式为树形结构的面向对象的实现,提供了一种灵活的解决方案,通过叶子节点和树枝几点的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单

缺点:
1)使用组合模式的前提,你的业务场景必须能够表示成树形结构。所以,组合模式的应用场景很局限,他不是一种很常用的设计模式。

2.6.5.3 适用场景

1)处理树形结构,如公司人员组织架构、订单信息
2)跨越多个层次结构,聚合数据,比如,统计文件夹下的文件数
3)统一处理一个结构中的多个对象,比如遍历文件夹下的所有XML类型文件内容

2.7 享元模式(Flyweight Pattern)

2.7.1 概念

摒弃了在每个对象中保存所有数据的方式。而是通过共享多个对象所共有的相同状态,从而让我们能在有效的内存容量中载入更多对象。享元模式要解决的核心问题就是节约内存,使用的办法是找出相似对象之间共有特征,然后复用这些特征,所谓"享元",就是被共享的单元。享元模式通过共享技术,实现相同或者相似的对象的重用,在逻辑上每一个出现的字符,都有一个对象与之对应,然而在物理上,他们却是共享一个享元对象。享元模式中的两种状态:内部状态:不会随着环境的改变而改变的,可共享部分。外部状态:随着环境改变而改变,不可共享部分。

2.7.2 UML
2.7.2.1 角色

**享元工厂角色:**负责创建、管理享元角色,作为存储享元对象的享元池,用户获取对象时,先从享元池中获取,有则返回,没有创建新的返回给用户。
**抽象享元角色:**定义了具体享元角色中的公共的方法
**可共享的具体享元角色:**在具体享元类中,需要将内部状态和外部状态分开处理
**非共享的具体角色:**重写了抽象享元角色的方法

2.7.2.2 UML

在这里插入图片描述

2.7.3 代码实现

享元抽象类

public abstract class Flyweight {

    /**
     * 可共享的
     *
     * @param state
     * @return void
     * @throws
     * @method operation
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 15:07
     */
    public abstract void operation(String state);

}

具体享元类

public class ConcreteFlyweight extends Flyweight {

    /**
     * 内部状态:inState作为一个成员变量,同一个享元对象的内部状态是一致的。
     */
    private final String inState;


    public ConcreteFlyweight(String inState) {
        this.inState = inState;
    }

    /**
     * 外部状态在使用的时候,通常是有外部设置,不保存在享元对象中,即使是同一个对象
     *
     * @param state
     * @return void
     * @throws
     * @method operation
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 15:14
     */
    @Override
    public void operation(String state) {
        System.out.println("内部状态: " + inState + "   外部状态:" + state);

    }
}

不同享享元类

public class UnSharedFlyweight extends Flyweight {

    private String inState;

    public UnSharedFlyweight(String inState) {
        this.inState = inState;
    }

    @Override
    public void operation(String state) {
        System.out.println("使用不共享对象,内部状态:" + inState + "  外部状态:" + state);
    }
}

享元工厂

public class FlyweightFactory {

    /**
     * 用于存储享元对象,充当享元池效果 存储了对象传递之间共有的状态,其实就是一创建这些对象时,就有这些状态
     */
    private Map<String, Flyweight> pool = new ConcurrentHashMap<>();

    /**
     * 让我们享元对象之间状态的传递
     *
     * @param
     * @return
     * @throws
     * @method FlyweightFactory
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 16:38
     */
    public FlyweightFactory() {
        //添加对应享元类内部状态
        pool.put("A", new ConcreteFlyweight("A"));
        pool.put("B", new ConcreteFlyweight("A"));
        pool.put("C", new ConcreteFlyweight("A"));
        pool.put("D", new ConcreteFlyweight("A"));
    }


    /**
     * @param key
     * @return com.plan.dream.pattern.flyweight.demo1.Flyweight
     * @throws
     * @method getFlyweight
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 16:41
     */
    public Flyweight getFlyweight(String key) {
        Flyweight result = null;

        if (pool.containsKey(key)) {
            System.out.println("享元池中存在,直接使用,key:" + key);
            result = pool.get(key);
        } else {
            System.out.println("享元池中不存在,先添加,key:" + key);
            result = new ConcreteFlyweight("A");
            pool.put(key, result);
        }
        return result;
    }

}

调用者

public class Client {

    public static void main(String[] args) {
        //先获取工厂对象
        final FlyweightFactory flyweightFactory = new FlyweightFactory();
        //通过工厂对象获取共享的享元对象
        final Flyweight a1 = flyweightFactory.getFlyweight("F");
        a1.operation("a1");
        final Flyweight a2 = flyweightFactory.getFlyweight("F");
        a2.operation("a2");
        System.out.println(a1 == a2);
        //非共享享元对象,就是普通对象,直接用就可以了
        final UnSharedFlyweight u1 = new UnSharedFlyweight("A");
        final UnSharedFlyweight u2 = new UnSharedFlyweight("A");
        System.out.println(u1 == u2);
    }
}


输出结果
享元池中不存在,先添加,key:F
内部状态: A   外部状态:a1
享元池中存在,直接使用,key:F
内部状态: A   外部状态:a2
true
false
2.7.4 具体案例实现

五子棋中有大量的黑白子,他们的形状大小都是一样的,只是出现的位置不同,所以一个棋子作为一个独立的对象,存储在内存中,会导致大量的内存的浪费,我们使用享元模式来进行优化。
在这里插入图片描述

2.7.4.1 UML

在这里插入图片描述

2.7.4.2 代码实现

抽象享元类

public abstract class GobangFlyweight {


    /**
     * 获取颜色
     *
     * @param
     * @return java.lang.String
     * @throws
     * @method getColor
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 17:08
     */
    public abstract String getColor();


    /**
     * 显示
     *
     * @param
     * @return void
     * @throws
     * @method display
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 17:09
     */
    public void display() {
        System.out.println("棋子颜色:" + this.getColor());
    }
}

具体享元类

public class BlackGobang extends GobangFlyweight {


    /**
     * 获取颜色
     *
     * @param
     * @return java.lang.String
     * @throws
     * @method getColor
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 17:10
     */
    @Override
    public String getColor() {
        return "黑色";
    }
}

具体享元类

public class WriteGobang extends GobangFlyweight {

    /**
     * 获取颜色
     *
     * @param
     * @return java.lang.String
     * @throws
     * @method getColor
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 17:10
     */
    @Override
    public String getColor() {
        return "白色";
    }

}

享元工厂

public class GobangFactory {


    /**
     * 享元池
     */
    private final Map<String, GobangFlyweight> pool = new ConcurrentHashMap<>();

    /**
     * 初始化
     *
     * @param
     * @return
     * @throws
     * @method GobangFactory
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 17:21
     */
    private GobangFactory() {
        pool.put("b", new BlackGobang());
        pool.put("w", new WriteGobang());

    }

    /**
     * 静态内部类
     *
     * @param
     * @return com.plan.dream.pattern.flyweight.demo2.GobangFactory
     * @throws
     * @method getInstance
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 17:20
     */
    public static GobangFactory getInstance() {
        return SingletonHolder.INSTANCE;
    }


    /**
     * 全局工厂所以用到单例
     */
    private static class SingletonHolder {
        private static final GobangFactory INSTANCE = new GobangFactory();
    }


    /**
     * 获取棋子
     *
     * @param key
     * @return com.plan.dream.pattern.flyweight.demo2.GobangFlyweight
     * @throws
     * @method getGobang
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/3 17:24
     */
    public GobangFlyweight getGobang(String key) {
        return pool.get(key);
    }

}

调用类

public class Client {

    public static void main(String[] args) {

        final GobangFlyweight w = GobangFactory.getInstance().getGobang("w");
        final GobangFlyweight b = GobangFactory.getInstance().getGobang("b");
        final GobangFlyweight b1 = GobangFactory.getInstance().getGobang("b");
        w.display();

        System.out.println(b == b1);

    }
}

输出
棋子颜色:白色
true
2.7.5 总结
2.7.5.1 优缺点

优点
1)极大减少内存中相似或相同是对象数量,节约系统资源,提升系统性能
2)享元模式中的外部状态独立,且不影响内部状态

缺点
为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使逻辑变得复杂

2.7.5.1 使用场景

1)一个系统有大量相同或相似的对象,造成内存的大量消耗
2)JDK中valueOf

3. 行为模式(Behavioral Pattern)

用于描述程序在运行时的流程控制,即描述多个类或对象之间,怎样互相协同完成,单个对象都午饭单独完成的任务,他涉及到算法与对象间职责的分配。行为模式分为类行为模式和行为模式,前者采用继承机制在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度更低,满足“合成复用原则”,所以对象行为模式,比类行为模式具有更大的灵活性。
共11种行为模式,除了模板方法模式和解释器模式是类行为模式,其他的全部属于对象行为模式。

3.1 观察者模式(Observer Pattern)

3.1.1 概念

观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者一些产品的设计思路。都有这种模式的影子。我们常说的事件驱动的架构,也是观察者模式的最佳实践。当我们观察某一对象时,对象传递出的每一个行为,都被看成一个事件,观察者通过处理每个事件来完成自身的操作处理。生活中也有许多观察者模式的应用,比如汽车与红绿灯的关系,“红灯停”,“绿灯行”,在这个过程中,交通信号灯是汽车的观察目标,而汽车是观察者。观察者模式原始定于是:定义对象之间的一对多的依赖关系,这样当一个对象改变状态时,他的所有依赖项,都会自动得到通知和更新。观察者模式,他是建立一种对象与对象之间的依赖关系,一个对象发生改变时,将自动通知其他对象,其他对象做出相应的做出反应。在观察者模式中,反生改变的对象称为观察目标,而被通知改变的对象被称为观察者,一个观察目标,可以应对多个观察者,而且这些观察者之间,可以没有任何联系。可以根据需要增加或删除观察者,使得系统易于扩展。观察者模式的别名有发布-订阅(Publish/Subscribe)模式,模式-视图模式(Model-View),源-监听模式(Source-listener)等。

3.1.2 UML
3.1.2.1 角色

抽象主题(抽象被观察者 Subject):抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
具体主题(具体被观察者 ConcreteSubject):具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
抽象观察者(Observer):抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
具体观察者(ConcrereObserver):具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要与具体目标保持一致.

3.1.2.2 类图

在这里插入图片描述

3.1.3 代码实现

*Observer

public interface Observer {

    /**
     * 为不同的观察者更新行为,定义一个相同的接口,不同的观察者对该接口有不同的实现
     *
     * @param
     * @return void
     * @throws
     * @method update
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/6 10:40
     */
    void update();
}

ConcreteObserver1

public class ConcreteObserver1 implements Observer {

    /**
     * 具体实现
     *
     * @param
     * @return void
     * @throws
     * @method update
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/6 10:53
     */
    @Override
    public void update() {
        System.out.println("ConcreteObserver1 等到通知,更新状态!");

    }
}

ConcreteObserver2

public class ConcreteObserver2  implements Observer{


    /**
     * 具体实现
     *
     * @param
     * @return void
     * @throws
     * @method update
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/6 10:53
     */
    @Override
    public void update() {
        System.out.println("ConcreteObserver2 等到通知,更新状态!");
    }
}

Subject

public abstract class Subject {


    /**
     * 绑定
     *
     * @param ob
     * @return void
     * @throws
     * @method attach
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/6 10:54
     */
    public abstract void attach(Observer ob);

    /**
     * 通知
     *
     * @return void
     * @throws
     * @method attach
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/6 10:54
     */
    public abstract void observerNotify();

    /**
     * 解除绑定
     *
     * @param ob
     * @return void
     * @throws
     * @method attach
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/6 10:54
     */
    public abstract void detach(Observer ob);

}

ConcreteSubject

public class ConcreteSubject extends Subject {

    private List<Observer> obsList = new ArrayList<>();

    /**
     * 注册方法 向观察者集合增加一个观察者
     *
     * @param ob
     * @return void
     * @throws
     * @method attach
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/6 10:58
     */
    @Override
    public void attach(Observer ob) {
        obsList.add(ob);
    }

    /**
     * 通知
     *
     * @return void
     * @throws
     * @method notify
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/6 10:59
     */
    @Override
    public void observerNotify() {
        for (Observer observer : obsList) {
            observer.update();
        }
    }

    /**
     * 解绑
     *
     * @param ob
     * @return void
     * @throws
     * @method detach
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/6 10:59
     */
    @Override
    public void detach(Observer ob) {
        obsList.remove(ob);
    }
}

Client

public class Client {
    public static void main(String[] args) {
        final Subject subject = new ConcreteSubject();
        final ConcreteObserver1 concreteObserver1 = new ConcreteObserver1();
        final ConcreteObserver2 concreteObserver2 = new ConcreteObserver2();
        subject.attach(concreteObserver1);
        subject.attach(concreteObserver2);
        subject.observerNotify();
        System.out.println(subject);

        subject.detach(concreteObserver1);
        subject.observerNotify();
    }
}

输出结果:
ConcreteObserver1 等到通知,更新状态!
ConcreteObserver2 等到通知,更新状态!
com.plan.dream.pattern.observe.demo1.ConcreteSubject@71f4dd
ConcreteObserver2 等到通知,更新状态!
3.1.4 总结
3.1.4.1 优缺点

优点
1)降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系
2)被观察者发送通知,所有注册的观察者都会收到信息

缺点
1)如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时,
2)如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃

3.1.4.2 适用场景

1)当一个对象状态的改变,需要改变其他对象时,比如,商品库存数量发生改变,需要通知商品详情页,购物车等系统改变数量
2)一个对象发生改变时,只需要发送通知,而不需要知道接收者是谁,比如,订阅微信公众号的文章,发送者通过公众号发送,订阅者并不知道哪些用户订阅了公众号
3)需要创建一种链式触发机制时,比如在一个系统中,创建了一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象,这样通过观察者模式能够更好的实现
4)微信或微博朋友圈发送的场景,这是观察者模式的典型应用场景,一个人发微博或朋友圈,只要关联的朋友都会受到通知,一旦取消关注,此人就不会收到相关通知,
5)需要建立基于事件触发的场景,比如Java UI的编程,所有键盘和鼠标的事件都由监听它的侦听器对象或指定函数处理,当用户单击鼠标时,订阅鼠标单击事件的函数将被调用,并将所有上下文数据作为操作方法参数传递给他

3.1.4.3 JDK对观察者模式的支持

自己可以仔细看一下Observer接口,Observable类。

3.2 模板方法模式(Template method Pattern)

3.2.1 概念

在操作中,定义算法的框架,将一些步骤推迟到子类中,模板方法让子类在不改变算法接口的情况下,重新定义算法的某些步骤。模板方法中的算法可以理解成为,广义上的业务逻辑,并不是特指某一个实际算法,定义中所说的算法框架,就是模板,包含算法框架的方法就是模板方法。例如,我们去医院看病一般要经历四个步骤:挂号、取号、排队、医生问诊等,其中挂号、取号、排队对每个病人都是一样的,可以在父类中实现,但是医生如何根据病情开药,每个人都不一样,所以开药流程要推迟到子类中实现。模板方法是一种基于继承的代码复用技术,他是一种类行为模式,模板方法其结构中只存在父类与子类之间继承的关系,模板方法的主要作用是提供代码的复用性扩展性。复用性是指,所有子类可以服用父类中提供的模板方法。扩展是指,框架通过模板模式,提供功能扩展点,让框架用户可以在不改变框架源码的情况下,基于扩展点定制化框架的功能。

3.2.2 UML

在这里插入图片描述

3.2.3 代码实现

抽象模板类

public abstract class AbstractClassTemplate {

    /**
     * 抽象模板方法1
     *
     * @param key
     * @return void
     * @throws
     * @method step1
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/7 18:39
     */
    void step1(String key) {
        System.out.println("执行步骤1");
        if (step2(key)) {
            step3();
        }else {
            step4();
        }
        step5();
    }

    /**
     * 抽象模板方法2
     *
     * @param key
     * @return java.lang.Boolean
     * @throws
     * @method step2
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/7 18:39
     */
    Boolean step2(String key) {
        System.out.println("执行步骤2");
        if (key.equals("x")) {
            return true;
        }
        return false;
    }

    /**
     * 子类实现3
     *
     * @param
     * @return void
     * @throws
     * @method step3
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/6 17:47
     */
    abstract void step3();

    /**
     * 子类实现4
     *
     * @param
     * @return void
     * @throws
     * @method step3
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/6 17:47
     */
    abstract void step4();

    /**
     * 抽象执行
     *
     * @param
     * @return void
     * @throws
     * @method step5
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/6 17:48
     */
    void step5() {
        System.out.println("执行步骤5");
    }

具体实现类1

public class ConcreteTemplate1 extends AbstractClassTemplate {

    /**
     * 子类1调用
     *
     * @param
     * @return void
     * @throws
     * @method step3
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/7 18:42
     */
    @Override
    void step3() {
        System.out.println("在子类1中执行step3");
    }

    /**
     * 子类1调用
     *
     * @param
     * @return void
     * @throws
     * @method step4
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/7 18:42
     */
    @Override
    void step4() {
        System.out.println("在子类1中执行step4");
    }
}

具体实现类2

public class ConcreteTemplate2 extends AbstractClassTemplate {

    /**
     * 子类2调用
     *
     * @param
     * @return void
     * @throws
     * @method step4
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/7 18:42
     */
    @Override
    void step3() {
        System.out.println("在子类2中执行step3");
    }

    /**
     * 子类2调用
     *
     * @param
     * @return void
     * @throws
     * @method step4
     * @author Rocky Qian
     * @version 1.0
     * @date 2023/11/7 18:42
     */
    @Override
    void step4() {
        System.out.println("在子类2中执行step4");
    }
}

调用者

public class Client {

    public static void main(String[] args) {
        ConcreteTemplate1 concreteTemplate1 = new ConcreteTemplate1();
        ConcreteTemplate2 concreteTemplate2 = new ConcreteTemplate2();
        concreteTemplate1.step1("x");
        System.out.println("==============");
        concreteTemplate2.step1("2");
    }
}
输出结果:
执行步骤2
在子类1中执行step3
执行步骤5
==============
执行步骤1
执行步骤2
在子类2中执行step4
执行步骤5
3.2.4 总结
3.2.3.1 优缺点

优点:
1)在父类中形式化的定义了一个算法(方法),而由他的子类来实现细节处理,在子类实现详细的处理代码时,并不会改变父类算法中,步骤的执行顺序
2)模板方法可以实现一种方向的控制结构,通过子类覆盖父类的钩子方法,来决定某一特定步骤是否需要执行。
3)在模板方法模式中,可以通过子类覆盖父类的基本方法,不同的子类可以提供基本方法的不同实现,更换和增加新的子类很方便,符合开闭原则。
缺点:
1)对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更抽象
2)父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种方向的控制结构,他提高了代码阅读的难度

3.2.3.2 适用场景

1)多个类由相同的方法,并且逻辑可以公用时
2)将通用的算法或者固定流程设计为模板,在每个具体的子类中,再继续优化算法步骤或流程步骤时,
3)重构超长代码逻辑时,发现某个经常适用的公有方法

3.3 策略模式(Strategy Pattern)

3.3.1 概念

定义一些列的算法,将每个算法封装起来,并使他们可以相互替换,策略模式让算法可以独立于适用他的客户端。在软甲开发中,开发一个功能可以通过多个算法去实现,我们可以将所有的算法集中到一个类中,在这个类中提供多个方法,每个方法对应一个算法,或者我们也可以将这些算法,封装在同一的一个方法中,适用if…else…等条件语句进行选择,但是这两种方式,都存在硬编码的问题,后期需要增加算法, 就需要修改源代码,这导致代码的维护成本更高。比如,我们原来系统中,清算功能,就是用了很多不同的算法, 来实现的。我们将每个算法封装起来,在不同的场景下适用不用的清算算法。

3.3.2 UML

在这里插入图片描述

3.3.3 代码实现

抽象策略

public abstract class AbstractStrategy {

    abstract void algorithm();
}

具体策略

public class ConcreteStrategyA extends AbstractStrategy{

    /**
     * 具体策略方法
     */
    @Override
    void algorithm() {
        System.out.println("执行A策略方法");
    }
}

具体策略

public class ConcreteStrategyB extends AbstractStrategy{

    /**
     * 具体策略方法
     */
    @Override
    void algorithm() {
        System.out.println("执行N策略方法");
    }
}

执行具体粗略方法的上下文类

public class Context {

    private AbstractStrategy abstractStrategy;

    /**
     * 构造方法
     * @param abstractStrategy
     */
    public Context(AbstractStrategy abstractStrategy){
        this.abstractStrategy = abstractStrategy;
    }

    /**
     * 调用具体策略方法
     */
    public void algorithm(){
        abstractStrategy.algorithm();
    }

}

调用者

public class Client {

    public static void main(String[] args) {

        new Context(new ConcreteStrategyA()).algorithm();
        System.out.println("=============");
        new Context(new ConcreteStrategyB()).algorithm();

    }
}

3.3.3 总结
3.3.3.1 优缺点

优点:
1)策略类之间可以自由切换,由于策略类都实现了同一个接口,所以使他们之间可以自由切换
2)易于扩展,增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合开闭原则,
3)避免适用多重条件选择语句(if…else),充分体现面向对象设计思想
缺点:
1)客户端必须要知道所有的策略类,并自行决定适用哪一个具体的策略类
2)策略模式将造成产生很多的策略类,可以通过使用享元模式在一定程度上减少对象的数量。

3.3.3.2 适用场景

1)一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中
2)一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句形式出现,可将每个条件分支移入,他们各自的策略类中以代替条件语句
3)系统要求使用算法的客户端不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构

设计模式原则和思想其实比设计模式更加朴实和重要,掌握了代码的设计原则和思想,我们自然而然的就可以使用到设计模式,还有可能创造一种新的设计模式。

3.4 责任链模式(Chain of Responsibility Pattern)

3.4.1 概念

也叫职责链模式,避免请一个请求的发送者与接收者耦合在一起,让多个对象都有机会处理请求。将接收请求的对象,连成一条链,并且沿着这条链,传递请求,直到有一个对象能够处理完他为止。在责任链模式中,多个处理器(也就是刚刚定义中说的“接收对象”)依次处理同一个请求,一个请求先经过A处理器处理,再经过B处理器处理,再经过C处理器处理,以此类推,形成一个链条。链条上的每个处理器,各自承担各自的处理职责,所以叫职责链模式。

3.4.2 UML
3.4.2.1 角色

抽象处理者:定义了一个处理请求的接口,定义了一个成员变量,保存后续处理器的引用
具体处理者:实现了抽象处理者处理方法,判断是否能够处理本次请求。如果可以就处理,否则交给继任者
客户端:创建处理列,并向链头的具体处理者对象提交请求,之后就不在关系具体的处理细节

3.4.2.2 UML

在这里插入图片描述

3.4.3 代码实现

参数值

@Data
@AllArgsConstructor
@NoArgsConstructor
public class RequestData {

    private String data;
}

具体抽象处理类

public abstract class Handler {

    public Handler successor;

    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }

    public abstract void handle(RequestData requestData);
}

具体处理类A

public class HandlerA extends Handler {


    @Override
    public void handle(RequestData requestData) {
        System.out.println("handlerA 执行处理请求" + requestData.getData());

        if (requestData.getData().contains("A")) {
            String replace = requestData.getData().replace("A", "");
            requestData.setData(replace);
            System.out.println("继续执行");
            successor.handle(requestData);
        } else {
            System.out.println("处理结束");
        }
    }
}

具体处理类B

public class HandlerB extends Handler{


    @Override
    public void handle(RequestData requestData) {
        System.out.println("handlerB 执行处理请求" + requestData.getData());

        if (requestData.getData().contains("B")) {
            String replace = requestData.getData().replace("B", "");
            requestData.setData(replace);
            System.out.println("继续执行");
            successor.handle(requestData);
        } else {
            System.out.println("处理结束");
        }
    }
}

具体处理类C

public class HandlerC extends Handler{
    @Override
    public void handle(RequestData requestData) {
        System.out.println("handlerC 执行处理请求" + requestData.getData());

        if (requestData.getData().contains("C") && successor != null) {
            String replace = requestData.getData().replace("C", "");
            requestData.setData(replace);
            System.out.println("继续执行");
            successor.handle(requestData);
        } else {
            System.out.println("处理结束");
        }
    }
}

客户端

public class Client {

    public static void main(String[] args) {
        HandlerA a = new HandlerA();
        HandlerB b = new HandlerB();
        HandlerC c = new HandlerC();

        //创建处理链
        a.setSuccessor(b);
        b.setSuccessor(c);
        c.setSuccessor(c);
        RequestData requestData = new RequestData("请求数据ABCD");
        a.handle(requestData);
    }
}

输出结果:
handlerA 执行处理请求请求数据ABCD
继续执行
handlerB 执行处理请求请求数据BCD
继续执行
handlerC 执行处理请求请求数据CD
继续执行
handlerC 执行处理请求请求数据D
处理结束
3.4.4 总结
3.4.4.1 优缺点

优点:
1)降低了对象之间的耦合度,该模式降低了请求发送者和接收者的耦合度
2)增强了系统的可扩展性,可以根据需要增加新的请求处理类,满足开闭原则
3)增强了给对象指派职责的灵活性,当工作流发生变化时,可以动态地改变链内的成员,或者修改他们的次序,也可以动态地新增或者删除职责。
4)责任链简化了对象之间的连接,一个对象只需保持一个指向后继者的引用,不需要保持其他所有处理者的引用,避免了很多的if…else语句
5)责任分责,每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的职责范围,符合累的单一职责原则。
缺点:
1)不能保证每个请求都能被处理,由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理
2)对于较长的责任链,请求的处理可能涉及多个处理对象,系统性能将受到一定的影响。
3)职责链建立的合理性要考客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置,而导致系统出错,有可能造成循环调用

3.4.4.2 适用场景

1)运行时需要动态使用,多个关联对象来,处理同一次请求时,比如请假流程、员工入职流程、编译打包发布流程等
2)不想让使用者知道具体的处理逻辑时,比如做权限校验的登陆拦截器
3)需要动态更换处理对象时,比如工单处理系统、网关API的过滤规则系统
4)责任链常被用于框架开发中,来实现框架的过滤器,拦截器等功能,让框架使用者,在不修改源码的情况下,添加新的过滤拦截功能。

3.5 状态模式(STATE PATTERN)

3.5.1 概念

自然界很多事物都有多种状态,而不同的状态下具有不同的行为,这些状态在特定的条件下还会发生相互转换,比如水。在软件系统中,有些对象也像水一样具有多种状态,这些状态,在某些情况下,能够相互转换。而且对象在不同状态下,也将具有不同的行为。定义:允许一个对象在其内部状态改变时,改变她的行为,对象看起来似乎修改了他的类。状态模式,就是用于解决系统中复杂对象的状态转换,以及不同状态下为行为的封装问题。状态模式讲一个对象的状态从该对象中分离出来,封装到专门的状态类中(用类来表示状态),使得对象的状态可以灵活变化。

3.5.2 UML

在这里插入图片描述

3.5.3 代码实现

抽象状态类

public interface State {

    void handle(Context context);
}

上下文类

@Data
public class Context {

    private State currentState;

    public Context(){
        this.currentState = null;
    }

    public Context(State currentState){
        this.currentState = currentState;
    }

    public State getCurrentState() {
        return currentState;
    }

    public void setCurrentState(State currentState) {
        this.currentState = currentState;
    }

    @Override
    public String toString() {
        return "Context{" +
                "currentState=" + currentState +
                '}';
    }
}

具体状态类1

public class ConcreteState1 implements State {
    @Override
    public void handle(Context context) {
        System.out.println("进入到状态模式2...........");
        context.setCurrentState(this);
    }

    @Override
    public String toString() {
        return "当前状态:ConcreteState1{}";
    }
}

具体状态类2

public class ConcreteState2 implements State{
    @Override
    public void handle(Context context) {
        System.out.println("进入到状态模式1...........");
        context.setCurrentState(this);
    }

    @Override
    public String toString() {
        return "当前状态:ConcreteState2{}";
    }
}

客户端

public class Client {

    public static void main(String[] args) {
        Context context = new Context();
        ConcreteState1 state1 = new ConcreteState1();
        ConcreteState2 state2 = new ConcreteState2();
        state1.handle(context);
        System.out.println(context.getCurrentState());
        System.out.println("=================================");
        state2.handle(context);
        System.out.println(context.getCurrentState());
    }
}

输出结果:
进入到状态模式1...........
当前状态:ConcreteState1{}
=================================
进入到状态模式2...........
当前状态:ConcreteState2{}
3.5.4 总结
3.5.4.1 优缺点

优点:
1)将所有与某个状态相关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象的状态,即可改变对象的行为
2)允许状态转换逻辑与状态对象合为一体,而不是某一个巨大的条件语句块
缺点:
1)状态模式的使用必然后增加系统类和对象的个数
2)状态模式的结构与实现都较为复杂,如果适用不放将导致程序结构与代码的混乱
3)状态模式对开闭原则的支持并不友好

3.5.4.2 适用场景

1)对象根据自身状态的变化,进行不同行为的操作时,比如购物订单状态
2)兑现需要根据自身变量的当前值改变行为,不期望使用大量if…else语句时,比如商品库存状态
3)对于某些确定的状态和行为,不想重复适用代码时,比如某一个会员当前的购物浏览记录

3.6 迭代器模式

3.6.1 概念

3.7 访问者模式

3.7.1 概念

3.8 备忘录模式

3.8.1 概念

3.9 命令模式

3.9.1 概念

3.10 解释器模式

3.10.1 概念

3.11中介者模式

3.11.1 概念

总结

从2023年10月23日,重阳节开始学习,复杂的设计模式一天学习一个,简单的一天三四个。为什么要疯狂学习呢?哎,也许是年纪大了,更也许是压力太大了。跟同龄的一比,自己确实很菜。没办法,提升自己吧,我把这个专题定义为我要进大厂,这也是我的短期目标。总之不管市场行情怎么样,学习肯定没错的,加油吧。。。

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值