Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git

目录

什么是设计模式?

设计模式目的

设计模式七大原则: 

单一职责原则:

接口隔离原则

依赖倒转原则(Dependence Inversion Principle)

在这顺带说明聚合和组合的区别

里氏替换原则(Liskov Substitution Principle)

开闭原则(Open Closed Principle)

迪米特法则(Demeter Principle)

合成复用原则(Composite Reuse Principle)

设计模式类型

创建型模式:

单例模式

八种方式

工厂模式

抽象工厂模式

原型模式

建造者模式

结构型模式:

适配器模式

桥接模式

装饰模式 

组合模式

外观模式

享元模式

代理模式:

静态代理优缺点

基本介绍

行为型模式:

模版模式

命令模式

访问者模式

迭代器模式

观察者模式

中介者模式

备忘录模式

解释器模式(Interpreter 模式)

状态模式

代码

策略模式

职责

链模式(责任链模式)


什么是设计模式?

       设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。

设计模式目的

设计模式是为了让程序(软件),具有更好的

  • 1)可复用性(即:相同功能的代码,不用多次编写,也叫做代码重用性)
  • 2)可读性(即:编程规范性,便于其他程序员的阅读和理解)
  • 3)可扩展性(即:当需要增加新的功能时,非常的方便,也叫做可维护性)
  • 4)可靠性(即:当我们增加新的功能后,对原来的功能没有影响)
  • 5)使程序呈现高内聚,低耦合的特性

设计模式七大原则: 

单一职责原则:

对类来说的,即一个类应该只负责一项职责。如类A负责两个不同职责:职责1,职责2。当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1,A2

有时候可以适当用方法单一来替代类的单一。

  • 类分解,可能成本较高
  • 解决方案:不在类级别上“单一职责”,往下沉,在方法级别上“单一职责”

       通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中方法数量足够少,可以在方法级别保持单一职责原则

接口隔离原则

客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。

如下图:当类 A 通过接口 Interface1 依赖类 B,类 C 通过接口 Interface1 依赖类 D,

       存在问题:如果接口 Interface1 对于类 A 和类 C 来说不是最小接口,那么类 B 和类 D 必须去实现他们不需要的方法

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第1张图片

     问题解决:

  • 将接口 Interface1 拆分为独立的几个接口,类 A 和类 C 分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则 
  • 接口 Interface1 中出现的方法,根据实际情况拆分为三个接口,如下图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第2张图片

 由代码可知,通过接口拆分的方法B和D实现了需要的方法,避免了实现各自不需要的方法

interface Interface1 {
    void operation1();
}

interface Interface2 {
    void operation2();

    void operation3();
}

interface Interface3 {
    void operation4();

    void operation5();
}

class B implements Interface1, Interface2 {

    @Override
    public void operation1() {
        System.out.println("B 实现了 operation1");
    }

    @Override
    public void operation2() {
        System.out.println("B 实现了 operation2");
    }

    @Override
    public void operation3() {
        System.out.println("B 实现了 operation3");
    }
}

class D implements Interface1, Interface3 {

    @Override
    public void operation1() {
        System.out.println("D 实现了 operation1");
    }

    @Override
    public void operation4() {
        System.out.println("D 实现了 operation4");
    }

    @Override
    public void operation5() {
        System.out.println("D 实现了 operation5");
    }
}

/**
 * A类通过接口Interface1,Interface2依赖(使用)B类,但是只会用到1,2,3方法
 */
class A {
    public void depend1(Interface1 i) {
        i.operation1();
    }

    public void depend2(Interface2 i) {
        i.operation2();
    }

    public void depend3(Interface2 i) {
        i.operation3();
    }
}

/**
 * C类通过接口Interface1,Interface3依赖(使用)D类,但是只会用到1,4,5方法
 */
class C {
    public void depend1(Interface1 i) {
        i.operation1();
    }

    public void depend4(Interface3 i) {
        i.operation4();
    }

    public void depend5(Interface3 i) {
        i.operation5();
    }
}

依赖倒转原则(Dependence Inversion Principle)

设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或抽象类,细节就是具体的实现类。面向接口编程,细节依赖抽象。使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

应用实例:Person接收消息

方式1:


class Email {
    public String getInfo() {
        return "电子邮件信息:Hello World!";
    }
}

class Person {
    public void receive(Email email) {
        System.out.println(email.getInfo());
    }
}

 方式1分析
  1.简单,比较容易想到
  2.如果我们获取的对象是微信,短信等等,则新增类,同时 Peron也要增加相应的接收方法,这样做非常不利于程序扩展和优化
解决思路:
       引入一个抽象的接口IReceiver,表示接收者,这样Person类与接口IReceiver发生依赖,因为Email,Weixin等等属于接收的范围,他们各自实现IReceiver接口就ok,这样我们就符合依赖倒转原则。实现代码如下:通过接口传递实现的依赖倒转

interface IReceiver {
    String getInfo();
}

class Email implements IReceiver {
    @Override
    public String getInfo() {
        return "电子邮件信息:Hello World!";
    }
}

class Weixin implements IReceiver {
    @Override
    public String getInfo() {
        return "微信消息:Hello World!";
    }
}

class ShortMessage implements IReceiver {
    @Override
    public String getInfo() {
        return "短信信息:Hello World!";
    }
}

class Person {
    public void receive(IReceiver receiver) {
        System.out.println(receiver.getInfo());
    }
}

依赖倒转三种实现方式:

 接口传递

//方式1:通过接口传递实现依赖
//开关的接口
interface IOpenAndClose {
    void open(ITV tv);//抽象方法,接收接口
}
//ITV接口
interface ITV {
    void play();
}
//实现接口
class OpenAndClose implements IOpenAndClose {
    public void open(ITV tv){
        tv.play();
    }
}

构造方法实现

//方式2:通过构造函数实现依赖
//开关的接口
interface IOpenAndClose {
    void open();//抽象方法
}
//ITV接口
interface ITV {
    public void play();
}
//实现接口
class OpenAndClose implements IOpenAndClose {
    private ITV tv; // 成员
    
    public OpenAndClose(ITV tv){ // 构造器
        this.tv = tv;
    }
    
    public void open(){
        this.tv.play();
    }
}

setter方式传递

//方式3,通过setter方法传递
interface IOpenAndClose{
    void open();//抽象方法
    void setTv(ITV tv);
}
//ITV接口
interface ITV{
    void play();
}
//实现接口
class OpenAndClose implements IOpenAndClose{
    private ITV tv;
    public void setTv(ITV tv){
        this.tv=tv;
    }
    public void open(){
        this.tv.play();
    }
}

构造方法和setter方式都属于通过聚合方式传递 

在这顺带说明聚合和组合的区别

       现实生活中,人和人和手,脚是组合关系,因为当人死亡后人的手也就不复存在了。人和他的电脑是聚合关系。

      组合实例:

public class Person {
    private Hand hand;
 
    public Person() {
        hand=new Hand();
    }
 
    private void go(){
        hand.hand();
    }
 
    public static void main(String[] args) {
        new Person().go();//person消亡时,hand也消亡
    }
}
class Hand{
    public void hand(){
        System.out.print("hand");
    }
}

聚合实例:

public class Person2 {
    private Computer c;
    public Person2(){
 
    }
    public Person2(Computer c){
        this.c=c;
    }
 
    public void go(){
        c.computer();
    }
 
    public static void main(String[] args) {
        Computer computer=new Computer();
        Person2 person = new Person2(computer);
        person.go();//person消亡时,不影响computer
    }
}
class Computer{
    public void computer(){
        System.out.print("computer");
    }
}

比如在A类中引用B类的一个引用b,当A类消亡时,b这个引用所指对象也
同时消亡(没有任何一个引用指向它,成了垃圾对象),这种情况叫组合,
反之b所指的对象还会有另外的引用它,这种情况叫聚合

里氏替换原则(Liskov Substitution Principle)

继承性的思考和说明

  • 1)继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏
  • 2)继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障
  • 3)问题提出:在编程中,如何正确使用继承?=>里氏替换原则

基本介绍

  • 1)里氏替换原则在1988年,由麻省理工学院的以为姓里的女士提出的
  • 2)如果对每个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象
  • 3)在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
  • 4)里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合、组合、依赖来解决问题

一个程序引出的问题和思考

先看个程序,思考下问题和解决思路

public void test() {
    A a = new A();
    System.out.println("11-3=" + a.func1(11, 3));
    System.out.println("1-8=" + a.func1(1, 8));
    System.out.println("---------------------");

    B b = new B();
    System.out.println("11-3=" + b.func1(11, 3));
    System.out.println("1-8=" + b.func1(1, 8));
    System.out.println("11+3+9=" + b.func2(11, 3));
}

class A {
    //返回两个数的差
    public int func1(int num1, int num2) {
        return num1 - num2;
    }
}

class B extends A {
    @Override
    public int func1(int num1, int num2) {
        return num1 + num2;
    }

    //增加了一个新功能:完成两个数相加,然后和9求和
    public int func2(int num1, int num2) {
        return func1(num1, num2) + 9;
    }
}

发现问题:我们发现原来运行正常的相减功能发生了错误。原因就是类 B 无意中重写了父类的方法,造成原有功能出现错误。在实际编程中,我们常常会通过重写父类的方法完成新的功能,这样写起来虽然简单,但整个继承体系的复用性会比较差,特别是运行多态比较频繁的时候。

解决方法:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合、组合等关系代替

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第3张图片

//创建一个更加基础的基类
class Base {
    //将更基础的成员和方法写到Base类中
}

class A extends Base {
    //返回两个数的差
    public int func1(int num1, int num2) {
        return num1 - num2;
    }
}

class B extends Base {
    //如果B需要使用A类的方法,使用组合关系
    private A a;
    //组合实现
    public B(A a) {
        this.a = a;
    }

    public int func1(int num1, int num2) {
        return num1 + num2;
    }

    //增加了一个新功能:完成两个数相加,然后和9求和
    public int func2(int num1, int num2) {
        return func1(num1, num2) + 9;
    }

    public int func3(int num1, int num2) {
        return this.a.func1(num1, num2);
    }
}

开闭原则(Open Closed Principle)

基本介绍

  • 1)开闭原则是编程中最基础、最重要的设计原则
  • 2)一个软件实体如类、模块和函数应该对扩展开放(对提供者而言),对修改关闭(对使用者而言)。用抽象构建框架,用实现扩展细节
  • 3)当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化
  • 4)编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原

一个画图形的功能,

方式1:类图设计如下:

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第4张图片

 代码如下:

public class GraphicEditor {
    public static void main(String[] args) {
        GraphicEditor graphicEditor = new GraphicEditor(new RectangleShape());
        graphicEditor.drawShape();
    }

    private Shape shape;

    public GraphicEditor(Shape shape) {
        this.shape = shape;
    }

    public void drawShape() {
        if (this.shape.m_type == 1) {
            drawRectangle(this.shape);
        } else if (this.shape.m_type == 2) {
            drawCircle(this.shape);

        } else if (this.shape.m_type == 3) {
            drawTriangle(this.shape);
        }
    }

    public void drawRectangle(Shape r) {
        System.out.println("矩形");
    }

    public void drawCircle(Shape r) {
        System.out.println("圆形");
    }

    public void drawTriangle(Shape r) {
        System.out.println("三角形");
    }
}

class Shape {
    public int m_type;

    public Shape() {

    }

    public Shape(Integer m_type) {
        this.m_type = m_type;
    }
}

class RectangleShape extends Shape {
    RectangleShape() {
        m_type = 1;
    }
}

class CircleShape extends Shape {
    CircleShape() {
        m_type = 2;
    }
}

class TriangleShape extends Shape {
    TriangleShape() {
        m_type = 3;
    }
}

方式 1 的优缺点:

  • 优点是比较好理解,简单易操作
  • 缺点是违反了设计模式的 OCP 原则,即对扩展开放(提供方),对修改关闭(使用方)。即当我们给类增加新功能的时喉,尽量不修改代码,或者尽可能少修改代码
  • 比如我们这时要新增加一个图形种类,对原本代码的修改量很大,违背了开闭原则

方式 1 的改进的思路:

把创建 Shape 类做成抽象类,并提供一个抽象的 draw 方法,让子类去实现即可

这样我们有新的图形种类时,只需要让新的图形类继承 Shape,并实现 draw 方法即可

使用以上方法在增加新的图形时不需要修改原有代码,满足了开闭原则

方式 2 来解决

1)方式 2 的设计方案:定义一个 Shape 抽象类

2)看代码示例

public class GraphicEditor01 {
    public static void main(String[] args) {
        GraphicEditor01 graphicEditor01 = new GraphicEditor01();
        graphicEditor01.drawShape(new CircleShape());
    }

    public void drawShape(Shape s) {
        s.draw();
    }
}

abstract class Shape {
    int m_type;

    public abstract void draw();
}

class RectangleShape extends Shape {
    RectangleShape() {
        m_type = 1;
    }

    @Override
    public void draw() {
        System.out.println("矩形");
    }
}

class CircleShape extends Shape {
    CircleShape() {
        m_type = 2;
    }

    @Override
    public void draw() {
        System.out.println("圆形");
    }
}

class TriangleShape extends Shape {
    TriangleShape() {
        m_type = 3;
    }

    @Override
    public void draw() {
        System.out.println("三角形");
    }
}

 方式2实现了OCP原则。

迪米特法则(Demeter Principle)

基本介绍

  • 一个对象应该对其他对象保持最少的了解
  • 类与类关系越密切,耦合度越大
  • 迪米特法则又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的 public 方法,不对外泄露任何信息
  • 迪米特法则还有个更简单的定义:只与直接的朋友通信
  • 直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多:依赖、关联、组合、聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部

注意事项和细节

  • 迪米特法则的核心是降低类之间的耦合
  • 但是注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系,并不是要求完全没有依赖关系

合成复用原则(Composite Reuse Principle)

基本介绍

原则是尽量使用合成/聚合的方式,而不是使用继承

设计原则核心思想

  • 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
  • 针对接口编程,而不是针对实现编程
  • 为了交互对象之间的松耦合设计而努力

设计模式类型

设计模式分为至种类型,共 23 种

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

创建型模式

单例模式

介绍

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)

比如 Hibernate 的 SessionFactory,它充当数据存储源的代理,并负责创建 Session 对象。SessionFactory 并不是轻量级的,一般情况下,一个项目通常只需要一个 SessionFactory 就够,这是就会使用到单例模式

八种方式


1、饿汉式(静态常量)

  • 1)构造器私有化(防止外部 new)
  • 2)类的内部创建对象
  • 3)向外暴露一个静态的公共方法 getInstance
public class Singleton {
    // 1、构造器私有化
    private Singleton() {
    }

    // 2、类的内部创建对象
    private static final Singleton instance = new Singleton();

    // 3、向外暴露一个静态的公共方法
    public static Singleton getInstance() {
        return instance;
    }
}

优缺点

  • 1)优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题
  • 2)缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费
  • 3)这种方式基于 classloder 机制避免了多线程的同步问题。不过,instance 在类装载时就实例化,在单例模式中大多数都是调用getlnstance 方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 就没有达到 Lazy loading 的效果
  • 4)结论:这种单例模式可用,可能造成内存浪费

 2、饿汉式(静态代码块)

  • 1)构造器私有化
  • 2)类的内部声明对象
  • 3)在静态代码块中创建对象
  • 4)向外暴露一个静态的公共方法
public class Singleton {
    // 1、构造器私有化
    private Singleton() {
    }

    // 2、类的内部声明对象
    private static Singleton instance;

    // 3、在静态代码块中创建对象
    static {
        instance = new Singleton();
    }

    // 4、向外暴露一个静态的公共方法
    public static Singleton getInstance() {
        return instance;
    }
}

优缺点

  • 1)这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
  • 2)结论:这种单例模式可用,但是可能造成内存浪费

3、懒汉式(线程不安全) 只适合单例

  • 1)构造器私有化
  • 2)类的内部创建对象
  • 3)向外暴露一个静态的公共方法,当使用到该方法时,才去创建 instance
class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    //提供一个静态的公有方法,当使用到该方法时,才去创建 instance
    //即懒汉式
    public static Singleton getInstance() {
        //当两个线程同时进入if (instance == null),就会破坏单例
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

 使用线程池模拟多线程,测试线程是否安全,拿测试结果证明,测试代码如下:

public class SingletonTest03 {
    public static void main(String[] args) {
        System.out.println("懒汉式1 , 线程不安全~");
        System.out.println("此电脑的CPU核数" + Runtime.getRuntime().availableProcessors());
        int maximumPoolSize = Runtime.getRuntime().availableProcessors();
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,
                maximumPoolSize,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        //由运行结果可知,instance在多线程的环境下并不是单一实例
        try {
            //最大承载 max+queue
            System.out.println("测试懒汉式线程不安全的情况");
            for (int i = 1; i <= 11; i++) {
                // 使用了线程池后,使用线程池来创建线程
                int a = i;
                threadPool.execute(() -> {
                    Singleton instance = Singleton.getInstance();
                    System.out.println("instance.hashCode=" + instance.hashCode());
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }
		/*Singleton instance = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		System.out.println(instance == instance2); // true
		System.out.println("instance.hashCode=" + instance.hashCode());
		System.out.println("instance2.hashCode=" + instance2.hashCode());*/
    }

}

 测试结果为:

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第5张图片

优缺点

  • 1)起到了 Lazy Loading 的效果,但是只能在单线程下使用
  • 2)如果在多线程下,一个线程进入了判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例
  • 3)结论:在实际开发中,不要使用这种方式

4、懒汉式(线程安全,同步方法) 

  • 1)构造器私有化
  • 2)类的内部创建对象
  • 3)向外暴露一个静态的公共方法,加入同步处理的代码,解决线程安全问题
