(原创)23种设计模式一网打尽,看这篇就够了!

设计模式的分类

总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、建造者模式、单例模式、原型模式。
结构型模式,共七种:适配器模式、代理模式、装饰器模式、桥接模式、组合模式、外观模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

设计模式的原则

设计模式一般遵循以下六大原则:

单一职责原则 (Single Responsibility Principle)

单一职责原则重点在于类的职责要单一,一个类尽量只完成一个功能
比如一个Person类:

public class Person {
    
    private String name = "";

    public String getName() {
        return name;
    }


//    private int capacity = 0;

//    public int getCapacity() {
//        return capacity;
//    }

//    充电操作,应该交由电池这个类来处理
//    public void charge() {
//        capacity++;
//    }
}

可以看到,他除了有name属性,还有个容量和充电的功能
但其实这个功能不应该归Person类管
这个Person类就应该重点关注它自身的职责,如得到姓名等
充电的这个功能应该剥离开来
交由电池类来完成。如下:

public class Person {

    private String name = "";

    public String getName() {
        return name;
    }

}
public class Battery {

    protected int capacity = 0;

    public int getCapacity() {
        return capacity;
    }

    public void charge() {
        capacity++;
    }
}

开放-关闭原则 (Open-Closed Principle)

开闭原则重点在于对扩展开放,对修改关闭
对于一个类,当要添加新功能的时候,它可以支持扩展,但尽量不要去修改它
比如上文的电池类
如果要新增一个快速充电的方法
就可以这样:

public class NewBattery extends Battery {
    
    public void fastCharge() {
        capacity += 10;
    }

}

里氏替换原则 (Liskov Substitution Principle)

里氏替换就是说要维护好继承体系
比如一个子类B继承父类A
只要父类能出现的地方,子类就可以出现
并且替换为子类也不会产生错误
子类可以扩展父类的方法,但不能改变父类的方法
先看一个反面例子:

public class Client {
    public static void main(String[] args) {
        User user = new VipUser();
        user.getMsg();
    }
}

class User {
    public void getMsg() {
        System.out.println("这是一个学员");
    }
}

class VipUser extends User {
    public void getMsg() {
        System.out.println("这是一个VIP学员");
    }
}

在这里,子类覆盖了父类原有的功能
导致同一个名为getMsg的方法,子类和父类返回的结果却是不同的
正确的写法如下:

class User {
    public void getMsg() {
        System.out.println("这是一个学员");
    }
}

class VipUser extends User {
    public void getMsg() {
        super.getMsg();
    }
    public void getVipMsg() {
        System.out.println("这是一个VIP学员");
    }
}

可以看到:子类完全具有父类的原本功能(getMsg),额外新增了自己的特性功能(getVipMsg)

依赖倒置原则 (Dependence Inversion Principle)

所谓依赖倒置原则,就是面向接口编程
其实就是尽量用接口的方式实现上层和下层的交互,比如:

public interface Fruit {
    String getFruitName();
}

class Apple implements Fruit{
    @Override
    public String getFruitName() {
        return "苹果";
    }
}

class Orange implements Fruit{

    @Override
    public String getFruitName() {
        return "橘子";
    }
}

依赖接口的好处是:
一个方法的功能,你可以有多种实现方式
比如上面的水果名称,可以根据不同的水果得到具体的名称
再比如一个操作行为,如粉刷
可以根据具体的场景来决定是刷成什么颜色或者使用什么根据来粉刷

接口隔离原则 (Interface Segregation Principle)

接口的设计,颗粒度尽量要小
尽量一个接口只给一个子模块或者业务使用
接口隔离原则和单一职责原则的区别:
1:角度不同,单一职责要求接口职责单一,不同的业务在不同的类中处理
2:接口隔离是指一个接口只给一个子模块或者业务
举个反面例子:

public interface Login {

    boolean doLogin();//登录

    boolean doLoginOut();//退出
    
  //List<String> getOrders();//得到订单列表

}

这个Login接口只负责登录相关的功能,属于单一职责原则
它只能给登录相关的业务和子模块提供功能,但是订单模块相关的功能就不应该继续用这个接口。属于接口隔离原则
改动后如下:

public interface Login {

    boolean doLogin();//登录

    boolean doLoginOut();//退出

}

interface Order{
    List<String> getOrders();//得到订单列表
}

迪米特法则(Law Of Demeter)

又叫最少知识原则
指的是一个类对于其他类的了解要尽量少
从而降低耦合
比如现在有一个算账的类,他能根据你输入的价格
计算出满减、打折后的价格
如下:

class Calculation {

    //得到最终价格
    public double getPrice(double price) {
        double reduce = reduce(price);
        return discount(reduce);
    }

    //打折
    private double discount(double price) {
        return price * 0.8d;
    }

    //满100减20
    private double reduce(double price) {
        if (price >= 100) {
            return price - 20;
        }
        return price;
    }

}

可以看到,这个类被调用时最终只需要输出准确的价格
所以满减逻辑,打折逻辑都应该封装在内部。而不能被外部得知
所以用private封装起来
使用的时候就像这样:

public class Client {
    public static void main(String[] args) {
        Calculation calculation=new Calculation();
        double price = calculation.getPrice(100);
        System.out.println("price:"+price);
    }
}

六大设计模式大概就介绍到这里,可以用下面一张图表示:
在这里插入图片描述

创建型模式

1.工厂方法模式

简单工厂(静态工厂)模式

在讲工厂方法模式前
我们需要了解简单工厂模式,又叫静态工厂模式
注意:这个模式不属于23种设计模式之一,但他是工厂模式的基础
所以我们先了解下这个模式

简单工厂模式。说白了就是用一个专业的类来负责一种对象的创建
比如一个人想吃苹果,可以new一个Apple
但想吃橘子,又要去new一个Orange
每次都要自己动手。
何不如创建一个水果工厂,要水果就从工厂里面拿呢?
比如现在有一个水果的基类:

public interface Fruit {
    void msg();
}

以及两个实现类:

public class Apple implements Fruit{
    @Override
    public void msg() {
        System.out.println("这是苹果");
    }
}
public class Orange implements Fruit {
    @Override
    public void msg() {
        System.out.println("这是橘子");
    }
}

我们就创建一个水果工厂

public class FruitFactory {
    public static final int TYPE_APPLE = 1;//苹果
    public static final int TYPE_ORANGE = 2;//橘子

    public static Fruit getFruit(int type) {
        if (type == TYPE_APPLE) {
            return new Apple();
        } else {
            return new Orange();
        }
    }

    public static Fruit getFruitApple() {
        return new Apple();
    }

    public static Fruit getFruitOrange() {
        return new Orange();
    }

}

这样使用时就是:

    public static void main(String[] args) {
        Fruit fruit1 = FruitFactory.getFruit(FruitFactory.TYPE_APPLE);
        fruit1.msg();
        Fruit fruit2 = FruitFactory.getFruit(FruitFactory.TYPE_ORANGE);
        fruit2.msg();
    }

打印结果:
在这里插入图片描述
这就是一个简单工厂模式

工厂方法模式

上面的简单工厂模式有两个问题:
1:不符合单一职责原则:一个类负责了各类水果的创建
2:不符合开闭原则:扩展一个新的水果时,需要修改已有代码
这时候我们变引入了工厂方法模式
我们对FruitFactory这个类做一个改造
他是所有水果工厂的基类

public interface FruitFactory {
    public Fruit getFruit();
}

然后我们根据这个基类扩展出不同的水果工厂
每个工厂只负责创建一种水果
比如苹果工厂,香蕉工厂,橘子工厂…

public class AppleFactory implements FruitFactory{
    @Override
    public Fruit getFruit() {
        System.out.println("进入苹果工厂");
        return new Apple();
    }
}
public class OrangeFactory implements FruitFactory{
    @Override
    public Fruit getFruit() {
        System.out.println("进入橘子工厂");
        return new Orange();
    }
}

测试代码:

    public static void main(String[] args) {
        FruitFactory factory1=new AppleFactory();
        Fruit fruit1 = factory1.getFruit();
        fruit1.msg();
        FruitFactory factory2=new OrangeFactory();
        Fruit fruit2 = factory2.getFruit();
        fruit2.msg();
    }

打印结果:
在这里插入图片描述
这样,一个工厂方法模式就完成了

2.抽象工厂模式

抽象工厂模式有点像工厂方法模式的进阶版
如果一个类的产品还有相关的多个系列区分时,为了按照系列生产产品,就使用抽象工厂模式
比如,现在我们的水果在出厂时,还需要进行对应的打包
苹果要放入苹果盒子,橘子要放入橘子盒子
这时候,难道我们又要搞一个盒子工厂嘛?
其实大可不必
因为苹果和苹果盒子是一个系列,二者是有关联的
而且如果分开来设置工厂,打包的时候容易出错
把苹果装入到了橘子盒子中
于是我们看下抽象工厂模式是如何处理的。
首先我们定义一个盒子的基类

public interface FruitBag {
    void toBag();
}

然后是两个实现类:苹果盒子和橘子盒子

public class AppleFruitBag implements FruitBag {
    @Override
    public void toBag() {
        System.out.println("给苹果打包");
    }
}
public class OrangeFruitBag implements FruitBag {
    @Override
    public void toBag() {
        System.out.println("给橘子打包");
    }
}

然后还是对FruitFactory进行改造
新增一个得到包装盒的方法

public interface FruitFactory {
    public Fruit getFruit();
    public FruitBag getFruitBag();
}

这样我们的苹果和橘子工厂也要改动下

public class AppleFactory implements FruitFactory{
    @Override
    public Fruit getFruit() {
        System.out.println("进入苹果工厂");
        return new Apple();
    }

    @Override
    public FruitBag getFruitBag() {
        return new AppleFruitBag();
    }
}
public class OrangeFactory implements FruitFactory{
    @Override
    public Fruit getFruit() {
        System.out.println("进入橘子工厂");
        return new Orange();
    }

    @Override
    public FruitBag getFruitBag() {
        return new OrangeFruitBag();
    }
}

上面这样处理,就把盒子和水果的获取都放到FruitFactory这个水果工厂类了
免去了再去创建盒子工厂的过程
而且避免了装错盒子的问题
水果也好,水果盒也好,都围绕着一个FruitFactory工厂类来了
测试代码如下:

    public static void main(String[] args) {
        FruitFactory factory1=new AppleFactory();
        Fruit fruit1 = factory1.getFruit();
        fruit1.msg();
        FruitBag fruitBag1 = factory1.getFruitBag();
        fruitBag1.toBag();

        FruitFactory factory2=new OrangeFactory();
        Fruit fruit2 = factory2.getFruit();
        fruit2.msg();
        FruitBag fruitBag2 = factory2.getFruitBag();
        fruitBag2.toBag();
    }

结果如下:
在这里插入图片描述

3.建造者模式

所谓建造者模式,就是使用多个简单的对象一步一步构建成一个复杂的对象,
有点像造房子一样一步步从地基做起到万丈高楼。
相比于工厂模式,他会更关注建造的过程
比如ABCD的四个步骤,分别采用何种方式去实现
建造者模式更加关注与零件装配的顺序,一般用来创建更为复杂的对象
建造者模式的实现例子有好多,有造人、造车、造房子、造世界的…等好多。
但归类后有两种实现方式。
(1)通过Client、Director、Builder和Product形成的建造者模式

(2)通过静态内部类方式实现零件无序装配话构造
下面就开始介绍这两种方式

第一种方式

通过Client、Director、Builder和Product形成的建造者模式
一般有以下几个角色:
抽象建造者(builder):描述具体建造者的公共接口,一般用来定义建造细节的方法,并不涉及具体的对象部件的创建。
具体建造者(ConcreteBuilder):描述具体建造者,并实现抽象建造者公共接口。
指挥者(Director):调用具体建造者来创建复杂对象(产品)的各个部分,并按照一定顺序(流程)来建造复杂对象。
产品(Product):描述一个由一系列部件组成较为复杂的对象。
假设我们要造一个房子
首先需要一个房子的类(Product)
建造过程分为ABCD,也用四个方法表示
代码如下:

/**
 * Product.java
 * 产品(房子)
 */
public class Product {
    private String buildA;
    private String buildB;
    private String buildC;
    private String buildD;

    public String getBuildA() {
        return buildA;
    }

    public void setBuildA(String buildA) {
        this.buildA = buildA;
    }

    public String getBuildB() {
        return buildB;
    }

    public void setBuildB(String buildB) {
        this.buildB = buildB;
    }

    public String getBuildC() {
        return buildC;
    }

    public void setBuildC(String buildC) {
        this.buildC = buildC;
    }

    public String getBuildD() {
        return buildD;
    }

    public void setBuildD(String buildD) {
        this.buildD = buildD;
    }

    @Override
    public String toString() {
        return buildA + "\n" + buildB + "\n" + buildC + "\n" + buildD + "\n" + "房子验收完成";
    }
}

这时候我们创建一个建造者(Builder)的接口类,负责房子的搭建

/**
 * Builder.java
 *  建造者
 */
abstract class Builder {
    //地基
    abstract void bulidA();
    //钢筋工程
    abstract void bulidB();
    //铺电线
    abstract void bulidC();
    //粉刷
    abstract void bulidD();
    //完工-获取产品
    abstract Product create();
}

具体的实现就交给工人(ConcreteBuilder)这个类去完成
工人就是具体的建造者

/**
 * ConcreteBuilder.java
 *  具体建造者(工人)
 */
public class ConcreteBuilder extends Builder{
    private Product product;
    public ConcreteBuilder() {
        product = new Product();
    }
    @Override
    void bulidA() {
        product.setBuildA("地基");
    }
    @Override
    void bulidB() {
        product.setBuildB("钢筋工程");
    }
    @Override
    void bulidC() {
        product.setBuildC("铺电线");
    }
    @Override
    void bulidD() {
        product.setBuildD("粉刷");
    }
    @Override
    Product create() {
        return product;
    }
}

