23种设计模式的介绍(Java)

  • Post author:
  • Post category:java



目录


一.总括


二.设计模式的七大原则的介绍


三.23种设计模式的深度剖析


1.创建型模式


(1)单例模式


(2)原型模式


(3)工厂模式


(4)建造者模式


2.结构型模式


(1)适配器模式(Adapter)


(2)桥接模式


(3)过滤器模式


(4)组合模式


(5)装饰器模式


(6)外观模式(Facade Pattern)


(7)享元模式


(8)代理模式(Proxy)


3.行为型模式


[ 1 ] 模板方法模式(Template Pattern)


[ 2 ] 策略模式(Strategy)


[ 3 ] 状态模式(State)


[ 4 ] 中介者模式(Mediator)


[ 5 ] 观察者模式


[ 6 ] 备忘录模式(Memento)


[ 7 ] 解释器模式


[ 8 ] 命令模式(Command)


[ 9 ] 迭代器模式(Iterator)


[ 10 ] 访问者模式(Visitor)


[ 11 ] 职责链模式(Chain Of Resonsibility)


一.总括

(1)创建型设计模式(概念:类对象的创建所采用的设计模式)

> 单例模式

> 原型模式

> 工厂方法模式

> 抽象工厂模式

> 建造者模式

(2)结构型模式(概念:在类的定义和代码编写上所采用的设计模式)

> 代理模式

> 适配器模式

> 桥接模式

> 装饰模式

> 外观模式

> 享元模式

> 组合模式

> 过滤器模式

(3)行为型模式(概念:在类方法的调用上所采用的设计模式)

> 模板方法模式

> 策略模式

> 命令模式

> 职责链模式

> 状态模式

> 观察者模式

> 中介者模式

> 迭代器模式

> 访问者模式

> 备忘录模式

> 解释器模式

二.设计模式的七大原则的介绍

(1)开闭原则:对修改关闭,对扩展开放(扩展类的功能而不是在原有代码上修改类的功能)

(2)里氏替换原则:继承父类而不是改变父类(使用父类的已定义方法,不是在父类上进行方法的修改,而是继承于父类扩展方法)

(3)依赖倒置原则:面向接口编程,而不是面向具体编程(方法定义的参数采用的都是接口类型而不是实现类的具体数据类型,这样可以提高方法的扩展性)

(4)单一职责原则:每个类只负责自己的事情,而不是变成万能

(5)接口隔离原则:各个类建立自己的专用接口,而不是万能接口(每一种相似功能的实现类所实现接口的功能都不相同)

(6)迪米特原则:无需交互的两个类,如果需要交互,需要有中介作为桥梁(注意:不要过多采用迪米特原则,不然项目当中会产生大量的中介类,从而增加系统的复杂性)

(7)合成复用原则:优先组合其次继承(不要一开始就使用继承)

三.23种设计模式的深度剖析

1.创建型模式

> 关注点:怎么创建对象

> 特点:

[ 1 ] 将对象的创建和使用分离 [ 2 ] 降低系统的耦合度 [ 3 ] 使用者无需过多关注对象的床创建细节


> 分类


(1)单例模式


> 原理:一个单一的类,负责创建自己的对象,同时确保系统当中只有单个对象被创建


> 特点:


某个类有且只有一个实例


它必须自行创建实例


它必须向整个系统提供这个实例(对外提供实例化方法)


> 使用场景


  • 多线程的线程池(整个系统共享一个线程池)
  • 数据库的连接池(同一个数据库使用一个连接池)
  • 系统环境对象(系统的配置信息对象只有一个)
  • ServletContext对象(一个web项目只有一个全局作用域)

> 代码实现

// 单例模式的实现(只考察懒汉模式)
// 考虑多线程环境下的实现方式
public class SingletonDemo {
    private String name;
    private Integer age;
    // 实例对象为静态变量,是一个类对象
    private static SingletonDemo demo;
    // 单例模式的第一个特点:构造器私有化,防止外部类创建该类的实例
    private SingletonDemo() {

    }
    // 提供实例化方法给予外部类获取类对象
    // 两种解决多线程环境下的问题
    // 双重检查锁和内存可见性(检查堆内存当中是否有且只存在一个本类对象,确保单实例的操作)
    public static SingletonDemo getSingletonDemo() {
        if (demo == null) {
            synchronized (SingletonDemo.class) {
                // 如果没有双重判断的操作的话,那么有可能会导致堆内存当中存在了两个该类的实例对象
                if (demo == null) {
                    // 同步锁和双重检查锁校验通过后,直接返回就可以了
                    demo = new SingletonDemo();
                    return demo;
                }
            }
        }
        // 如果有的话,直接返回就可以了
        return demo;
    }
}