class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    //提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
    //即懒汉式  效率低,每次线程进入getInstance方法都要进行同步
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

优缺点

  • 1)解决了线程不安全问题
  • 2)效率太低了,每个线程在想获得类的实例时候,执行getlnstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低
  • 3)结论:在实际开发中,不推荐使用这种方式

5、懒汉式(线程不安全) 

  • 1)构造器私有化
  • 2)类的内部创建对象
  • 3)向外暴露一个静态的公共方法,加入同步处理的代码块
public class Singleton {
    // 1、构造器私有化
    private Singleton() {
    }

    // 2、类的内部声明对象
    private static Singleton instance;

    // 3、向外暴露一个静态的公共方法,加入同步处理的代码,解决线程安全问题
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }
}

 测试代码:

public class SingletonTest05 {

    public static void main(String[] args) {
        int maximumPoolSize = Runtime.getRuntime().availableProcessors();
        System.out.println("本电脑最大线程数" + maximumPoolSize);
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,
                maximumPoolSize,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        try {
            //最大承载 max+queue
            System.out.println("测试懒汉式线程安全的情况");
            long startTime = System.currentTimeMillis();
            for (int i = 1; i <= 11; i++) {
                // 使用了线程池后,使用线程池来创建线程
                threadPool.execute(() -> {
                    Singleton instance = Singleton.getInstance();
                    System.out.println("instance.hashCode=" + instance.hashCode());
                });
            }
            long endTime = System.currentTimeMillis();
            System.out.println(endTime - startTime + "ms");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }
    }
}

测试结果: 

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第6张图片

优缺点

  • 1)这种方式,本意是想对第四种实现方式的改进,因为前面同步方法效率太低,改为同步产生实例化的的代码块
  • 2)但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例
  • 3)结论:在实际开发中,不能使用这种方式

6、双重检查


●1)构造器私有化
●2)类的内部创建对象,同时用volatile关键字修饰修饰
●3)向外暴露一个静态的公共方法,加入同步处理的代码块,并进行双重判断,解决线程安全问题

// 懒汉式(线程安全,同步方法)
class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
    }

    //提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题
    //同时保证了效率, 推荐使用
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }

        }
        return instance;
    }
}

优缺点

  • 1)Double-Check 概念是多线程开发中常使用到的,我们进行了两次检查,这样就可以保证线程安全了
  • 2)这样实例化代码只用执行一次,后面再次访问时直接 return 实例化对象,也避免的反复进行方法同步
  • 3)线程安全;延迟加载;效率较高
  • 4)结论:在实际开发中,推荐使用这种单例设计模式

7、静态内部类

●1)构造器私有化
●2)定义一个静态内部类,内部定义当前类的静态属性
●3)向外暴露一个静态的公共方法
 

public class Singleton {
    // 1、构造器私有化
    private Singleton() {
    }

    // 2、定义一个静态内部类,内部定义当前类的静态属性
    private static class SingletonInstance {
        private static final Singleton instance = new Singleton();
    }

    // 3、向外暴露一个静态的公共方法
    public static Singleton getInstance() {
        return SingletonInstance.instance;
    }
}

优缺点

  • 1)这种方式采用了类装载的机制,来保证初始化实例时只有一个线程
  • 2)静态内部类方式在 Singleton 类被装载时并不会立即实例化,而是在需要实例化时,调用getlnstance方法,才会装载Singletonlnstance 类,从而完成 Singleton 的实例化
  • 3)类的静态属性只会在第一次加载类的时候初始化,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的
  • 4)优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
  • 5)结论:推荐使用

8、枚举

public enum Singleton {
    INSTANCE;

    public void sayHello() {
        System.out.println("Hello World");
    }
}

优缺点

  • 1)这借助 JDK1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
  • 2)这种方式是 Effective Java 作者 Josh Bloch 提倡的方式
  • 3)结论:推荐使用

注意事项和细节说明

  • 1)单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
  • 2)当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new
  • 3)单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多但又经常用到的对象(即:重量级对象)、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)


工厂模式

看一个具体的需求

看一个披萨的项目:要便于披萨种类的扩展,要便于维护

  • 1)披萨的种类很多(比如 GreekPizz、CheesePizz 等)
  • 2)披萨的制作有 prepare、bake、cut、box
  • 3)完成披萨店订购功能

传统方式

UML 类图

 

核心代码
 

public abstract class Pizza {
    protected String name;

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

    public abstract void prepare();

    public void bake() {
        System.out.println(name + " baking...");
    }

    public void cut() {
        System.out.println(name + " cutting...");
    }

    public void box() {
        System.out.println(name + " boxing...");
    }
}

//希腊风味披萨
public class GreekPizza extends Pizza {
    @Override
    public void prepare() {
        setName("GreekPizza");
        System.out.println(name + " preparing...");
    }
}

// 奶酪披萨
public class CheesePizza extends Pizza {
    @Override
    public void prepare() {
        setName("CheesePizza");
        System.out.println(name + " preparing...");
    }
}

public class OrderPizza {
    public OrderPizza() {
        Pizza pizza = null;
        String orderType;
        do {
            orderType = getType();
            if ("cheese".equals(orderType)) {
                pizza = new CheesePizza();
            } else if ("greek".equals(orderType)) {
                pizza = new GreekPizza();
            } else {
                System.out.println("输入类型错误,程序退出");
                break;
            }
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        } while (true);
    }

    private String getType() {
        System.out.println("请输入披萨类型:");
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        try {
            return reader.readLine();
        } catch (IOException e) {
            e.printStackTrace();
            return "";
        }
    }
}

传统方式优缺点 

  • 1)优点是比较好理解,简单易操作
  • 2)缺点是违反了设计模式的 OCP 原则,即对扩展开放,对修改关闭。即当我们给类增加新能的时候,尽量不修改代码,或者尽可能少修改代码
  • 3)比如我们这时要新增加一个Pizza的种类(Cheese技萨),我们需要做如下修改

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第7张图片

// 胡椒披萨
public class PepperPizza extends Pizza {
    @Override
    public void prepare() {
        setName("PepperPizza");
        System.out.println(name + " preparing...");
    }
}

public class OrderPizza {
    public OrderPizza() {
        // ...
        else if ("pepper".equals(orderType)) {
            pizza = new PepperPizza();
        } 
        // ...
    }
    // ...
}

改进的思路分析

  • 分析:修改代码可以接受,但是如果我们在其它的地方也有创建 Pizza 的代码,就意味着也需要修改。而创建Pizza的代码,往往有多处
  • 思路:把创建 Pizza 对象封装到一个类中,这样我们有新的 Pizza 种类时,只需要修改该类就可,其它有创建到 Pizza 对象的代码就不需要修改了 ==> 简单工厂模式

简单工厂模式

●1)简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
●2)简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
●3)在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式

UML 类图
Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第8张图片
核心代码

public class PizzaFactory {
    public Pizza createPizza(String orderType) {
        Pizza pizza = null;
        switch (orderType) {
            case "cheese":
                pizza = new CheesePizza();
                break;
            case "greek":
                pizza = new GreekPizza();
                break;
            case "pepper":
                pizza = new PepperPizza();
                break;
            default:
                break;
        }
        return pizza;
    }
}

public class OrderPizza {
    private PizzaFactory pizzaFactory;

    public OrderPizza(PizzaFactory pizzaFactory) {
        this.pizzaFactory = pizzaFactory;
        orderPizza();
    }

    public void orderPizza() {
        Pizza pizza = null;
        do {
            pizza = pizzaFactory.createPizza(getType());
            if (pizza == null) {
                System.out.println("Failed to Order Pizza");
            } else {
                pizza.prepare();
                pizza.bake();
                pizza.cut();
                pizza.box();
            }
        } while (true);
    }
    // ...
}

静态工厂模式 

静态工厂模式也是简单工厂模式的一种,只是将工厂方法改为静态方法

UML 类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第9张图片

核心代码

public class PizzaFactory {
    public static Pizza createPizza(String orderType) {
        // ...
    }
}

public class OrderPizza {
    public OrderPizza() {
        Pizza pizza;
        do {
            pizza = PizzaFactory.createPizza(getType());
            // ...
        } while (true);
    }

工厂方法模式

工厂方法模式设计方案:将披萨项目的实例化功能抽象成抽象方法,在不同的口味点餐子类中具体实现

工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类

看一个新的需求

披萨项目新的需求:客户在点披萨时,可以点不同口味的披萨,比如北京的奶酪 Pizza、北京的胡椒 Pizza 或者是伦敦的奶酪 Pizza、伦敦的胡椒 Pizza

思路1:使用简单工厂模式,创建不同的简单工厂类,比如 BJPizzaFactory、LDPizzaFactory 等等。从当前这个案例来说,也是可以的,但是考虑到项目的规模,以及软件的可维护性、可扩展性并不是特别好

思路2:使用工厂方法模式

UML 类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第10张图片

核心代码

public abstract class OrderPizza {

    public void orderPizza() {
        Pizza pizza = null;
        do {
            pizza = createPizza(getType());
            if (pizza == null) {
                System.out.println("Failed to Order Pizza");
            } else {
                pizza.prepare();
                pizza.bake();
                pizza.cut();
                pizza.box();
            }
        } while (true);
    }

    public abstract Pizza createPizza(String orderType);
     
    // ...
}

public class LDOrderPizza extends OrderPizza {
    @Override
    public Pizza createPizza(String orderType) {
        Pizza pizza = null;
        switch (orderType) {
            case "cheese":
                pizza = new LDCheesePizza();
                break;
            case "pepper":
                pizza = new LDPepperPizza();
                break;
            default:
                break;
        }
        return pizza;
    }
}

public class BJOrderPizza extends OrderPizza {
    @Override
    public Pizza createPizza(String orderType) {
        Pizza pizza = null;
        switch (orderType) {
            case "cheese":
                pizza = new BJCheesePizza();
                break;
            case "pepper":
                pizza = new BJPepperPizza();
                break;
            default:
                break;
        }
        return pizza;
    }
}

抽象工厂模式

  • 1)抽象工厂模式:定义了一个 interface 用于创建相关或有依赖关系的对象簇,而无需指明具体的类
  • 2)抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合
  • 3)从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)
  • 4)将工厂抽象成两层,AbsFactory(抽象工厂)和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展

UML 类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第11张图片

核心代码

public interface AbsPizzaFactory {
    Pizza createPizza(String orderType);
}

public class BJPizzaFactory implements AbsPizzaFactory {
    @Override
    public Pizza createPizza(String orderType) {
        Pizza pizza = null;
        switch (orderType) {
            case "cheese":
                pizza = new BJCheesePizza();
                break;
            case "pepper":
                pizza = new BJPepperPizza();
                break;
            default:
                break;
        }
        return pizza;
    }
}

public class LDPizzaFactory implements AbsPizzaFactory {
    @Override
    public Pizza createPizza(String orderType) {
        Pizza pizza = null;
        switch (orderType) {
            case "cheese":
                pizza = new LDCheesePizza();
                break;
            case "pepper":
                pizza = new LDPepperPizza();
                break;
            default:
                break;
        }
        return pizza;
    }
}

小结

  • 1)工厂模式的意义:将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性
  • 2)三种工厂模式:简单工厂模式(静态工厂方法也是简单工厂模式的一种)、工厂方法模式、抽象工厂模式
  • 3)设计模式的依赖抽象原则
    • 创建对象实例时,不要直接 new 类,而是把这个 new 类的动作放在一个工厂的方法中并返回。有的书上说,变量不要直接持有具体类的引用
    • 不要让类继承具体类,而是继承抽象类或者是实现 interface(接口)
    • 不要覆盖基类中已经实现的方法

原型模式

克隆羊问题 

现在有一只羊,姓名为 Tom,年龄为 1,颜色为白色,请编写程序创建和 Tom 羊属性完全相同的 10 只羊

传统方法

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第12张图片

public class Sheep {
    private String name;
    private Integer age;

    public Sheep(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

public class Client {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Sheep sheep = new Sheep("Tom", 1, "白色");
            System.out.println(sheep);
        }
    }
}

传统方法优缺点

  • 1)优点是比较好理解,简单易操作
  • 2)在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低
  • 3)总是需要重新初始化对象,而不是动态地获得对象运行时的状态,不够灵活

改进的思路分析

Java 中 Object 类是所有类的根类,Object 类提供了一个 clone 方法,该方法可以将一个 Java 对象复制一份,但是需要实现 clone 的 Java 类必须要实现一个接口 Cloneable,该接口表示该类能够复制且具有复制的能力 ==> 原型模式

基本介绍

  • 1)原型模式(Prototype 模式)是指:用原型实例指定创建对象种类,并通过拷贝原型创建新的对象
  • 2)原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
  • 3)工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即对象.clone()

原型模式解决克隆羊问题

使用原型模式改进传统方式式,让程序具有更高的效率和扩展性

UML 类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第13张图片

核心代码  实现Cloneable接口

public class Sheep implements Cloneable {
    private String name;
    private Integer age;
    private String color;

    public Sheep(String name, Integer age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

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

    @Override
    protected Object clone() {
        Sheep sheep = null;
        try {
            sheep = (Sheep) super.clone();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sheep;
    }
}

public class Client {
    public static void main(String[] args) {
        Sheep sheep = new Sheep("Tom", 1, "白色");
        for (int i = 0; i < 10; i++) {
            Sheep sheep1 = (Sheep) sheep.clone();
            System.out.println(sheep1);
        }
    }
}

JDK 源码分析

Spring 框架中,创建ApplicationContext时,使用的getBean方法中使用到了原型模式

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第14张图片

浅拷贝和深拷贝

浅拷贝基本介绍

●1)对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象
●2)对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
●3)前面我们克隆羊就是浅拷贝
●4)浅拷贝是使用默认的 clone 方法来实现:sheep=(Sheep)super.clone();

深拷贝基本介绍

●1)复制对象的所有基本数据类型的成员变量值
●2)为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝
●3)深拷贝实现方式 1:重写 clone 方法来实现深拷贝
●4)深拷贝实现方式 2:通过对象序列化实现深拷贝
 

public class DeepClonableTarget implements Serializable, Cloneable {

    private String cloneName;
    private String cloneClass;

    public DeepClonableTarget(String cloneName, String cloneClass) {
        this.cloneName = cloneName;
        this.cloneClass = cloneClass;
    }

    public String getCloneName() {
        return cloneName;
    }

    public void setCloneName(String cloneName) {
        this.cloneName = cloneName;
    }

    public String getCloneClass() {
        return cloneClass;
    }

    public void setCloneClass(String cloneClass) {
        this.cloneClass = cloneClass;
    }

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

public class DeepPrototype implements Serializable, Cloneable {
    private String name;
    private DeepClonableTarget deepClonableTarget;

    public DeepPrototype() {
    }

    public String getName() {
        return name;
    }

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

    public DeepClonableTarget getDeepClonableTarget() {
        return deepClonableTarget;
    }

    public void setDeepClonableTarget(DeepClonableTarget deepClonableTarget) {
        this.deepClonableTarget = deepClonableTarget;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        //基本数据类型拷贝
        Object object = super.clone();
        //引用类型拷贝
        DeepPrototype deepPrototype = (DeepPrototype) object;
        deepPrototype.deepClonableTarget = (DeepClonableTarget) deepClonableTarget.clone();
        return object;
    }
}

public class DeepTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        DeepPrototype prototype = new DeepPrototype();
        prototype.setName("宋江");
        prototype.setDeepClonableTarget(new DeepClonableTarget("及时雨", "及时雨的类"));

        DeepPrototype clone1 = (DeepPrototype) prototype.clone();
        DeepPrototype clone2 = (DeepPrototype) prototype.clone();
        DeepPrototype clone3 = (DeepPrototype) prototype.clone();
        DeepPrototype clone4 = (DeepPrototype) prototype.clone();
        DeepPrototype clone5 = (DeepPrototype) prototype.clone();
        
        System.out.println(prototype.getName() + ", " + prototype.getDeepClonableTarget().hashCode()); // 宋江, 1554874502
        System.out.println(clone1.getName() + ", " + clone1.getDeepClonableTarget().hashCode()); // 宋江, 1846274136
        System.out.println(clone2.getName() + ", " + clone2.getDeepClonableTarget().hashCode()); // 宋江, 1639705018
        System.out.println(clone3.getName() + ", " + clone3.getDeepClonableTarget().hashCode()); // 宋江, 1627674070
        System.out.println(clone4.getName() + ", " + clone4.getDeepClonableTarget().hashCode()); // 宋江, 1360875712
        System.out.println(clone5.getName() + ", " + clone5.getDeepClonableTarget().hashCode()); // 宋江, 1625635731
    }
}

深拷贝方式 2

public class DeepClonableTarget implements Serializable, Cloneable {
    private String cloneName;
    private String cloneClass;