工人负责建造房子,但是ABCD四个建造步骤,由谁来决定呢?
就有了我们的指挥者(Director)

/**
 * Director.java
 *  指挥者
 */
public class Director {
    //指挥工人按顺序造房
    public Product create(Builder builder) {
        builder.bulidA();
        builder.bulidB();
        builder.bulidC();
        builder.bulidD();
        return builder.create();
    }
}

最后看我们的测试代码

/**
 * Test.java
 *  测试类
 */
public class Test {
    public static void main(String[] args) {
        //定义指挥者
        Director director = new Director();
        //指挥者指挥具体的工人进行房子的搭建
        Product create = director.create(new ConcreteBuilder());
        //打印搭建的房子的信息
        System.out.println(create.toString());
    }
}

结果如下
在这里插入图片描述

第二种方式

第一种方式把建造的过程控制都交给了指挥者
但其实可以由我们自己指挥,也就是去掉指挥者的角色
只剩下抽象建造者、具体建造者和产品这三个角色
这种方式使用更加灵活,更符合定义。内部有复杂对象的默认实现,
使用时可以根据用户需求自由定义更改内容。
并且无需改变具体的构造方式,就可以生产出不同复杂产品
这一次我们以点餐为例子
客户去麦当劳可以点不同的餐点
首先还是看我们的产品:

/**
 * Product.java
 *  产品(麦当劳套餐)
 */
public class Product {
    private String buildA="汉堡";
    private String buildB="饮料";
    private String buildC="薯条";
    private String buildD="甜品";
    public String getBuildA() {
        return buildA;
    }
    public void setBuildA(String buildA) {
        this.buildA = buildA;
    }
    public String getBuildB() {
        return buildB;
    }
    public void setBuildB(String buildB) {
        this.buildB = buildB;
    }
    public String getBuildC() {
        return buildC;
    }
    public void setBuildC(String buildC) {
        this.buildC = buildC;
    }
    public String getBuildD() {
        return buildD;
    }
    public void setBuildD(String buildD) {
        this.buildD = buildD;
    }
    public String show() {
        return buildA+"\n"+buildB+"\n"+buildC+"\n"+buildD+"\n"+"组成套餐";
    }
}

然后是建造者:

/**
 * Builder.java
 *  建造者
 */
abstract class Builder {
    //汉堡
    abstract Builder bulidA(String mes);
    //饮料
    abstract Builder bulidB(String mes);
    //薯条
    abstract Builder bulidC(String mes);
    //甜品
    abstract Builder bulidD(String mes);
    //获取套餐
    abstract Product create();
}

具体的建造交由服务员:

/**
 * ConcreteBuilder.java
 *  具体建造者(服务员)
 */
public class ConcreteBuilder extends Builder{

    private Product product;
    public ConcreteBuilder() {
        product = new Product();
    }

    @Override
    Product create() {
        return product;
    }

    @Override
    Builder bulidA(String mes) {
        product.setBuildA(mes);
        return this;
    }

    @Override
    Builder bulidB(String mes) {
        product.setBuildB(mes);
        return this;
    }

    @Override
    Builder bulidC(String mes) {
        product.setBuildC(mes);
        return this;
    }

    @Override
    Builder bulidD(String mes) {
        product.setBuildD(mes);
        return this;
    }
}

使用的时候,就由我们自己来指挥,来决定使用什么搭配

public class Test {
    public static void main(String[] args) {
        //创建建造者
        ConcreteBuilder concreteBuilder = new ConcreteBuilder();
        //开始定制化套餐的搭配
        Product build = concreteBuilder
                .bulidA("牛肉煲")
                .bulidC("全家桶")
                .bulidD("冰淇淋")
                .create();
        //输出套餐的搭配
        System.out.println(build.show());
    }
}

打印结果:
在这里插入图片描述

第二种方式的内部类形式

第二种方式比较灵活
但实际开发中,我们还会做一点改进
去掉抽象的建造者
把具体的建造者作为内部类
包裹这个内部类的Builder,我们称作外部Builder
比如我们安卓里的AlertDialog就是这样
产品代码不变,我们看具体建造者修改后的样子:

/**
 * ConcreteBuilder.java
 * 具体建造者(服务员)
 */
public class ConcreteBuilder {
    public static class Builder {
        private Product product;

        public Builder() {
            this.product = new Product();
        }


        public Product create() {
            return product;
        }

        public Builder bulidA(String mes) {
            product.setBuildA(mes);
            return this;
        }

        public Builder bulidB(String mes) {
            product.setBuildB(mes);
            return this;
        }

        public Builder bulidC(String mes) {
            product.setBuildC(mes);
            return this;
        }

        public Builder bulidD(String mes) {
            product.setBuildD(mes);
            return this;
        }
    }

}

这时候我们看测试代码,就会发现
这就是我们最常看到的建造者模式的样子了

public class Test {
    public static void main(String[] args) {
        Product build = new ConcreteBuilder.Builder()
                .bulidA("牛肉煲")
                .bulidB("咖啡")
                .bulidC("全家桶")
                .bulidD("冰淇淋")
                .create();
        //输出套餐的搭配
        System.out.println(build.show());
    }
}

打印结果如下:
在这里插入图片描述

4.单例模式

单例模式太简单了
就不介绍了
直接贴代码
1:线程安全的饿汉式
(用final修饰,保证变量的不可变,所以线程安全)

class Single {
    private static final Single single = new Single();

    private Single() {
    }

    public static Single getInstance() {
        return single;
    }
}

2:一般的懒汉式(线程不安全)

class Single {
    private static Single single = null;

    private Single() {
    }

    public static Single getInstance() {
        if (single == null) {
            single = new Single();
        }
        return single;
    }
}

3:加了同步的懒汉式(线程安全)
(外面再嵌套single是否为空的判断,
这样以后就不用重复判断锁了,
提高多线程效率,这就是DCL:双重检查锁定)
记得加锁volatile关键字
防止编译器重排序,导致空指针异常

class Single {
    private volatile static Single single = null;

    private Single() {
    }

    public static Single getInstance() {
        if (single == null) {
            synchronized (Single.class) {
                if (single == null) {
                    single = new Single();
                }
            }
        }
        return single;
    }

}

5.原型模式

原型模式其实就是对对象的赋值
当我们需要创建多个重复对象,他们的属性值全部或者部分相同。这个时候我们就可以用到原型模式。
比如要批量创建一些商品类、学生类等
这里用到的其实就是拷贝技术
根据创建的对象的深度和层次的区别
原型模式又分为深拷贝和浅拷贝
具体可以看这个博客
(原创)java的拷贝方式:对象拷贝,浅拷贝,深拷贝
例子也写的很清楚了
这里就不再赘述了
通过上面博客可知
实现原型模式,可以有两种方式:
1:实现Cloneable接口,然后重写clone方法
2:让类实现Serializable接口,然后通过字节流序列化实现深拷贝
这里补充一点:
对象的克隆,或者说拷贝
对单例模式会造成破坏
解决办法也有两种:
1:不实现Cloneable接口
2:对clone方法进行重写,返回单例所创建的对象即可

结构型模式

6.适配器模式

还是拿例子来说明
比如我们现在有一台typec接口的设备,只有typec接口才能帮它充电
但是我们只有USB接口充电线
这时候我们就需要一个转接头
把USB接口转为typec接口
这样我们设备,使用这个转接头,就可以完成充电了
这个转接头,就是我们说的适配器模式里的适配器
类图如下:
在这里插入图片描述
我们要如何完成一个适配器模式呢?
首先,两个充电接口类我们需要定义出来

/**
 * typec接口
 */
public class TypeCInterface {
    public void startCharge(){
        System.out.println("Type-C接口正在为你充电");
    }
}
/**
 * USB接口
 */
public class USBInterface {
    public void startCharge() {
        System.out.println("USB接口正在为你充电");
    }
}

然后就是我们的适配器类

/**
 * USB接口转换为typec接口的转接头
 */
public class TypeCInterfaceAdapter extends TypeCInterface {

    USBInterface USBAdapter;


    public TypeCInterfaceAdapter(USBInterface USBAdapter) {
        this.USBAdapter = USBAdapter;
    }

    public void  startCharge(){
        USBAdapter.startCharge();
    }
}

可以看到,它继承了typec接口
支持用户传入一个USB接口类
于是在重写typec接口的startCharge充电方法时
用传入的USB接口类的startCharge充电方法去完成充电
调用的方式如下:

    public static void main(String[] args) {
        //USB接口充电线
        USBInterface usbInterface=new USBInterface();
        //创建转接头,连接USB接口充电线
        TypeCInterfaceAdapter typeCInterfaceAdapter = new TypeCInterfaceAdapter(usbInterface);
        TypeC_hole(typeCInterfaceAdapter);
    }

    /**
     * 需要typeC充电孔的设备
     */
    static void TypeC_hole(TypeCInterface typeCInterface){
        //给typeC接口设备充电
        typeCInterface.startCharge();
    }

打印结果:
在这里插入图片描述
可以看到,最终用的还是USB充电的功能,去完成对typec设备的充电

7.代理模式

代理模式:一个类要实现一个功能,不是直接去实现,而是借用另外一个类
举个例子:
一个软件,刚进入时没有登录功能
所有的按钮点击后都要求自动跳转到登录界面
这时候肯定就不能针对每一个按钮做一个跳转
而是用另外一个类去实现,对每一个按钮的点击做一个代理即可
具体看代码
首先,我们需要一个Click接口

interface Click {
    void onClick();
}

然后需要一个BtnClick类实现这个接口
这个BtnClick就是被代理的哪个类
他自己的功能是负责按钮点击逻辑
其他的登录判断他不管

class BtnClick implements Click {

    @Override
    public void onClick() {
        System.out.println("按钮点击逻辑");
    }
}

这时我们再来一个代理类
也要实现Click接口
负责判断逻辑

class ClickProxy implements Click {

    private Click click;

    public ClickProxy(Click click) {
        this.click = click;
    }

    @Override
    public void onClick() {
        System.out.println("判断是否登录,未登录则跳转登录");
        click.onClick();
        System.out.println("按钮点击之后,做的一些操作");
    }
}

最后测试代码如下:

        Click btn = new BtnClick();
        ClickProxy proxy  = new ClickProxy(btn);
        proxy.onClick();

打印结果:
在这里插入图片描述
可以看到,代理类把判断逻辑都做了
内部也执行了原来的点击按钮逻辑

上面讲的是java的静态代理模式
其实java一共三种代理模式
静态代理,动态代理,Cglib代理
动态代理用到的就是反射
感兴趣的可以看下这篇博客
Java代理(Proxy)模式

8.装饰器模式

装饰器模式:对类的功能的扩展
下面也是用一个例子来说明装饰器模式的用法和思想
假设我们现在有一个充电器的接口
这个接口有一个充电的方法

//充电器
interface Charger {
    void charge();
}

然后我们去实现这个接口,做出一个普通的充电器的类

class ChargerDecorator implements Charger {
    @Override
    public void charge() {
        System.out.println("充电器正在充电,进度:65%");
    }
}

到这一步,我们要充电,就new这个ChargerDecorator类就好了
但是如果有一天,我们需要对这个类进行扩展
希望加上蓝牙充电,或者充电安全保护什么的
这时候我们就需要装饰器模式
对充电器的这个类进行不同的功能扩展
于是我们有了一个扩展功能的类

class NewChargerDecorator implements Charger {
    private Charger charger;

    public NewChargerDecorator(Charger charger) {
        this.charger = charger;
    }

    @Override
    public void charge() {
        charger.charge();
    }
}

这个类接收了一个充电器接口的类
并且自己充电方法内部调用了传入的接口充电方法
但这还不够,这个类并不是拿来直接使用的
我们需要一个能够实现充电保护的装饰器类来继承这个类
于是就这样:

class SafeChargerDecorator extends NewChargerDecorator {

    public SafeChargerDecorator(Charger charger) {
        super(charger);
    }

    @Override
    public void charge() {
        System.out.println("开启充电保护功能");
        super.charge();
        System.out.println("关闭充电保护功能");
    }
}

这样一来,我们的SafeChargerDecorator类
就是在保证原来充电功能的前提下,多加了一个充电保护的功能
然后,我们又推出了新的产品
一款支持蓝牙充电的类

class BlueToothChargerDecorator extends NewChargerDecorator {

    public BlueToothChargerDecorator(Charger charger) {
        super(charger);
    }

    @Override
    public void charge() {
        System.out.println("开启蓝牙充电");
        super.charge();
        System.out.println("关闭蓝牙充电");
    }
}

这时候,我们想使用蓝牙充电功能
就使用这个蓝牙的类
但是如果我们充电保护和蓝牙充电这两个功能都想要
那么就这样做:

        Charger charger = new ChargerDecorator();
        charger = new SafeChargerDecorator(new BlueToothChargerDecorator(charger));
        charger.charge();

可以看到,一个原来普通的充电器
装饰了两个功能
就变成了一个带蓝牙充电和充电保护的充电器了
我们来看下完整的测试代码:

        System.out.println("--------------普通充电器------------\n");
        Charger charger = new ChargerDecorator();
        charger.charge();
        System.out.println("\n-----------装饰了功能的充电器--------\n");
        charger = new SafeChargerDecorator(new BlueToothChargerDecorator(charger));
        charger.charge();

打印结果:
在这里插入图片描述
装饰器的妙处就在这里
你可以灵活的扩展功能
需要蓝牙就装饰蓝牙功能
需要充电保护就装饰充电保护
两个都需要就嵌套使用即可

装饰器模式和代理模式的区别