(2)原型模式

> 原理:是用于创建重复的对象,同时又能确保保持性能

> 特点:

  • 采用了原型模式的类只有在获取方法被调用的时候才会被初始化操作
  • 外部类所获取的实例是实例对象的克隆对象,防止外部类进行修改
  • 原型模式在系统内存当中有可能存在多个实例

> 场景

  • 资源优化
  • 性能和安全要求
  • 一个对象多个修改者的场景
  • ……

> 代码实现(关键实现步骤:要确保获取的实例是本体实例对象的克隆体,确保数据安全)

>

建议:建议类的定义需要实现Cloneable接口,实现clone()方法

class User implements Cloneable {
    ....
    @Override
    public Object clone() throws CloneNotSupportedException {
        User user = new User();
        user.setName(this.name);
        user.setAge(this.age);
        return user; // 注意返回的user对象和原本的实例对象的存储地址是不同,也就是说修改克隆体的数据不会影响本体的数据
    }
}

public class PrototypeDemo {
    private static Map<String,Object> cache = new HashMap<>(); // 缓存集合
    public User getUser(String username) throws CloneNotSupportedException {
        if (!cache.containsKey(username)) {
            // 说明是没有存储在缓存集合当中的
            User dataFromDB = getDataFromDB();
            cache.put(username,dataFromDB);
            return (User) dataFromDB.clone(); // 获取本体对象的克隆体对象
        }
        return (User) ((User) cache.get(username)).clone(); // 同样获取的克隆体对象
    }
    // 模拟从数据库当中获取数据
    public User getDataFromDB() {
        User user = new User();
        user.setAge(12);
        user.setName("张三");
        return user;
    }
}

(3)工厂模式

> 原理:提供的一种创建对象的最佳方式,我们不必知晓创建对象的细节,只需要根据不同的实际情况获取对象即可

> 难点:如何写好工厂类

> 分类:简单工厂,工厂方法,抽象工厂模式

[ 1 ] 简单工厂

> 缺点:

违背开闭原则,扩展不易

> 代码实现

public class SimpleFactory {

    public static Object newInstance(String className) {
        Object target = null;
        if ("User".equals(className)) {
            target = new User();
        }
        // 如果需要扩展的话,就需要继续根据if-else语句判断创建哪一个对象
        // 假如一个项目有很多的类,那么就会导致代码冗余,而且发现只在一个方法的代码进行修改,并没有对内容进行扩展
        return target;
    }
}

[ 2 ] 抽象工厂

> 原理:设计一个抽象工厂对应抽象创建类,每一个抽象工厂的实现都对应一种产品的实现

> 实现步骤:

  1. 设置一个抽象总厂
  2. 抽象总厂需要创建多少不同功能的类,再将其分化出来
  3. 之后具体工厂类实现抽象工厂类,对应一种产品的实现

[ 3 ] 工厂方法模式

> 原理:

设置一个抽象工厂,在定义一个创建一种产品等级结构的抽象方法,在被其他的工厂实现,之后创建具体子类对象

> 缺点:系统复杂度增加,产品单一

> 代码实现

> 总结:工厂方法模式在原有的抽象工厂模式上提供了创建一种实例的工厂方法的能力

// 注意:工厂方法模式当中使用了抽象工厂模式
public abstract class AbstractFactory {
    public abstract Car newCar(); // 提供一个抽象方法,给予抽象类的实现类实现

}
// 调用工厂方法单独负责一种产品的实现操作
public class WuLinFactory extends AbstractFactory {
    @Override
    public Car newCar() {
        return new WuLinCar();
    }
}

public class VarCarFactory extends AbstractFactory{
    @Override
    public Car newCar() {
        return new VarCar();
    }
}


[ 4 ] 工厂模式的使用场景

  • NumerFormat,SimpleDateFormat
  • LoggerFactory
  • SqlSessionFactory
  • BeanFactory,Spring的BeanFactory

(4)建造者模式

> 原理:

创建的东西很复杂,还必须暴露给使用者,屏蔽过程而不屏蔽细节

> 使用场景:

  • StringBuilder: append()
  • Swaffer-ApiBuilder
  • 快速实现-Lombok-Builder模式

> 代码实现

> 注意代码当中的两种建造模式

> 总结:建造模式就是使用在一些初始化时比较复杂的实例对象,也就是属性的设置操作

// 抽象建造者
public abstract class AbstractBuilder {
    protected Phone phone;
    public abstract AbstractBuilder customizeCpu(String name);
    public abstract AbstractBuilder customizeDisk(String name);
    public abstract AbstractBuilder customizeMem(String name);
    public abstract AbstractBuilder customizeCam(String name);