    public DeepClonableTarget(String cloneName, String cloneClass) {
        this.cloneName = cloneName;
        this.cloneClass = cloneClass;
    }

    public String getCloneName() {
        return cloneName;
    }

    public void setCloneName(String cloneName) {
        this.cloneName = cloneName;
    }

    public String getCloneClass() {
        return cloneClass;
    }

    public void setCloneClass(String cloneClass) {
        this.cloneClass = cloneClass;
    }
}

public class DeepPrototype implements Serializable, Cloneable {
    private String name;
    private DeepClonableTarget deepClonableTarget;

    public DeepPrototype() {
    }

    public String getName() {
        return name;
    }

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

    public DeepClonableTarget getDeepClonableTarget() {
        return deepClonableTarget;
    }

    public void setDeepClonableTarget(DeepClonableTarget deepClonableTarget) {
        this.deepClonableTarget = deepClonableTarget;
    }

    public DeepPrototype 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 (DeepPrototype) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                if (ois != null) {
                    ois.close();
                }
                if (bis != null) {
                    bis.close();
                }
                if (oos != null) {
                    oos.close();
                }
                if (bos != null) {
                    bos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

public class DeepTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        DeepPrototype prototype = new DeepPrototype();
        prototype.setName("宋江");
        prototype.setDeepClonableTarget(new DeepClonableTarget("及时雨", "及时雨的类"));

        DeepPrototype clone1 = prototype.deepClone();
        DeepPrototype clone2 = prototype.deepClone();
        DeepPrototype clone3 = prototype.deepClone();
        DeepPrototype clone4 = prototype.deepClone();
        DeepPrototype clone5 = prototype.deepClone();

        System.out.println(prototype.getName() + ", " + prototype.getDeepClonableTarget().hashCode()); // 宋江, 644117698
        System.out.println(clone1.getName() + ", " + clone1.getDeepClonableTarget().hashCode()); // 宋江, 317574433
        System.out.println(clone2.getName() + ", " + clone2.getDeepClonableTarget().hashCode()); // 宋江, 885284298
        System.out.println(clone3.getName() + ", " + clone3.getDeepClonableTarget().hashCode()); // 宋江, 1389133897
        System.out.println(clone4.getName() + ", " + clone4.getDeepClonableTarget().hashCode()); // 宋江, 1534030866
        System.out.println(clone5.getName() + ", " + clone5.getDeepClonableTarget().hashCode()); // 宋江, 664223387
    }
}

方式 1 和方式 2 对比

  • 在对象引用类型的成员属性较少时,方式 1 简单;在对象引用类型的成员属性较多时,方式 2 简单
  • 在对象引用类型的成员属性经常发生变化时,方式 1 需要同步修改,方式 2 不用修改
  • 推荐使用方式 2:耦合性低、可维护性强、扩展性高

注意事项和细节

  • 1)优点:创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
  • 2)优点:不用重新初始化对象,而是动态地获得对象运行时的状态
  • 3)优点如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
  • 4)缺点:在实现深克隆的时候可能需要比较复杂的代码
  • 5)缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了OCP 原则

建造者模式

盖房项目需求

  • 1)需要建房子:这一过程为打桩、砌墙、封顶
  • 2)房子有各种各样的,比如普通房,高楼,别墅,各种房子的过程虽然一样,但是要求不要相同的

传统方式

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第15张图片

问题分析

  • 1)优点是比较好理解,简单易操作
  • 2)设计的程序结构,过于简单,没有设计缓存层对象,程序的扩展和维护不好。也就是说,这种设计方案把产品(即:房子)和创建产品的过程(即:建房子流程)封装在一起,耦合性增强了
  • 3)解决方案:将产品和产品建造过程解耦 ==> 建造者模式

基本介绍

  • 1)建造者模式Builder Pattern)又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象
  • 2)建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节

建造者模式的四个角色

  • 1)Product(产品角色):一个具体的产品对象
  • 2)Builder(抽象建造者):可建一个 Product 对象的各个部件指定的接口 / 抽象类
  • 3)ConcreteBuilder(具体建造者):实现接口,构建和装配各个部件
  • 4)Director(指挥者):构建一个使用 Builder 接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用
    • 一是隔离了客户与对象的生产过程
    • 二是负责控制产品对象的生产过程

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第16张图片

public class House {
    private String pile;
    private String wall;
    private String roof;

    public String getPile() {
        return pile;
    }

    public void setPile(String pile) {
        this.pile = pile;
    }

    public String getWall() {
        return wall;
    }

    public void setWall(String wall) {
        this.wall = wall;
    }

    public String getRoof() {
        return roof;
    }

    public void setRoof(String roof) {
        this.roof = roof;
    }
}

public abstract class HouseBuilder {
    private House house = new House();

    public abstract void piling();
    
    public abstract void walling();
    
    public abstract void capping();

    public House build() {
        return house;
    }
}

public class NormalRoomBuilder extends HouseBuilder {
    @Override
    public void piling() {
        System.out.println("普通房打桩...");
    }

    @Override
    public void walling() {
        System.out.println("普通房砌墙...");
    }

    @Override
    public void capping() {
        System.out.println("普通房封顶...");
    }
}

public class HighRiseBuilder extends HouseBuilder {
    @Override
    public void piling() {
        System.out.println("高楼打桩...");
    }

    @Override
    public void walling() {
        System.out.println("高楼砌墙...");
    }

    @Override
    public void capping() {
        System.out.println("高楼封顶...");
    }
}

public class VillaBuilder extends HouseBuilder {
    @Override
    public void piling() {
        System.out.println("别墅打桩...");
    }

    @Override
    public void walling() {
        System.out.println("别墅砌墙...");
    }

    @Override
    public void capping() {
        System.out.println("别墅封顶...");
    }
}

public class HouseDirector {
    private HouseBuilder houseBuilder;

    public HouseDirector() {
    }

    public void setHouseBuilder(HouseBuilder houseBuilder) {
        this.houseBuilder = houseBuilder;
    }

    public House buildHouse() {
        houseBuilder.piling();
        houseBuilder.walling();
        houseBuilder.capping();
        return houseBuilder.build();
    }
}

public class BuilderTest {
    public static void main(String[] args) {
        HouseDirector houseDirector = new HouseDirector();
        House house;

        houseDirector.setHouseBuilder(new NormalRoomBuilder());
        house = houseDirector.buildHouse();
        houseDirector.setHouseBuilder(new HighRiseBuilder());
        house = houseDirector.buildHouse();
        houseDirector.setHouseBuilder(new VillaBuilder());
        house = houseDirector.buildHouse();
    }
}

注意事项及细节

  • 1)客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
  • 2)每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象

  • 3)可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程
  • 4)增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”
  • 5)建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制
  • 6)如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。因此在这种情况下,要考虑是否选择建造者模式

结构型模式

适配器模式

泰国旅游使用插座问题

现实生活中的适配器例子

泰国插座用的是两孔的(欧标),可以买个多功能转换插头(适配器),这样就可以使用了

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第17张图片

基本介绍

  • 1)适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
  • 2)适配器模式属于结构型模式
  • 3)主要分为三类:类适配器模式、对象适配器模式、接口适配器模式

工作原理

  • 1)适配器模式:将一个类的接口转换成另一种接口,让原本接口不兼容的类可以兼容
  • 2)从用户的角度看不到被适配者,是解耦的
  • 3)用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
  • 4)用户收到反馈结果,感觉只是和目标接口交互,如图

 Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第18张图片


类适配器模式

案例

基本介绍:Adapter 类,通过继承 src 类,实现 dst 类接口,完成 src -> dst 的适配

以生活中充电器的例子来讲解适配器,充电器本身相当于 Adapter,220V 交流电相当于 src(即被适配者),我们的 dst(即目标)是 5V 直流电

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第19张图片

UML 类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第20张图片

// 被适配的类
public class Voltage220V {
    public Integer output220V() {
        int src = 220;
        System.out.println("电压=" + src + "伏");
        return src;
    }
}

// 适配接口
public interface IVoltage5V {
    Integer output5V();
}

// 适配器
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
    @Override
    public Integer output5V() {
        int src = output220V();
        int dst = src / 44;
        System.out.println("电压=" + dst + "伏");
        return dst;
    }
}

// 使用适配器方法
public class Phone {
    public void charing(IVoltage5V iVoltage5V) {
        if (iVoltage5V.output5V() == 5) {
            System.out.println("电压=5伏,正在充电~");
        } else {
            System.out.println("电压!=5伏,无法充电~");
        }
    }
}

// 客户端
public class Client {
    public static void main(String[] args) {
        Phone phone = new Phone();
        phone.charing(new VoltageAdapter());
    }
}

注意事项和细节

  • 1)Java 是单继承机制,所以类适配器需要继承 src 类这一点算是一个缺点,因为这要求 dst 必须是接口,有一定局限性
  • 2)src 类的方法在 Adapter 中都会暴露出来,也增加了使用的成本
  • 3)由于其继承了 src 类,所以它可以根据需求重写 src 类的方法,使得 Adapter 的灵活性增强了


对象适配器模式

  • 1)基本思路和类的适配器模式相同,只是将 Adapter 类作修改,不是继承 src 类,而是持有 src 类的实例,以解决兼容性的问题。即:持有 src 类,实现 dst 类接口,完成 src->dst 的适配
  • 2)根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系
  • 3)对象适配器模式是适配器模式常用的一种

以生活中充电器的例子来讲解适配器,充电器本身相当于 Adapter,220V 交流电相当于 src(即被适配者),我们的 dst(即目标)是 5V 直流电,使用对象适配器模式完成

UML 类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第21张图片

public class VoltageAdapter implements IVoltage5V {
    private Voltage220V voltage220V;

    public VoltageAdapter(Voltage220V voltage220V) {
        this.voltage220V = voltage220V;
    }

    @Override
    public Integer output5V() {
        if (voltage220V == null) {
            return 0;
        }
        int src = voltage220V.output220V();
        int dst = src / 44;
        System.out.println("电压=" + dst + "伏");
        return dst;
    }
}

public class Client {
    public static void main(String[] args) {
        Phone phone = new Phone();
        phone.charing(new VoltageAdapter(new Voltage220V()));
    }
}

注意事项和细节

1)对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承 src 的局限性问题,也不再要求 dst 必须是接口

  • 2)使用成本更低,更灵活

接口适配器模式

  • 1)一些书籍称为:适配器模式或缺省适配器模式(Default Adapter Pattern)
  • 2)当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
  • 3)适用于一个接口不想使用其所有的方法的情况
public interface Interface4 {
    void operation1();

    void operation2();

    void operation3();

    void operation4();
}

public abstract class AbsAdapter implements Interface4 {
    @Override
    public void operation1() {
    }

    @Override
    public void operation2() {
    }

    @Override
    public void operation3() {
    }

    @Override
    public void operation4() {
    }
}

public class Client {
    public static void main(String[] args) {
        AbsAdapter absAdapter = new AbsAdapter() {
            @Override
            public void operation1() {
                System.out.println("调用operation1方法");
            }
        };
        absAdapter.operation1();
    }
}

SpringMVC 框架源码分析

1)SpringMVC 中的 HandlerAdapter,就使用了适配器模式

2)SpringMVC 处理请求的流程回顾

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第22张图片

具体步骤:

  1. 用户发送请求至前端控制器DispatcherServlet;
  2. DispatcherServlet收到请求调用HandlerMapping处理器映射器;
  3. 3处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispathcerServlet;
  4. DispatcherServlet通过HandlerAdapter处理器适配器调用处理器
  5. 执行处理器(Controller,也叫后端控制器);
  6. Controller执行完成后返回ModelAndView;
  7. HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet;
  8. DispatcherServlet将ModelAndView传给ViewResolver视图解析器;
  9. ViewResolver解析后返回具体View对象;
  10. DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中),或者说视图渲染是将模型数据在ModelAndView对象中填充到request域);
  11. DispathcerServlet响应用户。

在第4步,使用了适配器模式:

       在Spring MVC中,DispatcherServlet作为用户,HandlerAdapter作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller作为需要适配的类。

 为什么要在Spring MVC中使用适配器模式?Spring MVC中的Controller种类众多,不同类型的Controller通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet直接获取对应类型的Controller,需要的自行来判断,像下面这段代码一样:
 

if(mappedHandler.getHandler() instanceof MultiActionController){  
   ((MultiActionController)mappedHandler.getHandler()).xxx  
}else if(mappedHandler.getHandler() instanceof XXX){  
    ...  
}else if(...){  
   ...  
}  

这样假设如果我们增加一个HardController,就要在代码中加入一行 if(mappedHandler.getHandler() instanceof HardController)
这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭。

因此也就引入了适配器模式,来看看它是怎么运用的:

首先定义一个适配器接口:

public interface HandlerAdapter {  

    boolean supports(Object handler);

    ModelAndView handle(HttpServletRequest request, HttpServletResponse 

    response, Object handler) throws Exception;  
}

     实现该接口的适配器每一个Controller都有一个适配器与之对应,这样的话,每自定义一个Controller需要定义一个实现HandlerAdapter的适配器。

      适配器与Controller有对应关系,而各个适配器又都是适配器接口的实现类,因此,它们都遵循相同的适配器标准,因此用户可以按照相同的方式,通过不同的Controller去处理请求。

    当Spring容器启动后,会将所有定义好的适配器对象存放在一个List集合中,当一个请求来临时,DispatcherServlet会通过handler的类型找到对应适配器,并将该适配器对象返回给用户,然后就可以统一通过适配器的hanle()方法来调用Controller中的用于处理请求的方法。
 

自己动手写 SpringMVC

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第23张图片

public interface Controller {
}

public class AnnotationController implements Controller {
    public void doAnnotationHandler() {
        System.out.println("annotation...");
    }
}

public class HttpController implements Controller {
    public void doHttpHandler() {
        System.out.println("http...");
    }
}

public class SimpleController implements Controller {
    public void doSimplerHandler() {
        System.out.println("simple...");
    }
}

//定义一个Adapter接口
public interface HandlerAdapter {
    boolean supports(Object handler);

    void handle(Object handler);
}

public class AnnotationHandlerAdapter implements HandlerAdapter {
    @Override
    public void handle(Object handler) {
        ((AnnotationController) handler).doAnnotationHandler();
    }

    @Override
    public boolean supports(Object handler) {
        return (handler instanceof AnnotationController);
    }
}

public class HttpHandlerAdapter implements HandlerAdapter {
    @Override
    public void handle(Object handler) {
        ((HttpController) handler).doHttpHandler();
    }

    @Override
    public boolean supports(Object handler) {
        return (handler instanceof HttpController);
    }
}

public class SimpleHandlerAdapter implements HandlerAdapter {
    @Override
    public void handle(Object handler) {
        ((SimpleController) handler).doSimplerHandler();
    }

    @Override
    public boolean supports(Object handler) {
        return (handler instanceof SimpleController);
    }
}

public class DispatchServlet {
    public static List handlerAdapters = new ArrayList<>();

    public DispatchServlet() {
        handlerAdapters.add(new AnnotationHandlerAdapter());
        handlerAdapters.add(new HttpHandlerAdapter());
        handlerAdapters.add(new SimpleHandlerAdapter());
    }

    public void doDispatch() {
        // 此处模拟 SpringMVC 从 request 取 handler 的对象,适配器可以获取到希望的 Controller
        //HttpController controller = new HttpController();
        SimpleController controller = new SimpleController();
        //AnnotationController controller = new AnnotationController();
        // 得到对应适配器
        HandlerAdapter adapter = getHandler(controller);
        //通过适配器执行对应的controller对应方法
        adapter.handle(controller);
    }

    public HandlerAdapter getHandler(Controller controller) {
        //遍历:根据得到的controller(handler),返回对应适配器
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(controller)) {
                return adapter;
            }
        }
        return null;
    }
}

说明

  • Spring 定义了一个适配接口,使得每一种 Controller 有一种对应的适配器实现类
  • 适配器代替 Controller 执行相应的方法
  • 扩展 Controller 时,只需要增加一个适配器类就完成了 SpringMVC 的扩展了

注意事项和细节

  • 1)三种命名方式,是根据 src 是以怎样的形式给到 Adapter(在Adapter里的形式)来命名的
  • 2)三种适配器模式
    • 类适配器:以类给到,在 Adapter 里将 src 作为一个类,继承
    • 对象适配器:以对象给到,在Adapter 里将 src 作为一个对象,持有
    • 接口适配器:以接口给到,在 Adapter 里将 src 作为一个接口,实现
  • 3)Adapter 模式最大的作用还是将原本不兼容的接口融合在一起工作

桥接模式

传统方式解决手机操作问题

现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等),如图:

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第24张图片

UML 类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第25张图片

问题分析

  1. 扩展性问题(类爆炸):如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类;同样如果我们增加一个手机品牌,也要在各个手机样式类下增加
  2. 违反了单一职责原则:当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本
  3. 解决方案——使用桥接模式

桥接模式基本介绍

  1. 桥接模式(Bridge模式):一种结构型设计模式:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变
  2. Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责
  3. 它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展

原理类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第26张图片