装饰器模式的代码,和代理模式的代码有点相像
故而很多人无法区分
其实很简单,我们可以这样理解
1:如果我们无法做到某件事,我们就需要一个代理
比如我们人类无法飞翔,就需要把自己放入飞机里,
这样飞机就可以帮助我们实现飞行的功能
这就是代理模式
2:如果我们需要做某件事,但是还不会
就需要去学习
比如我们去考驾照,就可以开车
去考证书,就可以获得相关的任职资格
这些证书加持在我们身上
就代表我们新拥有的各种功能
这就是装饰器模式

一般来说,一个类可以加上多个装饰器
从而实现多个功能
所以装饰器模式一般是用来扩展和类有一定关联
但是又不是这个类必须要拥有的功能
就像上面的充电器

而代理模式,则是把类丢给一个代理器
让代理器完成某项指定的功能
一般代理模式完成的功能
和类本身是没什么关联的
只是类需要一个这样的功能流程
比如校验、初始化等
这时候就交给代理器去代理

用图来做个表示

代理模式如下图
在这里插入图片描述

装饰器模式如下图
在这里插入图片描述

9.桥接模式

当我们描述的一个物体具有多个不同的维度属性时,我们需要用到这个模式。
比如现在有一个袋子,按照大小来分,可以有大,中,小三种。
按照材质来分,可以有纸,塑料,亚麻等
如下图:
在这里插入图片描述

我们总不能给一个不同的维度都创建一个实体类。
比如上图,就需要九个类才能描述完
如果这时候又引入一个价格维度来分,又有不同的价格。
又要创建更多的类。
这个时候,我们就需要用到桥接模式来表述了。
一般这种情况,我们首先要做的
就是确立一个主维度。
比如上面的例子,我可以设置大小为主维度
其他的维度为副维度,如下图:
在这里插入图片描述

当然,你也可以设置材质为维度,这个要看具体业务
于是,我们就可以先设计出这样一个类:

public abstract class BagAbstraction {
    protected Material material;

    public void setMaterial(Material material) {
        this.material = material;
    }

   public abstract void pick();

}

pick方法是打印这个袋子类的相关信息。可以看到,这个类还有一个属性是材质,。这意味着我们需要用另外一个接口来描述材质这个维度:

/**
 * 材质类
 */
public interface Material {
    void draw();
}

我们的主维度类BagAbstraction,有几个它的实现。
也就是大袋子,中袋子,小袋子

/**
 * 大袋子
 */
public class bigBag extends BagAbstraction {
    @Override
    public void pick() {
        System.out.println("开始放入袋子中...");
        this.material.draw();
        System.out.println("袋子大小为大");
    }
}
/**
 * 中等袋子
 */
public class MediumBag extends BagAbstraction {
    @Override
    public void pick() {
        System.out.println("开始放入袋子中...");
        this.material.draw();
        System.out.println("袋子大小为中等");
    }
}
/**
 * 小袋子
 */
public class SmallBag extends BagAbstraction {
    @Override
    public void pick() {
        System.out.println("开始放入袋子中...");
        this.material.draw();
        System.out.println("袋子大小为小");
    }
}

而材质的接口,也有几个实现
分别是纸袋,塑料袋,亚麻袋

public class PaperMaterial implements Material{
    @Override
    public void draw() {
        System.out.println("这是一个纸袋子");
    }
}
public class PlasticMaterial implements Material{
    @Override
    public void draw() {
        System.out.println("这是一个塑料袋");
    }
}
public class SackMaterial implements Material{
    @Override
    public void draw() {
        System.out.println("这是一个亚麻袋子");
    }
}

最后,这些维度类写好后,我们看下我们的示例代码:

    public static void main(String[] args) {
        BagAbstraction bag=new bigBag();//一个大袋子
        Material material = new PaperMaterial();//材质:纸
        bag.setMaterial(material);//给大袋子设置材质:纸
        bag.pick();//打印袋子信息
    }

打印结果:
在这里插入图片描述
也许有人会有疑问:
这样还是用了八个类来描述这些维度啊,没看到有什么优化。
但是我们一想:如果袋子的维度有四个选项
材质的维度也有四个选项。
按照最开始的设计
我们就会有4x4=16个类
而按照桥接模式,则只有两个维度基类和八个不同具体维度的实现类。也才10个类
如果维度一共有三种,每种有四个选项
那么最开始的设计,需要:3x4x4=48个类
桥接模式只需要3个维度基类加上(3x4)=12个维度实现类
一共15个类
这样做的好处就很明显的看出来了
最后说下桥接模式和适配器模式的区别:
适配器模式是合并,两个不能通用的物体,通过一个适配类,做到一个转换和共通的作用。比如usb接口和typeC接口
而桥接模式重在分离,一个物体的多个维度,通过不同的类来描述,把一个物体分离成一个主维度+多个子维度。

10.组合模式

当我们描述的事物,具有明显的层级关系时,我们可以用到组合模式。
比如一个国家有多个省,一个省有多个市,一个市有多个镇,一个镇有多个村。我们总不能针对每一个层级,都去定义一个类。这样我们就有五个类了,如果以后村下面又有村镇,这样又要新增类。
再比如一个文件夹下面又有多个文件和文件夹,我们也不可能针对每一层都定义类。理论上这个可以有无限层。
那么组合模式是怎么做的呢?
它抓住了这些事物之间的层级关系,目的就是为了减少这些重复类的创建。
比如原来的层级关系如下图:
在这里插入图片描述
现在我们可以转换成这样:
在这里插入图片描述
一个根Node加一个具有子层级的Node以及一个没有子层级的Node(也就是叶子节点)。

第一种方式

这里还是拿文件夹举例,毕竟文件夹的层级理论上是无限的
比那个省市镇的层级多得多
首先我们需要一个基类,也就是根Node

/**
 * 文件基类
 */
public interface IFile {
    //打印自己
    public void displaySelf();

    //打印全路径
    public void displayAll();

    public boolean add(IFile file);

    public boolean remove(IFile file);

    //得到子文件/文件夹
    public List<IFile> getChild();

}

可以看到,这个文件基类,可以添加子文件或者子文件夹
也可以得到自己的子文件/文件夹列表
那么针对文件和文件夹,我们都各自创建一个类来继承这个基类:

/**
 * 文件类:
 * 没有子文件/文件夹
 * 无法打印自身下面的路径,因为他不是一个文件夹
 * 文件类说白了,只有个自身的名字
 */
public class File implements IFile {

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

    private String name;

    public String getName() {
        return name;
    }

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

    @Override
    public void displaySelf() {
        System.out.println(name);

    }

    @Override
    public void displayAll() {

    }

    @Override
    public boolean add(IFile file) {
        return false;
    }

    @Override
    public boolean remove(IFile file) {
        return false;
    }

    @Override
    public List<IFile> getChild() {
        return null;
    }
}

因为文件下面没有包含文件和文件夹
所以它的add,remove,getChild,displayAll都没什么意义
而文件夹则不一样:

/**
 * 文件夹类:
 * 有子文件/文件夹
 * 可以打印自身下面的路径
 * 文件夹类除了有自身的名字,还有自己下面的文件和文件夹
 */
public class Folder implements IFile {

    private String name;
    private List<IFile> children;

    public Folder(String name) {
        this.name = name;
        children=new ArrayList<>();
    }

    public String getName() {
        return name;
    }

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


    @Override
    public void displaySelf() {
        System.out.println(name);

    }

    @Override
    public void displayAll() {
        if (children.size()>0){
            System.out.println(name+"-->");
        }else {
            //空文件夹
            displaySelf();
        }
        for (int i = 0; i < children.size(); i++) {
            IFile iFile = children.get(i);
            if (iFile instanceof File){
                iFile.displaySelf();//打印自身
            }else if (iFile instanceof Folder){
                iFile.displayAll();//然后打印子目录下的文件/文件夹
            }
        }
        if (children.size()>0){
            System.out.println(name+"<--");
        }
    }

    @Override
    public boolean add(IFile file) {
        return children.add(file);
    }

    @Override
    public boolean remove(IFile file) {
        return children.remove(file);
    }

    @Override
    public List<IFile> getChild() {
        return children;
    }
}

最后我们看下自己的测试用例:

    public static void main(String[] args) {
        IFile rootFile = new Folder("D盘");
        IFile file1=new File("测试.txt");
        IFile folder1=new Folder("一级文件夹1");
        IFile folder2=new Folder("一级文件夹2");
        IFile folder3=new Folder("二级文件夹1");
        folder1.add(new File("一级文件1"));
        folder1.add(new File("一级文件2"));
        rootFile.add(file1);
        rootFile.add(folder1);
        rootFile.add(folder2);
        folder1.add(folder3);
        rootFile.displayAll();
    }

其实就是创建文件夹类,然后塞入文件或者文件夹。
子层级下面的文件夹又可以继续塞入新的文件或者文件夹。
然后打印出来
在这里插入图片描述

第二种方式

上面的例子是把文件基类做了两个实现,一个是文件夹,一个是文件。
也就是一个根Node加一个具有子层级的Node以及一个没有子层级的Node(也就是叶子节点)。
其实我们也有另外的写法,那就是把具有子层级的Node和没有子层级的Node合并为同一个Node
在这个文件层级的例子中,就是把文件和文件夹类也合并起来。
比如一个IFile,如果它的child的集合大小为0
其实我们就可以认定它是文件或者一个空文件夹
否则就是一个文件夹。
包括行政区域也是这样
如果它下面没有子层级或者子层级数量为0,那么就说明它是最后一个层级了
这时候我们就可以这样写:
首先还是一个描述基类:

/**
 * 文件基类
 */
public abstract class IFile {

    protected String name;
    protected List<IFile> children = new ArrayList<>();


    public IFile(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    public abstract List<IFile> getChild();

    public abstract void displaySelf();

    public abstract void displayAll();

    public abstract boolean add(IFile file);

    public abstract boolean remove(IFile file);

}

可以看到很多东西放到基类了,这个看你自己,不一定都要全放到基类。
然后是一个文件节点类:

/**
 * 文件/文件夹实现类
 */
public class FileNode extends IFile {


    public FileNode(String name) {
        super(name);
    }

    @Override
    public List<IFile> getChild() {
        return children;
    }

    @Override
    public boolean add(IFile file) {
        return children.add(file);
    }

    @Override
    public boolean remove(IFile file) {
        return children.remove(file);
    }

    @Override
    public void displaySelf() {
        System.out.println(name);
    }

    @Override
    public void displayAll() {
        if (children.size()>0){
            //说明是一个文件夹
            System.out.println(name + "-->");//先打印自身
            for (int i = 0; i < children.size(); i++) {
                IFile iFile = children.get(i);
                if (iFile.getChild().size() == 0) {
                    iFile.displaySelf();//一个文件或者空文件夹,就打印自身
                } else {
                    iFile.displayAll();//然后打印子目录下的文件/文件夹
                }
            }
            System.out.println(name + "<--");//先打印自身

        }else {
            //说明是一个文件或者一个空文件夹
            displaySelf();
        }
    }

}

可以看到,这时候文件节点的打印
就是根据这个类的child列表的数量
来判断是有层级的文件夹还是文件或者空文件夹的
测试例子如下:

    public static void main(String[] args) {
        IFile rootFile = new FileNode("D盘");
        IFile file1 = new FileNode("测试.txt");
        IFile folder1 = new FileNode("一级文件夹1");
        IFile folder2 = new FileNode("一级文件夹2");
        IFile folder3 = new FileNode("二级文件夹1");
        folder1.add(new FileNode("一级文件1"));
        folder1.add(new FileNode("一级文件2"));
        rootFile.add(file1);
        rootFile.add(folder1);
        rootFile.add(folder2);
        folder1.add(folder3);
        rootFile.displayAll();
    }

打印结果和上面是一样的,这里就不贴出来了
最后我们发现,这种层级关系
通过组合模式,我们可以做到类的数量不超过3个
这样我们利用组合模式,就可以灵活地描述层级结构
就不用根据每个层级都去创建不同的层级类了

11.外观模式

外观模式,又叫门面模式
一般场景是:当我们有一个比较复杂的流程,这个流程涉及到好几块的功能的时候,我们可以创建出一个外观类,让外观类给使用者提供一些简单的方法,至于内部复杂的控制流程,则交由外观类自身的不同功能类去完成。也就是说,外观类本身不具备任何能力,他内部所有的功能,都是基于他内部的功能类去做的。
有点像服务台的接待员,他只负责接待,通知真正的工作人员去完成工作,然后把结果告知客户。
这里我们用代码模拟一个场景:
现在用户去下单一件农产品
农产品的下单流程很复杂,涉及到农产品的采摘,清洗,消毒,发货等等
于是我们便有了四个类,负责这四个功能:

public class Pick {
    public void doPick(){
        System.out.println("开始采摘农产品");
        System.out.println("-------------");
    }
}
public class Clean {
    public void doClean(){
        System.out.println("开始给农产品清洗");
        System.out.println("-------------");
    }
}
public class Disinfect {
    public void doDisinfect(){
        System.out.println("开始给农产品消毒");
        System.out.println("-------------");
    }
}
public class Pack {
    public void doPack(){
        System.out.println("开始给农产品打包");
        System.out.println("-------------");
    }
}

这四个功能都比较复杂,如果让我们用户自己去完成整个下单流程
他就需要分别创建这四个功能类,然后还要按照一定顺序去调用他们的功能方法
这就太麻烦了!
于是我们创建一个农产品下单的门面工具类
他负责做这些工作

/**
 * 农产品下单门面类
 */
public class Facade {

    private Pick pick;
    private Clean clean;
    private Disinfect disinfect;
    private Pack pack;

    public Facade() {
        pick = new Pick();
        clean = new Clean();
        disinfect = new Disinfect();
        pack = new Pack();
    }