    public Phone buildPhoneInstance() {
        return phone;
    }
}

public class PhoneBuilder extends AbstractBuilder{
    public PhoneBuilder() {
        this.phone = new Phone();
    }
    @Override
    public AbstractBuilder customizeCpu(String name) {
        phone.setCpu(name);
        return this;
    }

    @Override
    public AbstractBuilder customizeDisk(String name) {
        phone.setDisk(name);
        return this;
    }

    @Override
    public AbstractBuilder customizeMem(String name) {
        phone.setMem(name);
        return this;
    }

    @Override
    public AbstractBuilder customizeCam(String name) {
        phone.setCam(name);
        return this;
    }
}
  // 第一种格式:分层次展露细节
        AbstractBuilder builder = new PhoneBuilder();
        builder.customizeCpu("骁龙");
        builder.customizeCam("12亿像素");
        builder.customizeDisk("512G");
        builder.customizeMem("16G");

        Phone phone = builder.buildPhoneInstance();

        System.out.println(phone);

        // 第一种模式:链式调用展露细节,建议采取
        Phone phone1 = builder.customizeCpu("骁龙").customizeDisk("512G").customizeCam("12亿像素").customizeMem("16G").buildPhoneInstance();

2.结构型模式

> 结构型模式关注:”怎样组合对象 / 类”

> 类结构型模式关注类的组合

> 对象结构型模式关注对象的组合

>

根据合成复用原则,在系统中尽量使用组合代替继承关系,因此大部分结构型模式都是对象结构型模式


(1)适配器模式(Adapter)


> 原理:



一个接口转换客户希望的另一个接口

,适配器模式使接口不兼容的那些类可以一起工作,适配器模式分为类结构型模式(继承)和对象结构型模式(组合)两种,前者(继承)类之间的耦合度比后者高,且要求程序员了解现有组件库的相关组件的内部结构,

所以应用相对较少

> 别名还可以是Wrapper,包装器

[ 1 ] 类结构型模式

> 代码实现(场景:模拟中文电影的外文翻译)

> 总结:类结构型的适配器模式只需要了解就可以了,不需要过多去深入

public class JPMoviePlayer extends Zh_JPTranslator implements Player{
    private Player player;
    public JPMoviePlayer(Player target) {
        this.player = target;
    }

    @Override
    public String player() {
        String content = player.player();
        String translate = translate(content);
        System.out.println("日文字幕:" + translate);
        return translate;
    }
}

[ 2 ] 对象结构型模式

> 代码实现(重点掌握)

// 通过组合的方式将原本不适配的两个接口放在了一起,没有使用继承,但是通过组合的方式将两者组合到了一起
public class JPMoviePlayer implements Player{
    private Translator translator;
    private Player player;
    public JPMoviePlayer(Translator translator,Player target) {
        this.translator = translator;
        this.player = target;
    }
    
    @Override
    public String player() {
        String content = player.player();
        String translate = translator.translate(content);
        System.out.println("日文字幕:" + translate);
        return translate;
    }
}

> 代码实现2(该实例更容易让你理解适配器模式)

> 场景分析:在缓存当中要存放一个list集合,但是每一次外部类获取的时候都要对其进行修改操作,为了不让其修改操作影响到缓存本体集合的数据,我们需要将本体的克隆体返回,但是由于list集合已经是Java的系统包下的类,我们不能将其进行修改,违背开闭原则,

那么就可以使用适配器模式将list接口和Cloneable接口组合在一起

public class ListWrapper implements Cloneable{
    private List<String> list; // list集合本身是不实现cloneable接口的
    
    public void setList(List<String> target) {
        this.list = target;
    }
    // 从而实现了list集合的克隆体返回操作,返回的list集合和原本的list集合的存储地址是不一样的
    public List<String> getList() throws CloneNotSupportedException {
        return ((ListWrapper) this.clone()).getList();
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        ListWrapper wrapper = new ListWrapper();
        List<String> target = new ArrayList<>();
        target.addAll(this.list);
        wrapper.setList(target);
        return wrapper;
    }
}

(2)桥接模式

> 原理:将抽象和实现解耦,使两者都可以独立变化

> 概括:桥接将继承转为关联,降低类之间的耦合度,减少代码量

> 涉及角色


  1. 系统设计期间,如果这个类里的一些东西,会扩展很多,这个东西就应该分离出来

  2. 抽象化角色:定义抽象类,并包含一个对实现化对象的引用

  3. 扩展抽象化角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法

  4. 实现化角色:定义实现化角色的接口,供扩展抽象化角色调用