原理类图说明

  • Client:桥接模式的调用者
  • Abstraction:Abstraction 充当桥接类,维护了 Implementor,即 ConcreteImplementorA / ConcreteImplementorB
  • RefinedAbstraction:Abstraction 抽象类的子类
  • Implementor:行为实现类的接口
  • ConcreteImplementorA / ConcreteImplementorB:行为的具体实现类
  • 这里的抽象类和接口是聚合的关系,也是调用者和被调用者的关系

桥接模式解决手机操作问题

UML 类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第27张图片

 核心代码

// 行为接口——品牌接口
public interface Branch {
    void open();

    void call();

    void close();
}
// 行为实现类——华为品牌
public class Huawei implements Branch {
    @Override
    public void open() {
        System.out.println("华为手机开机");
    }

    @Override
    public void call() {
        System.out.println("华为手机打电话");
    }

    @Override
    public void close() {
        System.out.println("华为手机关机");
    }
}
// 行为实现类——小米品牌
public class Xiaomi implements Branch {
    @Override
    public void open() {
        System.out.println("小米手机开机");
    }

    @Override
    public void call() {
        System.out.println("小米手机打电话");
    }

    @Override
    public void close() {
        System.out.println("小米手机关机");
    }
}
// 行为实现类——苹果品牌
public class iPhone implements Branch {
    @Override
    public void open() {
        System.out.println("苹果手机开机");
    }

    @Override
    public void call() {
        System.out.println("苹果手机打电话");
    }

    @Override
    public void close() {
        System.out.println("苹果手机关机");
    }
}

// 桥接类——手机抽象类
public abstract class Phone {
    private Branch branch;

    public Phone(Branch branch) {
        this.branch = branch;
    }

    public void open() {
        branch.open();
    }

    public void call() {
        branch.call();
    }

    public void close() {
        branch.close();
    }
}
// 桥接子类——翻盖式手机
public class FlipPhone extends Phone {
    public FlipPhone(Branch branch) {
        super(branch);
        System.out.println("翻盖式手机");
    }

    @Override
    public void open() {
        super.open();
    }

    @Override
    public void call() {
        super.call();
    }

    @Override
    public void close() {
        super.close();
    }
}
// 桥接子类——滑盖式手机
public class SlidePhone extends Phone {
    public SlidePhone(Branch branch) {
        super(branch);
        System.out.println("滑盖式手机");
    }

    @Override
    public void open() {
        super.open();
    }

    @Override
    public void call() {
        super.call();
    }

    @Override
    public void close() {
        super.close();
    }
}
// 桥接子类——直立式手机
public class UprightPhone extends Phone {
    public UprightPhone(Branch branch) {
        super(branch);
        System.out.println("直立式手机");
    }

    @Override
    public void open() {
        super.open();
    }

    @Override
    public void call() {
        super.call();
    }

    @Override
    public void close() {
        super.close();
    }
}

注意事项和细节

  1. 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来。这有助于系统进行分层设计,从而产生更好的结构化系统
  2. 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成
  3. 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本
  4. 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程
  5. 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的后限性,即需要有这样的应用场景

桥接模式其他应用场景

对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用

常见的应用场景

  1. JDBC 驱动程序
  1. 银行转账系统
    • 转账分类:网上转账、柜台转账、AMT 转账
    • 转账用户类型:普通用户、银卡用户、金卡用户
  1. 消息管理
    • 消息类型:即时消息、延时消息
    • 消息分类:手机短信、邮件消息、QQ消息… 

装饰模式 

星巴克咖啡订单项目

星巴克咖啡订单项目(咖啡馆):

  • 1)咖啡种类/单品咖啡:Espresso(意大利浓咖)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
  • 2)调料:Mik、Soy(豆浆)、Chocolate
  • 3)要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
  • 4)使用 OO 的来计算不同种类咖啡的费用:客户可以点单品咖啡,也可以单品咖啡+调料组合

方案 1-解决星巴克咖啡订单项目(较差的方案)

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第28张图片

方案 1-解决星巴克咖啡订单问题分析

  • 1)Drink 是一个抽象类,表示饮料
  • 2)description 就是对咖啡的描述,比如咖啡的名字
  • 3)cost 方法就是计算费用,Drink 类中做成一个抽象方法
  • 4)Decaf 就是单品咖啡,继承 Drink,并实现 cost
  • 5)Espresso && Milk 就是单品咖啡+调料,这个组合很多
  • 6)问题:这样设计,会有很多类。当我们增加一个单品咖啡,或者一个新的调料,类的数量就会倍增,出现类爆炸

方案 2-解决星巴克咖啡订单项目(好点的方案)

前面分析到方案 1 因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内置到 Drink 类,这样就不会造成类数量过多。从而提高项目的维护性(如图)

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第29张图片

说明:Milk、Soy、Chocolate 可以设计为 Boolean,表示是否要添加相应的调料

方案 2-解决星巴克咖啡订单问题分析

  • 1)方案 2 可以控制类的数量,不至于造成很多的类
  • 2)在增加或者删除调料种类时,代码的维护量很大
  • 3)考虑到用户可以添加多份调料时,可以将 hasMilk 返回一个对应 int
  • 4)考虑使用装饰者模式

装饰者模式

定义

1)装饰者模式:动态地将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式体现了开闭原则(OCP)

2)这里提到的动态的将新功能附加到对象和 OCP 原则,在后面的应用实例上会以代码的形式体现,请同学们注意体会

原理

  • 1)装饰者模式就像打包一个快递
    • 主体:比如陶瓷、衣服(Component)
    • 包装:比如报纸填充、塑料泡沫、纸板、木板(Decorator)
  • 2)主体(Component):比如前面的 Drink
  • 3)具体的主体(ConcreteComponent):比如前面的各个单品咖啡
  • 4)装饰者(Decorator):比如各调料
  • 4)Component 与 ConcreteComponent 之间,如果 ConcreteComponent 类很多,还可以设计一个缓冲层,将共有的部分提取出来,抽象成一个类

装饰者模式解决星巴克咖啡订单项目

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第30张图片

说明

  • 1)Drink 就是抽象类 Component
  • 2)ShortBlack 单品咖啡就是具体的主体
  • 3)Decorator 是一个装饰类,含有一个被装饰的对象(Drink)
  • 4)Decorator 的 cost 方法进行一个费用的叠加,递归地计算价格

装饰者模式下的订单:2份巧克力 + 一份牛奶的 LongBlack

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第31张图片

说明

  • 1)Milk 包含了 LongBlack
  • 2)一份 Chocolate 包含了 Milk + LongBlack
  • 3)一份 Chocolate 包含了 Chocolate + Milk + LongBlack
  • 4)这样不管是什么形式的单品咖啡 + 调料组合,通过递归方式可以方便的组合和维护

UML类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第32张图片

 核心代码“

// 抽象主体
public abstract class Drink {
    private String desc;
    private Float price;

    public String getDesc() {
        return desc;
    }

    protected void setDesc(String desc) {
        this.desc = desc;
    }

    public Float getPrice() {
        return price;
    }

    protected void setPrice(Float price) {
        this.price = price;
    }

    public abstract Float cost();
}
// 具体主体
public class Coffee extends Drink {

    @Override
    public Float cost() {
        return super.getPrice();
    }
}
public class Decaf extends Coffee {
    public Decaf() {
        setDesc("无因咖啡");
        setPrice(20.0f);
    }
}
public class Espresso extends Coffee {
    public Espresso() {
        setDesc("意大利浓咖");
        setPrice(30.0f);
    }
}
public class ShortBlack extends Coffee {
    public ShortBlack() {
        setDesc("短黑咖啡");
        setPrice(40.0f);
    }
}
public class LongBlack extends Coffee {
    public LongBlack() {
        setDesc("美式咖啡");
        setPrice(50.0f);
    }
}
//装饰者
public class Decorator extends Drink {
    private Drink drink;

    public Decorator(Drink drink) {
        this.drink = drink;
    }

    @Override
    public Float cost() {
        return super.getPrice() + drink.cost();
    }
}
public class Milk extends Decorator {
    public Milk(Drink drink) {
        super(drink);
        setDesc("牛奶");
        setPrice(3.0f);
    }
}
public class Soy extends Decorator {
    public Soy(Drink drink) {
        super(drink);
        setDesc("豆浆");
        setPrice(4.0f);
    }
}
public class Chocolate extends Decorator {
    public Chocolate(Drink drink) {
        super(drink);
        setDesc("巧克力");
        setPrice(5.0f);
    }
}
// 调用者
public class CoffeeBar {
    public static void main(String[] args) {
        Drink drink = new Espresso();
        System.out.println("意大利浓咖:" + drink.cost() + "美元"); // 意大利浓咖:30.0美元

        drink = new Milk(drink);
        System.out.println("意大利浓咖 + 1份牛奶:" + drink.cost() + "美元"); // 意大利浓咖 + 1份牛奶:33.0美元

        drink = new Chocolate(drink);
        System.out.println("意大利浓咖 + 1份牛奶 + 1份巧克力:" + drink.cost() + "美元"); // 意大利浓咖...:38.0美元

        drink = new Chocolate(drink);
        System.out.println("意大利浓咖 + 1份牛奶 + 2份巧克力:" + drink.cost() + "美元"); // 意大利浓咖...:43.0美元
    }
}

 说明

  • 1)Drink 就是抽象类 Component
  • 2)ShortBlack 单品咖啡就是具体的主体
  • 3)Decorator 是一个装饰类,含有一个被装饰的对象(Drink)
  • 4)Decorator 的 cost 方法进行一个费用的叠加,递归地计算价格

组合模式

学校院系展示需求

编写程序展示一个学校院系结构:

需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。如图:

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第33张图片

传统方式解决学校院系展示(类图)

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第34张图片

问题分析

  • 1)将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的
  • 2)实际上我们的要求是:在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。因此这种方案,不能很好实现的 管理 的操作,比如对学院、系的添加、删除、遍历等
  • 3)解决方案:把学校、院、系都看做是组织结构,他们之间没有继承的关系,而是一个树形结构,可以更好的实现管理操作 ==> 组合模式

2、组合模式基本介绍

  • 1)组合模式(Composite Pattern),又叫部分整体模式。它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系
  • 2)组合模式依据树形结构来组合对象,用来表示部分以及整体层次
  • 3)这种类型的设计模式属于结构型模式
  • 4)组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象

原理类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第35张图片

对原理结构图的说明一即组合模式的角色及职责

  • 1)Component:这是组合中对象声明接口。在适当情况下,实现所有类共有的接口默认行为,用于访问和管理 Component子部件。Component可以是抽象类或者接口
  • 2)Leaf:在组合中表示叶子结点,叶子结点没有子节点
  • 3)Composite:非叶子结点,用于存储子部件,在Component接口中实现子部件的相关操作。比如增加、删除

解决的问题

组合模式解决这样的问题,当我们的要处理的对象可以生成一棵树形结构,而我们要对树上的节点和叶子进行操作时,它能够提供一致的方式,而不用考虑它是节点还是叶子

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第36张图片

组合模式解决学校院系展示

UML 类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第37张图片

核心代码 

// Component 抽象类
public abstract class OrganizationComponent {
    private String name;

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

    public String getName() {
        return name;
    }

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

    public void add(OrganizationComponent organizationComponent) {
        throw new UnsupportedOperationException();
    }

    public void remove(OrganizationComponent organizationComponent) {
        throw new UnsupportedOperationException();
    }

    public abstract void print();
}
// Composite 非叶子节点
public class University extends OrganizationComponent {
    List organizationComponentList = new ArrayList<>();

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

    @Override
    public void add(OrganizationComponent organizationComponent) {
        organizationComponentList.add(organizationComponent);
    }

    @Override
    public void remove(OrganizationComponent organizationComponent) {
        organizationComponent.remove(organizationComponent);
    }

    @Override
    public void print() {
        for (OrganizationComponent organizationComponent : organizationComponentList) {
            organizationComponent.print();
        }
    }
}
public class College extends OrganizationComponent {
    List organizationComponentList = new ArrayList<>();

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

    @Override
    public void add(OrganizationComponent organizationComponent) {
        organizationComponentList.add(organizationComponent);
    }

    @Override
    public void remove(OrganizationComponent organizationComponent) {
        organizationComponent.remove(organizationComponent);
    }

    @Override
    public void print() {
        System.out.println("=============" + getName() + "=============");
        for (OrganizationComponent organizationComponent : organizationComponentList) {
            organizationComponent.print();
        }
    }
}
// Leaf 叶子结点
public class Major extends OrganizationComponent {
    public Major(String name) {
        super(name);
    }

    @Override
    public void print() {
        System.out.println(getName());
    }
}
// 客户端
public class Client {
    public static void main(String[] args) {
        //大学
        OrganizationComponent university = new University("清华大学");
        //学院
        OrganizationComponent computerCollege = new College("计算机学院");
        OrganizationComponent infoEngineerCollege = new College("信息工程学院");
        //专业
        computerCollege.add(new Major("软件工程"));
        computerCollege.add(new Major("网络工程"));
        computerCollege.add(new Major("计算机科学与技术"));
        infoEngineerCollege.add(new Major("通信工程"));
        infoEngineerCollege.add(new Major("信息工程"));

        university.add(computerCollege);
        university.add(infoEngineerCollege);
        university.print();
        //=============计算机学院=============
        //软件工程
        //网络工程
        //计算机科学与技术
        //=============信息工程学院=============
        //通信工程
        //信息工程
    }
}

注意事项和细节

●1)简化客户端操作:客户端只需要面对一致的对象,而不用考虑整体部分或者节点叶子的问题
●2)具有较强扩展性:当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动
●3)方便创建复杂的层次结构:客户端不用理会组合里面的组成细节,容易添加节点或者叶子,从而创建出复杂的树形结构
●4)需要遍历组织机构,或者处理的对象具有树形结构时,非常适合使用组合模式
●5)要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式


外观模式

外观模式基本介绍

外观模式(Facade),也叫过程模式

外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节,这个接口使得这一子系统更加容易使用

原理类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第38张图片

原理类图的说明(外观模式的角色)

  • 1)外观类(Facade):为调用端提供统一的调用接口,外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当子系统对象
  • 2)调用者(Client):外观接口的调用者
  • 3)子系统的集合:指模块或者子系统,处理 Facade 对象指派的任务,是功能的实际提供者

传统方式解决影院管理

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第39张图片

 使用外观模式解决:

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第40张图片

外观模式的注意事项和细节

  • 1)外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
  • 2)外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易维护和扩展
  • 3)通过合理的使用外观模式,可以帮我们更好的划分访问的层次
  • 4)当系统需要进行分层设计时,可以考虑使用 Facade 模式
  • 5)在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个 Facade 类,来提供遗留系统的比较清晰简单的接口,让新系统与 Facade 类交互,提高复用性
  • 6)不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。要以让系统有层次,利于维护为目的

享元模式

展示网站项目需求

小型的外包项目,给客户 A 做一个产品展示网站,客户 A 的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同:

  • 1)有客户要求以新闻的形式发布
  • 2)有客户人要求以博客的形式发布
  • 3)有客户希望以微信公众号的形式发布

传统方案解决网站展现项目

  • 1)直接复制粘贴一份,然后根据客户不同要求,进行定制修改
  • 2)给每个网站租用一个空间
  • 3)方案设计示意图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第41张图片

传统方案解决网站展现项目-问题分析

  • 1)需要的网站结构相似度很高,而且都不是高问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费
  • 2)解决思路:整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源
  • 3)对于代码来说,由于是一份实例,维护和扩展都更加容易
  • 4)上面的解决思路就可以使用享元模式来解决

享元模式基本介绍

  • 1)享元模式(Flyweight Pattern)也叫蝇量模式:运用共享技术有效地支持大量细粒度的对象
  • 2)常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个
  • 3)享元模式能够解决重复对象的内存浪费的问题。当系统中有大量相似对象,需要缓冲池时,不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率
  • 4)享元模式经典的应用场景就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第42张图片

享元模式的原理类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第43张图片

对原理图的说明——即模式的角色和职责

  • 1)Flyweight:抽象的享元角色,是抽象的产品类,同时定义出对象的外部状态和内部状态的接口和实现
  • 2)ConcreteFlyweight:具体的享元角色,是具体的产品类,实现抽象角色定义的相关业务
  • 3)UnsharedConcreteFlyweight:不可共享的角色,一般不会出现在享元工厂中
  • 4)FlyweightFactory:享元工厂类,用于构建一个池容器(集合),同时提供从池中获取对象的方法

内部状态和外部状态

比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一点。所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同。当我们落子后,落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态

  • 1)享元模式提出了两个要求:细粒度和共享对象。即将对象的信息分为两个部分:内部状态和外部状态
  • 2)内部状态:指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
  • 3)外部状态:指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态

举个例子:围模理论上有 361 个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生。因为内存空间有限,一台服务器很难支持更多的玩家玩围模游戏。如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题

享元模式解决网站展现项目

原理类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第44张图片

UML 类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第45张图片

核心代码

/**
 * 内部状态,共享角色
 */
public enum Type {
    新闻,
    博客,
    微信公众号
}
/**
 * 外部状态,非共享角色
 */
public class User {
    private String name;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
/**
 * 抽象的享元角色
 */
public abstract class Website {
    public abstract void use(User user);
}
/**
 * 具体的享元角色
 */
public class ConcreteWebsite extends Website {
    private Type type;

    public ConcreteWebsite(Type type) {
        this.type = type;
    }

