Java设计模式

概述:
本质:面向对象设计原则的实际运用,是对类的封装、继承和多态性以及类的关联关系和组合关系的充分理解。
分类:

1. 创建型模式:共5种,用于描述“怎样创建对象”,主要特点是“将对象的创建与使用分离”(解耦)。客户程序仅仅需要去使用对象,而不再关心创建对象过程中的逻辑。
2. 结构型模式:共7种,用于描述如何将类或对象按某种布局组成更大的结构。
3. 行为型模式:共11种,用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责。

一、6大设计原则

1. 单一职责原则 (SRP)

英文:Single Responsibility Principle

定义有且仅有一个原因引起类的变更

优点

​ 1.类的复杂性降低

​ 2.可读性提高

​ 3.可维护性提高

​ 4.变更引起的风险降低

难点:职责界限的划分

适用范围:接口(一定)、类(尽量)、方法(尽可能)


2. 里式替换原则 (LSP)

英文:Liskov Substitution Principle

定义

​ 定义1:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。

​ 定义2:所有引用基类的地方必须能透明地使用其子类的对象。

​ 通俗解释:父类出现的地方,子类能完美替换父类,且不会产生任何错误和异常;反之,子类出现的地方,父类未必能适应。

定义拆解

​ 4层含义:

​ 1.子类必须完全实现父类的方法

​ 2.子类可以有自己的个性

​ 3.[契约设计]覆盖或实现父类的方法时输入参数,要么一样(覆写),要么可以被放大(重载)

​ 4.覆写或实现父类的方法时输出结果(返回类型)可以被缩小

优点:增强程序的健壮性

应用:版本升级(待实践)

最佳实践:在项目中,采用LSP时,尽量避免子类的“个性”,一旦子类有“个性”,这个子类和父类之间的关系就很难调和了,把子类当做父类使用,子类的“个性”被抹杀–委屈了点;把子类单独作为一个业务来使用,则会让代码间的耦合关系变的扑朔迷离–缺乏替换的标准。


3. 依赖导致原则 (DIP)

英文:Dependence Inversion Principle

定义:High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions.

定义拆解

​ 1.高层模块不应该依赖底层模块,两者都应该依赖其抽象

​ 2.抽象不应该依赖细节,细节应该依赖抽象

优点:减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。

依赖三种写法

​ 1.构造函数传递依赖对象

​ 2.Setter方法传递依赖对象

​ 3.接口声明依赖对象

本质

​ 遵循规则:

​ 1.基本要求:通过抽象(接口或抽象类),使各类或模块的实现彼此独立,不相相互影响,实现模块间的松耦合。

​ 2.变量的表面类型尽量是接口或者是抽象类:一个变量可以有两种类型:表面类型和实际类型,表面类型是在定义的时候赋予的类型,实际类型是对象的类型。

​ 3.任何类都不应该从具体类派生

​ 4.尽量不要覆写基类的方法

​ 5.结合里氏替换原则使用

​ 核心:“面向接口编程”


4. 接口隔离原则 (ISP)

英文: Interface Segregation Principle

定义

​ 定义1:客户端不应该依赖它不需要的接口

​ 定义2:类间的依赖关系应该建立在最小的接口上

​ 通俗解释:建立单一接口,不要臃肿肥大的接口。接口尽量细化,接口中的方法尽量少。

区分:单一职责原则(SRP) 和 接口隔离原则(ISP)的区别,SRP强调单一职责,要求类和职责单一,注重职责,这是逻辑业务上划分,而ISP要求接口的方法尽量少。

规范拆解

​ 1.接口尽量小:“小”有限度,必须 满足单一职责 (SRP)

​ 2.接口要高内聚:高内聚就是提高接口、类、模块的处理能力,减少对外的交互。接口中尽量少公布public方法,接口的对外的承诺越少对系统的开发越有利,变更的风险就越少,同时也有利于降低成本。

​ 3.定制服务:单独为一个个体体用优良的服务。只提供访问者需要的方法。

​ 4.接口设计是由限度的:接口设计粒度越小越灵活,灵活带来了接口复杂化,开发难度增加,可维护性降低。把握好“度”。


5. 迪米特法则(LKP/LoD)

英文:Law of Demeter 也称 Least Knowledge Principle

关键词:高内聚、低耦合

定义:一个对象应该对其他对象有最少的了解。

​ 通俗解释:一个类应该对自己需要耦合或者调用的类知道得越少(即解耦、弱耦)

定义拆解

​ 4层含义:

​ 1.只与直接的朋友通信 ,朋友类:出现在成员变量、方法的输入输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类

​ 2.朋友间是有距离的:尽量不要对外公布太多的public方法和非静态的public变量,尽量内敛,多使用private、package-private、protect等访问权限

​ 3.是自己的就是自己的:如果一个方法放在本类中,即不增加类间关系,也对本类不产生负面影响,那就放置在本类中。

​ 4.谨慎使用Serializable

最佳实践:一个类跳转两次以上才能访问到另一个类,就需要想办法进行重构


6. 开闭原则(OCP)

英文:Open Closed Principle

定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭


二、23种设计模式(5)

1. 创建型模式

1.1. 简单工厂模式(SFP)

英文:Simple Factory Pattern

定义:由一个工厂对象决定创建出哪一种类型实例。客户端只需传入工厂类的参数,无需关心创建过程。

优点:具体产品从客户端代码中抽离出来,解耦。

缺点:工厂类职责过重,增加新的类型时,得修改工厂类的代码,违背OCP。

最佳实践:严格意义来说,SFP不属于设计模式的一种

举例:简单工厂模式-糖果生产

  1. 创建糖果抽象类
public abstract class Candy {
    public abstract void eat();
}
  1. 创建抽象糖果实现类-巧克力等
public class Chocolate extends Candy{
    @Override
    public void eat() {
        System.out.println("eat chocolate ing...");
    }
}
  1. 创建糖果工厂类
public class CandyFactory {
    public Candy product(String candyName){
        if("chocolate".equalsIgnoreCase(candyName)){
            return new Chocolate();
        }else{
            return null;
        }
    }
}
  1. 测试
public class SfpTest {
    public static void main(String[] args) {
        CandyFactory candyFactory = new CandyFactory();
        Candy chocolate = candyFactory.product("chocolate");
        chocolate.eat();
    }
}
  1. 输出
eat chocolate ing...

1.2. 工厂方法模式(FMP)

英文:Factory Method Pattern

定义:定义创建对象的接口,让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类进行。

优点:

  1. 具体产品从客户端代码中抽离出来,实现了解耦。

  2. 加入新的类型时,只需添加新的工厂方法(无需修改旧的工厂方法代码),符合OCP。

缺点:类的个数容易过多,增加复杂度。

最佳实践:

举例

  1. 创建糖果抽象类
public abstract class Candy {
    public abstract void eat();
}
  1. 创建抽象工厂类
public abstract class CandyFactory {
    public abstract Candy product();
}
  1. 创建抽象糖果实现类-玉米软糖等
public class CornFudge extends Candy{
    @Override
    public void eat() {
        System.out.println("eat corn fudge ing...");
    }
}
  1. 创建抽象工厂实现类-玉米软糖工厂等
public class CornFudgeFactory extends CandyFactory {
    @Override
    public Candy product() {
       return new CornFudge();
    }
}
  1. 测试
public class FmpTest {
    public static void main(String[] args) {
        CornFudgeFactory cornFudgeFactory = new CornFudgeFactory();
        Candy cornFudge = cornFudgeFactory.product();
        cornFudge.eat();
    }
}

6.输出

eat corn fudge ing...

1.3. 抽象工厂模式(AFP)

英文:Abstract Factory Pattern

定义:提供了一系列相关或者相互依赖的对象的接口

优点:

  1. 具体产品从客户端代码中抽离出来,实现解耦。
  2. 将一个系列的产品族统一到一起创建

缺点:拓展新的功能困难,需要修改抽象工厂的接口

最佳实践:

举例

  1. 创建抽象糖果类
public abstract class Candy {
    public abstract void eat();
    public abstract void buy();
}
  1. 创建抽象价格类
public abstract class Price {
    public abstract void pay();
}
  1. 创建抽象工厂接口
public interface CandyFactory {
    Candy getCandy();
    Price getPrice();
}
  1. 实现抽象工厂接口-棒棒糖工厂
public class LollipopFactory implements CandyFactory{
    @Override
    public Candy getCandy() {
        return new Lollipop();
    }

    @Override
    public Price getPrice() {
        return new LollipopPrice();
    }
}
  1. 创建产品族
public class Lollipop extends Candy {
    @Override
    public void eat() {
        System.out.println("eat lollipop ing...");
    }

    @Override
    public void buy() {
        System.out.println("buy lollipop");
    }
}
public class LollipopPrice extends Price{
    @Override
    public void pay() {
        System.out.println("lollipop sell $2");
    }
}
  1. 测试
public class afpTest {
    public static void main(String[] args) {
        LollipopFactory lollipopFactory = new LollipopFactory();
        lollipopFactory.getPrice().pay();
        lollipopFactory.getCandy().buy();
        lollipopFactory.getCandy().eat();
    }
}
  1. 输出
lollipop sell $2
buy lollipop
eat lollipop ing...

1.4. 建造者模式(BP)

英文:Builder Pattern

定义:也称为生成器模式,将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。简单来说就是,相同的过程可以创建不同的产品。

优点:

  1. 封装性好,创建和使用分离
  2. 拓展性好,建造类之间独立,一定程度上解耦。