> 代码实现

>

总结:桥接模式与适配器模式的区别在于:桥接模式的桥接对象和被桥接对象本身两者并不存在接口不适配的问题,同时,桥接模式是本类所延伸出来的类的适配方式

// 被桥接的对象
public abstract class AbstractPhone {
    AbstractSale sale; //分离渠道(桥接的关注点,sale可以扩展出很多的分支)

    //当前手机的描述
    abstract String getPhone();
    // 桥接方法,真正进行桥接的方法,与适配器模式不同的是:桥接模式是以方法参数的方式设置进入桥接器当中的,而且是整一个进行传入,而不是部分方法的调用操作
    // 而且桥接对象和被桥接对象两者并不存在接口不适配的问题
    public void setSale(AbstractSale sale) {
        this.sale = sale;
    }
}
// 桥接参数
abstract class AbstractSale {
    private String type;
    private Integer price;
    public AbstractSale (String type,Integer price) {
        this.type = type;
        this.price = price;
    }
    String getSaleInfo() {
        return "渠道:" + type + ",价格:" + price;
    }
}

> 使用场景

  1. 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时

  2. 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时

  3. 当一些需要需要在构件的抽线化角色和具体化角色之间增加更多的灵活性时

  4. InputStreamReader桥接模式

    1. 底层不但实现了桥接模式而且还实现了适配器模式,extend Reader,表示使用Reader的方法,实际上真正是写到Stream流的里面的

(3)过滤器模式

> 原理:使用不同的标准来过滤一个对象,通过逻辑判断的方式把他们连接起来,结合多个标准来获得单一标准

> 代码实现


由于过滤器模式见名知义,就好比现实当中生产工序的过滤步骤,将合格的放行,将不合格的拦截下来,可以参考于JavaEE的Filter,就实现了过滤器模式

(4)组合模式

> 原理:把一组相似的对象当作一个单一的对象,如:树形菜单

> 核心步骤:将一个对象当作一个单一对象的属性

> 使用场景:天天都在用

> 规范:合成复用原则(多组合,少继承)

> 代码实现:

参考上面实体类的定义,适配器的对象结构型模式,桥接模式等设计模式,都使用了组合模式作为底层设计模式

(5)装饰器模式

> 原理:向一个现有的对象添加新的功能,同时又不改变其结构,属于对象结构型模式

> 创建一个装饰类,用来包装原有的类,并在保持类方法签名完整的前提下,提供了额外的功能

> 注意:装饰器模式和适配器模式很相似,但是

  • 适配器模式是连接两个类,增强一个类
  • 装饰器模式是在一个类上进行功能扩展,但不需要连接两个类
  • 装饰器只关注于一个类的增强,是在类的本身进行增强

> 使用场景:无处不在

> 代码实现

> 场景模拟:抖音播放器美颜效果的开启

> 总结:

装饰器模式当中的被装饰对象和装饰器本身并没有作为接口不适配的调整适配器


装饰器模式别名也可以是Wrapper,但是其功能是功能扩展,而不是接口不适配问题的解决

public class MeiYanTiktokDecorator {

    private Tiktok tiktok;  // 被装饰对象(一个类)

    public MeiYanTiktokDecorator(Tiktok tiktok) {
        this.tiktok = tiktok;
    }

    public void enable() {
        System.out.println("开启了美颜效果"); // 对装饰对象的功能扩展
    }
    public void play() {
        enable();
        tiktok.play(); // 在原有一个办法的基础之上,外部类对其进行了扩展操作
    }
}

(6)外观模式(Facade Pattern)

> 原理:又叫做门面模式,是一种通过为多个复杂的子系统提供一个统一的接口,而使得这些子系统更容易被访问的模式

> 概括:使得操作流程由复杂简单化的模式

> 现实生活场景模拟:去医院看病,可能要去挂号,门诊,划价,取药,让患者和家属觉得很复杂,如果有接待人员,只让接待人员来处理这些流程的话,那么就会很方便

> 使用场景:

  1. Java的三层开发模式(请求只需到达处理器对象,其余的由处理器去操作就可以了,不需要请求本身去不断地访问controller,service,dao三层)

  2. 分布式系统的网关(在真正访问后台服务器之前有一层网关需要进行验证才能访问到后台服务器)

  3. Tomcat源码的RequestFacade

> 代码实现

> 场景模拟:孩子要上学,需要经过公安局开证明,教育局开证明,社会群体组织开证明,        这些流程过于复杂,假如有一个系统可以一条龙完成三项的操作,这就是外观模式

> 总结:

假如在当前系统中的某一个流程过于复杂,就可以使用门面模式一次性处理整一个流程,具体流程处理代码由实例成员的实例方法进行处理,例如:工厂的配置信息处理,缓存数据的CRUD流程处理,请求调度器的请求路径分配处理流程,都可以使用门面模式来处理,向外只暴露一个方法,但其实内部的流程很复杂,这些内部流程都不需要使用者去关心!

// 处理上学流程的门面
public class HandleSchoolProcessFacade {
    private Police police;
    private Edu edu;
    private Social social;

    public void handle () {
        // 一个门面内部就已经完成了三个对象的访问操作,使得使用者只需关注是否处理成功,不需要关注具体流程就可以了
        police.handle();
        edu.handle();
        social.handle();
    }
}

(7)享元模式

> 原理:运用共享技术有效地支持大量细粒度对象的复用,系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用,对象结构型

> 概念

  • 在享元模式中

    可以共享的相同内容称为内部状态

    ,而那些需要外部环境来设置的

    不能共享的内容称为外部状态

    ,由于区分了内部状态和外部状态,因此可以通过设置不同的外部状态使得相同的对象可以具有一些不同的特征,而相同的内部状态是可以共享的

  • 在享元模式中通常会出现工厂模式,需要创建

    一个享元工厂来负责维护一个享元池

    (Flyweight Pool)用于存储具有相同内部状态的享元对



> 概括:享元技术就是池技术的思想,规定了实现池技术所需要注意的内容,规定了可以需要共享的内容和不可共享的内容


> 代码实现


> 总结:享元模式的设计思路就是使用一个总体对象来管理和调度一些复用对象的分配管理问题


> 使用场景

  1. 典型的代表:数据库连接池(连接到数据库获取到数据之后归还到连接池,需要连接的时候再次使用即可)

  2. 所有的池化技术(线程池,缓存,连接池)

  3. 享元技术和原型模式有什么区别?享元是预先准备号的对象进行复用,而原型没法确定预先有哪些(需要使用的时候返回对象的克隆体进行使用)

public class ConnectionPool {
    private static Map<String,AbstractConnection> map = new HashMap<>();

    static  {
        //默认池子当中有数据
        AbstractConnection connection = new MySqlConnection();
        connection.setDriver("com.mysql.jdbc.Driver");
        connection.setUsername("user");
        connection.setPassword("123456");
        connection.setUrl("jdbc:mysql://localhost:3306/amour");
        AbstractConnection connection1 = new MySqlConnection();
        connection1.setDriver("com.mysql.jdbc.Driver");
        connection1.setUsername("user");
        connection1.setPassword("123456");
        connection1.setUrl("jdbc:mysql://localhost:3306/amour");
        map.put(String.valueOf(connection.getId()),connection);
        map.put(String.valueOf(connection1.getId()),connection1);
    }

    public AbstractConnection getConnnection() {
        //采用的随机分配的方式
        Collection<AbstractConnection> values = map.values();
        for (AbstractConnection value : values) {
            if (value.isCanConnectToDB()) {
                return value;
            }
        }
        //表示没有可以使用的信息
        return null;
    }
}

(8)代理模式(Proxy)

> 原理:给某一个对象提供一个代理,并由代理对象控制对原对象的引用,这就是静态代理

> 注意事项:

  • 静态代理就是装饰器(对装饰类的功能扩展就是代理类)
  • 装饰器模式是代理模式的一种
  • 动态代理对象和目标类对象都是实现同一个接口的(JDK动态代理)

> 使用场景

  • Mybatis的Mapper到此是什么?怎么生成的

    • Mybatis使用的是jdk动态代理,dao定义的都是接口,mybatis帮我们实现了

    • UserMapper、CityMapper,借助的是MapperProxy

  • Seata的DataSourceProxy是什么

  • DruidDataSource存在的Proxy模式

    • 监控链…

>

JDK动态代理



具体可以参考我编写的JDK动态代理,基本就可以理解动态代理的思路

>

CGLIB动态代理

核心:代理对象继承目标类对象,自然就能对目标类对象的方法进行功能增强

好处:不需要关注目标类对象是否有接口实现,只需要获得目标类对象的字节码信息即可

// 创建增强器对象
Enhancer enhancer = new Enhancer();

// 设置要增强哪个类的功能,增强器为这个类动态添加一个类
enhancer.setSuperClass(Demo.class);

// 设置回调
enhancer.setCallback(new MethodInterceptor() {
    
    ...;
})

// 创建代理对象
Object obj = enhancer.create();

3.行为型模式

> 关注点:怎样运行对象 / 类

> 行为型模式用于描述程序在运行时复杂的流程控制

> 描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配

> 行为型模式分为

类行为模式