    @Override
    public void use(User user) {
        System.out.println("网站正在使用中:类型为" + type.name() + ",使用者为" + user.getName());
    }
}
/**
 * 享元工厂类
 */
public class WebsiteFactory {
    private static Map pool = new HashMap<>();

    public static Website getWebsiteCategory(Type type) {
        if (pool.get(type) == null) {
            pool.put(type, new ConcreteWebsite(type));
        }
        return pool.get(type);
    }

    public static Integer getSize() {
        return pool.size();
    }
}

测试代码: 

public class Client {

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		// 创建一个工厂类
		WebSiteFactory factory = new WebSiteFactory();

		// 客户要一个以新闻形式发布的网站
		WebSite webSite1 = factory.getWebSiteCategory("新闻");


		webSite1.use(new User("tom"));

		// 客户要一个以博客形式发布的网站
		WebSite webSite2 = factory.getWebSiteCategory("博客");

		webSite2.use(new User("jack"));

		// 客户要一个以博客形式发布的网站
		WebSite webSite3 = factory.getWebSiteCategory("博客");

		webSite3.use(new User("smith"));

		// 客户要一个以博客形式发布的网站
		WebSite webSite4 = factory.getWebSiteCategory("博客");

		webSite4.use(new User("king"));

		System.out.println("网站的分类共 = " + factory.getWebSiteCount());
	}

}

结果: 

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第46张图片

Integer 源码分析

首先先看一段代码测试

Integer x = Integer.valueOf(127);
Integer y = new Integer(127);
Integer z = Integer.valueOf(127);
Integer w = new Integer(127);
System.out.println(x.equals(y)); // true
System.out.println(x == y);      // false
System.out.println(x == z);      // true
System.out.println(w == x);      // false
System.out.println(w == y);      // false

我们知道:equals比较的是对象的内容,==比较的是对象的实例
●x.equals(y)结果为true:比较的是大小,所以结果为true
●x == y、w == x、w == y结果为false:由于 y 是 new 出来的,所以结果为false
●x == z结果为true:这是为什么呢???
Integer对象的valueOf方法源码

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第47张图片

这里的low和high是多少呢?

 Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第48张图片

 通过IntegerCache中源码大概基本分析出

●low为-128
●high为127

所以当Integer在[-128, 127]时,会返回IntegerCache的cache[]数组内容;否则,valueOf方法相当于new Integer了

也就是说,Integer.valueOf(x)方法使用的就是享元模式

另外,我们也可以分析出:

●当数值范围在时,使用方法执行速度比更快[-128, 127]valueOfnew


享元模式的注意事项和细节

●1)在享元模式这样理解,“享”就表示共享,“元”表示对象
●2)系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式
●3)用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用 HashMap/HashTable 存储
●4)享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
●5)享元模式提高了系统的复杂度,需要分离出内部状态和外部状态。而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方
●6)使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制
●7)享元模式经典的应用场景是需要缓冲池的场景,比如 String 常量池、数据库连接池


代理模式:

代理模式的基本介绍

  • 1)代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象
  • 2)这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能
  • 3)被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
  • 4)代理模式有不同的形式,主要有三种:
    • 静态代理
    • 动态代理:JDK 代理、接口代理
    • Cglib 代理:可以在内存动态的创建对象,而不需要实现接口,它是属于动态代理的范畴

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第49张图片

静态代理

基本介绍

       静态代理在使里时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现租同的接口或者是继承和同父类—
应用实例

应用实例

  • 1)定义一个接口:ITeacherDao
  • 2)目标对象TeacherDAO实现接口ITeacherDAO
  • 3)使用静态代理方式,就需要在代理对象TeacherDAOProxy中也实现ITeacherDAO
  • 4)调用的时候通过调用代理对象的方法来调用目标对象
  • 5)特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法

UML 类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第50张图片

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第51张图片

核心代码

/**
 * 代理接口
 */
public interface ITeacherDao {
    void teach();
}
/**
 * 被代理对象
 */
public class TeacherDao implements ITeacherDao {
    @Override
    public void teach() {
        System.out.println("老师授课中...");
    }
}
/**
 * 代理对象
 */
public class TeacherDaoProxy implements ITeacherDao {
    private ITeacherDao iTeacherDao;

    public TeacherDaoProxy(ITeacherDao iTeacherDao) {
        this.iTeacherDao = iTeacherDao;
    }

    @Override
    public void teach() {
        System.out.println("准备授课...");
        iTeacherDao.teach();
        System.out.println("结束授课...");
    }
}

调用代理

//创建被代理对象
TeacherDao teacherDao = new TeacherDao();
//创建代理对象,聚合被代理对象
TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);
//通过代理对象,调用被代理对象的方法
teacherDaoProxy.teach();

静态代理优缺点

  • 1)优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展
  • 2)缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
  • 3)缺点:一旦接口增加方法,目标对象与代理对象都要维护

动态代理

基本介绍

  • 1)代理对象不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
  • 2)代理对象的生成,是利用 JDK 的 APl,动态的在内存中构建代理对象
  • 3)动态代理也叫做:JDK 代理、接口代理

JDK 中生成代理对象的 API

  • 1)代理类所在包:java.lang.reflect.Proxy
  • 2)JDK 实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:
static Object newProxylnstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)

UML 类图 

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第52张图片

核心代码 

// ITeacherDao与TeacherDao同上
/**
 * 代理工厂
 */
public class TeacherFactory {
    /**
     * 目标对象
     */
    private Object target;

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

    public Object newProxyInstance() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("JDK代理授课开始...");
                Object returnVal = method.invoke(target, args);
                System.out.println("JDK代理授课结束...");
                return returnVal;
            }
        });
    }
}

其中几个参数

  • 1)ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法固定
  • 2)Class[] interfaces:目标对象实现的接口类型,使用泛型方法确认类型
  • 3)InvocationHandler h:事情处理,执行目标对象的方法时触发事情处理器方法,把当前执行的目标对象方法作为参数传入

Cglib 代理

基本介绍

  • 1)静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理——这就是 Cglib 代理
  • 2)Cglib 代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将 Cglib 代理归属到动态代理。
  • 3)Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口。它广泛的被许多 AOP 的框架使用,例如 Spring AOP,实现方法拦截
  • 4)在 AOP 编程中如何选择代理模式:
    • 目标对象需要实现接口,用 JDK 代理
    • 目标对象不需要实现接口,用 Cglib 代理
  • 5)Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类

实现步骤

  • 1)需要引入cglib的 jar 文件

    Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第53张图片

  • 2)在内存中动态构建子类,注意代理的类不能为final,否则报错java.lang.IllegalArgumentException
  • 3)目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法

应用实例

UML 类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第54张图片

 核心代码

/**
 * 被代理对象
 */
public class TeacherDao {
    public String teach() {
        System.out.println("老师授课中...");
        return "Good";
    }
}

/**
 * 代理工厂类
 */
public class ProxyFactory implements MethodInterceptor {
    /**
     * 目标对象
     */
    private Object target;

    /**
     * 构造函数
     *
     * @param target
     */
    public ProxyFactory(Object target) {
        this.target = target;
    }

    /**
     * 返回代理对象
     *
     * @return
     */
    public Object getProxyInstance() {
        // 1、创建工具类
        Enhancer enhancer = new Enhancer();
        // 2、设置父类
        enhancer.setSuperclass(target.getClass());
        // 3、设置回调函数
        enhancer.setCallback(this);
        // 4、创建子类对象,即代理对象
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("cglib代理开始...");
        Object retVal = method.invoke(target, args);
        System.out.println("cglib代理结束...");
        return retVal;
    }
}

调用代理 

//创建目标对象
TeacherDao teacherDao = new TeacherDao();
//通过代理工厂创建代理对象
TeacherDao proxyInstance = (TeacherDao) new ProxyFactory(teacherDao).getProxyInstance();
//通过代理对象调用目标对象方法
String retVal = proxyInstance.teach();
System.out.println("retVal=" + retVal);

代理模式的变体

几种常见的代理模式介绍一几种变体

  • 1)防火墙代理:内网通过代理穿透防火墙,实现对公网的访问
  • 2)缓存代理:比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则 ok;如果取不到资源,再到公网或者数据库取,然后缓存
  • 3)远程代理:远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息

  • 4)同步代理:主要使用在多线程编程中,完成多线程间同步工作


行为型模式

模版模式

豆浆制作问题

编写制作豆浆的程序,说明如下:

  • 1)制作豆浆的流程选材 ----> 添加配料 ----> 浸泡 ----> 放到豆浆机打碎
  • 2)通过添加不同的配料,可以制作出不同口味的豆浆
  • 3)选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的
  • 4)请使用模板方法模式完成

说明:因为模板方法模式比较简单,很容易就想到这个方案,因此就直接使用,不再使用传统的方案来引出模板方法模式

基本介绍

  • 1)模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行
  • 2)简单说,模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤
  • 3)这种类型的设计模式属于行为型模式

原理类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第55张图片

对原理类图的说明——即模板方法模式的角色和职责

  • AbstractClass抽象类中实现了模板方法,定义了算法的骨架,具体子类需要去实现其抽象方法或重写其中方法
  • ConcreteClass实现了抽象方法,已完成算法中特定子类的步骤

模板模式解决豆浆制作问题

UML 类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第56张图片

核心代码

/**
 * 抽象方法
 */
public abstract class SoyaMilk {
    /**
     * 模板方法,定义为final禁止覆写
     */
    public final void make() {
        System.out.println(">>>>>>豆浆制作开始<<<<<<");
        useSoyBean();
        addIngredients();
        soak();
        mash();
        System.out.println(">>>>>>豆浆制作结束<<<<<<");
    }

    protected void useSoyBean() {
        System.out.println("Step1. 选用上好的黄豆.");
    }

    protected abstract void addIngredients();

    protected void soak() {
        System.out.println("Step3. 对黄豆和配料进行水洗浸泡.");
    }

    protected void mash() {
        System.out.println("Step4. 将充分浸泡过的黄豆和配料放入豆浆机中,开始打豆浆.");
    }
}
/**
 * 花生豆浆
 */
public class PeanutSoyaMilk extends SoyaMilk {
    public PeanutSoyaMilk() {
        System.out.println("============花生豆浆============");
    }
    @Override
    protected void addIngredients() {
        System.out.println("Step2. 加入上好的花生.");
    }
}
/**
 * 红豆豆浆
 */
public class RedBeanSoyaMilk extends SoyaMilk {
    public RedBeanSoyaMilk() {
        System.out.println("============红豆豆浆============");
    }
    @Override
    protected void addIngredients() {
        System.out.println("Step2. 加入上好的红豆.");
    }
}
/**
 * 芝麻豆浆
 */
public class SesameSoyaMilk extends SoyaMilk {
        public SesameSoyaMilk() {
        System.out.println("============芝麻豆浆============");
    }
    @Override
    protected void addIngredients() {
        System.out.println("Step2. 加入上好的芝麻.");
    }
}

 调用模板方法:

SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
peanutSoyaMilk.make();
SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
redBeanSoyaMilk.make();
SoyaMilk sesameSoyaMilk = new SesameSoyaMilk();
sesameSoyaMilk.make();
/*
============花生豆浆============
>>>>>>豆浆制作开始<<<<<<
Step1. 选用上好的黄豆.
Step2. 加入上好的花生.
Step3. 对黄豆和配料进行水洗浸泡.
Step4. 将充分浸泡过的黄豆和配料放入豆浆机中,开始打豆浆.
>>>>>>豆浆制作结束<<<<<<
============红豆豆浆============
>>>>>>豆浆制作开始<<<<<<
Step1. 选用上好的黄豆.
Step2. 加入上好的红豆.
Step3. 对黄豆和配料进行水洗浸泡.
Step4. 将充分浸泡过的黄豆和配料放入豆浆机中,开始打豆浆.
>>>>>>豆浆制作结束<<<<<<
============芝麻豆浆============
>>>>>>豆浆制作开始<<<<<<
Step1. 选用上好的黄豆.
Step2. 加入上好的芝麻.
Step3. 对黄豆和配料进行水洗浸泡.
Step4. 将充分浸泡过的黄豆和配料放入豆浆机中,开始打豆浆.
>>>>>>豆浆制作结束<<<<<<
*/

注意事项和细节

  • 1)基本思想:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
  • 2)实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用
  • 3)既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现
  • 4)不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
  • 5)一般模板方法都加上final关键字,防止子类重写模板方法
  • 6)使用场景:当要完成在某个过程,该过程要执行一系列步骤,这一系列的步骤基本相同,但其个别步骤在实现时可能不同,通常考虑用模板方法模式来处理


命令模式

智能生活项目需求


●1)我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,只要在手机上安装 APP 就可以控制这些家电的工作
●2)这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个 APP 分别控制,我们希望只要一个 APP 就可以控制全部智能家电
●3)要实现一个 APP 控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口给 APP 调用,这时就可以考虑使用命令模式
●4)命令模式可将“动作的请求者”从“动作的执行者”对象中解耦出来
●5)在我们的例子中,动作的请求者是手机 APP,动作的执行者是每个厂商的一个家电产品


基本介绍

●1)命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可。此时可以使用命令模式来进行设计
●2)命令模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦
●3)在命令模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作
●4)通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色:
○将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)
○Invoker是调用者(将军),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有接收对

原理类图
Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第57张图片



命令模式的角色及职责

●Invoker调用者角色,只需要发布命令就可以控制接收者的行为
●Receiver接收者角色,知道如何实施或执行请求的相关操作
●Command命令角色,需要执行的所有命令都定义在这里,可以是接口或抽象类
●ConcreteCommand具体的命令角色,将一个接收者和一个动作绑定,调用接收者相应的操作,实现execute


3、命令模式解决智能生活项目

UML 类图
Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第58张图片



核心代码

/**
 * 命令角色
 */
public interface Command {
    void execute();

    void undo();
}
/**
 * 空命令,什么也不干
 */
public class NonCommand implements Command {
    @Override
    public void execute() {

    }

    @Override
    public void undo() {

    }
}
/**
 * 调用者
 */
public class RemoteController {
    private Command[] onCommands;
    private Command[] offCommands;
    private Command restoreCommand;

    public RemoteController() {
        onCommands = new Command[5];
        offCommands = new Command[5];
        for (int i = 0; i < 5; i++) {
            onCommands[i] = new NonCommand();
            offCommands[i] = new NonCommand();
        }
    }

    public void setCommands(int no, Command onCommand, Command offCommand) {
        onCommands[no] = onCommand;
        offCommands[no] = offCommand;
    }

    public void onBtnCommand(int no) {
        restoreCommand = onCommands[no];
        restoreCommand.execute();
    }

    public void offBtnCommand(int no) {
        restoreCommand = offCommands[no];
        restoreCommand.execute();
    }

    public void undoBtnCommand() {
        restoreCommand.undo();
    }
}
/**
 * 接收者
 */
public class LightReceiver {
    public void on() {
        System.out.println("电灯打开了...");
    }

    public void off() {
        System.out.println("电灯关闭了...");
    }
}
public class TVReceiver {
    public void on() {
        System.out.println("电视打开了...");
    }

    public void off() {
        System.out.println("电视关闭了...");
    }
}
/**
 * 具体的命令角色
 */
public class LightOnCommand implements Command{
    private LightReceiver light;

    public LightOnCommand(LightReceiver light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.on();
    }

    @Override
    public void undo() {
        light.off();
    }
}
public class LightOffCommand implements Command {
    private LightReceiver light;

    public LightOffCommand(LightReceiver light) {
        this.light = light;
    }

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

    @Override
    public void undo() {
        light.on();
    }
}
public class TVOnCommand implements Command {
    private TVReceiver tv;

    public TVOnCommand(TVReceiver tv) {
        this.tv = tv;
    }

    @Override
    public void execute() {
        tv.on();
    }

    @Override
    public void undo() {
        tv.off();
    }
}
public class TVOffCommand implements Command {
    private TVReceiver tv;

    public TVOffCommand(TVReceiver tv) {
        this.tv = tv;
    }

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

    @Override
    public void undo() {
        tv.on();
    }
}

 调用命令

// 初始化遥控器
RemoteController remoteController = new RemoteController();
// 操作电灯
int no = 0;
LightReceiver light = new LightReceiver();
remoteController.setCommands(no, new LightOnCommand(light), new LightOffCommand(light));
remoteController.onBtnCommand(no);
remoteController.offBtnCommand(no);
remoteController.undoBtnCommand();
// 操作电视
no = 1;
TVReceiver tv = new TVReceiver();
remoteController.setCommands(no, new TVOnCommand(tv), new TVOffCommand(tv));
remoteController.onBtnCommand(no);
remoteController.offBtnCommand(no);
remoteController.undoBtnCommand();
// 电灯打开了...
// 电灯关闭了...
// 电灯打开了...
// 电视打开了...
// 电视关闭了...
// 电视打开了...

命令模式的注意事项和细节

  • 1)将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:“请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用
  • 2)容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
  • 3)容易实现对请求的撤销和重做
  • 4)不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在使用的时候要注意
  • 5)空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦
  • 6)命令模式经典的应用场景:界面的一个按钮对应一条命令、模拟 CMD(DOS命令)订单的撤销/恢复、触发-反馈机制


访问者模式