    /**
     * 下单
     */
    public void doOrder() {
        pick.doPick();
        clean.doClean();
        disinfect.doDisinfect();
        pack.doPack();
    }
}

可以看到,这个门面类把功能和流程都封装好了
那么我们使用这个门面类下单,就很简单了:

    public static void main(String[] args) {
        Facade f = new Facade();
        f.doOrder();
    }

打印结果:
在这里插入图片描述

12.享元模式

享元模式主要用于减少创建对象的数量以及避免重复创建对象,以减少内存占用和提高性能。
享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象
下面拿代码说话
比如我们有一个teacher类,其中Uid代表工号
每一个工号对应的都是一个teacher对象

public class Teacher {
    private String Uid;//工号
    private String name = "";
    private String sex = "";
    private int age = 0;

    public Teacher(String Uid) {
        this.Uid = Uid;
    }

    public String getUid() {
        return Uid;
    }

    public void setUid(String uid) {
        Uid = uid;
    }

    public String getName() {
        return name;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

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


    public void resetMsg() {
        name = "";
        sex = "";
        age = 0;
    }
}

然后我们创建一个TeacherFactory
把工号和teacher对象用Map绑定起来
这样当我们要创建teacher时
就不用直接new teacher()
而是用TeacherFactory的创建方法
我们传入唯一的工号进去
如果我们已经创建了这个工号对应的teacher
就不用再次创建,返回这个teacher即可

public class TeacherFactory {

    private Map<String, Teacher> pool;

    public TeacherFactory() {
        pool = new HashMap<>();
    }

    public Teacher getTeacher(String Uid) {
        Teacher teacher = pool.get(Uid);
        if (teacher == null) {
            teacher = new Teacher(Uid);
            pool.put(Uid,teacher);
        }
        return teacher;
    }
}

可以看到代码很简单,用键值对保存就好
测试一下

public static void main(String[] args) {
    TeacherFactory factory=new TeacherFactory();
    Teacher teacher1 = factory.getTeacher("0001");
    Teacher teacher2 = factory.getTeacher("0002");
    Teacher teacher3 = factory.getTeacher("0001");
    Teacher teacher4 = factory.getTeacher("0004");
    if (teacher1==teacher3){
        System.out.println("是同一个对象");
    }else {
        System.out.println("不是同一个对象");
    }
}

打印结果:
在这里插入图片描述
这是实现享元模式的一种方式
其实在安卓系统里
不少地方都用到了这种模式
来节省内存开销
比如Message这个类
不过他的原理是创建了一个缓存池子
这个池子是一个单链表的结构
最大上线是50

我们使用时,如果池子里有Message。就从池子里拿
没有就重新创建

使用完回收Message,判断池子还没超过50个,就放入池子
最后再加个锁保证线程安全
这里我模仿着原理自己也写了一个,代码如下:

public class MyMessage {
    public String msg;//消息内容
    public int usedFlags=1;//是否被使用中
    private MyMessage next;//链表的下一个
    public static final Object sPoolSync = new Object();//锁
    private static MyMessage sPool;
    private static int sPoolSize = 0;//msg池里的数量
    private static final int MAX_POOL_SIZE = 50;//msg池数量的上限

    public static MyMessage obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                MyMessage m = sPool;
                sPool = m.next;
                m.next = null;
                m.usedFlags = 1; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new MyMessage();
    }

    void recycle() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        usedFlags = 0;
        msg = null;
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

}

测试一下:

public static void main(String[] args) {
    MyMessage msg1 = MyMessage.obtain();
    msg1.recycle();
    msg1.msg="msg1的信息";
    MyMessage msg2 = MyMessage.obtain();
    MyMessage msg3 = MyMessage.obtain();
    System.out.println("msg1_hashCode:"+msg1.hashCode());
    System.out.println("msg2_hashCode:"+msg2.hashCode());
    System.out.println("msg3_hashCode:"+msg3.hashCode());
    System.out.println("msg1:"+msg1.msg);
    System.out.println("msg2:"+msg2.msg);
}

在这里插入图片描述

可以看到,msg1虽然被回收,但还是可以使用,
msg2用的就是池子里的msg1,他们的hashCode也是一样的
当然,我们不建议继续使用msg1,因为他已经调用recycle方法了
这时候就让系统把他回收就好了

行为型模式

13.策略模式

一个类的行为或其算法可以在运行时根据不同的策略进行更改。
这种类型的设计模式属于行为型模式。
举个例子,我们去网上买东西时
手上有不同的优惠券
满减的,首单优惠的,打九折的
我们就可以根据不同的优惠券,设计出不同的优惠方案
最终选择我们觉的最划算的方案
这其实就是一种策略
对应到代码层面就是策略模式
我们用代码来完成这个策略模式
首先我们需要一个打折策略接口:

/**
 * 优惠策略接口
 */
public interface DisCount {
    /**
     * 优惠的计算方法
     * @param money
     * @return
     */
    public float calculate(float money);
}

然后写出三种打折策略接口的实现类

/**
 * 满减打折策略
 */
public class FullDisCount implements DisCount {

    @Override
    public float calculate(float money) {
        if (money >= 200) {
            System.out.println("满200减20");
            return money - 20;
        }
        return money;
    }
}
/**
 * 首单优惠打折策略
 */
public class FirstDisCount implements DisCount {
    @Override
    public float calculate(float money) {
        System.out.println("首单优惠25");
        if (money > 25)
            return money - 25;
        else
            return 0;
    }
}
/**
 * 打九折的打折策略
 */
public class NinetyPercentDisCount implements DisCount {
    @Override
    public float calculate(float money) {
	    System.out.println("打九折");
        return money * 0.9f;
    }
}

然后是我们的结账类:

/**
 * 结账类
 */
public class CheckMoney {

    //优惠策略
    private DisCount disCount;

    public DisCount getDisCount() {
        return disCount;
    }

    public void setDisCount(DisCount disCount) {
        this.disCount = disCount;
    }

    /**
     * 结算方法,根据传入的优惠策略来结账
     * @param money
     * @return
     */
    public float doCalculation(float money) {
        return disCount.calculate(money);
    }
}

最终,我们去计算出三种优惠策略的优惠结果:

public class Test {

    private static Map<String ,DisCount> disCounts=new HashMap<>();
    static {
        disCounts.put("Full",new FullDisCount());
        disCounts.put("First",new FirstDisCount());
        disCounts.put("NinetyPercent",new NinetyPercentDisCount());
    }
    public static void main(String[] args) {
        CheckMoney cm=new CheckMoney();
        cm.setDisCount(disCounts.get("Full"));
        float calculation = cm.doCalculation(200.2f);
        System.out.println("满减方案——实付金额:"+calculation);

        cm.setDisCount(disCounts.get("First"));
        calculation = cm.doCalculation(200.2f);
        System.out.println("首单优惠方案——实付金额:"+calculation);

        cm.setDisCount(disCounts.get("NinetyPercent"));
        calculation = cm.doCalculation(200.2f);
        System.out.println("打九折方案——实付金额:"+calculation);
    }
}

打印结果如下:
在这里插入图片描述

14.模板方法模式

如果一个事务,它的处理主流程是一样的
分为子流程1,子流程2,子流程3…
但是个别的子流程又有不同的情况
拿网络购物来说
选择商品,选择支付方式,选择快递方式,下单运送
这几个主流程是一样的
但是之间的支付方法和快递方式可能有所不同
这时候我们就可以用到模板方法模式
自己来定义自己的具体流程
下面就根据这个例子,写一个模板方法模式的代码
首先我们定义一个主流程的类

public abstract class OnlineShop {

    private String GoodsName;//商品名称

    private float money;//商品价格

    public OnlineShop(String goodsName, float money) {
        GoodsName = goodsName;
        this.money = money;
    }

    /**
     * 选择商品,确认价格
     */
    private void seleGoodsMsg() {
        System.out.println("你选择的商品为" + GoodsName + ", 价格为" + money + "元");
    }

    /**
     * 支付方式
     */
    abstract void payMode();

    /**
     * 快递方式
     */
    abstract void expressMode();

    /**
     * 下单完成,开始运输
     */
    private void transport() {
        System.out.println("你已下单商品:" + GoodsName + ",商品正在运输中...");
    }

    /**
     * 购物流程
     */
    public void shopping() {
        seleGoodsMsg();
        payMode();
        expressMode();
        transport();
    }
}

可以看到,shopping方法里面主流程是不变的
提供了两个抽象方法
分别给子类用于支付方式和快递方式的具体实现
然后我们写一个子类继承这个模板类

public class ShopTest extends OnlineShop {
    public ShopTest(String goodsName, float money) {
        super(goodsName, money);
    }

    @Override
    void payMode() {
        System.out.println("你选择的支付方式为:微信支付");
    }

    @Override
    void expressMode() {
        System.out.println("你选择的快递方式为:顺丰快递");
    }
}

可以看到,子类里面
对于支付方式选择了微信支付
快递方式选择了顺丰快递
这时候我们运行测试用例

    public static void main(String[] args) {
        OnlineShop shopTest1 = new ShopTest("男士衬衫", 125.5f);
        OnlineShop shopTest2 = new ShopTest("儿童玩具", 35.1f);
        shopTest1.shopping();
        System.out.println("---------------------------------------");
        shopTest2.shopping();
    }

执行结果如下:
在这里插入图片描述
这样我们就对一个购物流程的模板类实现了具体的实现
如果有另外的实现,我们也可以继续写实现的子类
这就是模板方法设计模式

15.观察者模式

被观察者添加观察者
被观察者有状态变化时,调用观察者的类似onchange抽象方法
这样观察者就可以实现onchange抽象方法,对被观察者实现监听
java提供了两个类,方便我们快速地实现观察者模式,而且是线程安全的
之所以线程安全,因为里面存储观察者的是一个Vector的列表
这两个类分别是
java.util.Observer 观察者 interface
java.util.Observable 被观察者 class

实现观察者接口,只需要实现其中的update方法即可
继承被观察者,就可以调用相关的方法进行操作
比如:
addObserver 添加观察者
deleteObserver 移除观察者
deleteObservers 移除所有观察者
countObservers 得到加入的观察者的数量
setChanged 设置changed属性为true,这个属性是用来判断是否可以通知观察者的标记
clearChanged 设置changed属性为false
hasChanged 得到changed属性的值
notifyObservers 通知观察者,方法里面可以传入具体的通知内容,也可以不传

16.迭代器模式

案例演示

当我们用到一个集合时,一般会对两种操作,一种是存储集合里面的数据
另外一种则是操作集合里面的数据(遍历,增,删,改,查等)
因为集合有很多种数据结构,比如链表,堆,栈,Map等
那么我们遍历的时候,也会有不同的遍历方式
比如我们一个类里面,有ArrayList集合,也有HashMap集合
那么我们的遍历都要写在类里面
这样存储数据和遍历数据的操作都耦合在了这个类
那么我们可不可以把遍历数据的操作,乃至操作数据的操作
用一个迭代器来完成了
相当于把这部分功能独立出来。
这其实就是迭代器模式。下面用代码具体演示一下
首先我们定义一个迭代器基类,其实就是要对数据做那些操作
这里只演示下遍历操作。删除和添加等操作,可以根据自己业务来

public interface Iterator<T> {
    public boolean hasNext();

    public T next();
}

然后我们定义一个获得迭代器的基类
这个类提供不同的迭代器的获取方式
这里有两种:顺序遍历迭代器和逆序遍历迭代器

public interface Container {
    public Iterator getListIterator();
    public Iterator getReverseListIterator();
}

最后看我们的实体类:

public class MyList<T> implements Container {
    private List<T> mlist = new ArrayList<>();

    public MyList(List<T> mlist) {
        this.mlist = mlist;
    }

    public Iterator<T> getListIterator() {
        return new ListItr();
    }

    public Iterator<T> getReverseListIterator() {
        return new ReverseListItr();
    }

    private class ListItr implements Iterator<T> {
        int index = 0;

        @Override
        public boolean hasNext() {
            return index < mlist.size();
        }

        @Override
        public T next() {
            return mlist.get(index++);
        }
    }

    private class ReverseListItr implements Iterator<T> {
        int index = mlist.size() - 1;

        @Override
        public boolean hasNext() {
            return index >= 0;
        }

        @Override
        public T next() {
            return mlist.get(index--);
        }

        public void resetIndex() {
            index = mlist.size() - 1;
        }
    }
}

可以看到:
1:实体类内部有一个ArrayList集合
2:通过实现获得迭代器的基类Container,返回了两个自己实现的迭代器。
迭代器通过实现迭代器基类Iterator,拥有了自己的具体遍历功能
3:迭代器实现类定义在这个实体类的内部,这是因为内部类可以持有外部类的引用和变量
然后运行我们的测试代码:

    public static void main(String[] args) {
        ArrayList<String> strLists = new ArrayList<>();
        strLists.add("11111111111");
        strLists.add("22222222222");
        strLists.add("33333333333");
        MyList<String> myList=new MyList<>(strLists);
        Iterator<String> listIterator = myList.getListIterator();
        System.out.println("顺序遍历--------------------");
        while (listIterator.hasNext()){
            System.out.println(listIterator.next());
        }
        System.out.println("逆序遍历--------------------");
        Iterator<String> reverseListIterator = myList.getReverseListIterator();
        while (reverseListIterator.hasNext()){
            System.out.println(reverseListIterator.next());
        }
    }

遍历结果:
在这里插入图片描述
可以看到正常打印了出来
最后补充一下:
迭代器模式可以把对数据的操作独立出来
不仅仅是遍历,删除和添加元素也可以交由迭代器完成
优点就是独立出了功能,用户不需要关注内部是怎么完成遍历等操作的
而不需要关注内部的数据结构。
比如上面的例子,我们如果内部又要新增一个HashMap变量
那么就可以新增HashMap的迭代器来完成对HashMap的遍历
缺点就是新增了数据操作,或者有了新的迭代器,都要去修改这个类的代码

源码示例

其实java代码里面,很多数据结构都用到了迭代器模式
比如HashMap,ArrayList等
现在我们就可以看下ArrayList的源码,是如何实现迭代器模式的
ArrayList提供了listIterator()和iterator()方法来给我们返回需要的迭代器
那么他们有什么区别呢?
我们先看iterator()方法
返回的是一个Itr类
这个类实现的就是Iterator的迭代接口
可以说Itr就是一个通用的迭代类
而listIterator()方法返回的是一个ListItr迭代类
它继承了Itr类
拥有了基本的迭代功能
同时实现了ListIterator接口
而ListIterator其实是对Iterator迭代基类做的功能扩展
这样一来我们就明白了了:
Iterator是最基本的迭代基类,它只有下面四个方法:
在这里插入图片描述
ListIterator是扩展了功能的迭代器基类,它的方法和功能更多:
在这里插入图片描述
所以在这两个迭代器基类的基础上,有了两个迭代器
ListItr是针对ArrayList做了功能扩展的迭代器,适合复杂的迭代需求
Itr就是一个基础的迭代器,只具有获取集合元素的功能
画图如下:
在这里插入图片描述

17.责任链模式

假如我们的一个请求,需要经过好几个流程处理
而这个流程之间又互相有关联
比如C流程需要B流程处理完才能处理
B流程要A流程处理完才能处理
这种情况我们就可以用责任链模式
在Android开发中,最常见的就是okhttp的拦截器
五大拦截器就是用责任链模式来完成的
像一个链表一样,一个节点处理完
就传递给下一个节点。层层向下
最后一个节点处理完,又把处理结果层层向上抛给上一个节点
最后返回给我们请求的结果
Android中的View事件分发也是这样的责任链模式
下面,我们就用代码来实现一个仿照的okhttp拦截器
注意,重在责任链和仿照,拦截器并不是真的有拦截和请求网络的功能
首先,我们需要一个拦截器的基类:

/**
 * 拦截器接口
 */
public interface Interceptor {
    /**
     * 拦截方法
     * @param request
     * @return
     */
    public String intercept(String request);
}

然后是Okhttp拦截器的基类。实现上面的基类

/**
 * Okhttp拦截器抽象类
 */
public abstract class OkhttpInterceptor implements Interceptor {
    protected OkhttpInterceptor nextInterceptor;

    public OkhttpInterceptor(OkhttpInterceptor nextInterceptor) {
        this.nextInterceptor = nextInterceptor;
    }

    @Override
    public String intercept(String request) {
        if (nextInterceptor != null) {
           return nextInterceptor.intercept(request);
        }
        return request;
    }
}

注意这边intercept方法的处理
会调用下一个拦截器的intercept方法,相当于链表的层层遍历
然后是五个拦截器的实现类,功能其实是一样的,如下:

/**
 * 重试重定向拦截器
 */
public class RetryAndFollowUpInterceptor extends OkhttpInterceptor {


    public RetryAndFollowUpInterceptor(OkhttpInterceptor nextInterceptor) {
        super(nextInterceptor);
    }

    @Override
    public String intercept(String request) {
        request = "重试重定向拦截器请求体\n"+request;
        return super.intercept(request) + "\n重试重定向拦截器返回体";
    }
}
/**
 * 桥接拦截器
 */
public  class BridgeInterceptor extends OkhttpInterceptor {


    public BridgeInterceptor(OkhttpInterceptor nextInterceptor) {
        super(nextInterceptor);
    }

    @Override
    public String intercept(String request) {
        request = "桥接拦截器请求体\n"+request;
        return super.intercept(request) + "\n桥接拦截器返回体";
    }
}
/**
 * 缓存拦截器
 */
public  class CacheInterceptor extends OkhttpInterceptor {


    public CacheInterceptor(OkhttpInterceptor nextInterceptor) {
        super(nextInterceptor);
    }

    @Override
    public String intercept(String request) {
        request = "缓存拦截器请求体\n"+request;
        return super.intercept(request) + "\n缓存拦截器返回体";
    }
}
/**
 * 连接拦截器
 */
public  class ConnectInterceptor extends OkhttpInterceptor {


    public ConnectInterceptor(OkhttpInterceptor nextInterceptor) {
        super(nextInterceptor);
    }

    @Override
    public String intercept(String request) {
        request = "连接拦截器请求体\n"+request;
        return super.intercept(request) + "\n连接拦截器返回体";
    }
}
/**
 * 请求服务拦截器
 */
public  class CallServerInterceptor extends OkhttpInterceptor {


    public CallServerInterceptor(OkhttpInterceptor nextInterceptor) {
        super(nextInterceptor);
    }

    @Override
    public String intercept(String request) {
        request = "请求服务拦截器请求体\n"+request;
        return super.intercept(request) + "\n请求服务拦截器返回体";
    }
}

最后是我们的okhttp类,他发起请求时使用了这些拦截器,代码如下:

public class Okhttp {

    private static final RetryAndFollowUpInterceptor RETRY_AND_FOLLOW_UP_INTERCEPTOR;
    private static final BridgeInterceptor BRIDGE_INTERCEPTOR;
    private static final CacheInterceptor CACHE_INTERCEPTOR;
    private static final ConnectInterceptor CONNECT_INTERCEPTOR;
    private static final CallServerInterceptor CALL_SERVER_INTERCEPTOR;

    static {
        CALL_SERVER_INTERCEPTOR = new CallServerInterceptor(null);
        CONNECT_INTERCEPTOR = new ConnectInterceptor(CALL_SERVER_INTERCEPTOR);
        CACHE_INTERCEPTOR = new CacheInterceptor(CONNECT_INTERCEPTOR);
        BRIDGE_INTERCEPTOR = new BridgeInterceptor(CACHE_INTERCEPTOR);
        RETRY_AND_FOLLOW_UP_INTERCEPTOR = new RetryAndFollowUpInterceptor(BRIDGE_INTERCEPTOR);
    }

    public Okhttp() {

    }

    /**
     * 发起请求
     *
     * @return
     */
    public String doRequest(String request) {
        return RETRY_AND_FOLLOW_UP_INTERCEPTOR.intercept(request);
    }
}

这样,我们使用起来就很简单了:

    public static void main(String[] args) {
        Okhttp okhttp=new Okhttp();
        String Response = okhttp.doRequest("请求体");
        System.out.println(Response);
    }

通过打印可以看到,请求体都被拦截器各自加上了自己的头和尾
打印结果如下:
在这里插入图片描述

18.命令模式

所谓命令,其实就是一个操作
比如我们的鼠标,就有滑动,单击,右击等操作
再比如我们请求后台数据,也有什么列表数据,详情数据
还有我们的遥控器,更是有很多按键,每个按键都会发送一个操作命令。
想象一下,如果我们把所有的命令都封装成一个命令类
如果以后命令新增或者修改了
我们都要去改这个类,这样其实就不符合开闭原则
故而我们引入命令模式
一般来说,命令模式,我们需要关注四个角色:
1:接收者角色(Receiver):负责具体执行一个命令请求,多个命令就有多个接收者来处理
2:命令角色(ICommand):所有命令行为的基类。
3:具体的命令角色(ConcreteCommand):内部维护一个Receiver接收者,不同的命令有不同的命令角色来发送
4:请求者角色(Invoker):接收命令端发送的命令,并执行命令
我们还是以鼠标来作为举例:
首先我们有单击,右击和滑动三个命令的处理
我们都把它们作为一个命令接收类:

/**
 * 单击的处理
 */
public class SingleClickReceiver {
    public void SingleClick() {
        System.out.println("接收指令:单击鼠标,现在执行...执行完成");
    }
}
/**
 * 右击的处理
 */
public class RightClickReceiver {
    public void RightClick() {
        System.out.println("接收指令:右击鼠标,现在执行...执行完成");
    }
}
/**
 * 滑动的处理
 */
public class SlideReceiver {
    public void slide() {
        System.out.println("接收指令:滑动鼠标,现在执行...执行完成");
    }
}

然后我们创建一个命令行为的基类:

/**
 * 命令基类
 */
public interface MouseCommand {
    void execute();
}

基于这个基类,我们有对应的三个命令类
它们内部的命令执行方法,都有对应的命令接收类来处理:

/**
 * 单击命令发送器
 */
public class SingleClickCommand implements MouseCommand {

    private SingleClickReceiver singleClickReceiver = new SingleClickReceiver();

    @Override
    public void execute() {
        singleClickReceiver.SingleClick();
    }
}
/**
 * 右击命令发送器
 */
public class RightClickCommand implements MouseCommand {

    private RightClickReceiver rightClickReceiver = new RightClickReceiver();

    @Override
    public void execute() {
        rightClickReceiver.RightClick();
    }
}
/**
 * 滑动命令发送器
 */
public class SlideClickCommand implements MouseCommand {

    private SlideReceiver slideReceiver = new SlideReceiver();

    @Override
    public void execute() {
        slideReceiver.slide();
    }
}

定义好命令类后,我们来创建命令请求类
这里创建了一个屏幕类,用来接收鼠标的命令操作:

/**
 * 大屏幕:负责接收命令然后处理
 */
public class Screen {
    public List<MouseCommand> commands = new ArrayList<>();

    public void addCommand(MouseCommand command) {
        commands.add(command);
    }

    public void addCommands(List<MouseCommand> commands) {
        this.commands.addAll(commands);
    }

    public void display() {
        for (MouseCommand command : commands) {
            command.execute();
        }
    }
}

最后是测试代码:

    public static void main(String[] args) {
        Screen screen = new Screen();
        screen.addCommand(new SingleClickCommand());
        screen.addCommand(new DoubleClickCommand());
        screen.addCommand(new SlideClickCommand());
        screen.display();
    }

打印如下:
在这里插入图片描述
可以看到,这样一来,我们如果以后新增一个操作:鼠标双击。
我们就可以做到不改动原来的这些类,只需要创建新的对应的双击发送类和处理类就好了。如下:

/**
 * 双击的处理
 */
public class DoubleClickReceiver {
    public void DoubleClick() {
        System.out.println("接收指令:双击鼠标,现在执行...执行完成");
    }
}
/**
 * 双击命令发送器
 */
public class DoubleClickCommand implements MouseCommand {

    private DoubleClickReceiver doubleClickReceiver = new DoubleClickReceiver();

    @Override
    public void execute() {
        doubleClickReceiver.DoubleClick();
    }
}

命令模式和观察者模式的区别:
命令模式不关注命令的发送者是谁,谁都可以调用发送命令的操作
而观察者模式,观察者和被观察者是绑定的关系。观察者只能接收到被观察者的变化

19.备忘录模式

备忘录模式允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态
这也是它的优点(不暴露对象实现细节)和功能(保存和恢复对象之前的状态)
最常见的情况有:下棋的悔棋操作、游戏的存档操作、文档的保存操作等
当我们需要记录一个对象的内部状态,方便以后恢复时,就可以用这种设计模式
备忘录模式一般有三个角色:
Originator :要保存自己状态的对象
Memento:主要用来保存被恢复的对象的内部状态,也就是备忘录类
CareTaker:具体的操作类,负责从 Memento 中恢复对象的状态
这里以下象棋为例
假设现在有一个棋子对象,它挪动的时候肯定是要保存位置的(方便悔棋,哈哈)

public class PieceOriginator {
    // 内部状态:棋子名字
    private String pieceName;
    // 内部状态:棋子x坐标
    private int x;
    // 内部状态:棋子y坐标
    private int y;

    public PieceOriginator(String pieceName, int x, int y) {
        this.pieceName = pieceName;
        this.x = x;
        this.y = y;
    }

    public String getPieceName() {
        return pieceName;
    }

    public void setPieceName(String pieceName) {
        this.pieceName = pieceName;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    @Override
    public String toString() {
        String state = "棋子: " + pieceName + " 当前的位置为:X轴:" + x + ",Y轴为:" + y;
        System.out.println(state);
        return state;
    }

    //创建一个备忘录
    public PieceMemento createMemento() {
        return new PieceMemento(pieceName, x, y);
    }

    // 从备忘录恢复
    public void restoreMemento(PieceMemento memento) {
        if (memento != null) {
            setPieceName(memento.getPieceName());
            setX(memento.getX());
            setY(memento.getY());
        }
    }
}

可以看到,棋子类有被挪动的棋子的名字和X,Y坐标
同时还有创建备忘录和从备忘录恢复的方法
其实就是把自己的状态信息存到备忘录,需要悔棋的时候再从备忘录拿
然后把拿到的信息重新给自己内部变量赋值
然后来看这个备忘录类:

public class PieceMemento {
    // 保存状态:棋子名字
    private String pieceName;
    // 保存状态:棋子x坐标
    private int x;
    // 保存状态:棋子y坐标
    private int y;

    public PieceMemento(String pieceName, int x, int y) {
        this.pieceName = pieceName;
        this.x = x;
        this.y = y;
    }

    public String getPieceName() {
        return pieceName;
    }

    public void setPieceName(String pieceName) {
        this.pieceName = pieceName;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }
}

可以看到,这个类其实就是拥有了和棋子类一样的属性,其他的没了
因为它本来就是用来存棋子类的状态的,所以你甚至可以让他有一个棋子类的对象
用对象变量来保存状态。
然后看我们的操作类:

public class PieceCaretaker {
    private List<PieceMemento> pieceMementos = new ArrayList<>();

    public PieceMemento getPieceMemento() {
        System.out.println("不行,我要悔棋!");
        if (pieceMementos.size() > 0)
            return pieceMementos.remove(pieceMementos.size() - 1);
        else return null;
    }

    public void setPieceMemento(PieceMemento pieceMemento) {
        System.out.println("落子无悔!");
        this.pieceMementos.add(pieceMemento);
    }
}

操作类其实就是做了保存和悔棋操作
保存的时候就是把备忘录类存入自己集合里
恢复就是从集合里面取出
最后看测试代码:

    public static void main(String[] args) {
        //先初始化创建一个棋子
        PieceOriginator pieceOriginator = new PieceOriginator("車",0,0);
        //打印下状态信息
        pieceOriginator.toString();
        //来一个备份管理
        PieceCaretaker pieceCaretaker = new PieceCaretaker();
        //先备份一波
        pieceCaretaker.setPieceMemento(pieceOriginator.createMemento());
        //移动棋子的x轴
        pieceOriginator.setX(1);
        pieceOriginator.toString();
        //再次备份
        pieceCaretaker.setPieceMemento(pieceOriginator.createMemento());
        //挪动另外一个棋子
        pieceOriginator.setPieceName("炮");
        pieceOriginator.setY(5);
        pieceOriginator.toString();
        //悔棋
        pieceOriginator.restoreMemento(pieceCaretaker.getPieceMemento());
        pieceOriginator.toString();
        //再次悔棋
        pieceOriginator.restoreMemento(pieceCaretaker.getPieceMemento());
        pieceOriginator.toString();
    }

打印结果:
在这里插入图片描述
最后需要注意:备忘录模式因为要保存状态,其实缺点也很明显了
就是比较浪费内存,所以实际业务,可以和原型模式结合使用

20.状态模式

现实生活中,我们会遇到这种情况
比如一个学生的成绩有不同的等级划分(状态),90以上为A,70-90为B,60-70为C,60以下为D。
我们要根据不同的分数来判断等级,也就是状态
还有更复杂一点的
比如一台机器,有好几个状态:打开,运行,关闭
我们按下不同的按钮,执行不同的操作,都会改变他的状态。比如按下开启就进入开启状态,按下关闭就进入关闭状态。
对于这种较为复杂,但是又有明显的规律的实体,我们应该如何描述呢?
这里就通过一个具体的例子来描述我们要降到的状态模式

原始写法

比如现在有一个水果加工机器,它有五个按钮,分别是:
打开机器 水果清洗 水果消毒 水果打包 关闭机器
不同的按钮按下后,又会进入不同的状态,状态有:
初始状态–》清洗状态–》消毒状态–》打包状态–》停止状态
比如在清洗状态按下关闭机器按钮,就会让机器进入停止状态
而如果在清洗状态重复按下清洗按钮,系统就会提示,但不会改变现有的清洗状态
而如果在关闭状态点击清洗按钮,或者在清洗状态按下打开机器按钮
都会有相应的提示。
这种情况,我们的水果加工机器,该如何设计呢?
在不使用状态模式的时候,我们一般是不是会这么写:

/**
 * 水果机器类
 * 五个操作:打开机器 水果清洗 水果消毒 水果打包 关闭机器
 * 五个状态:初始状态--》清洗状态--》消毒状态--》打包状态--》停止状态
 */
public class FruitMachineContext {

    private String fruitMachineState = "closeState";//机器状态,默认为关闭

    //打开机器
    public void openMachine() {
        switch (fruitMachineState) {
            case "initState":
                System.out.println("机器已打开,请勿重复操作");
                break;
            case "disinfectState":
                System.out.println("正在对水果进行消毒,请勿重复打开机器");
                break;
            case "packState":
                System.out.println("正在对水果进行打包,请勿重复打开机器");
                break;
            case "clearState":
                System.out.println("正在对水果进行清洗,请勿重复打开机器");
                break;
            case "closeState":
                System.out.println("正在打开机器,机器已打开");
                fruitMachineState = "initState";
                break;
        }
        System.out.println("当前状态:" + fruitMachineState);
    }

    //清理水果
    public void clearFruit() {
        switch (fruitMachineState) {
            case "initState":
            case "disinfectState":
            case "packState":
                System.out.println("正在对水果进行清洗");
                fruitMachineState = "clearState";
                break;
            case "clearState":
                System.out.println("正在对水果进行清洗,请勿重复操作");
                break;
            case "closeState":
                System.out.println("机器已关闭,无法清洗");
                break;
        }
        System.out.println("当前状态:" + fruitMachineState);
    }

    //水果消毒
    public void disinfectFruit() {
        switch (fruitMachineState) {
            case "initState":
            case "clearState":
            case "packState":
                System.out.println("正在对水果进行消毒");
                fruitMachineState = "disinfectState";
                break;
            case "disinfectState":
                System.out.println("正在对水果进行消毒,请勿重复操作");
                break;
            case "closeState":
                System.out.println("机器已关闭,无法消毒");
                break;
        }
        System.out.println("当前状态:" + fruitMachineState);
    }

    //水果打包
    public void packFruit() {
        switch (fruitMachineState) {
            case "initState":
            case "disinfectState":
            case "clearState":
                System.out.println("正在对水果进行打包");
                fruitMachineState = "packState";
                break;
            case "packState":
                System.out.println("正在对水果进行打包,请勿重复操作");
                break;
            case "closeState":
                System.out.println("机器已关闭,无法清洗");
                break;
        }
        System.out.println("当前状态:" + fruitMachineState);
    }

    //关闭机器
    public void closeMachine() {
        switch (fruitMachineState) {
            case "initState":
            case "clearState":
            case "packState":
            case "disinfectState":
                System.out.println("正在关闭机器,机器已关闭");
                fruitMachineState = "closeState";
                break;
            case "closeState":
                System.out.println("机器已关闭,请勿重复操作");
                break;
        }
        System.out.println("当前状态:" + fruitMachineState);
    }
}

然后运行我们的测试代码:

    public static void main(String[] args) {
        //声明一个水果加工类
        FruitMachineContext fruitMachineContext=new FruitMachineContext();
        //先打开机器
        fruitMachineContext.openMachine();
        //清洗水果
        fruitMachineContext.clearFruit();
        //水果消毒
        fruitMachineContext.disinfectFruit();
        //异常测试:再次消毒
        fruitMachineContext.disinfectFruit();
        //水果打包
        fruitMachineContext.packFruit();
        //关闭机器
        fruitMachineContext.closeMachine();
        //异常测试:水果打包
        fruitMachineContext.packFruit();
        //打开机器
        fruitMachineContext.openMachine();
        //异常测试:再次打开机器
        fruitMachineContext.openMachine();
        //关闭机器
        fruitMachineContext.closeMachine();
    }

我们来看下打印结果:
在这里插入图片描述
功能没发现有异常,说明这个效果我们是实现了。
但是我们也发现了一些问题,比如:
1:每按下一个按钮,都要判断一次状态
2:一些状态的操作是重复的,比如初始状态和打包状态,执行水果清洗操作后都会进入清洗状态
3:各个状态很分散,不好维护,后期改动比较麻烦
下面我们看状态模式是如何处理的

第一种方式

针对刚刚第一种模式的弊端
我们引入了状态模式
这个模式一般有三种角色:
1:状态基类(AbsState):状态的基类,把具体的操作封装为抽象方法
2:状态实现角色(State):具体的状态,内部维护自身状态下的操作
3:具体操作类(Context):内部维护一个状态基类,执行操作时,让状态属性去执行即可。状态属性内部会自动完成操作,并且自动完成状态的切换。
下面来看具体实现:
首先创建水果状态基类:

/**
 * 水果机器状态基类
 * 五个操作:打开机器 水果清洗 水果消毒 水果打包 关闭机器
 * 五个状态:初始状态--》清洗状态--》消毒状态--》打包状态--》停止状态
 */
public abstract class FruitMachineState {
    protected FruitMachineContext fruitMachineContext;

    public void setFruitMachineContext(FruitMachineContext fruitMachineContext) {
        this.fruitMachineContext = fruitMachineContext;
    }

    //打开机器
    public abstract void openMachine();

    //水果清洗
    public abstract void clearFruit();

    //水果消毒
    public abstract void disinfectFruit();

    //水果打包
    public abstract void packFruit();

    //关闭机器
    public abstract void closeMachine();

    //输出下状态
    protected void printState() {
        System.out.println("当前状态:" + fruitMachineContext.getFruitMachineState().getClass().getSimpleName());
    }
}

可以看到,这基类持有一个操作类对象,同事封装好了各个操作的抽象方法
然后看下具体的实现类。因为有五个状态,所以自然有五个状态实现类:
初始状态:

/**
 * 机器打开,初始状态
 */
public class InitState extends FruitMachineState{


    @Override
    public void openMachine() {
        System.out.println("机器已打开,请勿重复操作");
        printState();
    }

    @Override
    public void clearFruit() {
        System.out.println("正在对水果进行清洗");
        fruitMachineContext.setFruitMachineState(FruitMachineContext.CLEAR_STATE);
        printState();
    }

    @Override
    public void disinfectFruit() {
        System.out.println("正在对水果进行消毒");
        fruitMachineContext.setFruitMachineState(FruitMachineContext.DISINFECT_STATE);
        printState();
    }

    @Override
    public void packFruit() {
        System.out.println("正在对水果进行打包");
        fruitMachineContext.setFruitMachineState(FruitMachineContext.PACK_STATE);
        printState();
    }

    @Override
    public void closeMachine() {
        System.out.println("正在关闭机器,机器已关闭");
        fruitMachineContext.setFruitMachineState(FruitMachineContext.CLOSE_STATE);
        printState();
    }
}

清洗水果状态:

/**
 * 清洗水果状态
 */
public class clearState extends FruitMachineState{


    @Override
    public void openMachine() {
        System.out.println("正在对水果进行清洗,请勿重复打开机器");
        printState();
    }

    @Override
    public void clearFruit() {
        System.out.println("正在对水果进行清洗,请勿重复操作");
        printState();
    }

    @Override
    public void disinfectFruit() {
        System.out.println("正在对水果进行消毒");
        fruitMachineContext.setFruitMachineState(FruitMachineContext.DISINFECT_STATE);
        printState();
    }

    @Override
    public void packFruit() {
        System.out.println("正在对水果进行打包");
        fruitMachineContext.setFruitMachineState(FruitMachineContext.PACK_STATE);
        printState();
    }

    @Override
    public void closeMachine() {
        System.out.println("正在关闭机器,机器已关闭");
        fruitMachineContext.setFruitMachineState(FruitMachineContext.CLOSE_STATE);
        printState();
    }
}

水果消毒状态

/**
 * 水果消毒状态
 */
public class disinfectState extends FruitMachineState{


    @Override
    public void openMachine() {
        System.out.println("正在对水果进行消毒,请勿重复打开机器");
        printState();
    }

    @Override
    public void clearFruit() {
        System.out.println("正在对水果进行清洗");
        fruitMachineContext.setFruitMachineState(FruitMachineContext.CLEAR_STATE);
        printState();
    }

    @Override
    public void disinfectFruit() {
        System.out.println("正在对水果进行消毒,请勿重复操作");
        printState();
    }

    @Override
    public void packFruit() {
        System.out.println("正在对水果进行打包");
        fruitMachineContext.setFruitMachineState(FruitMachineContext.PACK_STATE);
        printState();
    }

    @Override
    public void closeMachine() {
        System.out.println("正在关闭机器,机器已关闭");
        fruitMachineContext.setFruitMachineState(FruitMachineContext.CLOSE_STATE);
        printState();
    }
}

水果打包状态:

/**
 * 水果打包状态
 */
public class packState extends FruitMachineState {


    @Override
    public void openMachine() {
        System.out.println("正在对水果进行打包,请勿重复打开机器");
        printState();
    }

    @Override
    public void clearFruit() {
        System.out.println("正在对水果进行清洗");
        fruitMachineContext.setFruitMachineState(FruitMachineContext.CLEAR_STATE);
        printState();
    }

    @Override
    public void disinfectFruit() {
        System.out.println("正在对水果进行消毒");
        fruitMachineContext.setFruitMachineState(FruitMachineContext.DISINFECT_STATE);
        printState();
    }


    @Override
    public void packFruit() {
        System.out.println("正在对水果进行打包,请勿重复操作");
        printState();
    }

    @Override
    public void closeMachine() {
        System.out.println("正在关闭机器,机器已关闭");
        fruitMachineContext.setFruitMachineState(FruitMachineContext.CLOSE_STATE);
        printState();
    }
}

机器关闭状态:

/**
 * 机器关闭状态
 */
public class closeState extends FruitMachineState{


    @Override
    public void openMachine() {
        System.out.println("正在打开机器,机器已打开");
        fruitMachineContext.setFruitMachineState(FruitMachineContext.INIT_STATE);
        printState();
    }

    @Override
    public void clearFruit() {
        System.out.println("机器已关闭,无法清洗");
        printState();
    }

    @Override
    public void disinfectFruit() {
        System.out.println("机器已关闭,无法消毒");
        printState();
    }

    @Override
    public void packFruit() {
        System.out.println("机器已关闭,无法打包");
        printState();
    }

    @Override
    public void closeMachine() {
        System.out.println("机器已关闭,请勿重复操作");
        printState();
    }
}

可以看到,实现类自己内部,完成了具体的操作以及状态的切换
然后看我们的水果操作类,也就是水果机器类:

/**
 * 水果机器类
 * 五个操作:打开机器 水果清洗 水果消毒 水果打包 关闭机器
 * 五个状态:初始状态--》清洗状态--》消毒状态--》打包状态--》停止状态
 */
public class FruitMachineContext {

    private FruitMachineState fruitMachineState;

    public FruitMachineState getFruitMachineState() {
        return fruitMachineState;
    }

    //声明常量
    public static final FruitMachineState INIT_STATE = new InitState();
    public static final FruitMachineState CLEAR_STATE = new clearState();
    public static final FruitMachineState DISINFECT_STATE = new disinfectState();
    public static final FruitMachineState PACK_STATE = new packState();
    public static final FruitMachineState CLOSE_STATE = new closeState();