对象行为模式

,前者采用继承体制来在类间分派行为,后者采用组合或聚合在对对象间分配行为。由于组合关系和聚合关系比继承关系耦合度更低,满足”合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性

> 除了模板设计模式和解释器模式是类型为模式,其他的全部属于对象行为型模式

[ 1 ] 模板方法模式(Template Pattern)

> 原理:一个抽象父类公开了执行它的方法的方式模板,它的子类可以按需要重写方法,但调用以抽象类定义的方式进行

> 分类:类行为模式(采用了继承的方式)

> 使用场景

  • Spring的整个流程基本都有使用模板模式
  • Spring的容器初始化十二大步当中就有模板方法模式的存在
  • JdbcTemplate和RedisTemplate都允许我们进行扩展

> 代码实现

> 场景模拟:在炒菜过程中,我们一般的步骤是:开火,加菜,加盐,翻炒,出锅,其中,开火,翻炒,出锅是固定的步骤,但是加菜和加盐的步骤不是固定的,所以

public abstract class Cook {

    public void cook() {
        // 抽象父类定义了流程
        heating();
        addFood();
        addSalt();
        strify();
        finish();
    }
    private void heating() {
        System.out.println("加热中");
    }
    // 模板方法的定义方式:是没有代码体的实例方法
    protected void addFood() {

    }
    // 这样定义的好处在于:子类并不需要强制性地去进行实现模板方法的操作
    protected void addSalt() {

    }

    private void strify() {
        System.out.println("翻炒中");
    }

    private void finish() {
        System.out.println("出锅");
    }
}

// 具体子类,可以实现模板方法也可以不实现模板方法
public class FanQieChaoJiDan extends Cook{
    @Override
    protected void addFood() {
        System.out.println("加入鸡蛋和番茄");
    }

    @Override
    protected void addSalt() {
        System.out.println("加入了3.0g盐");
    }
}

[ 2 ] 策略模式(Strategy)

> 原理:定义了一系列算法,并将每一个算法封装起来,使他们可以相互替换,且算法的变化不会影响算法的用户,属于对象行为型模式

> 使用场景

  1. 使用策略模式可以避免使用多重复的语句,如if,else语句

  2. 什么是Spring的InstantiationStrategy

  3. 线程池拒绝策略

> 代码实现

> 总结:通过设置不同的策略对象,来执行不同的代码,但是,最终执行结果跟用户预期的结构是一致的,只是中间执行过程的不一致,在没有进行策略的设置的时候,依然可以继续执行

> 场景:模拟战争过程中采取不同的策略进行作战,最终都取得了胜利的场景

public class TeamGNR {
    private GameStrategy strategy; // 策略对象

    public void setStrategy(GameStrategy strategy) {
        this.strategy = strategy;
    }

    public void startGame() {
        System.out.println("开始战斗");

        //设置策略
        strategy.warStrategy();
        
        // 不管设置的是什么类型的策略,其最终结果都不会发生改变
        System.out.println("win");
    }
}

[ 3 ] 状态模式(State)

> 原理:对有状态的对象,把复杂的判断逻辑提取到不同的状态对象当中,允许状态对象在其内部状态发生改变时改变其行为

> 概括:把状态作为一个属性,不同的状态影响目标对象的行为变化

> 与策略模式的差异

  • 策略模式关注的是策略方式影响环境的变化(结果不会改变,但达到最终结果的步骤不一致)
  • 状态模式关注的是不同状态造成环境的不同行为(结果有可能会发生改变)

> 核心


  • 有流转方法(从一个状态切换另一个状态的方法)

  • 状态模式一定要有状态的切换

> 代码实现

> 场景模拟:流程框架和状态机

> 总结:状态模式的核心就在于类的内部有状态机的存在,该状态机的状态切换不是由外部环境决定的,而是由类内部自动进行切换的

> 不同状态执行的代码不同

public class TeamSNK {
    private TeamState teamState;
    public TeamSNK(TeamState teamState) {
        this.teamState = teamState;
    }
    public void play() {
        teamState.play();
    }
    
    public void next() {
        teamState.next(); // 类当中存在内部状态机,进切切换
    }
}

[ 4 ] 中介者模式(Mediator)

> 原理:用一个中介对象来封装一系列对象的交互,中介者使各对象不需要显示地相互作用,减少对象间混乱的依赖关系,从而使得耦合松散,而且可以独立地改变他们的交互

> 分类:对象行为型模式

> 核心:把网状关系变为星状关系(由一到多分支)