基本介绍

  • 1)访问者模式(Visitor Pattern),封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作
  • 2)主要将数据结构与数据操作分离,解决数据结构和操作耦合性问题
  • 3)访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者的接口
  • 4)访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),同时需要避免让这些操作“污染“这些对象的类,可以选用访问者模式解决

完成测评系统需求

        将人、观众分为男人和女人,对歌手进行测评,当看完某个歌手表演后,得到他们对该歌手不同的评价(评价有不同的种类,比如成功、失败等)

 传统方案

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第59张图片

传统方式的问题分析

  • 1)如果系统比较小,还是 ok 的,但是考虑系统增加越来越多新的功能时,对代码改动较大,违反了 OCP 原则,不利于维护
  • 2)扩展性不好,比如增加了新的人员类型,或者管理方法,都不好做
  • 3)引出我们会使用新的设计模式——访问者模式

 访问者模式实现测评系统

 uml类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第60张图片

 核心代码

/**
 * 抽象访问者
 */
public abstract class Action {
    public abstract void getManResult(Man man);

    public abstract void getWomanResult(Woman woman);
}
/**
 * 具体访问者
 */
public class Success extends Action {
    @Override
    public void getManResult(Man man) {
        System.out.println("男生给了通过");
    }

    @Override
    public void getWomanResult(Woman woman) {
        System.out.println("女生给了通过");
    }
}
public class Fail extends Action {
    @Override
    public void getManResult(Man man) {
        System.out.println("男生给了不通过");
    }

    @Override
    public void getWomanResult(Woman woman) {
        System.out.println("女生给了不通过");
    }
}
public class Wait extends Action {
    @Override
    public void getManResult(Man man) {
        System.out.println("男生给了待定");
    }

    @Override
    public void getWomanResult(Woman woman) {
        System.out.println("女生给了待定");
    }
}
/**
 * 抽象元素
 */
public abstract class Person {
    public abstract void accept(Action action);
}
/**
 * 具体元素
 */
public class Man extends Person {
    @Override
    public void accept(Action action) {
        action.getManResult(this);
    }
}
public class Woman extends Person {
    @Override
    public void accept(Action action) {
        action.getWomanResult(this);
    }
}
/**
 * 对象结构
 */
public class ObjectStructure {
    private List personList = new ArrayList<>();

    public void attach(Person p) {
        personList.add(p);
    }

    public void detach(Person p) {
        personList.remove(p);
    }

    public void display(Action action) {
        for (Person person : personList) {
            person.accept(action);
        }
    }
}

测试代码 

ObjectStructure objectStructure = new ObjectStructure();
objectStructure.attach(new Man());
objectStructure.attach(new Woman());
objectStructure.display(new Success());
System.out.println("============");
objectStructure.display(new Fail());
System.out.println("============");
objectStructure.display(new Wait());
//男生给了通过
//女生给了通过
//============
//男生给了不通过
//女生给了不通过
//============
//男生给了待定
//女生给了待定

双分派

该例中我们使用到了双分派

  • 第一次分派:首先在客户端程序中,将具体状态作为参数传递Woman
  • 第二次分派:然后Woman类调用作为参数的具体方法getWomanResult,同时将自己this作为参数传入

所谓双分派是指不管类怎么变化,我们都能找到期望的方法运行

双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型

以上述实例为例,假设我们要添加一个Wait的状态类,考察Man类和Woman类的反应

由于使用了双分派,只需增加一个 Action 子类即可在客户端调用即可,不需要改动任何其他类的代码

访问者模式的注意事项和细节


优点
●1)访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高
●2)访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据结构相对稳定的系统

缺点
●1)具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的,这样造成了具体元素变更比较困难
●2)违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素
●3)因此,如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的


迭代器模式

学校院系结构展示需求

编写程序展示一个学校院系结构,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。如图:

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第61张图片

传统方案分析

  • 1)将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的
  • 2)实际上我们的要求是:在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系,因此这种方案,不能很好实现的遍历的操作
  • 3)解决方案:迭代器模式

基本介绍

  • 1)迭代器模式(lterator Pattern)是常用的设计模式,属于行为型模式
  • 2)如果我们的集合元素是用不同方式实现的,有数组、集合或者其他方式。当客户端要遍历这些集合元素的时候就要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决
  • 3)迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,不需要知道集合对象的底层表示,即:不暴露其内部的结构

原理类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第62张图片

迭代器模式的角色及职责

  • Iterator迭代器接口:系统提供,含有hasNextnextremove
  • ConcreteIterator具体的迭代器:管理相关的迭代
  • Aggregate聚合接口:将客户端和具体的聚合解耦
  • ConcreteAggregate具体的聚合类:提供一个方法,返回可以正确遍历集合的迭代器
  • Client客户端:通过Iterator迭代器接口和Aggregate聚合接口依赖其具体的迭代器和聚合子类

迭代器完成学校院系结构展示需求

UML 类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第63张图片

核心代码

具体的迭代器

/**
 * 计算机学院迭代器类
 */
public class ComputerCollegeIterator implements Iterator {
    private Department[] departments;
    private Integer position = -1;

    public ComputerCollegeIterator(Department[] departments) {
        this.departments = departments;
    }

    @Override
    public boolean hasNext() {
        return position + 1 < departments.length && departments[position + 1] != null;
    }

    @Override
    public Object next() {
        return departments[++position];
    }
}
/**
 * 信息学院迭代器类
 */
public class InfoCollegeIterator implements Iterator {
    private List departments;
    private Integer position = -1;

    public InfoCollegeIterator(List departments) {
        this.departments = departments;
    }

    @Override
    public boolean hasNext() {
        return position + 1 < departments.size();
    }

    @Override
    public Object next() {
        return departments.get(++position);
    }
}

聚合接口

public interface College {
    String getName();
    
    Iterator createIterator();
}

具体的聚合类 

/**
 * 计算机学院
 */
public class ComputerCollege implements College {
    private Department[] departments;
    private Integer position = 0;

    public ComputerCollege() {
        departments = new Department[5];
        departments[position++] = new Department("Java专业");
        departments[position++] = new Department("PHP专业");
        departments[position++] = new Department("Python专业");
    }

    @Override
    public String getName() {
        return "计算机学院";
    }

    @Override
    public Iterator createIterator() {
        return new ComputerCollegeIterator(departments);
    }
}
/**
 * 信息学院
 */
public class InfoCollege implements College {
    private List departments;

    public InfoCollege() {
        departments = new ArrayList<>();
        departments.add(new Department("信息安全专业"));
        departments.add(new Department("网络安全专业"));
        departments.add(new Department("服务器安全专业"));
    }

    @Override
    public String getName() {
        return "信息学院";
    }

    @Override
    public Iterator createIterator() {
        return new InfoCollegeIterator(departments);
    }
}

 输出类

public class OutPutImpl {

    public OutPutImpl() {
    }

    public void printCollege(List collegeList) {
        Iterator iterator = collegeList.iterator();
        while (iterator.hasNext()) {
            College college = iterator.next();
            System.out.println("============" + college.getName() + "============");
            printDepartment(college);
        }
    }

    private void printDepartment(College college) {
        Iterator iterator = college.createIterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next().getName());
        }
    }
}

 调用测试

List collegeList = new ArrayList<>();
collegeList.add(new ComputerCollege());
collegeList.add(new InfoCollege());
new OutPutImpl().printCollege(collegeList);

 打印结果

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第64张图片

ArrayList 集合源码分析

UML 类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第65张图片

角色分析说明

●Iterator迭代器接口:由系统提供,定义了hasNext()和next()等方法
●Itr具体的迭代器实现类:作为ArrayList的内部类存在,实现了Iterator接口的hasNext()和next()等方法
●List聚合接口:定义了iterator()方法,返回一个迭代器接口对象
●ArrayList具体的聚合类:实现了iterator()方法

迭代器模式提供了一个不同集合类型(如ArrayList、LinkedList等)的统一遍历解决方案 


迭代器模式的注意事项和细节

优点

●1)提供一个统一的方法遍历对象,客户不用再考虑聚合的类型,使用一种方法就可以遍历对象了
●2)隐藏了聚合的内部结构,客户端要遍历聚合的时候只能取到迭代器,而不会知道聚合的具体组成
●3)提供了一种设计思想,就是一个类应该只有一个引起变化的原因(单一责任原则)。在聚合类中,我们把迭代器分开,就是要把管理对象集合和遍历对象集合的责任分开,这样一来集合改变的话,只影响到聚合对象。而如果遍历方式改变的话,只影响到了迭代器
●4)当要展示一组相似对象,或者遍历一组相同对象时使用,适合使用迭代器模式

缺点

●每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类


观察者模式

天气预报需求

具体要求如下:

  • 1)气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)
  • 2)需要设计开放型 API,便于其他第三方也能接入气象站获取数据
  • 3)提供温度、气压和湿度的接口
  • 4)测量数据更新时,要能实时的通知给第三方

天气预报需求方案之普通方案

WeatherData类

通过对气象站项目的分析,我们可以初步设计出一个WeatherData

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第66张图片

  • 1)通过getXxx方法,可以让第三方接入,并得到相关信息
  • 2)当数据有更新时,气象站通过调用dataChange()去更新数据,当第三方再次获取时,就能得到最新数据,当然也可以推送

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第67张图片

CurrentConditions(当前的天气情况)可以理解成是我们气象局的网站

核心代码

气象网站类

/**
 * 当前的天气情况:可以理解成是气象局的网站
 */
public class CurrentConditions {
    private Float temperature;
    private Float pressure;
    private Float humidity;

    /**
     * 更新天气情况,通过推送的方式,由 WeatherData 调用
     *
     * @param temperature
     * @param pressure
     * @param humidity
     */
    public void update(Float temperature, Float pressure, Float humidity) {
        // 更新最新天气数据
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        // 展示最新天气数据
        display();
    }

    /**
     * 公告板展示天气情况
     */
    public void display() {
        System.out.println("============最新天气============");
        System.out.println("*** 当前温度:" + this.temperature + " ℃ ***");
        System.out.println("*** 当前气压:" + this.pressure + " kPa ***");
        System.out.println("*** 当前湿度:" + this.humidity + " %RH ***");
    }
}

 气象数据类

/**
 * 核心类
 * 1、包含最新的天气信息情况
 * 2、含有 CurrentConditions 对象
 * 3、当数据更新时,主动调用 CurrentConditions 的 update() 方法
 */
public class WeatherData {
    private Float temperature;
    private Float pressure;
    private Float humidity;
    private CurrentConditions conditions;

    /**
     * 传入 CurrentConditions 对象
     *
     * @param conditions
     */
    public WeatherData(CurrentConditions conditions) {
        this.conditions = conditions;
    }

    public Float getTemperature() {
        return temperature;
    }

    public Float getPressure() {
        return pressure;
    }

    public Float getHumidity() {
        return humidity;
    }

    /**
     * 推送天气数据到网站
     */
    public void dataChange() {
        conditions.update(getTemperature(), getPressure(), getHumidity());
    }

    /**
     * 当天气数据发生变化时进行更新
     *
     * @param temperature
     * @param pressure
     * @param humidity
     */
    public void setData(Float temperature, Float pressure, Float humidity) {
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        dataChange();
    }
}

客户端调用 

// 创建气象网站对象
CurrentConditions currentConditions = new CurrentConditions();
// 创建气象数据对象,并传入气象网站对象
WeatherData weatherData = new WeatherData(currentConditions);
// 天气发生变化时,更新最新的气象数据
weatherData.setData(10f, 150f, 40f);
//weatherData.setData(15f, 130f, 60f);
//weatherData.setData(13f, 160f, 20f);

 测试结果

============最新天气============
*** 当前温度:10.0 ℃ ***
*** 当前气压:150.0 kPa ***
*** 当前湿度:40.0 %RH ***
============最新天气============
*** 当前温度:15.0 ℃ ***
*** 当前气压:130.0 kPa ***
*** 当前湿度:60.0 %RH ***
============最新天气============
*** 当前温度:13.0 ℃ ***
*** 当前气压:160.0 kPa ***
*** 当前湿度:20.0 %RH ***

问题分析

  • 1)其他第三方接入气象站获取数据的问题
  • 2)无法在运行时动态的添加第三方(新浪网站)
  • 3)违反OCP原则 => 观察者模式

WeatherData中增加第三方时,都需要创建对应的第三方公台板对象并加入到dataChange()方法中,既不是动态加入,也不利于维护

观察者模式原理

观察者模式类似订牛奶业务

  • 1)奶站 / 气象局:Subject
  • 2)用户 / 第三方网站:Observer

Subject:登记注册、移除和通知

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第68张图片

  • 1)registerObserver():注册
  • 2)removeObserver():移除
  • 3)notifyObservers():通知所有的注册的用户,根据不同需求,可以是更新数据,让用户来取,也可能是实施推送,看具体需求定

Observer:接收输入

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第69张图片

观察者模式:对象之间多对一依赖的一种设计方案,被依赖的对象为Subject,依赖的对象为ObserverSubject通知Observer变化,比如这里的奶站是Subject,是1的一方。用户是Observer,是多的一方

天气预报需求方案之观察者模式

UML 类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第70张图片

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第71张图片

核心代码

观察者对象Observer

/**
 * 观察者接口
 */
public interface Observer {
    void update(Float temperature, Float pressure, Float humidity);
}
/**
 * 观察者实现
 */
public class CurrentConditions implements Observer {
    private Float temperature;
    private Float pressure;
    private Float humidity;

    @Override
    public void update(Float temperature, Float pressure, Float humidity) {
        // 更新最新天气数据
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        // 展示最新天气数据
        display();
    }

    /**
     * 公告板展示天气情况
     */
    public void display() {
        System.out.println("============最新天气============");
        System.out.println("*** 当前温度:" + this.temperature + " ℃ ***");
        System.out.println("*** 当前气压:" + this.pressure + " kPa ***");
        System.out.println("*** 当前湿度:" + this.humidity + " %RH ***");
    }
}

 主体对象Subject

/**
 * 主体对象接口
 */
public interface Subject {
    void registerObserver(Observer o);

    void removeObserver(Observer o);

    void notifyObservers();
}
/**
 * 主体对象实现
 */
public class WeatherData implements Subject {
    private Float temperature;
    private Float pressure;
    private Float humidity;
    private List observerList;

    public WeatherData() {
        observerList = new ArrayList<>();
    }

    public Float getTemperature() {
        return temperature;
    }

    public Float getPressure() {
        return pressure;
    }

    public Float getHumidity() {
        return humidity;
    }

    /**
     * 推送天气数据到网站
     */
    public void dataChange() {
        notifyObservers();
    }

    /**
     * 当天气数据发生变化时进行更新
     *
     * @param temperature
     * @param pressure
     * @param humidity
     */
    public void setData(Float temperature, Float pressure, Float humidity) {
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        dataChange();
    }

    @Override
    public void registerObserver(Observer o) {
        observerList.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        if(o!= null && observerList.contains(o)) {
            observerList.remove(o);
        }
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observerList) {
            observer.update(temperature, pressure, humidity);
        }
    }
}

 观察者对象Observer

public interface Observer {
    void update(Float temperature, Float pressure, Float humidity);
}

调用测试 

// 创建气象网站对象
CurrentConditions currentConditions = new CurrentConditions();
// 创建气象数据对象
WeatherData weatherData = new WeatherData();
// 注册气象网站对象
weatherData.registerObserver(currentConditions);
// 天气发生变化时,更新最新的气象数据
weatherData.setData(10f, 150f, 40f);
//============最新天气============
//*** 当前温度:10.0 ℃ ***
//*** 当前气压:150.0 kPa ***
//*** 当前湿度:40.0 %RH ***

观察者模式的好处

  • 1)观察者模式设计后,会以集合的方式来管理用户Observer,包括注册、移除和通知
  • 2)这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类WeatherData不会修改代码,遵守了ocp原则

例如,我们新增SinaWebSiteBaiDuWebSite两个三方网站,接口气象局。此时三方只需实现相应接口即可,WeatherData不需要有任何的改变

/**
 * 新增的三方观察者对象——新浪网
 */
public class SinaWebSite implements Observer {
    private Float temperature;
    private Float pressure;
    private Float humidity;

    /**
     * 更新天气情况,通过推送的方式,由 WeatherData 调用
     *
     * @param temperature
     * @param pressure
     * @param humidity
     */
    @Override
    public void update(Float temperature, Float pressure, Float humidity) {
        // 更新最新天气数据
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        // 展示最新天气数据
        display();
    }

    /**
     * 公告板展示天气情况
     */
    public void display() {
        System.out.println("============新浪网-最新天气============");
        System.out.println("*** 新浪网-当前温度:" + this.temperature + " ℃ ***");
        System.out.println("*** 新浪网-当前气压:" + this.pressure + " kPa ***");
        System.out.println("*** 新浪网-当前湿度:" + this.humidity + " %RH ***");
    }
}
/**
 * 新增的三方观察者对象——百度网
 */
public class BaiDuWebSite implements Observer {
    private Float temperature;
    private Float pressure;
    private Float humidity;

    /**
     * 更新天气情况,通过推送的方式,由 WeatherData 调用
     *
     * @param temperature
     * @param pressure
     * @param humidity
     */
    @Override
    public void update(Float temperature, Float pressure, Float humidity) {
        // 更新最新天气数据
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        // 展示最新天气数据
        display();
    }