    public void setFruitMachineState(FruitMachineState fruitMachineState) {
        this.fruitMachineState = fruitMachineState;
        this.fruitMachineState.setFruitMachineContext(this);
    }

    //打开机器
    public void openMachine() {
        fruitMachineState.openMachine();
    }


    //水果清洗
    public void clearFruit() {
        fruitMachineState.clearFruit();
    }

    //水果消毒
    public void disinfectFruit() {
        fruitMachineState.disinfectFruit();
    }

    //水果打包
    public void packFruit() {
        fruitMachineState.packFruit();
    }

    //关闭机器
    public void closeMachine() {
        fruitMachineState.closeMachine();
    }
}

水果操作类维护了五个具体的状态,具体的操作交给自己的状态属性去完成
并且在设置状态的方法里面传递自身过去,让状态持有自己的引用
这样状态实现类内部会自动完成操作并且帮助操作类去切换状态
测试代码如下:

public static void main(String[] args) {
        //声明一个水果加工类
        FruitMachineContext fruitMachineContext=new FruitMachineContext();
        //设置机器初始状态为关闭
        fruitMachineContext.setFruitMachineState(FruitMachineContext.CLOSE_STATE);
        //先打开机器
        fruitMachineContext.openMachine();
        //清洗水果
        fruitMachineContext.clearFruit();
        //水果消毒
        fruitMachineContext.disinfectFruit();
        //异常测试:再次消毒
        fruitMachineContext.disinfectFruit();
        //水果打包
        fruitMachineContext.packFruit();
        //关闭机器
        fruitMachineContext.closeMachine();
        //异常测试:水果打包
        fruitMachineContext.packFruit();
        //打开机器
        fruitMachineContext.openMachine();
        //异常测试:再次打开机器
        fruitMachineContext.openMachine();
        //关闭机器
        fruitMachineContext.closeMachine();
    }

打印结果和上面是一样的,这里就不再贴出了

第二种方式

上面第一种就是典型的状态模式的实现方式了
但是我们可以看到,操作类内部声明了五个状态常量
如果状态过多,这个常量也就越多
但实际每次都只处于一种状态,其他四个暂时用不上
所以第二种方式就改一下,在需要时才创建状态。
首先是状态基类:

/**
 * 水果机器状态基类
 * 五个操作:打开机器 水果清洗 水果消毒 水果打包 关闭机器
 * 五个状态:初始状态--》清洗状态--》消毒状态--》打包状态--》停止状态
 */
public abstract class FruitMachineState {

    //打开机器
    public abstract void openMachine(FruitMachineContext fruitMachineContext);

    //水果清洗
    public abstract void clearFruit(FruitMachineContext fruitMachineContext);

    //水果消毒
    public abstract void disinfectFruit(FruitMachineContext fruitMachineContext);

    //水果打包
    public abstract void packFruit(FruitMachineContext fruitMachineContext);

    //关闭机器
    public abstract void closeMachine(FruitMachineContext fruitMachineContext);

    //输出下状态
    protected void printState(FruitMachineContext fruitMachineContext) {
        System.out.println("当前状态:" + fruitMachineContext.getFruitMachineState().getClass().getSimpleName());
    }
}

可以看到,基类不再持有状态类的引用,而是通过入参的方式传递进来
然后是五个实现类:
初始状态:

/**
 * 机器打开,初始状态
 */
public class InitState extends FruitMachineState {


    @Override
    public void openMachine(FruitMachineContext fruitMachineContext) {
        System.out.println("机器已打开,请勿重复操作");
        printState(fruitMachineContext);
    }

    @Override
    public void clearFruit(FruitMachineContext fruitMachineContext) {
        System.out.println("正在对水果进行清洗");
        fruitMachineContext.setFruitMachineState(new clearState());
        printState(fruitMachineContext);
    }

    @Override
    public void disinfectFruit(FruitMachineContext fruitMachineContext) {
        System.out.println("正在对水果进行消毒");
        fruitMachineContext.setFruitMachineState(new disinfectState());
        printState(fruitMachineContext);
    }

    @Override
    public void packFruit(FruitMachineContext fruitMachineContext) {
        System.out.println("正在对水果进行打包");
        fruitMachineContext.setFruitMachineState(new packState());
        printState(fruitMachineContext);
    }

    @Override
    public void closeMachine(FruitMachineContext fruitMachineContext) {
        System.out.println("正在关闭机器,机器已关闭");
        fruitMachineContext.setFruitMachineState(new closeState());
        printState(fruitMachineContext);
    }
}

清洗水果状态:

/**
 * 清洗水果状态
 */
public class clearState extends FruitMachineState {


    @Override
    public void openMachine(FruitMachineContext fruitMachineContext) {
        System.out.println("正在对水果进行清洗,请勿重复打开机器");
        printState(fruitMachineContext);
    }

    @Override
    public void clearFruit(FruitMachineContext fruitMachineContext) {
        System.out.println("正在对水果进行清洗,请勿重复操作");
        printState(fruitMachineContext);
    }

    @Override
    public void disinfectFruit(FruitMachineContext fruitMachineContext) {
        System.out.println("正在对水果进行消毒");
        fruitMachineContext.setFruitMachineState(new disinfectState());
        printState(fruitMachineContext);
    }

    @Override
    public void packFruit(FruitMachineContext fruitMachineContext) {
        System.out.println("正在对水果进行打包");
        fruitMachineContext.setFruitMachineState(new packState());
        printState(fruitMachineContext);
    }

    @Override
    public void closeMachine(FruitMachineContext fruitMachineContext) {
        System.out.println("正在关闭机器,机器已关闭");
        fruitMachineContext.setFruitMachineState(new closeState());
        printState(fruitMachineContext);
    }
}

水果消毒状态:

/**
 * 水果消毒状态
 */
public class disinfectState extends FruitMachineState {


    @Override
    public void openMachine(FruitMachineContext fruitMachineContext) {
        System.out.println("正在对水果进行消毒,请勿重复打开机器");
        printState(fruitMachineContext);
    }

    @Override
    public void clearFruit(FruitMachineContext fruitMachineContext) {
        System.out.println("正在对水果进行清洗");
        fruitMachineContext.setFruitMachineState(new clearState());
        printState(fruitMachineContext);
    }

    @Override
    public void disinfectFruit(FruitMachineContext fruitMachineContext) {
        System.out.println("正在对水果进行消毒,请勿重复操作");
        printState(fruitMachineContext);
    }

    @Override
    public void packFruit(FruitMachineContext fruitMachineContext) {
        System.out.println("正在对水果进行打包");
        fruitMachineContext.setFruitMachineState(new packState());
        printState(fruitMachineContext);
    }

    @Override
    public void closeMachine(FruitMachineContext fruitMachineContext) {
        System.out.println("正在关闭机器,机器已关闭");
        fruitMachineContext.setFruitMachineState(new closeState());
        printState(fruitMachineContext);
    }
}

水果打包状态:

/**
 * 水果打包状态
 */
public class packState extends FruitMachineState {


    @Override
    public void openMachine(FruitMachineContext fruitMachineContext) {
        System.out.println("正在对水果进行打包,请勿重复打开机器");
        printState(fruitMachineContext);
    }

    @Override
    public void clearFruit(FruitMachineContext fruitMachineContext) {
        System.out.println("正在对水果进行清洗");
        fruitMachineContext.setFruitMachineState(new clearState());
        printState(fruitMachineContext);
    }

    @Override
    public void disinfectFruit(FruitMachineContext fruitMachineContext) {
        System.out.println("正在对水果进行消毒");
        fruitMachineContext.setFruitMachineState(new disinfectState());
        printState(fruitMachineContext);
    }


    @Override
    public void packFruit(FruitMachineContext fruitMachineContext) {
        System.out.println("正在对水果进行打包,请勿重复操作");
        printState(fruitMachineContext);
    }

    @Override
    public void closeMachine(FruitMachineContext fruitMachineContext) {
        System.out.println("正在关闭机器,机器已关闭");
        fruitMachineContext.setFruitMachineState(new closeState());
        printState(fruitMachineContext);
    }
}

机器关闭状态:

/**
 * 机器关闭状态
 */
public class closeState extends FruitMachineState {

    @Override
    public void openMachine(FruitMachineContext fruitMachineContext) {
        System.out.println("正在打开机器,机器已打开");
        fruitMachineContext.setFruitMachineState(new InitState());
        printState(fruitMachineContext);
    }

    @Override
    public void clearFruit(FruitMachineContext fruitMachineContext) {
        System.out.println("机器已关闭,无法清洗");
        printState(fruitMachineContext);
    }

    @Override
    public void disinfectFruit(FruitMachineContext fruitMachineContext) {
        System.out.println("机器已关闭,无法消毒");
        printState(fruitMachineContext);
    }

    @Override
    public void packFruit(FruitMachineContext fruitMachineContext) {
        System.out.println("机器已关闭,无法打包");
        printState(fruitMachineContext);
    }

    @Override
    public void closeMachine(FruitMachineContext fruitMachineContext) {
        System.out.println("机器已关闭,请勿重复操作");
        printState(fruitMachineContext);
    }

}

然后是操作类:

/**
 * 水果机器类
 * 五个操作:打开机器 水果清洗 水果消毒 水果打包 关闭机器
 * 五个状态:初始状态--》清洗状态--》消毒状态--》打包状态--》停止状态
 */
public class FruitMachineContext {

    private FruitMachineState fruitMachineState = new closeState();

    public FruitMachineState getFruitMachineState() {
        return fruitMachineState;
    }

    public void setFruitMachineState(FruitMachineState fruitMachineState) {
        this.fruitMachineState = fruitMachineState;
    }

    //打开机器
    public void openMachine() {
        fruitMachineState.openMachine(this);
    }


    //水果清洗
    public void clearFruit() {
        fruitMachineState.clearFruit(this);
    }

    //水果消毒
    public void disinfectFruit() {
        fruitMachineState.disinfectFruit(this);
    }

    //水果打包
    public void packFruit() {
        fruitMachineState.packFruit(this);
    }

    //关闭机器
    public void closeMachine() {
        fruitMachineState.closeMachine(this);
    }
}

因为操作类创建时就默认为关闭状态,所以测试代码不需要再设置一开始的关闭状态了
代码如下:

    public static void main(String[] args) {
        //声明一个水果加工类
        FruitMachineContext fruitMachineContext=new FruitMachineContext();
//        //设置机器初始状态为关闭
//        fruitMachineContext.setFruitMachineState(new closeState());
        //先打开机器
        fruitMachineContext.openMachine();
        //清洗水果
        fruitMachineContext.clearFruit();
        //水果消毒
        fruitMachineContext.disinfectFruit();
        //异常测试:再次消毒
        fruitMachineContext.disinfectFruit();
        //水果打包
        fruitMachineContext.packFruit();
        //关闭机器
        fruitMachineContext.closeMachine();
        //异常测试:水果打包
        fruitMachineContext.packFruit();
        //打开机器
        fruitMachineContext.openMachine();
        //异常测试:再次打开机器
        fruitMachineContext.openMachine();
        //关闭机器
        fruitMachineContext.closeMachine();
    }

打印结果也是一样的,这里就不再贴出
这种方式的好处,是不用一下子创建全部的状态
但坏处是每次状态切换时,都要创建出新的状态
当然我们也可以改进,把创建的状态放到操作类里保存起来。
其实写法都差不多,就看自己的需求了。

优缺点和差异

最后对于状态模式来做一个总结:
状态模式的优点是:
1:将不同的状态进行隔离,单个状态维护自己的操作
2:状态可以做到自动切换
3:可以让多个操作对象共享一个状态对象,从而减少系统中对象的个数。
状态模式的缺点是:
1:状态多的业务场景导致类的数量增加,系统变复杂
2:状态模式对"开闭原则"的支持并不太好,新增状态后,原有的状态类逻辑都要修改
状态模式和策略模式的异同
1:二者都是根据不同的状态/策略来改变内部的行为
2:状态模式是内部自动完成切换,策略模式一般是外部给到不同的策略
状态模式和责任链模式的区别
1:二者都是处理不同任务然后切换到下一节点
2:状态模式是自动切换状态,当前状态根据不同的操作,明确知道自己要进入哪一个状态。而责任链一般是根据业务来组装,自身并不知道要进入那个链条节点
3:状态模式的单个状态可以进入到其他的几个状态。是多向的。比如例子里的清洗状态可以进到机器关闭状态,也可以进入到消毒状态。但责任链模式重在传递,处理任务后会继续传递下去,是单向的

21.访问者模式

所谓访问者模式,其实就是分离对象的数据和行为
怎么理解这句话呢?
比如一个学习机,里面有软件和硬件
硬件配置如CPU,存储是不变的
可以理解为硬件就是这个学习机的数据结构
而每个软件的行为是不一样的,有播放音乐的音乐软件,有写字软件,阅读软件等
在学习机的硬件配置不变的前提下,对软件去进行升级,方便支持更多功能,和修复原有错误
这种场景下,我们就需要用到访问者模式
只关心软件的行为升级,不关心硬件的数据结构
访问者模式主要解决的就是稳定的数据结构和易变的操作耦合问题。
访问者模式主要的角色有:
1:访问者角色(Visitor)
2:具体访问者角色(Concrete Visitor)
3:元素角色(Element)
4:具体元素角色(Concrete Visitor)
5:对象结构角色(Object Structure)
类图如下:
在这里插入图片描述

下面就看具体的例子
首先有一个访问者角色:

/**
 * 访问者基类
 */
public interface Visitor {
      void visit(SingSoftware singSoftware);
      void visit(WriteSoftware writeSoftware);
}

然后是具体的访问者类:

public class SingVisitor implements Visitor{
    @Override
    public void visit(SingSoftware singSoftware) {
        singSoftware.versionNum++;
        System.out.println("唱歌软件已升级,当前版本:"+singSoftware.versionNum+",新增功能:听歌识曲功能");
    }