> 现实场景模拟:你需要租房,你会将租房的条件信息发布到网站上,中介看到之后,就会根据你的信息为你配对合适的房源,从而实现租房到房源之间的无差错的配对操作,其中不涉及到租客和房东的交互,但是租客和房东的间接交互取决于中介者

> 使用场景

  1. SpringMVC的DispatcherServlvet是一个中介者,他会提取Controller、Model、View来进行调用,而无需调用controller直接调用view之类的渲染方法

  2. 分布式系统中的一个网关

  3. 迪米特法则的一个典型应用

  4. 中介者和外观(门面)模式区别

    1. 中介者双向操作(具备返回结果),门面偏向于封装某一方

> 代码实现

这里就不具体实现了,可以观察SpringMVC的DispatchServlet观察源码信息,就可以观察其中介者模式的实现

[ 5 ] 观察者模式

> 原理:定义对象间的一种依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆可以得到通知并自动更新;观察者模式又叫做发布-订阅模式、模式-视图模式、源监听器模式或从属者模式。对象行为型模式

> 核心:类似于Dubbo的发布订阅模式,服务发布方发布服务到服务注册中心的时候,消费者订阅服务后直接获取服务的ip地址,当服务在服务注册中心进行更新的时候,注册中心会通知相对应的消费者告诉服务已经更新,请重新获取服务的相关信息(建议学习完分布式框架Dubbo再来学习观察者模式会更加容易)

> 区别:监听器监听的是内部Java程序事件状态的变化,而观察者是在外部监听服务的状态信息

> 使用场景

  1. Spring的事件机制如何实现

  2. Vue的双向绑定核心

  3. 响应式编程核心思想

> 代码实现

> 场景模拟:当主播开播的时候,会通知所有的粉丝来观看直播

> 总结:观察者模式就是有一个类作为观察者来观察某一个观察源的状态进而对订阅观察源头对象的一个通知操作

> 核心原理:当一个对象的状态发生变化的时候,观察者会根据状态的变化给所有的follower发布新的消息,以便follower可以获得最新的消息

public class HumanStar extends AbstractStar{
    //主播的状态发生了变化,从而作为一个观察者,通知所有的粉丝
    @Override
    void notifyFans(String message) {
        for (AbstractFan fan : fans) {
            fan.acceptMsg(message);
        }
    }
}

[ 6 ] 备忘录模式(Memento)

> 原理:在不破坏封装性的前提下,

捕获一个对象的内部状态

,并在该对象

之外

保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又交叫

快照模式

。对象行为型模式

> 在一个专门独立于重要程序的类中,备份记录需要记住的类的状态信息,在下一次客户端启动的时候进行状态恢复(注意:这个类一定是一个持久化文件,不能是一个随着内存而消失的数据)

> 使用场景

  1. 游戏存档

  2. 数据库保存点事务

  3. session活化钝化(序列化、反序列化)

  4. ……

> 代码实现

该模式的实现比较复杂,且实现的时候一般都是采用json数据文件来记录主体类的配置信息,由于较为复杂,就不进行实现了,可以借由上面的使用场景去理解

[ 7 ] 解释器模式

> 原理:给

分析对象定义一个语言,并定义该语言的文法表示,再设计一个解释器来解释语言中的句子

,也就是说:用编译语言的方式来分析应用中的实例,这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文,类行为型模式

> 使用场景

  1. Spring的表达式解析(AOP的expression表达式,SPEL表达式,…)

  2. Thymeleaf等模板引擎的语法解析

  3. 编译原理

  4. 编译器

> 代码实现

>

推荐:可以自己手写一种用于某种功能的程序语言(不需要过于复杂,只要你掌握了字符串的重要方法一般就可以进行实现),例如SPEL或注解的value值的解释器


[ 8 ] 命令模式(Command)

> 原理:将一个请求封装成一个对象,使

发出请求的责任和执行请求的责任

分隔开,这样两者之间通过

命令对象

进行沟通,这样方便将命令对象进行储存,传递,调用,增加与管理

> 目的:为了解耦合

> 使用场景

  • mvc三层架构就是典型的命令模式(controller调用service,service调用dao)

  • 当系统需要执行一组操作时,命令模式可以定义宏命令(一个命令组合了多个命令)来实现该功能

    宏命令:多个命令的结合体,执行一个宏命令就相当于执行多个命令

  • 结合备忘录模式还可以实现命令的恢复和撤销

> 代码实现

> 场景模拟:模拟一个人接收到出差和上课的命令的操作

> 总结:命令模式有点像是状态模式,接收到不同的命令执行不一样的操作

> 命令发送者就类似于Controller,命令的处理就类似于service,命令的获取就类似于dao

//类似于controller接口(发送命令)
public class Invoker {

    Command command;