缺点:

  1. 产生多余的Builder对象;
  2. 产品内部发生变化,建造者需要更改,成本较大。

最佳实践:

  1. 一个对象有非常复杂的内部结构(很多属性)
  2. 想将复杂对象的创建和使用分离。

举例

  1. 创建商铺类Store(包含多属性)
public class Store {
    private String name;
    private String location;
    private String type;

    @Override
    public String toString() {
        return "Store{" +
                "name='" + name + '\'' +
                ", location='" + location + '\'' +
                ", type='" + type + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}
  1. 创建商铺抽象生成器 StoreBuilder(包含和Store相同的属性及对应的抽象构造方法)
public abstract class StoreBuilder {

    private String name;
    private String location;
    private String type;

    public abstract void name(String name);
    public abstract void location(String location);
    public abstract void type(String type);

    public abstract Store build();
}
  1. 创建商铺抽象生成器的实现,糖果铺构造器CandyStoreBuilder
public class CandyStoreBuilder extends StoreBuilder {

    private Store store = new Store();

    @Override
    public void name(String name) {
        this.store.setName(name);
    }

    @Override
    public void location(String location) {
        this.store.setLocation(location);
    }

    @Override
    public void type(String type) {
        this.store.setType(type);
    }

    @Override
    public Store build() {
        return store;
    }
}
  1. 创建经销商类Dealer,用于通过StoreBuilder构建具体的商铺
public class Dealer {
    private StoreBuilder storeBuilder;

    public void setStoreBuilder(StoreBuilder storeBuilder) {
        this.storeBuilder = storeBuilder;
    }

    public Store build(String name, String location, String type) {
        this.storeBuilder.name(name);
        this.storeBuilder.location(location);
        this.storeBuilder.type(type);
        return storeBuilder.build();
    }
}
  1. 测试
public class BpTest {
    public static void main(String[] args) {
        Dealer dealer = new Dealer();
        dealer.setStoreBuilder(new CandyStoreBuilder());
        Store store = dealer.build("甜蜜蜜糖果店", "上海市陆家嘴77号", "糖果经销");
        System.out.println(store);
    }
}
  1. 输出
Store{name='甜蜜蜜糖果店', location='上海市陆家嘴77号', type='糖果经销'}

1.5. 单例模式 (SP)

英文:Singleton Pattern

定义:一个类只有一个实例

优点:

  1. 内存中只有一个实例,减少了内存开销;

  2. 避免对资源的多重占用;

缺点:没有接口,拓展困难。

最佳实践:

模式 描述 方式 优点 缺点 是否推荐
饿汉模式 不管需不需要用到实例都要去创建实例 - 线程安全 不管用到与否,类加载到内存后,就实例化一个单例 有缺点,简单实用,推荐使用
懒汉模式 需要用到创建实例了程序再去创建实例 方式一:锁,双重判空方式 1.线程安全
2.用的时候,再实例化
降低程序效率(判空+锁) 不推荐
方式二:静态内部类方式 1.线程安全(JVM保证单例)
2.加载外部类不会加载内部类,实现了懒加载
- 完美写法,推荐
枚举模式 - - 1. 解决线程同步
2. 防止反序列化
原因:由于枚举没有构造方法,不会被反射
反射原理:类的class文件加载到内存,反射(反序列化的方式)new一个实例(前提要有构造方法)
- 完美中的完美,推荐

举例

  1. 饿汉模式、懒汉模式、单例模式

    1.1. 饿汉模式

    方式一:间接声明-静态代码块new

public class SingletonHunger01 {
    //方式一 :间接声明-静态代码块new
    private static final SingletonHunger01 INSTANCE;
    static{
        INSTANCE = new SingletonHunger01();
    }
    //构造方法设为私有,使得其他类不能new
    private SingletonHunger01(){};
    public static SingletonHunger01 getInstance(){
        return INSTANCE;
    }
}

​ 方式二:直接声明-直接new

public class SingletonHunger02 {
    //方式二:直接声明-直接new
    private static final SingletonHunger02 INSTANCE = new SingletonHunger02();
    //构造方法设为私有,使得其他类不能new
    private SingletonHunger02(){};
    public static SingletonHunger02 getInstance(){
        return INSTANCE;
    }
}

​ 1.2. 懒汉模式

​ 方式一:锁,双重判空方式

public class SingletonLazy01 {
    public static SingletonLazy01 INSTANCE = null;
    //构造方法设为私有,使得其他类不能new
    private SingletonLazy01(){};
    public static SingletonLazy01 getInstance(){
        if(INSTANCE == null){ //降低每次进来都需要判断锁
            synchronized (SingletonLazy01.class){
                if(INSTANCE == null){
                    INSTANCE = new SingletonLazy01();
                }
            }
        }
        return INSTANCE;
    }
}

​ 方式二:静态内部类方式

public class SingletonLazy02 {
    private SingletonLazy02(){};
    //静态内部类
    private static class StaticInner{
        private final static SingletonLazy02 INSTANCE = new SingletonLazy02();
    }
    public static SingletonLazy02 getInstance(){
        return StaticInner.INSTANCE;
    }
}

​ 1.3. 枚举单例

public enum SingletonEnum {
    //单例枚举
    INSTANCE;
}
  1. 测试
public class SpTest {
    public static void main(String[] args) {
        System.out.println("HASHCODE-hash码,同一个类中的hash码不会相同,不同类的hashcode不能保证相同");
        System.out.println("饿汉模式-间接声明方式-"+SingletonHunger01.getInstance().hashCode());
        System.out.println("饿汉模式-直接声明方式-"+SingletonHunger02.getInstance().hashCode());
        System.out.println("懒汉模式-锁,双重判空方式-"+SingletonLazy01.getInstance().hashCode());
        System.out.println("懒汉模式-静态内部类方式-"+SingletonLazy02.getInstance().hashCode());
        System.out.println("单例模式-"+SingletonEnum.INSTANCE.hashCode());
    }
}
  1. 输出
HASHCODE-hash码,同一个类中的hash码不会相同,不同类的hashcode不能保证相同
饿汉模式-间接声明方式-1856056345
饿汉模式-直接声明方式-221036634
懒汉模式-,双重判空方式-1418370913
懒汉模式-静态内部类方式-707610042
单例模式-551734240

1.6. 原型模式(PP)

英文:Prototype Pattern

定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象

优点:

  1. 原型模式是在内存中二进制流的拷贝,要比new一个对象的性能要好,特别是需要产生大量对象时。

  2. 简化创建对象过程。

缺点:

  1. 对象必须重写Object克隆方法;
  2. 直接在内存中拷贝,构造函数是不会执行
  3. 复杂对象的克隆方法写起来较麻烦(浅克隆、深克隆)
  4. 克隆会破坏实现了Cloneable接口的单例对象

最佳实践:

  1. 如果类的初始化需要耗费较多的资源,那么可以通过原型拷贝避免这些消耗。
  2. 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
  3. 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以拷贝多个对象供调用者使用,即保护性拷贝。

拓展:

克隆分类 实现
浅克隆 只复制基本类型的数据,引用类型的数据只复制了引用的地址,引用的对象并没有复制,在新的对象中修改引用类型的数据会影响原对象中的引用。
深克隆 方式一:嵌套重写clone方法:实现Cloneable接口(引用数据类型也要实现Cloneable接口),重写clone方法,clone的嵌套,复制后的对象与原对象之间完全不会影响。
方式二:序列化对象:实现序列化Serializable接口(不实现Cloneable接口),(引用数据类型也要实现Serializable),对象序列化后写入流中,此时不存在引用数据类型的概念,从流中读取,生成新的对象,新对象和原对象之间也是完全互不影响的。

举例

1.1. 浅克隆(简单属性):创建对象,实现Cloneable,重写clone方法

public class CandyShallow implements Cloneable {

    private String name;
    private String color;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }

    //浅克隆
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

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

1.2. 测试

public class PpTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        CandyShallow candyShallow = new CandyShallow();
        ArrayList<CandyShallow> list = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            CandyShallow clone = (CandyShallow)candyShallow.clone();
            clone.setName("ALPENLIEBE"+i);
            clone.setColor("color"+i);
            list.add(clone);
        }
        System.out.println(list);
    }
}

1.3. 输出

[Candy{name='ALPENLIEBE0', color='color0'}, Candy{name='ALPENLIEBE1', color='color1'}, Candy{name='ALPENLIEBE2', color='color2'}]

2.1. 方式一:实现Cloneable,嵌套重写clone方法

public class CandyDeep implements Cloneable {

    private String name;
    private String color;
    private Stuffing stuffing;

    //--------------------------------------重点↓