    /**
     * 公告板展示天气情况
     */
    public void display() {
        System.out.println("============百度网-最新天气============");
        System.out.println("*** 百度网-当前温度:" + this.temperature + " ℃ ***");
        System.out.println("*** 百度网-当前气压:" + this.pressure + " kPa ***");
        System.out.println("*** 百度网-当前湿度:" + this.humidity + " %RH ***");
    }
}

调用测试 

// 新增三方气象网站,只需注册即可
weatherData.registerObserver(new SinaWebSite());
weatherData.registerObserver(new BaiDuWebSite());
// 天气发生变化时,更新最新的气象数据
weatherData.setData(15f, 120f, 80f);
//============最新天气============
//*** 当前温度:15.0 ℃ ***
//*** 当前气压:120.0 kPa ***
//*** 当前湿度:80.0 %RH ***
//============新浪网-最新天气============
//*** 新浪网-当前温度:15.0 ℃ ***
//*** 新浪网-当前气压:120.0 kPa ***
//*** 新浪网-当前湿度:80.0 %RH ***
//============百度网-最新天气============
//*** 百度网-当前温度:15.0 ℃ ***
//*** 百度网-当前气压:120.0 kPa ***
//*** 百度网-当前湿度:80.0 %RH ***

当三方网站不再需要时,只要做相应的移除即可 

// 移除气象网站
weatherData.removeObserver(currentConditions);
weatherData.setData(20f, 160f, 30f);
//============新浪网-最新天气============
//*** 新浪网-当前温度:20.0 ℃ ***
//*** 新浪网-当前气压:160.0 kPa ***
//*** 新浪网-当前湿度:30.0 %RH ***
//============百度网-最新天气============
//*** 百度网-当前温度:20.0 ℃ ***
//*** 百度网-当前气压:160.0 kPa ***
//*** 百度网-当前湿度:30.0 %RH ***


中介者模式

  • 1)中介者模式(Mediator Pattern),用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
  • 2)中介者模式属于行为型模式,使代码易于维护
  • 3)比如 MVC 模式,C(Controller控制器)是M(Model模型)和V(View视图)的中介者,在前后端交互时起到了中间人的作用

原理类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第72张图片

中介者模式角色及职责

  • Mediator【抽象中介者】:定义了同事对象到中介者对象的接口
  • ConcreteMediator【具体的中介者对象】:实现抽象中介者方法,需要知道所有具体的同事类,即以一个集合来管理HashMap,并接受某个同事对象消息,完成相应的任务
  • Colleague【抽象同事类】
  • ConcreteColleague【具体的同事类】:会有很多,只知道自己的行为,而不了解其他同事类的行为(方法),但他们都依赖中介者对象

中介者模式解决智能家庭管理问题


UML 类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第73张图片

智能家庭管理操作流程

  • 1)创建ConcreMediator对象
  • 2)创建各个同事类对象,比如:AlarmCoffeeMachineTV...
  • 3)在创建同事类对象时,直接通过构造器加入到colleagueMap
  • 4)同事类对象可以调用sendMessage,最终会去调用ConcreteMediatorgetMessage方法
  • 5)getMessage会根据接收到的同事对象发出的消息,来协调调用其它的同事对象,完成任务
  • 6)可以看到getMessage是核心方法,完成相应任务

核心代码

抽象中介者

public abstract class Mediator {
    public abstract void registerColleague(Colleague colleague);

    public abstract void getMsg(Integer state, String name);

    public abstract void sendMsg();
}

抽象同事类 

public abstract class Colleague {
    private Mediator mediator;

    public Colleague(Mediator mediator) {
        this.mediator = mediator;
    }

    public Mediator getMediator() {
        return this.mediator;
    }

    public void sendMsg(Integer state) {
        this.getMediator().getMsg(state, this.getClass().getSimpleName());
    }
}

 具体同事类

/**
 * 闹钟
 */
public class Alarm extends Colleague {
    public Alarm(Mediator mediator) {
        super(mediator);
        this.getMediator().registerColleague(this);
    }

    /**
     * 闹铃响起
     */
    public void openAlarm() {
        System.out.println(">>>闹铃响起");
        try {
            //模拟闹铃耗时
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        sendMsg(1);
    }

    /**
     * 闹铃关闭
     */
    public void closeAlarm() {
        System.out.println(">>>闹铃关闭");
        sendMsg(0);
    }
}
/**
 * 咖啡机
 */
public class CoffeeMachine extends Colleague {
    public CoffeeMachine(Mediator mediator) {
        super(mediator);
        this.getMediator().registerColleague(this);
    }

    /**
     * 煮咖啡
     */
    public void makeCoffee() {
        System.out.println(">>>煮咖啡中...");
        sendMsg(0);
        try {
            //模拟煮咖啡耗时
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 煮咖啡完毕
     */
    public void completeCoffee() {
        System.out.println(">>>咖啡已煮好");
        sendMsg(1);
    }
}
/**
 * 窗帘
 */
public class Curtain extends Colleague {
    public Curtain(Mediator mediator) {
        super(mediator);
        this.getMediator().registerColleague(this);
    }

    /**
     * 拉起窗帘
     */
    public void upCurtain() {
        System.out.println(">>>拉起窗帘...");
        sendMsg(1);
        try {
            //模拟拉起窗帘耗时
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 拉下窗帘
     */
    public void downCurtain() {
        System.out.println(">>>拉下窗帘...");
        sendMsg(0);
        try {
            //模拟拉下窗帘耗时
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
/**
 * 电视机
 */
public class TV extends Colleague {
    public TV(Mediator mediator) {
        super(mediator);
        this.getMediator().registerColleague(this);
    }

    /**
     * 打开电视
     */
    public void openTV() {
        System.out.println(">>>打开电视...");
        sendMsg(1);
    }

    /**
     * 关闭电视
     */
    public void closeTV() {
        System.out.println(">>>关闭电视...");
        sendMsg(0);
    }

    /**
     * 切换频道
     */
    public void switchChannel(Integer state) {
        System.out.println(">>>切换频道:" + state);
        sendMsg(state);
        try {
            //模拟看电视耗时
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 测试代码

//创建中介者
Mediator mediator = new ConcreteMediator();

//创建各个同事类,并加入Mediator中介者的Map对象中
Alarm alarm = new Alarm(mediator);
CoffeeMachine coffeeMachine = new CoffeeMachine(mediator);
Curtain curtain = new Curtain(mediator);
TV tv = new TV(mediator);

//闹钟响起
alarm.openAlarm();
coffeeMachine.completeCoffee();
tv.closeTV();

//>>>闹铃响起
//>>>闹铃关闭
//>>>煮咖啡中...
//>>>咖啡已煮好
//>>>拉下窗帘...
//>>>打开电视...
//>>>切换频道:101
//>>>关闭电视...
//>>>拉起窗帘...

中介者模式的注意事项和细节

优点

  • 1)多个类相互耦合,会形成网状结构,使用中介者模式将网状结构分离为星型结构,进行解耦
  • 2)减少类间依赖,降低了耦合,符合迪米特原则

缺点

  • 3)中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响
  • 4)如果设计不当,中介者对象本身变得过于复杂,这点在实际使用时,要特别注意

备忘录模式

备忘录模式基本介绍

●1)备忘录模式(Memento Pattern):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态
●2)可以这样理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情,或者是记录已经达成的共同意见的事情,以防忘记了。而在软件层面,备忘录模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作
●3)备忘录模式属于行为型模式

原理类图
Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第74张图片

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第75张图片

 
示例代码
 

/**
 * 源对象
 */
public class Originator {
    private String state;

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public Memento createMementor() {
        return new Memento(state);
    }

    public void revertStateFromMementor(Memento memento) {
        this.state = memento.getState();
    }
}
/**
 * 备忘录对象
 */
public class Memento {
    private String state;

    public Memento(String state) {
        this.state = state;
    }

    public String getState() {
        return state;
    }
}
/**
 * 守护者对象
 */
public class Caretaker {
    private List mementoList = new ArrayList<>();

    public void addMemento(Memento memento) {
        mementoList.add(memento);
    }

    public Memento getMemento(Integer index) {
        return mementoList.get(index);
    }
}

测试代码

Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
originator.setState("当前状态:" + " 状态#1 血量 100 ");
caretaker.addMemento(originator.createMementor());
System.out.println(originator.getState());
originator.setState("当前状态:" + " 状态#2 血量 80 ");
caretaker.addMemento(originator.createMementor());
System.out.println(originator.getState());
originator.setState("当前状态:" + " 状态#3 血量 60 ");
caretaker.addMemento(originator.createMementor());
System.out.println(originator.getState());

// 恢复到状态1
originator.revertStateFromMementor(caretaker.getMemento(0));
System.out.println("恢复状态:" + originator.getState());

//当前状态: 状态#1 血量 100
//当前状态: 状态#2 血量 80
//当前状态: 状态#3 血量 60
//恢复状态:当前状态: 状态#1 血量 100

备忘录模式中的角色和职责

  • Originator源对象:需要保存状态的对象
  • Memento备忘录对象:负责保存Originator内部状态
  • Caretaker守护者对象:负责存放多个Memento对象,使用集合管理,提高效率
  • 如果希望保存多个Originator对象的不同内部状态,也可以使用Map>

备忘录模式的注意事项和细节

优点

  • 1)给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态
  • 2)实现了信息的封装,使得用户不需要关心状态的保存细节

缺点

  • 3)如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存,这个需要注意

其他

  • 4)适用的应用场景:
    1. 后悔药
    2. 打游戏时的存档
    3. Windows里的ctrl+z
    4. IE中的后退
    5. 数据库的事务管理
  • 5)为了节约内存,备忘录模式可以和原型模式配合使用

解释器模式(Interpreter 模式)

  • 在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器
  • 解释器模式(Interpreter Pattern):是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)
  • 应用场景
  1. 应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
  2. 一些重复出现的问题可以用一种简单的语言来表达
  3. 一个简单语法需要解释的场景
  4. 这样的例子还有,比如编译器、运算表达式计算、正则表达式、机器人等

状态模式

APP 抽奖活动问题

编写程序完成 APP 抽奖活动具体要求如下:

  • 1)假如每参加一次这个活动要扣除用户 50 积分,中奖概率是 10%
  • 2)奖品数量固定,抽完就不能抽奖
  • 3)活动有四个状态:可以抽奖、不能抽奖、发放奖品和奖品领完
  • 4)活动的四个状态转换关系图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第76张图片

状态模式基本介绍

  • 1)状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换
  • 2)当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类

原理类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第77张图片

角色与职责

  • Context环境角色:维护一个State实例,这个实例定义了当前状态
  • State抽象状态角色:定义一个接口,封装与Context的一个特点接口相关行为
  • ConcreteState具体状态角色:实现一个与Context的一个状态相关行为

状态模式解决 APP 抽奖问题

  1. 应用实例要求完成 APP 抽奖活动项目,使用状态模式
  2. 思路分析和图解(类图)
  3. 定义出一个接口叫状态接口,每个状态都实现它
  4. 接口有扣除积分方法、抽奖方法、发放奖品方法

UML 类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第78张图片

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第79张图片

核心代码

抽象状态角色

/**
 * 抽象状态角色
 */
public interface State {
    Boolean reduceMoney();

    Boolean raffle();

    Boolean dispensePrize();
}

不能抽奖状态类

/**
 * 不能抽奖状态类
 */
public class NoRaffleState implements State {
    private RaffleActivity raffleActivity;
    // 模拟数据库积分值
    private int integral = 100;

    public NoRaffleState(RaffleActivity raffleActivity) {
        this.raffleActivity = raffleActivity;
    }

    @Override
    public Boolean reduceMoney() {
        if (integral < 50) {
            System.out.println("您的积分余额不足~");
            return false;
        }
        integral -= 50;
        raffleActivity.setCanRaffleState();
        System.out.println("已扣除50积分,可以进行抽奖啦~");
        return true;
    }

    @Override
    public Boolean raffle() {
        System.out.println("当前无法进行抽奖~");
        return false;
    }

    @Override
    public Boolean dispensePrize() {
        System.out.println("当前无法领取奖品~");
        return false;
    }
}

可以抽奖状态类 

/**
 * 可以抽奖状态类
 */
public class CanRaffleState implements State {
    private RaffleActivity raffleActivity;

    public CanRaffleState(RaffleActivity raffleActivity) {
        this.raffleActivity = raffleActivity;
    }

    @Override
    public Boolean reduceMoney() {
        System.out.println("已扣除50积分,可以进行抽奖啦~");
        return false;
    }

    @Override
    public Boolean raffle() {
        if (new Random().nextInt(10) == 0) {
            raffleActivity.setDispenseState();
            System.out.println("恭喜您,中奖了~");
            return true;
        }
        raffleActivity.setNoRaffleState();
        System.out.println("很遗憾,您没有中奖~");
        return false;
    }

    @Override
    public Boolean dispensePrize() {
        System.out.println("尚未进行抽奖,无法领取奖品!");
        return false;
    }
}

 发放奖品状态类

/**
 * 发放奖品状态类
 */
public class DispenseState implements State {
    private RaffleActivity raffleActivity;

    public DispenseState(RaffleActivity raffleActivity) {
        this.raffleActivity = raffleActivity;
    }

    @Override
    public Boolean reduceMoney() {
        System.out.println("已经进行过抽奖啦!");
        return false;
    }

    @Override
    public Boolean raffle() {
        System.out.println("已经进行过抽奖啦!");
        return false;
    }

    @Override
    public Boolean dispensePrize() {
        if (raffleActivity.getCount() <= 0) {
            raffleActivity.setDispenseOutState();
            System.out.println("今日奖品已领完,明天再来吧~");
            return false;
        }
        raffleActivity.setNoRaffleState();
        System.out.println("奖品领取成功~");
        return true;
    }
}

 奖品领完状态类

/**
 * 奖品领完状态类
 */
public class DispenseOutState implements State {
    private RaffleActivity raffleActivity;

    public DispenseOutState(RaffleActivity raffleActivity) {
        this.raffleActivity = raffleActivity;
    }

    @Override
    public Boolean reduceMoney() {
        System.out.println("今日奖品已领完,明天再来吧~");
        return false;
    }

    @Override
    public Boolean raffle() {
        System.out.println("今日奖品已领完,明天再来吧~");
        return false;
    }

    @Override
    public Boolean dispensePrize() {
        System.out.println("今日奖品已领完,明天再来吧~");
        return false;
    }
}

 测试代码

RaffleActivity raffleActivity = new RaffleActivity(2);
// 第一次抽奖
System.out.println("======第一次抽奖======");
raffleActivity.raffle();
// 第二次抽奖
System.out.println("======第二次抽奖======");
raffleActivity.raffle();
// 第三次抽奖
System.out.println("======第三次抽奖======");
raffleActivity.raffle();
//======第一次抽奖======
//已扣除50积分,可以进行抽奖啦~
//很遗憾,您没有中奖~
//======第二次抽奖======
//已扣除50积分,可以进行抽奖啦~
//恭喜您,中奖了~
//奖品领取成功~
//======第三次抽奖======
//您的积分余额不足~

借贷平台—流程审批 

状态模式本质上是一种基于状态和事件的状态机,下面是订单流程的状态图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第80张图片

通过状态图,我们再设计一张横纵坐标关系表来比较,如下图

状态 \ 事件

电审(1)

电审失败(2)

定价发布(3)

接单(4)

接单失败(5)

付款(6)

支付失效(7)

反馈(8)

订单生成(1)

已审核(2)

已完结(6)

已审核(2)

已发布(3)

已发布(3)

待付款(4)

已完结(6)

待付款(4)

已付款(5)

已完结(6)

已付款(5)

已完结(6)

已完结(6)

UML 类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第81张图片

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第82张图片

代码

状态接口

/**
 * 状态接口
 */
public interface State {
    /**
     * 电审
     *
     * @param context
     */
    void electronicAudit(Context context);

    /**
     * 电审失败
     *
     * @param context
     */
    void electronicAuditFail(Context context);

    /**
     * 定价发布
     *
     * @param context
     */
    void releasePricing(Context context);

    /**
     * 接单
     *
     * @param context
     */
    void acceptOrder(Context context);

    /**
     * 接单失败
     *
     * @param context
     */
    void acceptOrderFail(Context context);

    /**
     * 付款
     *
     * @param context
     */
    void payMoney(Context context);

    /**
     * 反馈【下款 或 拒贷】
     *
     * @param context
     */
    void feedback(Context context);

    String getCurrentState();
}

抽象状态类 

/**
 * 抽象状态类,默认实现
 */
public abstract class AbstractState implements State {
    private static final RuntimeException EXCEPTION = new RuntimeException("操作流程有误");

    @Override
    public void electronicAudit(Context context) {
        throw EXCEPTION;
    }

    @Override
    public void electronicAuditFail(Context context) {
        throw EXCEPTION;

    }

    @Override
    public void releasePricing(Context context) {
        throw EXCEPTION;
    }

    @Override
    public void acceptOrder(Context context) {
        throw EXCEPTION;
    }

    @Override
    public void acceptOrderFail(Context context) {
        throw EXCEPTION;
    }

    @Override
    public void payMoney(Context context) {
        throw EXCEPTION;
    }

    @Override
    public void feedback(Context context) {
        throw EXCEPTION;
    }
}

 具体状态类