    public void setCommand(Command command) {
        this.command = command;
    }
    public void execute() {
        command.execute();
    }
}
//命令执行
public class OnlineCommand extends Command{
    private LeiReceiver receiver;

    public void setReceiver(LeiReceiver receiver) {
        this.receiver = receiver;
    }

    @Override
    void execute() {
        receiver.online();
    }
}
//命令接收者
public class LeiReceiver {
    public void online() {
        //接收到onlineCommand命令的时候会执行该方法
        System.out.println("接收到上课命令,准备上课了");
    }
    public void travel() {
        System.out.println("接收到出差命令,准备出差了");
    }
}

[ 9 ] 迭代器模式(Iterator)

> 原理:提供一个对象来顺序访问聚合对象(迭代数据)中的一系列数据,而不暴露聚合对象的内部显示

> 分类:对象行为型模式

> 核心思想:不对外开放数据,但对外提供迭代器对集合数据进行遍历进行查看

> 使用场景:

  • list集合,set集合,array数据都有对应的迭代器可以获取

> 代码实现


> 这一部分建议读者可以建立一个类实现Iterator接口去实现其中的抽象方法,从而掌握迭代器模式


[ 10 ] 访问者模式(Visitor)

> 原理:将作用于

某种数据结构

中的

各元素



操作分离

出来封装,成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式,它将对数据的操作与数据结构进行分离,

是行为类模式中最复杂的一种模式

> 注意:该设计模式是最为难懂的一种设计模式,也是实现起来最为困难的一种设计模式

> 弊端:违背了开闭原则

> 核心:元素要能接收访问者的访问,访问者需要能访问元素

> 使用场景

  1. 在访问者模式中,每增加了一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了”开闭原则”

  2. 违反依赖倒置原则(元访问者模式依赖了具体类),而没有依赖于抽象类

  3. 破坏封装,访问者模式中具体元素对访问者公开细节,这破坏了对象的封装性

  4. 应用于对象结构相对稳定,但其操作算法经常变化的程序

  5. Spring反射工具中的MethodVisitor是什么

> 代码实现

> 总结:访问者可以访问本类内部的元素,元素可以被访问者所访问到,公开所有的细节

>


注意:由于访问者设计模式使用较少,因为其违背了开闭原则和依赖倒置原则

public class XiaoAi {

    private CPU cpu = new CPU("武汉天气");
    private Disk disk = new Disk("武汉天气");

    public void answerQuestion() {
        cpu.work();
        disk.work();
    }
    public void acceptUpdate(Visitor visitor) {
        //升级
        //访问模式
        visitor.visitCpu(cpu);
        visitor.visitDisk(disk);
    }
}

[ 11 ] 职责链模式(Chain Of Resonsibility)

> 原理:为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者

通过前一对象记住其下一个对象的引用而连成一条链

,当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止,属于对象行为型模式

> 核心:处理请求需要通过一条链进行业务的处理

> 处理步骤:只需要调用链上的链头就可以一直执行下去直到链尾结束

> 使用场景

> Filter链就是一种职责链模式和过滤器模式的组合运用

>

代码参考:可以借鉴于Filter实现的Filter Chain的代码实现(最推荐参考Spring的通知注解配置)


简单职责链模式

public class Teacher {
 private String name;
 //留下一个链条,链条的构造点
 private Teacher next;
 public Teacher(String name) {
     this.name = name;
 }

 public void setNext(Teacher next) {
     this.next = next;
 }

 public Teacher getNext() {
     return next;
 }

 public void handlerRequest() {
     System.out.println(this+ "正在处理请求");
     //处理完之后需要交割下一个链条引用
     if (next != null )next.handlerRequest();
 }

 @Override
 public String toString() {
     return "Teacher{" +
             "name='" + name + '\'' +
             ", next=" + next +
             '}';
 }
}

public class MainTest {
 public static void main(String[] args) {
     Teacher teacher = new Teacher("张老师");
     Teacher teacher1 = new Teacher("李老师");
     Teacher teacher2 = new Teacher("王老师");

     //构造职责链
     teacher.setNext(teacher1);
     teacher1.setNext(teacher2);

     //调用链头,就会一直执行下去
     teacher.handlerRequest();
 }
}

完整职责链模式(回旋职责链)

> 代码实现核心:游标cursor,FilterChain实现Filter接口

public void doFilter(Request request,Response response,FilterChain chain) {
	if(cursor < chain.size()) {
		Filter filter = chain.get(cursor);
		cursor++;
		filter.doFilter(request,response,chain);
	}
	else {
		//表示需要执行到目标对象了
		target.hello();
	}
}



版权声明:本文为m0_62933942原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。