    /**
     * 方式一:重写clone方法,实现深拷贝
     * 注:该方式的缺陷是需要单独处理所有要克隆的类中的引用数据类型(Stuffing)
     */
    @Override
    protected Object clone(){
        CandyDeep candyDeep = null;
        try {
            candyDeep = (CandyDeep) super.clone();
            candyDeep.stuffing = (Stuffing)this.stuffing.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return candyDeep;
    }

    //--------------------------------------重点↑

    @Override
    public String toString() {
        return "CandyDeep{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                ", stuffing=" + stuffing +
                '}';
    }

    public CandyDeep(String name, String color, Stuffing stuffing) {
        this.name = name;
        this.color = color;
        this.stuffing = stuffing;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
    public Stuffing getStuffing() {
        return stuffing;
    }
    public void setStuffing(Stuffing stuffing) {
        this.stuffing = stuffing;
    }

}
public class Stuffing implements Cloneable{

    private String body;
    private String form;
    private String color;

     //--------------------------------------重点↓

    //无引用数据类型,浅克隆即可
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    //--------------------------------------重点↑

    @Override
    public String toString() {
        return "Stuffing{" +
                "body='" + body + '\'' +
                ", form='" + form + '\'' +
                ", color='" + color + '\'' +
                '}';
    }

    public Stuffing(String body, String form, String color) {
        this.body = body;
        this.form = form;
        this.color = color;
    }

    public String getBody() {
        return body;
    }
    public void setBody(String body) {
        this.body = body;
    }
    public String getForm() {
        return form;
    }
    public void setForm(String form) {
        this.form = form;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
}

2.2. 方式二(推荐):序列化对象

public class CandyDeep2 implements Serializable {

    private static final long serialVersionUID = 1L;

    private String name;
    private String color;
    private Stuffing2 stuffing2;

    //--------------------------------------重点↓

    public CandyDeep2 deepClone() {

        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;

        try {
            //创建序列化流
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            //将当前对象以对象流的方式输出
            oos.writeObject(this);

            //创建反序列化流
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);

            //将流对象反序列化,从而实现类的深拷贝
            return (CandyDeep2) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                //资源释放
                bos.close();
                bis.close();
                oos.close();
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //--------------------------------------重点↑

    @Override
    public String toString() {
        return "CandyDeep2{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                ", stuffing2=" + stuffing2 +
                '}';
    }

    public CandyDeep2(String name, String color, Stuffing2 stuffing2) {
        this.name = name;
        this.color = color;
        this.stuffing2 = stuffing2;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
    public Stuffing2 getStuffing2() {
        return stuffing2;
    }
    public void setStuffing2(Stuffing2 stuffing2) {
        this.stuffing2 = stuffing2;
    }
}
public class Stuffing2 implements Serializable {

    private static final long serialVersionUID = 1L;

    private String body;
    private String form;
    private String color;

    @Override
    public String toString() {
        return "Stuffing2{" +
                "body='" + body + '\'' +
                ", form='" + form + '\'' +
                ", color='" + color + '\'' +
                '}';
    }

    public Stuffing2(String body, String form, String color) {
        this.body = body;
        this.form = form;
        this.color = color;
    }

    public String getBody() {
        return body;
    }
    public void setBody(String body) {
        this.body = body;
    }
    public String getForm() {
        return form;
    }
    public void setForm(String form) {
        this.form = form;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
}

2.3. 测试

 public static void main(String[] args) {
        System.out.println("深克隆,方式一:");
        CandyDeep candyDeep = new CandyDeep("黑色","酒心巧克力",new Stuffing("酒饮料","液体","无色"));
        System.out.println(candyDeep);
        //candyDeep 和 cloneCandy 非同一个对象
        CandyDeep cloneCandy = (CandyDeep) candyDeep.clone();
        System.out.println(cloneCandy);

        System.out.println("+++++++++++++++++++华丽的分界线++++++++++++++++++++++");

        System.out.println("深克隆,方式二:");
        CandyDeep2 candyDeep2 = new CandyDeep2("白色","果仁巧克力",new Stuffing2("果仁","固体","棕色"));
        System.out.println(candyDeep2);
        //candyDeep2 和 cloneCandy2 非同一个对象
        CandyDeep2 cloneCandy2 = candyDeep2.deepClone();
        System.out.println(cloneCandy2);
    }

2.4. 输出

深克隆,方式一:
CandyDeep{name='黑色', color='酒心巧克力', stuffing=Stuffing{body='酒饮料', form='液体', color='无色'}}
CandyDeep{name='黑色', color='酒心巧克力', stuffing=Stuffing{body='酒饮料', form='液体', color='无色'}}
+++++++++++++++++++华丽的分界线++++++++++++++++++++++
深克隆,方式二:
CandyDeep2{name='白色', color='果仁巧克力', stuffing2=Stuffing2{body='果仁', form='固体', color='棕色'}}
CandyDeep2{name='白色', color='果仁巧克力', stuffing2=Stuffing2{body='果仁', form='固体', color='棕色'}}

2.结构型模式(7)

2.1. 外观模式(FP)

英文:Facade Pattern

定义:外观模式又叫门面模式,提供了统一的接口,用来访问子系统中的一群接口。

优点:

  1. 简化了调用过程,无需了解深入子系统
  2. 减低耦合度;
  3. 更好的层次划分;
  4. 符合LKP。

缺点:

  1. 增加子系统,拓展子系统行为容易引入风险;
  2. 不符合OCP。

最佳实践:

  1. 子系统越来越复杂,增加外观模式提供简单接口调用;
  2. 构建多层系统结构,利用外观对象作为每层的入口,简化层间调用。

举例

客户购买现做蛋糕,客户直接和前台打交道,前台和后台(制作蛋糕,装饰,打包)传达信息,后台对于客户被透明化

  1. 创建蛋糕信息实体
public class CakeInfo {

    private String cakeName;
    private Double amount;
    private Integer num;

    @Override
    public String toString() {
        return "CakeInfo{" +
                "cakeName='" + cakeName + '\'' +
                ", amount=" + amount +
                ", num=" + num +
                '}';
    }

    public Double getAmount() {
        return amount;
    }
    public void setAmount(Double amount) {
        this.amount = amount;
    }
    public Integer getNum() {
        return num;
    }
    public void setNum(Integer num) {
        this.num = num;
    }
    public String getCakeName() {
        return cakeName;
    }
    public void setCakeName(String cakeName) {
        this.cakeName = cakeName;
    }
}
  1. 创建后台-制作服务
public class MakeService {
    public boolean makeCake(String cakeName){
        System.out.println("糕点师制作"+cakeName);
        return true;
    }
}
  1. 创建后台-装饰服务
public class DecorateService {
    public boolean decorateCake(String cakeName){
        System.out.println("装饰师装饰"+cakeName);
        return true;
    }
}
  1. 创建后台-打包服务
public class PackageService {
    public boolean packageCake(String cakeName){
        System.out.println("打包师打包"+cakeName+",并转交前台");
        return true;
    }
}
  1. 创建前台服务聚合后台服务
public class FrontDeskService {

    private MakeService maker = new MakeService();
    private DecorateService decorator = new DecorateService();
    private PackageService packer = new PackageService();

    public void sellCake(CakeInfo cakeInfo){
        System.out.println("前台从客户接受蛋糕信息:"+cakeInfo);
        String cakeName = cakeInfo.getCakeName();
        if(maker.makeCake(cakeName)){
            if(decorator.decorateCake(cakeName)){
                if(packer.packageCake(cakeName)){
                    System.out.println("前台收到蛋糕->转交客户->收银->销售成功");
                }
            }
        }
    }
} 
  1. 测试
public class FpTest {
    public static void main(String[] args) {
        CakeInfo cakeInfo = new CakeInfo();
        cakeInfo.setAmount(100.00);
        cakeInfo.setNum(1);
        cakeInfo.setCakeName("巧克力蛋糕");

        FrontDeskService frontDeskService = new FrontDeskService();
        frontDeskService.sellCake(cakeInfo);
    }
}
  1. 输出
前台从客户接受蛋糕信息:CakeInfo{cakeName='巧克力蛋糕', amount=100.0, num=1}
糕点师制作巧克力蛋糕
装饰师装饰巧克力蛋糕
打包师打包巧克力蛋糕,并转交前台
前台收到蛋糕->转交客户->收银->销售成功

2.2. 装饰者模式(DP)

英文:Decorator Pattern

定义:在不改变原有对象的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案

优点:

  1. 继承的有力补充,不改变原有对象的情况下给对象拓展功能
  2. 通过使用不同的装饰类、不同的组合方式,实现不同的效果
  3. 符合OCP

缺点:增加程序复杂性

最佳实践:

  1. 拓展一个类的功能
  2. 动态给对象添加功能,并且动态撤销

举例

客户买蛋糕,要求对蛋糕加不同材料(果切,奶油,巧克力,装饰画等),价格也随之变动

1.创建蛋糕套餐抽象类

public abstract class CakePackage {
    public abstract String remark();
    public abstract int price();
}

2.创建装饰器类继承蛋糕套餐抽象类

public class CakeDecorator extends CakePackage{

    private CakePackage cakePackage;

    public CakeDecorator(CakePackage cakePackage) {
        this.cakePackage = cakePackage;
    }

    @Override
    public String remark() {
        return cakePackage.remark();
    }
    @Override
    public int price() {
        return cakePackage.price();
    }
}

3.创建标准蛋糕套餐类

public class StandardCakePackage extends CakePackage{

    @Override
    public String remark() {
        return "蛋糕胚\n";
    }

    @Override
    public int price() {
        return 5;
    }
}

4.创建加奶油、水果、糖果等增值套餐类

public class CreamCakePackage extends CakeDecorator {
    public CreamCakePackage(CakePackage cakePackage) {
        super(cakePackage);
    }

    @Override
    public String remark() {
        return super.remark()+"加奶油\n";
    }

    @Override
    public int price() {
        return super.price()+5;
    }
}
public class FruitCakePackage extends CakeDecorator {

    public FruitCakePackage(CakePackage cakePackage) {
        super(cakePackage);
    }

    @Override
    public String remark() {
        return super.remark() + "加水果\n";
    }

    @Override
    public int price() {
        return super.price()+10;
    }
}
public class CandyCakePackage extends CakeDecorator {
    public CandyCakePackage(CakePackage cakePackage) {
        super(cakePackage);
    }

    @Override
    public String remark() {
        return super.remark()+"加糖果\n";
    }

    @Override
    public int price() {
        return super.price()+15;
    }
}

5.测试

public class DpTest {

    public static void main(String[] args) {
        CakePackage cake = new StandardCakePackage();
        cake = new CreamCakePackage(cake);
        cake = new FruitCakePackage(cake);
        cake = new CandyCakePackage(cake);
        System.out.println(cake.remark()+"价格:"+cake.price()+"元");
    }

}

6.输出

蛋糕胚
加奶油
加水果
加糖果
价格:35

2.3. 适配器模式(AP)

英文:Adapter Pattern

定义:将一个类的接口转换为期望的另一个接口,使原本不兼容的类可以一起工作

优点:

  1. 提高类的透明性和复用,现有的类复用但不需改变
  2. 目标类和适配器类解耦,提高程序拓展性
  3. 符合OCP

缺点:

  1. 适配器编写过程需要全面考虑,可能会增加系统的复杂性
  2. 降低代码可读性

最佳实践:已存在的类,它的方法和需求不匹配时(方法结果相同或者相似)

举例

原有的蛋糕产品线,新增一个物料产品线(水果、奶油等),在不修改原产品线的同时,两个产品线共同工作

  1. 创建蛋糕产品线类
public class Cake {
    public void makeCakes(){
        System.out.println("生产蛋糕");
    }
}
  1. 创建物料产品线接口
public interface Materiel {
    void make();
}
  1. 蛋糕产品类加入到物料产品线,创建一个适配器
//一般适配器
public class CreamCakeAdaptor extends Cake implements Materiel{
    @Override
    public void make() {
        System.out.println("生产奶油预备");
        super.makeCakes();
        System.out.println("混合成奶油蛋糕");
    }
}
//对象适配器
public class FruitCakeAdaptor implements Materiel{
    Cake cake = new Cake();
    @Override
    public void make() {
        System.out.println("生产果切预备");
        cake.makeCakes();
        System.out.println("混合成水果蛋糕");
    }
}
  1. 测试
public class ApTest {
    public static void main(String[] args) {
        CreamCakeAdaptor creamCakeAdaptor = new CreamCakeAdaptor();
        creamCakeAdaptor.make();
        System.out.println("++++++++++++++华丽的分割线+++++++++++++++");
        FruitCakeAdaptor fruitCakeAdaptor = new FruitCakeAdaptor();
        fruitCakeAdaptor.make();
    }
}
  1. 输出
生产奶油预备
生产蛋糕
混合成奶油蛋糕
++++++++++++++华丽的分割线+++++++++++++++
生产果切预备
生产蛋糕
混合成水果蛋糕

2.4. 享元模式(FP)

英文:Flyweight Pattern

定义:提供了减少对象数量从而改善应用所需的对象结构的方式,运用共享技术有效地支持大量细粒度的对象。

优点:减少对象的创建,降低内存占用;

缺点:

  1. 关注内部状态或外部状态,关注线程安全问题;(内部状态:享元对象的属性状态,不会因为外部的改变而改变; 外部状态:方法参数)

  2. 程序的逻辑复杂化。

最佳实践:

  1. 减少对象的创建,降低内存占用;

  2. 统拥有大量相似对象,需要缓冲池的场景。

举例:制作大量相同类型的蛋糕(代码关键是通过HashMap存储对象)

  1. 创建蛋糕接口
public interface Cake {
    void make() throws InterruptedException;
}
  1. 具体类(如:水果类型蛋糕)实现蛋糕接口
public class FruitCake implements Cake {

    private String name;
    private LocalDateTime productTime;

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

    public void setProductTime(LocalDateTime productTime) {
        this.productTime = productTime;
    }

    @Override
    public void make() {
        try {
            Thread.sleep(100);
            System.out.println(name+"生产时间:"+this.productTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  1. 创建水果蛋糕的工厂
public class FruitCakeFactory {
    //关键点
    private static final HashMap<String,FruitCake> CAKE_HASH_MAP = new HashMap<>();

    public static FruitCake product(String name){
        FruitCake fruitCake = CAKE_HASH_MAP.get(name);
        if(fruitCake == null){
            System.out.println("没有"+name+"制作方法,学习制作方法");
            fruitCake = new FruitCake(name);
            CAKE_HASH_MAP.put(name,fruitCake);
        }
        return fruitCake;
    }
}
  1. 测试
public class FpTest {

    private static final String[] CAKE_TYPE = {"蓝莓蛋糕","火龙果蛋糕","草莓蛋糕","香蕉蛋糕"};

    public static void main(String[] args) {

        IntStream.range(0,10).forEach((i)->{
            String name = CAKE_TYPE[(int)(Math.random() * CAKE_TYPE.length)];
            FruitCake cake = FruitCakeFactory.product(name);
            cake.setProductTime(LocalDateTime.now());
            cake.make();
        });

    }
}
  1. 输出

    10次循环中,只生产了4个对象,很好的描述了系统有大量相似对象,需要缓冲池的场景。

    JDK中的字符串常量池,数据库连接池等都是用的享元模式。

没有蓝莓蛋糕制作方法,学习制作方法
蓝莓蛋糕生产时间:2020-12-13T10:49:17.647076600
蓝莓蛋糕生产时间:2020-12-13T10:49:17.779721700
没有草莓蛋糕制作方法,学习制作方法
草莓蛋糕生产时间:2020-12-13T10:49:17.881450300
草莓蛋糕生产时间:2020-12-13T10:49:17.982180600
没有香蕉蛋糕制作方法,学习制作方法
香蕉蛋糕生产时间:2020-12-13T10:49:18.085903500
蓝莓蛋糕生产时间:2020-12-13T10:49:18.204594900
蓝莓蛋糕生产时间:2020-12-13T10:49:18.305322300
香蕉蛋糕生产时间:2020-12-13T10:49:18.430981100
草莓蛋糕生产时间:2020-12-13T10:49:18.541684900
蓝莓蛋糕生产时间:2020-12-13T10:49:18.645407200

2.5. 组合模式(CP)

英文:Composite Pattern

定义:又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。

优点:

  1. 层次清晰

  2. 客户端不必关系层次差异,方便控制

  3. 符合OCP

缺点:树形处理较为复杂

最佳实践:

  1. 客户端可以忽略组合对象与单个对象的差异

  2. 处理树形结构数据

举例:菜单按钮组成的树形

  1. 创建菜单按钮组合抽象类
public abstract class MenuButton {

    public void add(MenuButton menuButton) {
        throw new UnsupportedOperationException("不支持创建操作");
    }

    public String getName() {
        throw new UnsupportedOperationException("不支持名称获取");
    }

    public String getType() {
        throw new UnsupportedOperationException("不支持类型获取");
    }

    public String getIcon() {
        throw new UnsupportedOperationException("不支持图标");
    }

    public void print() {
        throw new UnsupportedOperationException("不支持打印操作");
    }
}
  1. 创建按钮类
public class Button extends MenuButton{

    private String name;

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

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

    @Override
    public String getType(){
        return "按钮";
    }

    @Override
    public void print() {
        System.out.println(getName() + "[" + getType() + "]");
    }
}
  1. 创建菜单类
public class Menu extends MenuButton {

    private List<MenuButton> items = new ArrayList<>();
    private String name;
    private String icon;
    private Integer level;

    public Menu(String name, String icon, Integer level) {
        this.name = name;
        this.icon = icon;
        this.level = level;
    }

    @Override
    public void add(MenuButton menuButton){
        items.add(menuButton);
    }

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

    @Override
    public String getType() {
        return "菜单";
    }

    @Override
    public String getIcon() {
        return this.icon;
    }

    @Override
    public void print() {
        System.out.println(getIcon() + getName() + "[" + getType() + "]");
        for (MenuButton item : items) {
            if (this.level != null) {
                for (int i = 0; i < this.level; i++) {
                    System.out.print("    ");
                }
            }
            item.print();
        }
    }
}
  1. 测试
public class CpTest {

    public static void main(String[] args) {
        Menu userMenu = new Menu("用户管理", "🧑", 2);
        Button createUser = new Button("新增用户");
        Button updateUser = new Button("修改用户");
        Button deleteUser = new Button("删除用户");
        userMenu.add(createUser);
        userMenu.add(updateUser);
        userMenu.add(deleteUser);

        Menu logMenu = new Menu("操作日志", "📃", 2);
        Button export = new Button("导出Excel");
        logMenu.add(export);

        Menu systemMenu = new Menu("系统管理", "🔨", 1);
        systemMenu.add(userMenu);
        systemMenu.add(logMenu);

        systemMenu.print();
    }
}
  1. 输出
🔨系统管理[菜单]
    🧑用户管理[菜单]
        新增用户[按钮]
        修改用户[按钮]
        删除用户[按钮]
    📃操作日志[菜单]
        导出Excel[按钮]

2.6. 桥接模式(BP)

英文:Bridge Pattern

定义:将抽象部分和具体实现部分分离,使它们都可以独立变化。通过组合的方式建立两个类之间的关系,而不是通过继承。

优点:

  1. 分离抽象部分和具体实现部分

  2. 提高了系统可拓展性

  3. 符合OCP和合成复用原则

缺点:增加了系统的理解和设计难度

最佳实践:

  1. 抽象和实体实现之间增加更多的灵活性

  2. 一个类存在多个独立变化的维度,并且需要独立拓展

  3. 不希望使用继承

举例

1.创建蛋糕的接口类

public interface Cake {
    Cake makeCake();
    void getCake();
}

2.创建蛋糕接口的实现类

水果蛋糕

public class FruitCake implements Cake {
    @Override
    public Cake makeCake() {
        System.out.println("制作水果蛋糕");
        return new FruitCake();
    }

    @Override
    public void getCake() {
        System.out.println("获得水果蛋糕");
    }
}

奶油蛋糕

public class CreamCake implements Cake {
    @Override
    public Cake makeCake() {
        System.out.println("制作奶油蛋糕");
        return new CreamCake();
    }

    @Override
    public void getCake() {
        System.out.println("获得奶油蛋糕");
    }
}

3.创建店铺抽象类,通过属性的方式和蛋糕接口相关联,目的是可以在不同的店铺实现类中灵活地制作各种蛋糕

店铺抽象类

public abstract class Store {

    protected Cake cake;

    public Store(Cake cake){
        this.cake = cake;
    }

    abstract Cake makeCake();
}

店铺实现类

public class ZhangSanStore extends Store{

    public ZhangSanStore(Cake cake) {
        super(cake);
    }

    @Override
    Cake makeCake() {
        System.out.println("张三的蛋糕店");
        return cake.makeCake();
    }
}
public class LiSiStore extends Store {

    public LiSiStore(Cake cake) {
        super(cake);
    }

    @Override
    Cake makeCake() {
        System.out.println("李四的蛋糕店");
        return cake.makeCake();
    }
}

4.测试

public class BpTest {

    public static void main(String[] args) {
        ZhangSanStore zhangSanStore = new ZhangSanStore(new FruitCake());
        Cake cake = zhangSanStore.makeCake();
        cake.getCake();
        System.out.println("++++++++++++++++华丽的分割线+++++++++++++++++");
        LiSiStore liSiStore = new LiSiStore(new CreamCake());
        Cake cake1 = liSiStore.makeCake();
        cake1.getCake();
    }
}

5.输出

张三的蛋糕店
制作水果蛋糕
获得水果蛋糕
++++++++++++++++华丽的分割线+++++++++++++++++
李四的蛋糕店
制作奶油蛋糕
获得奶油蛋糕

2.7. 代理模式(PP)

英文:Proxy Pattern

定义:为其他对象提供一种代理,以控制对这个对象的访问,代理对象在客户端和目标对象之间起到了中介的作用

优点:

  1. 将代理对象和真实被调用的目标对象分离
  2. 降低耦合,拓展性好
  3. 保护目标对象,增强目标对象

缺点:

  1. 造成类的数目增加,增加复杂度

  2. 客户端和目标对象增加代理对象,会造成处理速度变慢

最佳实践:

  1. 保护目标对象
  2. 增强目标对象
代理分类 功能 缺点
静态代理 通过在代码中显式地定义了一个代理类,在代理类中通过同名的方法对目标对象的方法进行包装,客户端通过调用代理类的方法来调用目标对象的方法。 每需要代理一个类,就需要手写对应的代理类
动态代理 JDK的动态代理只能代理接口,通过接口的方法名在动态生成的代理类中调用业务实现类的同名方法。
CGLib代理 通过继承来实现,生成的代理类就是目标对象类的子类,通过重写业务方法来实现代理
Spring对代理模式的拓展 1. 当Bean有实现接口时,使用JDK动态代理; 2. 当Bean没有实现接口时,使用CGLib代理。

举例

1.静态代理

(1)创建糖果类接口

public interface ICandyService {
    void makeCandy();
}

(2)创建糖果类实现类

public class CandyServiceServiceImpl implements ICandyService {
    @Override
    public void makeCandy() {
        System.out.println("制作糖果");
    }
}

(3)创建代理对象

public class ProxyService {

    private ICandyService iCandyService;

    public void makeCandy(){
        beforeMethod();
        iCandyService = new CandyServiceServiceImpl();
        iCandyService.makeCandy();
        afterMethod();
    }

    private void afterMethod() {
        System.out.println("包装");
    }

    private void beforeMethod() {
        System.out.println("准备材料");
    }
}

(4)测试

public class PpTest {
    public static void main(String[] args) {
        System.out.println("[代理模式-静态代理]");
        ProxyService proxyService = new ProxyService();
        proxyService.makeCandy();
    }
}

(5)输出

[代理模式-静态代理]
准备材料
制作糖果
包装

2.动态代理

(1)创建饼干类接口

public interface IBiscuitService {
    void makeBiscuit(String ingredients);
}

(2)创建饼干类实现类

public class BiscuitServiceImpl implements IBiscuitService {
    @Override
    public void makeBiscuit(String ingredients) {
        System.out.println("制作"+ingredients+"饼干");
    }
}

(3)创建动态代理

public class DynamicProxy implements InvocationHandler {

    // 代理的目标对象
    private Object object;

    public DynamicProxy(Object object) {
        this.object = object;
    }

    public Object proxy(){
        Class<?> clazz = object.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    /**
     * @param proxy  动态生成的代理对象
     * @param method 代理方法
     * @param args   代理方法的方法参数
     * @return 结果
     * @throws Throwable 异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        beforeMethod(object);
        // 反射执行代理对象的目标方法
        Object result = method.invoke(object, args);
        afterMethod(object);
        return result;
    }

    private void beforeMethod(Object object) {
        if (object instanceof IBiscuitService) {
            System.out.println("准备饼干配料");
        } else if (object instanceof ICandyService) {
            System.out.println("准备糖果配料");
        } else {
            throw new RuntimeException("暂不支持代理" + object.getClass() + "类型");
        }
    }

    private void afterMethod(Object object) {
        if (object instanceof IBiscuitService) {
            System.out.println("包装饼干");
        } else if (object instanceof ICandyService) {
            System.out.println("包装糖果");
        } else {
            throw new RuntimeException("暂不支持代理" + object.getClass() + "类型");
        }
    }
}

(4)测试

public class Pp2Test {
    public static void main(String[] args) {
        System.out.println("[代理模式-动态代理]");
        ICandyService candyService = (ICandyService) new DynamicProxy(new CandyServiceServiceImpl()).proxy();
        candyService.makeCandy();
        System.out.println("++++++++++++++++华丽的分割线+++++++++++++++++");
        IBiscuitService biscuitService = (IBiscuitService) new DynamicProxy(new BiscuitServiceImpl()).proxy();
        biscuitService.makeBiscuit("草莓");
        System.out.println("++++++++++++++++华丽的分割线+++++++++++++++++");
        biscuitService.makeBiscuit("奶油");
    }
}

(5)输出

[代理模式-动态代理]
准备糖果配料
制作糖果
包装糖果
++++++++++++++++华丽的分割线+++++++++++++++++
准备饼干配料
制作草莓饼干
包装饼干
++++++++++++++++华丽的分割线+++++++++++++++++
准备饼干配料
制作奶油饼干
包装饼干

3.CGLib代理

​ 通过继承来实现,生成的代理类就是目标对象类的子类,通过重写业务方法来实现代理。

4.Spring对代理模式的扩展

​ 可以通过以下配置强制使用CGLib代理;

spring:
  aop:
    proxy-target-class: true

3.行为型模式(11)

3.1. 模板方法模式(TP)

英文:Template Pattern

定义:模板方法模式定义了一个流程的骨架,由多个方法组成。并允许子类为一个或多个步骤提供实现。简而言之就是公共的不变的部分由父类统一实现,变化的部分由子类来个性化实现。

优点:

  1. 提高复用性

  2. 提高拓展性

  3. OCP

缺点:

  1. 类的数目增加

  2. 增加了系统实现的复杂度

  3. 父类添加新的抽象方法,所有子类都要改一遍

最佳实践:

举例:外卖
1.创建固定流程抽象类

public abstract class Takeaway {

    final void order() {
        System.out.println("(流程固定)下单");
    }

    final void packageSend() {
        System.out.println("(流程固定)打包派送");
    }

    protected abstract void make();

    protected boolean needTableware() {
        return true;
    }

    final void flow() {
        this.order();
        this.make();
        if (needTableware()) {
            System.out.println("赠送一次性餐具");
        }
        this.packageSend();
    }
}

2.创建子类实现类

public class CakeTakeaway extends Takeaway{
    @Override
    protected void make() {
        System.out.println("制作蛋糕");
    }

    @Override
    protected boolean needTableware(){
        return super.needTableware();
    }
}
public class BiscuitTakeaway extends Takeaway{
    @Override
    protected void make() {
        System.out.println("制作饼干");
    }

    @Override
    protected boolean needTableware() {
        return false;
    }
}

3.测试

public class TpTest {
    public static void main(String[] args) {
        CakeTakeaway cakeTakeaway = new CakeTakeaway();
        cakeTakeaway.flow();
        System.out.println("++++++++++++++华丽的分割线+++++++++++++++");
        BiscuitTakeaway biscuitTakeaway = new BiscuitTakeaway();
        biscuitTakeaway.flow();
    }
}

4.实现

(流程固定)下单
制作蛋糕
赠送一次性餐具
(流程固定)打包派送
++++++++++++++华丽的分割线+++++++++++++++
(流程固定)下单
制作饼干
(流程固定)打包派送

3.2. 迭代器模式(IP)

英文:Iterator Pattern

定义:迭代器模式,又称游标模式。这种模式提供一种方法访问一个容器对象中各个元素,而又不需要暴露该对象的内部细节。

优点:

1.它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。替换迭代器就可以切换遍历方法。

2.迭代器简化了聚合类。聚合对象可以不用自己再提供遍历方法。

3.在迭代器模式中由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足“开闭原则”的要求。

缺点:

1.由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器来,类的个数成对增加,这在一定程度上增加了系统的复杂性。

2.抽象迭代器设计难度相对较大,需要充分考虑到系统将来的扩展,,例如JDK内置迭代器Iterator就无法实现逆向遍历,如果需要实现逆向遍历,只能通过其子类ListIterator等来实现,而ListIterator迭代器无法用于操作Set类型的聚合对象。

最佳实践:

1.访问一个聚合对象的内容而无须暴露它的内部表示。将聚合对象的访问与内部数据的存储分离,使得访问聚合对象时无须了解其内部实现细节。

2.需要为一个聚合对象提供多种遍历方式。

3.为遍历不同聚合结构提供统一的接口,该接口的实现类中为不同的聚合结构提供不同的遍历方式,而客户端可以一致性的操作该接口。

举例

1.创建抽象容器

public interface  Aggregate {
    void add(Object obj);
    void remove(Object obj);
    Iterator iterator();
}

2.创建容器实现类

public class ConcreteAggregate implements Aggregate{

    private List list = new ArrayList();

    @Override
    public void add(Object obj) {
        list.add(obj);
    }

    @Override
    public void remove(Object obj) {
        list.remove(obj);
    }

    @Override
    public Iterator iterator() {
        return new ConcreteIterator(list);
    }
}

3.创建抽象迭代器

public interface Iterator {
    Object next();
    boolean hasNext();
}

4.创建具体迭代器实现类

public class ConcreteIterator implements Iterator {

    private List list = new ArrayList();
    private int cursor = 0;

    public ConcreteIterator(List list) {
        this.list = list;
    }

    @Override
    public Object next() {
        Object obj = null;
        if (this.hasNext()) {
            obj = this.list.get(cursor++);
        }
        return obj;
    }

    @Override
    public boolean hasNext() {
        if (cursor == list.size()) {
            return false;
        }
        return true;
    }
}

5.测试

public class IpTest {

    public static void main(String[] args) {
        Aggregate ag = new ConcreteAggregate();
        ag.add("a");
        ag.add("b");
        ag.add("c");
        Iterator it = ag.iterator();
        while(it.hasNext()){
            String str = (String)it.next();
            System.out.println(str);
        }
    }
}

6.输出

a
b
c

3.3. 策略模式(SP)

英文:Strategy Pattern

定义:策略模式定义了算法家族,分别封装起来,让它们之间可以互相替换。此模式让算法的变化不会影响到使用算法的用户。策略模式常用于消除大量的if else代码。

优点:

缺点:

最佳实践:

  1. 系统有很多类,它们的区别仅仅在于行为不同
  2. 一个系统需要动态地在几种算法中选择一种

举例:促销活动

1.创建活动接口

public interface Activity {
    void discount();
}

2.创建多接口实现类

public class StrategyOne implements Activity{
    @Override
    public void discount() {
        System.out.println("满减返现");
    }
}
public class StrategyTwo implements Activity {
    @Override
    public void discount() {
        System.out.println("满减打折");
    }
}

3.测试

public class SpTest {

    public static void main(String[] args) {
        String plan = "A";
        if("A".equalsIgnoreCase(plan)){
            StrategyOne strategyOne = new StrategyOne();
            strategyOne.discount();
        }else if("B".equalsIgnoreCase(plan)){
            StrategyTwo strategyTwo = new StrategyTwo();
            strategyTwo.discount();
        }else{
            throw new RuntimeException("暂不支持活动策略");
        }
    }
}

4.输出

满减返现

3.4. 解释器模式(IP)– TODO

英文:Interpreter Pattern

定义:

优点:

缺点:

最佳实践:

举例:最难模式,不常用,后补充


3.5. 观察者模式(OP)

英文:Observer Pattern

定义:观察者模式定义了对象之间的一对多依赖,让多个观察者同时监听某个主题对象,当主体对象发生变化时,它的所有观察者都会收到响应的通知。

优点:

  1. 观察者和被观察者之间建立一个抽象的耦合;

  2. 观察者模式支持广播通信。

缺点:

  1. 观察者之间有过多的细节依赖,提高时间消耗及程序复杂度

  2. 应避免循环调用

最佳实践:

举例:公众号推送文章

1.创建被观察者(公众号)

public class OfficialAccount extends Observable {

    private String accountName;

    public OfficialAccount(String accountName) {
        this.accountName = accountName;
    }

    public String getAccountName() {
        return accountName;
    }

    public void push(Article article) {
        System.out.println("【"+this.accountName+"】" +"发起推送>>>"+article.getAuthor()+"的《"+article.getName()+"》");
        // 设置标识位 changed = true,表示被观察者发生了改变
        setChanged();
        // 通知观察者,可以给观察者传递数据
        notifyObservers(article);
    }
}

2.创建观察者(订阅者)

public class Followers implements Observer {

    private String name;

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

    @Override
    public void update(Observable o, Object arg) {
        OfficialAccount officialAccount = (OfficialAccount) o;
        Article article = (Article) arg;
        System.out.println(name+":::收到【"+officialAccount.getAccountName()+"】的推送:"+article.getAuthor()+"的《"+article.getName()+"》");
    }
}

3.创建推送对象(文章)

public class Article {

    private String name;

    private String author;

    public Article(String name, String author) {
        this.name = name;
        this.author = author;
    }

    public String getName() {
        return name;
    }

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

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
}

4.测试

public class OpTest {

    public static void main(String[] args) {
        OfficialAccount officialAccount = new OfficialAccount("JAVA大本营");
        //添加订阅者
        Followers follower1 = new Followers("路人甲");
        officialAccount.addObserver(follower1);
        Followers follower2 = new Followers("路人乙");
        officialAccount.addObserver(follower2);
        //推送
        Article article = new Article("大话设计模式","菜鸟");
        officialAccount.push(article);
    }
}

5.输出

【JAVA大本营】发起推送>>>菜鸟的《大话设计模式》
路人乙:::收到【JAVA大本营】的推送:菜鸟的《大话设计模式》
路人甲:::收到【JAVA大本营】的推送:菜鸟的《大话设计模式》

3.6. 备忘录模式(MP)

英文:Memento Pattern

定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。

优点:

1.它提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。

2.备忘录实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动。备忘录保存了原发器的状态,采用列表、堆栈等集合来存储备忘录对象可以实现多次撤销操作。

缺点:资源消耗过大,如果需要保存的原发器类的成员变量太多,就不可避免需要占用大量的存储空间,每保存一次对象的状态都需要消耗一定的系统资源。

最佳实践:

  1. 保存一个对象在某一个时刻的全部状态或部分状态,这样以后需要时它能够恢复到先前的状态,实现撤销操作。

  2. 防止外界对象破坏一个对象历史状态的封装性,避免将对象历史状态的实现细节暴露给外界对象。

名称 作用
原发器类(Originator) 创建一个备忘录对象,使用备忘录存储它的内部状态
负责人类(CareTaker) 负责保存好备忘录对象,不能检查或操作备忘录的内容
备忘录类(Memento) 将原发器的内部状态存储起来,原发器根据需要决定备忘录存储原发器的哪些内部状态

举例:冒险岛闯关

1.创建游戏类

public class AdventureIslandGame {

    /**关卡*/
    private Integer point;

    /**建档*/
    public GameMemento createMemento(Integer point){
        return new GameMemento(point);
    }

    /**开始游戏*/
    public void play(){
        point = 1;
    }

    /**恢复备份*/
    public void restore(GameMemento gameMemento){
        this.point = gameMemento.getPoint();
    }

    public Integer getPoint() {
        return point;
    }

    public void setPoint(Integer point) {
        this.point = point;
    }
}

2.创建备份

public class GameMemento {

    /**关卡*/
    private Integer point;

    /**
     * 备份关卡
     */
    public GameMemento(int point){
        this.point = point;
    }

    public Integer getPoint() {
        return point;
    }

    public void setPoint(Integer point) {
        this.point = point;
    }
}

3.创建备份管理类

public class Caretaker {

    private GameMemento gameMemento;

    /**恢复备份*/
    public GameMemento retrieveMemento(){
        return this.gameMemento;
    }
    /**保存备份*/
    public void saveMemento(GameMemento gameMemento){
        this.gameMemento = gameMemento;
    }
}

4.测试

public class MpTest {
    public static void main(String[] args) {
        System.out.println("[冒险岛闯关游戏]");
        AdventureIslandGame game = new AdventureIslandGame();
        System.out.println("游戏开始");
        game.play();
        System.out.println("进入第"+game.getPoint()+"关");
        System.out.println("击杀第"+game.getPoint()+"关boss,进入下一关");
        System.out.println("创建新档...");
        GameMemento gameMemento = game.createMemento(game.getPoint());
        Caretaker caretaker = new Caretaker();
        caretaker.saveMemento(gameMemento);
        System.out.println("~~~~~~第"+game.getPoint()+"关,存档完成~~~~~~");

        game.setPoint(game.getPoint()+1);
        System.out.println("进入第"+game.getPoint()+"关");
        System.out.println("击杀第"+game.getPoint()+"关boss,进入下一关");
        gameMemento.setPoint(game.getPoint());
        caretaker.saveMemento(gameMemento);
        System.out.println("~~~~~~第"+game.getPoint()+"关,存档完成~~~~~~");

        game.setPoint(game.getPoint()+1);
        System.out.println("进入第"+game.getPoint()+"关");
        System.out.println("被第"+game.getPoint()+"关boss杀死,游戏结束");
        System.out.println("回退关卡...");

        game.restore(caretaker.retrieveMemento());
        System.out.println("~~~~~~回退完成,当前关卡第"+game.getPoint()+"关~~~~~~");
        game.setPoint(game.getPoint()+1);
        System.out.println("获取当前关卡:进入第"+game.getPoint()+"关");
    }
}

5.输出

[冒险岛闯关游戏]
游戏开始
进入第1关
击杀第1关boss,进入下一关
创建新档...
进入第2关
击杀第2关boss,进入下一关
​~~~~~~2,存档完成~~~~~~
进入第3关
被第3关boss杀死,游戏结束
回退关卡...~~~~~~回退完成,当前关卡第2~~~~~~
获取当前关卡:进入第3

3.7. 命令模式(CP)

英文:Command Pattern

定义: 将一个请求封装为一个对象,从而使我们可用不同的请求对用户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

名称 作用
抽象命令者(Command) 定义命令的接口,声明执行的方法。
具体命令类(ConcreteCommand) 命令接口实现对象,是“虚”的实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
接收者(Receiver) 真正执行命令的对象。任何类都可能成为一个接收者,只要它能够根据命令要求实现的相应功能。
调用者(Invoker) 要求命令要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这是用户端真正出发命令并要求命令执行相应操作的地方,也就是说,相当于使用命令对象的入口。
具体命令对象(client) 创建具体的命令对象,并且设置命令对象的接收者。也可以理解为装配者。

优点:

  1. 降低系统的耦合度。由于请求者与接收者之间不存在直接引用,因此请求者与接收者之间实现完全解耦,相同的请求者可以对应不同的接收者,同样,相同的接收者也可以供不同的请求者使用,两者之间具有良好的独立性。
  2. 新的命令可以很容易地加入到系统中。由于增加新的具体命令类不会影响到其他类,因此增加新的具体命令类很容易,无须修改原有系统源代码,甚至客户类代码,满足“开闭原则”的要求。

缺点:使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个对请求接收者的调用操作都需要设计一个具体命令类,因此在某些系统中可能需要提供大量的具体命令类,这将影响命令模式的使用。

最佳实践:

1.系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。

2.系统需要在不同的时间指定请求、将请求排队和执行请求。

举例:Siri语音助手

1.创建命令接口

public interface Command {
    //执行命令
    void execute();
}

2.创建具体实现命令实现类

public class OpenCommand implements Command {

    private Application app;

    public OpenCommand(Application app){
        this.app = app;
    }

    @Override
    public void execute() {
        app.on();
    }
}
public class CloseCommand implements Command {

    private Application app;

    public CloseCommand(Application app){
        this.app = app;
    }

    @Override
    public void execute() {
        app.off();
    }
}

3.创建应用抽象类

public abstract class Application {
    public abstract void on();
    public abstract void off();
}

4.创建应用实现类

public class WeChat extends Application {
    @Override
    public void on() {
        System.out.println("微信打开了");
    }

    @Override
    public void off() {
        System.out.println("微信关闭了");
    }
}
public class Alipay extends Application {
    @Override
    public void on() {
        System.out.println("支付宝打开了");
    }

    @Override
    public void off() {
        System.out.println("支付宝关闭了");
    }
}

5.实现命令发起者

public class Siri {

    private Command command;

    /**
     * 设置要执行的命令
     * @param command 命令
     */
    public void setCommand(Command command){
        this.command = command;
    }

    /**
     * 执行命令
     */
    public void executeCommand(){
        command.execute();
    }
}

6.测试

public class CpTest {

    public static void main(String[] args) {
        Siri siri = new Siri();

        System.out.println("嘿 siri, 打开微信");
        Application weChat = new WeChat();
        Command command = new OpenCommand(weChat);
        //siri传递命令
        siri.setCommand(command);
        siri.executeCommand();

        System.out.println("嘿 siri,打开支付宝");
        Application alipay = new Alipay();
        command = new OpenCommand(alipay);
        //siri传递命令
        siri.setCommand(command);
        siri.executeCommand();

    }
}

7.输出

嘿 siri, 打开微信
微信打开了
嘿 siri,打开支付宝
支付宝打开了

3.8. 中介者模式(MP)

英文:Mediator Pattern

定义:中介者模式是指用一个中介对象来封装一系列的对象交互。中介者使个对象不需要显示的相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

优点:

1.简化了对象之间的交互,它用中介者和同事的一对多交互代替了原来同事之间的多对多交互,一对多关系更容易理解、维护扩展,将原本难以理解的网状结构转换成相对简单的星型结构。

2.可将各同事对象解耦。中介者有利于各同事之间的松耦合,我们可以独立的改变和复用每一个同事和中介者,增加新的中介者和新的同事类都比较方便,更好的符合“开闭原则”。

3.可以减少子类生成,中介者将原本分布于多个对象间的行为集中在一起,改变这些行为只需生成新的中介者子类即可,这使得各个同事类可被重用,无须对同事类进行扩展。

缺点:在具体中介者类中包含了大量同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护。

最佳实践:

1.系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。

2.一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象。

3.想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。可以通过引入中介者类来实现,在中介者中定义对象交互的公共行为,如果需要改变行为则可以增加新的具体中介者类。

举例:同事类相互影响

1.创建同事类抽象类

public abstract class Colleague {
    protected int number;

    public int getNumber() {
        return number;
    }

    public void setNumber(int number){
        this.number = number;
    }

    public abstract void setNumber(int number, Mediator mediator);
}

2.创建同事类实现类

public class ColleagueA extends Colleague{
    @Override
    public void setNumber(int number, Mediator mediator) {
        this.number = number;
        mediator.A2B();
    }
}
public class ColleagueB extends Colleague{
    @Override
    public void setNumber(int number, Mediator mediator) {
        this.number = number;
        mediator.B2A();
    }
}

3.创建中介者抽象类

public abstract class Mediator {

    protected Colleague ca;
    protected Colleague cb;

    public Mediator(Colleague a, Colleague b) {
        this.ca = a;
        this.cb = b;
    }

    public abstract void A2B();
    public abstract void B2A();
}

4.创建中介者实现类

public class MediatorImpl extends Mediator {

    public MediatorImpl(Colleague a, Colleague b) {
        super(a, b);
    }

    @Override
    public void A2B() {
        int number = ca.getNumber();
        cb.setNumber(number*100);

    }

    @Override
    public void B2A() {
        int number = cb.getNumber();
        ca.setNumber(number/100);
    }
}

5.测试

public class MpTest {
    public static void main(String[] args) {
        ColleagueA colleagueA = new ColleagueA();
        ColleagueB colleagueB = new ColleagueB();

        Mediator mediator = new MediatorImpl(colleagueA,colleagueB);

        System.out.println("++++++++++++通过设置A影响B++++++++++++");
        colleagueA.setNumber(1000,mediator);
        System.out.println("A的值为:"+colleagueA.getNumber());
        System.out.println("B的值为A的10倍:"+colleagueB.getNumber());

        System.out.println("++++++++++++通过设置B影响A++++++++++++");
        colleagueB.setNumber(2000,mediator);
        System.out.println("B的值为:"+colleagueB.getNumber());
        System.out.println("A的值为B的0.1倍:"+colleagueA.getNumber());

    }
}

6.输出

++++++++++++通过设置A影响B++++++++++++
A的值为:1000
B的值为A的10倍:100000
++++++++++++通过设置B影响A++++++++++++
B的值为:2000
A的值为B的0.1倍:20

3.9. 职责链模式(CRP)

英文:Chain of Responsibility Pattern

定义:责任链模式是为了避免请求的发送者和接收者之间的耦合关系,使多个接收对象都有机会处理请求。将这些对象练成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

职责链分类 定义
纯的职责链模式 纯的职责链模式要求一个具体的处理者对象只能在两个行为中选择一个:一个是承担责任;二是把责任推给下家。不允许出现某一个具体处理者对象在承担了一部分责任后又把责任向下传的情况。在一个纯的职责链中,一个请求必须被某一个处理者对象所接受。
不纯的职责链模式 不纯的职责链模式中允许某个请求被一个具体处理者部分处理后再向下传递,或者一个具体处理者处理完某请求后其后继处理者可以继续处理该请求,而且一个请求可以最终不被任何处理者对象所接收。

优点:

1.请求不需要指出被哪个对象处理了,这样的效果是请求者和接收者之间的解耦,而且链中的对象也不需要清楚其他链的结构,也降低了耦合。

2.请求处理对象仅需要维护一个指向其后继者的的引用,而不需要维护所有的处理对象,简化了对象之间的相互连接。

3.在给对象分派职责时,职责链可以给我们更多的灵活性,可以通过在运行时对该链进行动态的增加或修改来增加或改变处理一个请求的职责。

4.新增一个请求处理对象,不需要改动现有代码,只需要重新设置连接即可,符合“开闭原则”。

缺点:

1.如果一个请求没有明确的接收者,那么就不能保证它一定会被处理,该请求可能一直到链的末端都得不到处理;一个请求也可能因职责链没有被正确配置而得不到处理。

2.对于比较长的职责链,请求的处理可能涉及到多个处理对象,不仅增加了代码的复杂性并且系统性能也将受到一定影响,而且在进行代码调试时不太方便。

3.若建链不当,可能会造成循环调用,将导致系统陷入死循环。

最佳实践:

1.有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定,客户端只需将请求提交到链上,而无须关心请求的处理对象是谁以及它是如何处理的。

2.在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。

3.可动态指定一组对象处理请求,客户端可以动态创建职责链来处理请求,还可以改变链中处理者之间的先后次序。其实在我们日常开发中也会有适用到责任链模式的场景,try/catch、servlet(各个servelt互相调用)、以及filter等

举例:请假审批

1.创建请假对象

public class Leave {
    //姓名
    private String name;
    //部门
    private String department;
    //天数
    private Double day;
    //原因
    private String cause;

    public String getName() {
        return name;
    }

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

    public String getDepartment() {
        return department;
    }

    public void setDepartment(String department) {
        this.department = department;
    }

    public Double getDay() {
        return day;
    }

    public void setDay(Double day) {
        this.day = day;
    }

    public String getCause() {
        return cause;
    }

    public void setCause(String cause) {
        this.cause = cause;
    }

    @Override
    public String toString() {
        return "Leave{" +
                "name='" + name + '\'' +
                ", department='" + department + '\'' +
                ", day=" + day +
                ", cause='" + cause + '\'' +
                '}';
    }
}

2.创建审批者抽象类

public abstract class Approver {

    //审核人名
    public String name;
    //下一个审核人
    public Approver nextApprove;

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

    //设置下一个审核人
    public void setNextApprove(Approver nextApprove) {
        this.nextApprove = nextApprove;
    }

    //审核
    public abstract void approval(Leave leave);
}

3.创建审批者实现类

public class Manager extends Approver {

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

    @Override
    public void approval(Leave leave) {
        if(leave.getDay() < 5){
            System.out.println(name+"权限范围内,批准了");
            System.out.println("结束审批");
        }else{
            if(Objects.nonNull(nextApprove)){
                System.out.println(name+"权限范围外,移交上一级领导");
                this.nextApprove.approval(leave);
            }else{
                System.out.println("领导不在,审批质押");
            }
        }
    }
}
public class Boss extends Approver {
    public Boss(String name) {
        super(name);
    }

    @Override
    public void approval(Leave leave) {
        if (leave.getDay() > 5 && leave.getDay() < 10) {
            System.out.println(name+":未超预期,批准");
            System.out.println("结束审批");
        } else {
            System.out.println(name+"时间太长了,影响公司挣钱,驳回");
            System.out.println("结束审批");
        }
    }
}

4.测试

public class CrpTest {

    public static void main(String[] args) {
        Leave leave = new Leave();
        leave.setName("小王");
        leave.setDepartment("研发部");
        leave.setDay(8.0D);
        leave.setCause("结婚");

        Manager manager = new Manager("李经理");
        Boss boss = new Boss("钱总");
        manager.setNextApprove(boss);

        System.out.println("请假发起者:"+leave.toString());
        manager.approval(leave);
    }

}

5.输出

请假发起者:Leave{name='小王', department='研发部', day=8.0, cause='结婚'}
李经理权限范围外,移交上一级领导
钱总:未超预期,批准
结束审批

3.10. 访问者模式(VP)

英文:Visitor Pattern

定义:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。

优点:

1.符合单一职责原则

2.优秀的扩展性

3.灵活性

缺点:

1.具体元素对访问者公布细节,违反了迪米特原则

2.具体元素变更比较困难

3.违反了依赖倒置原则,依赖了具体类,没有依赖抽象。

最佳实践:

1.对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。

2.需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作”污染”这些对象的类,也不希望在增加新操作时修改这些类。

注意事项:访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。

举例

场景:

CEO和CTO开始评定员工一年的工作绩效,员工分为工程师和经理,CTO关注工程师的代码量、经理的新产品数量;CEO关注的是工程师的KPI和经理的KPI以及新产品数量。由于CEO和CTO对于不同员工的关注点是不一样的,这就需要对不同员工类型进行不同的处理。访问者模式此时可以派上用场了。

1.创建抽象员工类

public abstract class Staff {
    //员工姓名
    public String name;
    //员工KPI
    public Integer kpi;

    public Staff(String name) {
        this.name = name;
        this.kpi = new Random().nextInt(10);
    }

    //接受visitor访问
    public abstract void accept(Visitor visitor);
}

2.创建员工实现类

public class Engineer extends Staff {

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

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

    public int getCodeLines(){
        return new Random().nextInt(100000);
    }
}
public class Manager extends Staff {
    public Manager(String name) {
        super(name);
    }

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

    public int getProductNum(){
        return new Random().nextInt(10);
    }
}

3.创建观接口

public interface Visitor {
    // 访问工程师类型
    void visit(Engineer engineer);

    // 访问经理类型
    void visit(Manager manager);
}

4.创建观察者接口实现类

public class CEO implements Visitor {

    private String name;

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

    @Override
    public void visit(Engineer engineer) {
        System.out.println(name+"查看:"+engineer.name+"的KPI:"+engineer.kpi);
    }

    @Override
    public void visit(Manager manager) {
        System.out.println(name+"查看:"+manager.name+"的KPI:"+manager.kpi);
    }
}
public class CTO implements Visitor {

    private String name;

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

    @Override
    public void visit(Engineer engineer) {
        System.out.println(name+"查看:"+engineer.name+"的代码数:"+engineer.getCodeLines());
    }

    @Override
    public void visit(Manager manager) {
        System.out.println(name+"查看:"+manager.name+"的产品数:"+manager.getProductNum());
    }
}

5.创建报表类

public class ExcelReport {
    private List<Staff> staffs = new ArrayList<>();

    public ExcelReport(){
        staffs.add(new Manager("李经理"));
        staffs.add(new Manager("张经理"));
        staffs.add(new Engineer("小王"));
        staffs.add(new Engineer("小李"));
    }

    public void showReport(Visitor visitor){
        for (Staff staff : staffs) {
            staff.accept(visitor);
        }
    }
}

6.测试

public class VpTest {

    public static void main(String[] args) {
        ExcelReport excelReport = new ExcelReport();
        excelReport.showReport(new CEO("吴董"));
        excelReport.showReport(new CTO("程总"));
    }
}

7.输出

吴董查看:李经理的KPI:3
吴董查看:张经理的KPI:7
吴董查看:小王的KPI:5
吴董查看:小李的KPI:8
程总查看:李经理的产品数:1
程总查看:张经理的产品数:3
程总查看:小王的代码数:55701
程总查看:小李的代码数:31420

3.11. 状态模式(SP)

英文:State Pattern

定义:允许一个对象在其状态改变时,改变它的行为,对象看起来似乎修改了它的类。

优点:

1.封装了状态的转换规则,在状态模式中可以将状态转换的工作封装在环境类或具体的状态类中,可以对状态转换码进行集中管理,而不是分散在一个个的业务中。

2.将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为。

3.允许状态转换逻辑与状态对象合为一体,而不是提供一个巨大的条件语句块,状态模式可以让我们避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起。

缺点:

1.状态模式的使用必然会增加系统中类和对象的个数,导致系统运行开销增大。

2.状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,增加系统设计的难度。

最佳实践:

1.对象的行为依赖于它的状态(如某些属性值),状态的改变将导致行为的变化。

2.在代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强。

举例:请假审核

1.创建请假对象

public class LeaveApply {
    //申请单初始状态是待提交状态
    private ApplyState applyState = new Audit();

    //设置状态
    public void setState(ApplyState state){
        applyState = state;
    }

   //状态变化后,更新对象自身的行为
    public void getResult(){
        applyState.changeHandle();
    }
}

2.创建审核状态接口

public interface ApplyState {
    //状态变化处理操作
    void changeHandle();
}

3.创建各审核状态具体实现类

public class Audit implements ApplyState {
    @Override
    public void changeHandle() {
        System.out.println("[审核中]");
    }
}
public class AuditPass implements ApplyState {
    @Override
    public void changeHandle() {
        System.out.println("[审核通过]");
    }
}
public class AuditReject implements ApplyState{
    @Override
    public void changeHandle() {
        System.out.println("[审核未通过]");
    }
}

4.测试

public class SpTest {
    public static void main(String[] args) {
        LeaveApply leaveApply = new LeaveApply();
        leaveApply.getResult();

        leaveApply.setState(new AuditPass());
        leaveApply.getResult();

        leaveApply.setState(new AuditReject());
        leaveApply.getResult();
    }
}

5.输出

[审核中]
[审核通过]
[审核未通过]

 Previous
学习指南 学习指南
学习本身比学习内容更重要 任务式学习:1.家庭2.工作3.成长 一、寻找学习资源1.1 搜索网站、公众号 1.2 专业平台工具1.3 跟人学​三级导师制:入门、进圈、拜佛 入门:第一级导师-任意干这行的人(询问牛人(关注他们,散射他们的
2021-01-12
Next 
系统优化小记:CPU长时间满负荷运行 系统优化小记:CPU长时间满负荷运行
CPU长时间满负荷运行,一次入门级降CPU系统优化实战 现象用户量增大,线上一个Java单应用服务长时间占用服务器CPU达到400%,使得接口响应速度非常慢,线程拥堵,出现各种响应超时问题,即便有时候没有用户操作,也会长时间占用大量C
2020-11-28
  TOC