    @Override
    public void visit(WriteSoftware writeSoftware) {

    }
}
public class WriteVisitor implements Visitor {
    @Override
    public void visit(SingSoftware singSoftware) {
    }

    @Override
    public void visit(WriteSoftware writeSoftware) {
        writeSoftware.versionNum++;
        System.out.println("写字软件已升级,当前版本:" + writeSoftware.versionNum + ",新增画笔:毛笔,解决书写灵敏度的bug");
    }

}

可以看到,这两个具体的访问者,主要是访问传入的被访问者
被访问者,其实就是元素角色
那么我们先来定义元素角色的基类:

public interface SoftwareElement {
     void accept(Visitor visitor);
}

基类的accept方法其实就是传入一个访问者基类
具体实现交由子类:

/**
 * 唱歌软件
 */
public class SingSoftware implements SoftwareElement{
    public int versionNum =1;//软件版本号

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}
/**
 * 写字软件
 */
public class WriteSoftware implements SoftwareElement{
    public int versionNum =1;//软件版本号

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

最后我们看学习机类,也就是对象结构角色:

/**
 * 学习机
 */
public class LearningMachine implements SoftwareElement {
    private SingSoftware singSoftware=new SingSoftware();
    private WriteSoftware writeSoftware=new WriteSoftware();

    @Override
    public void accept(Visitor visitor) {
        singSoftware.accept(visitor);
        writeSoftware.accept(visitor);
    }
}

对象结构内部有这些元素角色
根据传入的访问者,让访问者拿到这些元素对象
然后访问者对被访问者进行“升级”操作
测试代码如下:

    public static void main(String[] args) {
        LearningMachine learningMachine = new LearningMachine();

        Visitor singVisitor = new SingVisitor();
        learningMachine.accept(singVisitor);

        Visitor writeVisitor = new WriteVisitor();
        learningMachine.accept(writeVisitor);

    }

打印结果如下:
在这里插入图片描述
当然,在这里,如果唱歌软件还需要升级
要么修改SingVisitor这个类,让他对被访问者做操作
要么新增一个SingVisitor2类,让这个类访问唱歌软件
相当于对唱歌软件又是一次新的升级。
SingVisitor2如下:

public class SingVisitor2 implements Visitor{
    @Override
    public void visit(SingSoftware singSoftware) {
        singSoftware.versionNum++;
        System.out.println("唱歌软件已升级,当前版本:"+singSoftware.versionNum+",新增功能:联机对唱功能");
    }

    @Override
    public void visit(WriteSoftware writeSoftware) {

    }
}

测试代码如下:

    public static void main(String[] args) {
        LearningMachine learningMachine = new LearningMachine();

        Visitor singVisitor = new SingVisitor();
        learningMachine.accept(singVisitor);

        Visitor writeVisitor = new WriteVisitor();
        learningMachine.accept(writeVisitor);

        Visitor singVisitor2 = new SingVisitor2();
        learningMachine.accept(singVisitor2);
    }

打印结果:
在这里插入图片描述
在这个过程中,学习机这个LearningMachine类的数据结构是没有变过的
他一直都是两个软件和固定的硬件,但是软件的功能行为却根据升级的版本有了变化
这就是设计模式对数据结构和行为的分离

22.中介者模式

生活中经常会有这样的例子:
租房的时候面对多个房东,相亲的时候面对多个相亲对象
也就是不同的类和对象互相依赖的情况
这时候类之间关系往往比较复杂,不好描述
不一小心代码就写的很杂乱
这时候就需要中介者模式
由一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
中介者模式的好处是可以减少对象之间混乱无须的依赖关系,对象之间不直接进行交互,而是依赖中介者统一控制
比如租房有租房中介,相亲有婚姻介绍所等等
不需要一个对象拿着自己的信息和对方的信息,一个个的来匹配
而是把自己的信息全交给中介者,让中介者根据条件来匹配即可
一般多对多关系,网状解构就可以用这种模式

这里举一个塔台的例子:
多个飞机都需要在机场降落
他们要根据自己离机场的距离来决定降落的先后顺序
如果没有塔台,一台飞机就需要拿到其他所有的飞机距离信息
然后得出自己的降落顺序
降落后还要通知自己后面的飞机降落,非常麻烦
而有了塔台后,飞机只需要把自己的距离信息交由塔台
最后由塔台计算处理
同时塔台还可以根据飞机是否需要紧急降落
来调整这个顺序
让紧急降落的飞机先降落
先看飞机类的代码

public class Airplane {
    private Mediator mediator;
    private String airplanename;
    private int distance = Integer.MAX_VALUE;//距离机场还有多远
    private boolean isUrgent = false;//是否需要紧急降落

    public Airplane(String airplanename, int distance) {
        this.airplanename = airplanename;
        this.distance = distance;
    }

    public int getDistance() {
        return distance;
    }

    public void setDistance(int distance) {
        this.distance = distance;
    }

    public boolean isUrgent() {
        return isUrgent;
    }

    public void setUrgent(boolean urgent) {
        isUrgent = urgent;
    }

    public void sendRequest(Mediator mediator) {
        System.out.println(airplanename + "向塔台发起落地请求,距离塔台:" + distance + "公里" + (isUrgent ? ",情况紧急!!!" : ""));
        this.mediator = mediator;
        this.mediator.LandingRequest(this);
    }

    public void getAllowResult() {
        System.out.println(airplanename + "收到落地通知,现在落地");
    }

}

可以看到,飞机类只负责在发起降落请求时告知距离,等待塔台通知自己落地即可
然后是塔台的中介基类:

public interface Mediator {
    //落地请求
    void LandingRequest(Airplane airplane);
    //对落地的飞机进行排序
    void SortRequest();
}

塔台实现类:

public class controlTower implements Mediator {

    private List<Airplane> airplanes = new ArrayList<>();

    @RequiresApi(api = Build.VERSION_CODES.N)
    @Override
    public void LandingRequest(Airplane airplane) {
        airplanes.add(airplane);
        airplanes.sort(new Comparator<Airplane>() {
            @Override
            public int compare(Airplane air1, Airplane air2) {
                if (air1.isUrgent()) return -1;
                return air1.getDistance() - air2.getDistance();
            }
        });
    }

    @Override
    public void SortRequest() {
        for (Airplane air:airplanes){
            air.getAllowResult();
        }

    }
}

塔台实现类拥有保存所有飞机信息,以及发送落地指令这两个功能
最后是测试代码:

    public static void main(String[] args) {
        Airplane air1 = new Airplane("飞机1号", 100);
        Airplane air2 = new Airplane("飞机2号", 200);
        Airplane air3 = new Airplane("飞机3号", 300);
        Airplane air4 = new Airplane("飞机4号", 400);
        air4.setUrgent(true);

        controlTower ct = new controlTower();

        air1.sendRequest(ct);
        air2.sendRequest(ct);
        air3.sendRequest(ct);
        air4.sendRequest(ct);
        ct.SortRequest();
    }

结果如下:
在这里插入图片描述
中介者优点: 1、降低了类的复杂度,将一对多转化成了一对一。 2、各个类之间的解耦。 3、符合迪米特原则。
中介者缺点:中介者会庞大,变得复杂难以维护。
使用时需要注意:类直接的交互职责一定要明确,这样中介者才可以处理

23.解释器模式

所谓解释器模式,就是给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
这段话很难懂,加上解释器在开发中用的也很少
所以很多人不清楚。这边通过一个举例,大家应该就清楚了
假设有一天,我们考古学家,挖出一份古籍
上面写着一串神秘的符号,比如:
a@b c@d f#e g#h
一看是考古学家肯定是一脸懵逼的
毕竟是古文字,肯定看不懂
但是随着慢慢的破译,他们渐渐明白了,这些文字其实就是我们的数学表达式
类似于:
a+b c+d f-e g-h
abcdefgh这些符号都是一些数字
@代表加号+
#代表减号-
比如a是1,b是2
a@b其实就是1+2
结果自然就是3
于是科学家做了一个解释器
专门用来翻译这些古文字
然后把这个解释器放到计算器种
于是计算器就能翻译这些古文字的数学表达式,然后计算出结果了
现在大家明白了,原来解释器模式
就是对一些文法进行定义,然后创建出自己的解释器。就可以解释语言了
比如我们也可以定义@为乘法,#为除法
然后写自己的解释器。这样我们就有了解释的办法
解释器的类图如下:
在这里插入图片描述
了解了这些,我们再来看下解释器的代码:
首先,我们肯定需要一个解释器基类:

public abstract class Expression {
    //解析公式和数值,key是公式中的参数,value是具体的数值
    public abstract int interpreter(HashMap<String, Integer> var);
}

interpreter方法传入一个HashMap
用来存储变量对应的值
比如a@a
如果a=1,那么结果是2
如果a=2,那么结果是4
再看具体的变量解释器:

public class VarExpression extends Expression {

    /**
     * 变量名,在 变量名-值 的键值对中就是键
     */
    private String key;

    public VarExpression(String key) {
        this.key = key;
    }

    /**
     * 解释变量
     */
    @Override
    public int interpreter(HashMap<String, Integer> var) {
        return var.get(this.key);
    }

}

变量解释器也叫值解释器
其实就是根据HashMap存好的映射关系得到值
比如根据HashMap存好的映射关系得到a对应的值为1
然后就是符号解释器的基类
它是所有具体的符号运算解释器(比如+、-、@、#)的基类

public class SymbolExpression extends Expression {

    /**
     * 符号表达式的左表达式
     */
    protected Expression left;

    /**
     * 符号表达式的右表达式
     */
    protected Expression right;

    public SymbolExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    /**
     * 解释方法,默认实现,需要子类覆盖
     */
    @Override
    public int interpreter(HashMap<String, Integer> var) {
        return 0;
    }

}

最后看具体的子类
这边只写了加法和减法,更多的大家可以自己去扩展

public class AddExpression extends SymbolExpression {

    public AddExpression(Expression left, Expression right) {
        super(left, right);
    }

    /**
     * 重写了interpreter方法
     */
    public int interpreter(HashMap<String, Integer> var) {
        return super.left.interpreter(var) + super.right.interpreter(var);
    }

}
public class SubExpression extends SymbolExpression {

    public SubExpression(Expression left, Expression right) {
        super(left, right);
    }

    /**
     * 减法解释器中的解释方法
     */
    public int interpreter(HashMap<String, Integer> var) {
        // 例如:a - b == a-> 1 b->2 == 1 - 2 == -1
        return super.left.interpreter(var) - super.right.interpreter(var);
    }

}

最后看我们的计算器类:

public class Calculator {

    //定义表达式
    private Expression expression;

    //构造函数传参,并解析
    public Calculator(String expStr) {
        //安排运算先后顺序
        Stack<Expression> stack = new Stack<>();
        //表达式拆分为字符数组
        char[] charArray = expStr.toCharArray();

        Expression left = null;
        Expression right = null;
        for(int i=0; i<charArray.length; i++) {
            switch (charArray[i]) {
                case '@':    //加法
                case '+':    //加法
                    left = stack.pop();
                    right = new VarExpression(String.valueOf(charArray[++i]));
                    stack.push(new AddExpression(left, right));
                    break;
                case '#':    //减法
                case '-':    //减法
                    left = stack.pop();
                    right = new VarExpression(String.valueOf(charArray[++i]));
                    stack.push(new SubExpression(left, right));
                    break;
                default:    //公式中的变量
                    stack.push(new VarExpression(String.valueOf(charArray[i])));
                    break;
            }
        }

        //栈中最后只会有一个表达式对象
        this.expression = stack.pop();
    }

    //计算表达式
    public int run(HashMap<String, Integer> var) {
        return this.expression.interpreter(var);
    }

}

计算器的构造函数其实就是根据传进来的表达式字符串进行解析
得到对应的解释器
最后在run方法里面调用解释器的解释方法得到结果
这里把@和+,#和-的处理逻辑都做成一样了
最后看我们的测试代码:

public class Client {

    public static void main(String[] args) throws IOException {
        String expStr = getExpStr();
        HashMap<String, Integer> var = getValue(expStr);
        Calculator calculator = new Calculator(expStr);
        System.out.println("运算结果:" + expStr + "=" + calculator.run(var));
    }

    /**
     * 获得表达式
     *
     * @return 用户输入的表达式
     * @throws IOException
     */
    public static String getExpStr() throws IOException {
        System.out.print("请输入表达式:");
        return (new BufferedReader(new InputStreamReader(System.in))).readLine();
    }

    /**
     * 获得值映射
     *
     * @param expStr
     * @return 每一个变量对应的值,是一个Map
     * @throws IOException
     */
    public static HashMap<String, Integer> getValue(String expStr) throws IOException {
        HashMap<String, Integer> map = new HashMap<>();

        for (char ch : expStr.toCharArray()) {
            if (ch != '@' && ch != '#' && ch != '+' && ch != '-') {
                if (!map.containsKey(String.valueOf(ch))) {
                    System.out.print("请输入" + String.valueOf(ch) + "的值:");
                    String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
                    map.put(String.valueOf(ch), Integer.valueOf(in));
                }
            }
        }

        return map;
    }

}

解释下这几个方法的作用:
getExpStr:返回用户输入的表达式,比如a@b
getValue:把用户自己定义的映射关系保存起来并返回
然后就是把表达式传给计算器类处理就好
看下运行结果:
在这里插入图片描述
当然,也可以玩点骚操作:
在这里插入图片描述
这里把6解释成60,3解释成30,2解释成20
#为减号-
自然结果就是10
关于解释器模式,基本参考了这两篇博客
大家可以配合着一起看下:
解释器模式
设计模式(二十)解释器模式

结尾

参考资料:
装饰器模式 | 菜鸟教程

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值