/**
 * 订单生成状态类
 */
public class GeneratedState extends AbstractState {
    @Override
    public void electronicAudit(Context context) {
        context.setState(new AuditedState());
    }

    @Override
    public void electronicAuditFail(Context context) {
        context.setState(new FinishedState());
    }

    @Override
    public String getCurrentState() {
        return StateEnum.GENERATED.name();
    }
}
/**
 * 已审核状态类
 */
public class AuditedState extends AbstractState {
    @Override
    public void releasePricing(Context context) {
        context.setState(new PublishedState());
    }

    @Override
    public String getCurrentState() {
        return StateEnum.AUDITED.name();
    }
}
/**
 * 已发布状态类
 */
public class PublishedState extends AbstractState {
    @Override
    public void acceptOrder(Context context) {
        context.setState(new NotPaidState());
    }

    @Override
    public void acceptOrderFail(Context context) {
        context.setState(new FinishedState());
    }

    @Override
    public String getCurrentState() {
        return StateEnum.PUBLISHED.name();
    }
}
/**
 * 未付款状态类
 */
public class NotPaidState extends AbstractState {
    @Override
    public void payMoney(Context context) {
        context.setState(new PaidState());
    }

    @Override
    public void feedback(Context context) {
        context.setState(new FinishedState());
    }

    @Override
    public String getCurrentState() {
        return StateEnum.NOT_PAID.name();
    }
}
/**
 * 未付款状态类
 */
public class PaidState extends AbstractState {
    @Override
    public void feedback(Context context) {
        context.setState(new FinishedState());
    }

    @Override
    public String getCurrentState() {
        return StateEnum.PAID.name();
    }
}
/**
 * 未付款状态类
 */
public class FinishedState extends AbstractState {
    @Override
    public String getCurrentState() {
        return StateEnum.FINISHED.name();
    }
}

 状态枚举类

public enum StateEnum {
    GENERATED,
    AUDITED,
    PUBLISHED,
    NOT_PAID,
    PAID,
    FINISHED;
}

上下文环境类 

/**
 * 上下文环境类
 */
public class Context extends AbstractState {
    private State state;

    public Context() {
        state = new GeneratedState();
    }

    @Override
    public void electronicAudit(Context context) {
        state.electronicAudit(context);
        getCurrentState();
    }

    @Override
    public void electronicAuditFail(Context context) {
        state.electronicAuditFail(context);
        getCurrentState();
    }

    @Override
    public void releasePricing(Context context) {
        state.releasePricing(context);
        getCurrentState();
    }

    @Override
    public void acceptOrder(Context context) {
        state.acceptOrder(context);
        getCurrentState();
    }

    @Override
    public void acceptOrderFail(Context context) {
        state.acceptOrderFail(context);
        getCurrentState();
    }

    @Override
    public void payMoney(Context context) {
        state.payMoney(context);
        getCurrentState();
    }

    @Override
    public void feedback(Context context) {
        state.feedback(context);
        getCurrentState();
    }

    public State getState() {
        return state;
    }

    public void setState(State state) {
        this.state = state;
    }

    @Override
    public String getCurrentState() {
        System.out.println("当前状态:" + state.getCurrentState());
        return state.getCurrentState();
    }
}

 测试代码

Context context = new Context();
context.electronicAudit(context);
context.releasePricing(context);
context.acceptOrder(context);
context.payMoney(context);

context.electronicAuditFail(context);
context.acceptOrderFail(context);
//当前状态:AUDITED
//当前状态:PUBLISHED
//当前状态:NOT_PAID
//当前状态:PAID
//Exception in thread "main" java.lang.RuntimeException: 操作流程有误
//	at com.vectorx.pattern.t20_state.lendingplatform.AbstractState.(AbstractState.java:7)
//	at com.vectorx.pattern.t20_state.lendingplatform.Client.main(Client.java:5)

状态模式的注意事项和细节

优点

  1. 代码有很强的可读性:状态模式将每个状态的行为封装到对应的一个类中
  2. 方便维护:将容易产生问题的if-else语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多if-else语句,而且容易出错
  3. 符合开闭原则,容易增删状态

缺点

      会产生很多类:每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度

应用场景

      当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为时,可以考虑使用状态模式


策略模式

鸭子问题

编写鸭子项目,具体要求如下:

  • 1)有各鸭子(比如野鸭、北京鸭、水鸭等,鸭子有各种行为,比如叫、飞行等)
  • 2)显示鸭子的信息

传统方案解决鸭子问题

UML 类图

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第83张图片

核心代码

public abstract class Duck {
    public void quark() {
        System.out.println("鸭子嘎嘎叫~");
    }

    public void swim() {
        System.out.println("鸭子哗哗游~");
    }

    public void fly() {
        System.out.println("鸭子腾腾飞~");
    }

    public abstract void display();
}
public class WildDuck extends Duck {

    @Override
    public void display() {
        System.out.println("野鸭子");
    }
}
public class PekingDuck extends Duck {
    @Override
    public void display() {
        System.out.println("北京鸭~");
    }

    @Override
    public void fly() {
        System.out.println("北京鸭不会飞~");
    }
}
public class ToyDuck extends Duck {
    @Override
    public void display() {
        System.out.println("玩具鸭~");
    }

    @Override
    public void quark() {
        System.out.println("玩具鸭不会叫~");
    }

    @Override
    public void swim() {
        System.out.println("玩具鸭不会游~");
    }

    @Override
    public void fly() {
        System.out.println("玩具鸭不会飞~");
    }
}

传统的方式实现的问题分析和解决方案

●1)其它鸭子,都继承了Duck类,所以fly让所有子类都会飞了,这是不正确的
●2)上面说的问题,其实是继承带来的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分,会有溢出效应
●3)为了改进问题,我们可以通过覆盖fly方法来解决 => 覆盖解决
●4)问题又来了,如果我们有一个玩具鸭子ToyDuck,这样就需要ToyDuck去覆盖Duck的所有实现的方法 => 解决思路:策略模式

策略模式基本介绍

●1)策略模式(Strategy Pattern)中,定义算法族,分别封装起来,让他们之间可以互相替换。此模式让算法的变化独立于使用算法的客户
●2)这算法体现了几个设计原则
○第一、把变化的代码从不变的代码中分离出来
○第二、针对接口编程而不是具体类(定义了策略接口)
○第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)

原理类图
Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第84张图片


说明:从上图可以看到,客户Context有成员变量Strategy或者其他的策略接口。至于需要使用到哪个策略,可以在构造器中指定

策略模式解决鸭子问题

应用实例要求:编写程序完成前面的鸭子项目,要求使用策略模式
思路分析

  1. 策略模式:分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象
  2. 原则就是:分离变化部分,封装接口,基于接口编程各种功能。此模式让行为的变化独立于算法的使用者


代码实现

UML 类图
Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第85张图片

 Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第86张图片


核心代码

“叫”的行为
 

职责

/**
 * “叫”行为策略接口
 */
public interface QuarkBehavior {
    void quark();
}
/**
 * “不会叫”行为策略对象
 */
public class NoQuarkBehavior implements QuarkBehavior {
    @Override
    public void quark() {
        System.out.println("不会叫~");
    }
}
/**
 * “嘎嘎叫”行为策略对象
 */
public class GagaQuarkBehavior implements QuarkBehavior {
    @Override
    public void quark() {
        System.out.println("嘎嘎叫~");
    }
}
/**
 * “咯咯叫”行为策略对象
 */
public class GegeQuarkBehavior implements QuarkBehavior {
    @Override
    public void quark() {
        System.out.println("咯咯叫~");
    }
}

“游泳”的行为 

/**
 * ”游泳“行为策略接口
 */
public interface SwimBehavior {
    void swim();
}
/**
 * “不会游泳”行为策略对象
 */
public class NoSwimHehavior implements SwimBehavior {
    @Override
    public void swim() {
        System.out.println("不会游泳~");
    }
}
/**
 * “会游泳”行为策略对象
 */
public class CanSwimHehavior implements SwimBehavior {
    @Override
    public void swim() {
        System.out.println("会游泳~");
    }
}

“飞”的行为 

/**
 * “飞行”行为策略接口
 */
public interface FlyBehavior {
    void fly();
}
/**
 * “不会飞”行为策略对象
 */
public class NoFlyBehavior implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("不会飞~");
    }
}
/**
 * “不太会飞”行为策略对象
 */
public class BadFlyBehavior implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("不太会飞~");
    }
}
/**
 * “很会飞”行为策略对象
 */
public class GoodFlyBehavior implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("很会飞~");
    }
}

鸭子类 

/**
 * 抽象鸭子类
 */
public abstract class Duck {
    protected QuarkBehavior quarkBehavior;
    protected SwimBehavior swimBehavior;
    protected FlyBehavior flyBehavior;

    public Duck() {
        display();
    }

    public void quark() {
        if (quarkBehavior != null) {
            quarkBehavior.quark();
        }
    }

    public void swim() {
        if (swimBehavior != null) {
            swimBehavior.swim();
        }
    }

    public void fly() {
        if (flyBehavior != null) {
            flyBehavior.fly();
        }
    }

    public void setQuarkBehavior(QuarkBehavior quarkBehavior) {
        this.quarkBehavior = quarkBehavior;
    }

    public void setSwimBehavior(SwimBehavior swimBehavior) {
        this.swimBehavior = swimBehavior;
    }

    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }

    public abstract void display();
}
/**
 * 野鸭子
 */
public class WildDuck extends Duck {
    public WildDuck() {
        super();
        quarkBehavior = new GegeQuarkBehavior();
        swimBehavior = new CanSwimHehavior();
        flyBehavior = new GoodFlyBehavior();
    }

    @Override
    public void display() {
        System.out.println("======野鸭子======");
    }
}
/**
 * 北京鸭
 */
public class PekingDuck extends Duck {
    public PekingDuck() {
        super();
        quarkBehavior = new GagaQuarkBehavior();
        swimBehavior = new CanSwimHehavior();
        flyBehavior = new BadFlyBehavior();
    }

    @Override
    public void display() {
        System.out.println("======北京鸭======");
    }
}
/**
 * 玩具鸭
 */
public class ToyDuck extends Duck {
    public ToyDuck() {
        super();
        quarkBehavior = new NoQuarkBehavior();
        swimBehavior = new NoSwimHehavior();
        flyBehavior = new NoFlyBehavior();
    }

    @Override
    public void display() {
        System.out.println("======玩具鸭======");
    }
}

测试代码 

Duck wildDuck = new WildDuck();
wildDuck.quark();
wildDuck.swim();
wildDuck.fly();

Duck pekingDuck = new PekingDuck();
pekingDuck.quark();
pekingDuck.swim();
pekingDuck.fly();
System.out.println("===改变策略===");
pekingDuck.setFlyBehavior(new NoFlyBehavior());
pekingDuck.fly();

Duck toyDuck = new ToyDuck();
toyDuck.quark();
toyDuck.swim();
toyDuck.fly();

测试结果 

//======野鸭子======
//咯咯叫~
//会游泳~
//很会飞~
//======北京鸭======
//嘎嘎叫~
//会游泳~
//不太会飞~
//===改变策略===
//不会飞~
//======玩具鸭======
//不会叫~
//不会游泳~
//不会飞~

策略模式的注意事项和细节

●1)策略模式的关键是:分析项目中变化部分与不变部分
●2)策略模式的核心思想是:多用组合/聚合,少用继承;用行为类组合,而不是行为的继承,更有弹性
●3)体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if...else if...else)
●4)提供了可以替换继承关系的办法:策略模式将算法封装在独立的Strategy类中,使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展
●5)需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大


链模式(责任链模式)

OA系统的采购审批项目

学校 OA 系统的采购审批项目,需求是

●1)采购员采购教学器材
●2)如果金额小于等于 5000,由教学主任审批(0 < x ≤ 5000)
●3)如果金额小于等于 10000,由院长审批(5000 < x ≤ 10000)
●4)如果金额小于等于 30000,由副校长审批(10000< x ≤ 30000)
●5)如果金额超过 30000 以上,有校长审批(30000  < x)

请设计程序完成采购审批项目

传统方案解决 OA 系统审批,传统的设计方案(类图)
Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第87张图片

 
传统方案解决 OA 系统审批问题分析

●1)传统方式是:接收到一个采购请求后,根据采购金额来调用对应的Approver(审批人)完成审批
●2)传统方式的问题分析:客户端这里会使用到分支判断(比如switch)来对不同的采购请求处理,这样就存在如下问题
○(1)如果各个级别的人员审批金额发生变化,在客户端的也需要变化
○(2)客户端必须明确的知道有多少个审批级别和访问
●3)这样对一个采购请求进行处理和Approver(审批人)就存在强耦合关系,不利于代码的扩展和维护
●4)解决方案 =》职责链模式

职责链模式基本介绍

● 1)职责链模式(Chain of Responsibility Pattern),又叫责任链模式:为请求创建了一个接收者对象的链(简单示意图)。

Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第88张图片

这种模式对请求的发送者和接收者进行解耦
● 2)职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推
● 3)这种类型的设计模式属于行为型模式

原理类图
Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第89张图片



职责链模式使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系

将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止

●Handler抽象处理者:定义了一个处理请求的方法,同时含有另外一个Handler
●ConcreteHandler具体处理者:处理自己负责的请求,同时可以访问它的后继者(即下一个处理者) ;如果可以处理请求,则进行处理,否则交给后继者去处理,从而形成一个职责链
●Request含有很多属性,表示一个请求

职责链模式解决 OA 系统采购审批项目

UML 类图
Java 设计模式 本文代码拉取链接 https://gitlab.com/zhangpengweiLJ/designpettern.git_第90张图片


核心代码

请求类
 

/**
 * 采购申请类
 */
public class PurchaseRequest {
    private Integer id;
    private Float price;

    public PurchaseRequest(Integer id, Float price) {
        this.id = id;
        this.price = price;
    }

    public Integer getId() {
        return id;
    }

    public Float getPrice() {
        return price;
    }
}

抽象审批人对象

/**
 * 抽象审批人对象
 */
public abstract class Approver {
    protected Approver nextApprover;
    protected String name;

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

    /**
     * 设置后继者
     *
     * @param nextApprover
     */
    public void setNextApprover(Approver nextApprover) {
        this.nextApprover = nextApprover;
    }

    /**
     * 处理请求的方法
     */
    public abstract void processRequest(PurchaseRequest purchaseRequest);
}

具体审批人对象

/**
 * 教学主任审批人
 */
public class TeachDirectorApprover extends Approver {
    public TeachDirectorApprover(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest purchaseRequest) {
        if (purchaseRequest.getPrice() <= 5000) {
            System.out.println("请求编号:" + purchaseRequest.getId() + ",处理人:" + this.name);
        } else {
            nextApprover.processRequest(purchaseRequest);
        }
    }
}
/**
 * 院长审批人
 */
public class DepartmentHeadApprover extends Approver {
    public DepartmentHeadApprover(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest purchaseRequest) {
        if (purchaseRequest.getPrice() > 5000 && purchaseRequest.getPrice() <= 10000) {
            System.out.println("请求编号:" + purchaseRequest.getId() + ",处理人:" + this.name);
        } else {
            nextApprover.processRequest(purchaseRequest);
        }
    }
}
/**
 * 副校长审批人
 */
public class ViceChancellorApprover extends Approver {
    public ViceChancellorApprover(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest purchaseRequest) {
        if (purchaseRequest.getPrice() > 10000 && purchaseRequest.getPrice() <= 30000) {
            System.out.println("请求编号:" + purchaseRequest.getId() + ",处理人:" + this.name);
        } else {
            nextApprover.processRequest(purchaseRequest);
        }
    }
}
/**
 * 副校长审批人
 */
public class ChancellorApprover extends Approver {
    public ChancellorApprover(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest purchaseRequest) {
        if (purchaseRequest.getPrice() > 30000) {
            System.out.println("请求编号:" + purchaseRequest.getId() + ",处理人:" + this.name);
        } else {
            nextApprover.processRequest(purchaseRequest);
        }
    }
}

测试代码

//创建一个请求
PurchaseRequest purchaseRequest = new PurchaseRequest(1, 31000.0f);

//创建相关的审批人
TeachDirectorApprover teachDirectorApprover = new TeachDirectorApprover("童主任");
DepartmentHeadApprover departmentHeadApprover = new DepartmentHeadApprover("王院长");
ViceChancellorApprover viceChancellorApprover = new ViceChancellorApprover("钱副校长");
ChancellorApprover chancellorApprover = new ChancellorApprover("郑校长");

//设置后继者(处理人形成环形)
teachDirectorApprover.setNextApprover(departmentHeadApprover);
departmentHeadApprover.setNextApprover(viceChancellorApprover);
viceChancellorApprover.setNextApprover(chancellorApprover);
chancellorApprover.setNextApprover(teachDirectorApprover);

//发起一个请求
teachDirectorApprover.processRequest(purchaseRequest); //请求编号:1,处理人:郑校长

职责链模式的注意事项和细节

●1)将请求和处理分开,实现解耦,提高系统的灵活性
●2)简化了对象,使对象不需要知道链的结构
●3)性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
●4)调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
●5)最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假 / 加薪等审批流程、Java Web 中 Tomcat 对Encoding的处理、拦截器

你可能感兴趣的:(java,设计模式)