【设计模式】用Java手写21种常见设计模式

2023-11-14

文章目录

配套使用效果更佳: 本博客配套开源源码github点击跳转


引言

设计模式主要研究的是“变”与“不变”,以及如何将它们分离、解耦、组装,将其中“不变”的部分沉淀下来,避免“重复造轮子”,而对于“变”的部分则可以用抽象化、多态化等方式,增强软件的兼容性、可扩展性。

作为一个开发人员,在进行一个项目的设计与实现的过程中,应当具备软件架构的全局观,对项目进行模块化的设计,并充分考虑代码的可复用性,用最少的代码实现最完备的功能,使代码简洁、优雅。

优秀的系统应兼备功能强大、模块清晰、高扩展性,这离不开对各种设计模式的灵活运用。

  • 设计模式、面向对象六大原则:
    SRP:单一职责原则。 该设计原则是基于康威定律(Conway’s Law)的一个推论——一个软件系统的最佳结构高度依赖于开发这个系统的组织的内部结构。这样,每个软件模块都有且只有一个需要被改变的理由。很多程序员根据SRP这个名字想当然地认为这个原则就是指:每个模块都应该只做一件事。但实际上,更准确的定义应该是,任何一个软件模块都应该只对一个用户(User)或系统利益相关者(Stakeholder)负责。
    OCP:开闭原则。 该设计原则是由Bertrand Meyer在20世纪80年代大力推广的,其核心要素是:如果软件系统想要更容易被改变,那么其设计就必须允许新增代码来修改系统行为,而非只能靠修改原来的代码。设计良好的计算机软件应该易于扩展,同时抗拒修改。其实这也是我们研究软件架构的根本目的。如果对原始需求的小小延伸就需要对原有的软件系统进行大幅修改,那么这个系统的架构设计显然是失败的。
    LSP:里氏替换原则。 该设计原则是Barbara Liskov在1988年提出的一个著名的子类型定义。简单来说,这项原则的意思是如果想用可替换的组件来构建软件系统,那么这些组件就必须遵守同一个约定,以便让这些组件可以相互替换。在面向对象编程中的多态设计就体现了这一原则——能使用父类的地方,一定也可以替换为子类。
    ISP:接口隔离原则。 这项设计原则主要告诫软件设计师应该在设计中避免不必要的依赖,定义接口时应尽量拆分成较小的粒度,往往一个接口只对应一个功能。
    DIP:依赖反转原则。 该设计原则指出高层策略性的代码不应该依赖实现底层细节的代码,相反,那些实现底层细节的代码应该依赖高层策略性的代码。这一原则原则主要想告诉我们的是,如果想要设计一个灵活的系统,在源代码层次的依赖关系中就应该多引用抽象类型,而非具体实现。
    迪米特原则。 尽量减少对象之间的依赖,以减少系统中对象之间的耦合度。这样可以使得代码更加可维护、可扩展和易于理解。

  • 本文结构

本文第一章介绍面向对象的三大特性——封装、继承、多态,后续章节将展开介绍创建型、结构型、行为型三大类下的21种设计模式。

以下为本文的思维导图:

在这里插入图片描述

〇、面向对象及封装、继承、多态

  • 对象无处不在

在我们的生活中,对象无处不在,从我们周围的每一个人,到使用的电脑、桌椅,你的家,再到工作的公司、就读的学校,每一个都是一个对象。我们可以通过对这些对象进行模型建立,将现实世界“投射”到计算机的世界中去。

但是模型往往并不是孤立存在的,它们彼此之间存在着千丝万缕的联系,因此,我们应当充分利用面向对象的三大特性——也就是封装、继承、多态去建模,从而减少代码冗余、模块耦合。面向对象的三大特性是学习设计模式不可或缺的预备知识。

1.封装

  • 一个小例子

其实封装在我们的生活中也是随处可见的,就以我喜欢喝的烧仙草为例。在早期,许多奶茶店的烧仙草其实是采用塑封的方式进行包装的,这体现的就是一种封装的思想。封装隐藏了烧仙草内部的珍珠、芋圆、椰果、奶茶等,仅留给外界一根吸管用于访问,这根吸管相当于一个“接口”。

这种封装又能带来什么样的好处呢?作为一个喜欢喝烧仙草的人,我不必关注这个饮料是怎么做的以及它在瓶中的状态将如何变化,我仅仅只需要通过吸管这个“接口”去访问它的内部内容,这样做非常方便省事。其次,封装带来的好处就是,我既不用担心瓶中的饮料会倾洒出来,因为零散的数据被集中管理起来了;也不必担心内部会受到来自外界的污染,封装使得数据的安全性得到保障。

在这里插入图片描述

  • Java语言中的封装

在Java编程语言中,一对大括号“{}”就是类的外壳、边界,它能很好地把类的各种属性及行为包裹起来,将它们封装在类内部并固化成一个整体。封装好的类如同一个黑匣子,外部无法看到内部的构造及运转机制,而只能访问其暴露出来的属性或方法。

下面仍以实现一个烧仙草类为例,展示Java语言中的封装。

为了简化代码,而回归其原理本身,假使烧仙草GrassJelly中的组成为奶茶MilkTea、配料Material,则对于一个烧仙草类可以有以下实现。

package com.wang.design.chapter0;

/**
 * @author Tracy
 */
public class GrassJelly {
    //属性
    private String milkTea;
    private String material;

    //实例化对象
    public GrassJelly(String milkTea, String material) {
        this.milkTea = milkTea;
        this.material = material;
    }
    public GrassJelly(){

    }

    //访问属性
    public String getMilkTea() {
        return milkTea;
    }
    public String getMaterial() {
        return material;
    }
    public void show(){
        System.out.println("this is a cup of grass jelly.");
    }

    //修改属性
    public void setMilkTea(String milkTea) {
        this.milkTea = milkTea;
    }
    public void setMaterial(String material) {
        this.material = material;
    }
}

2.继承

现实生活中的对象是不计其数的,我们如果为每一个对象都单独创建一个类,不仅代码量会非常巨大,且其中的代码冗余会导致代码难以修改和维护,总之这样的做法实在是称不上优雅。

继承可以使父类的属性和方法延续到子类中,这样子类就不需要重复定义,并且子类可以通过重写来修改继承而来的方法实现,或者通过追加达到属性与功能扩展的目的。

下面我以交通工具体系为例,来展示如何通过Java中的继承来灵活扩展类。

  • Vehicle抽象类

首先,Vehicle为顶级抽象父类,因此将四种具体子类的公共特征与行为提取到其中,这样就不必在子类中重新定义,从一定程度上保证了代码的可复用性,降低了代码的冗余性。由于show()方法会因具体实现类的不同而不同,因此定义为抽象方法,为子类留有余地。

以下为代码:

package com.wang.design.chapter0;

/**
 * @author Tracy
 *
 * 0-2 继承
 */
public abstract class Vehicle {
    /**
     * 以下为所有交通工具共有的属性
     */
    protected String category;//"交通工具"
    protected String size;//"大","中","小"
    protected String name;//具体的名字"飞机"、"轮船"等
    protected String load;//载重

    /**
     * 构造函数
     */
    public Vehicle(String size, String load,String name) {
        this.category = "交通工具";
        this.size = size;
        this.name = name;
        this.load=load;
    }

    public Vehicle() {
    }

    /**
     * setter & getter
     *
     */

    public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }

    public String getSize() {
        return size;
    }

    public void setSize(String size) {
        this.size = size;
    }

    public String getName() {
        return name;
    }

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

    /**
     * 以下为所有交通工具共有的方法
     */
    public abstract void show();
}
  • Airplane子类

定义一个实现类Airplane子类继承自Vehicle父类,新增航班属性flightNumber。

以下为代码:

package com.wang.design.chapter0;

/**
 * @author Tracy
 *
 * 0-2 继承
 *
 * 继承自父类Vehicle
 */
public class Airplane extends Vehicle{
    protected String flightNumber;//专属属性:航班号

    public Airplane(String size,String load,String flightNumber) {
        super(size,load,"飞机");//调用父类构造方法
        this.flightNumber = flightNumber;
    }

    public String getFlightNumber() {
        return flightNumber;
    }

    public void setFlightNumber(String flightNumber) {
        this.flightNumber = flightNumber;
    }

    @Override
    public void show() {
        System.out.println(category+":"+size+""+name+",航班号为"+flightNumber+"。");
    }

    public static void main(String[] args) {
        new Airplane("大型","250t","12345").show();
    }
}

调用show():

在这里插入图片描述

  • Bus子类
    定义一个实现类Bus子类继承自Vehicle父类,新增路线属性route。
package com.wang.design.chapter0;

/**
 * @author Tracy
 *
 * 0-2 继承
 *
 * 继承自父类Vehicle
 */
public class Bus extends Vehicle{
    protected String route;//线路

    public Bus(String size,String load,String route) {
        super(size,load,"公交车");
        this.route = route;
    }

    public String getRoute() {
        return route;
    }

    public void setRoute(String route) {
        this.route = route;
    }

    @Override
    public void show() {
        System.out.println(category+":"+size+""+name+",线路为"+route+"路。");
    }

    public static void main(String[] args) {
        new Bus("中型","2t","220").show();
    }
}

调用show():

在这里插入图片描述

3.多态

对于父类定义的引用只能指向本类或者其子类实例化而来的对象,这就是一种多态。除此之外,还有其他形式的多态,例如抽象类引用指向子类对象,接口引用指向实现类的对象,其本质上都别无二致。

简单来说,多态就是指一个引用指向不同的类的对象,但这种指向只能是自己->自己,自己->子类,不能是子类->父类。如下所示:

package com.wang.design.chapter0;

/**
 * @author Tracy
 *
 * 0-3 多态
 */
public class Polymorphic {
    public static void main(String[] args) {
        /**
         * [交通工具]是[交通工具]
         */
        Vehicle vehicle = new Vehicle() {
            @Override
            public void show() {
                System.out.println("交通工具");
            }
        };
        vehicle.show();
        /**
         * [飞机]是[交通工具]
         */
        vehicle = new Airplane("大型","250t","12345");
        vehicle.show();
        /**
         * [公交车]是[交通工具]
         */
        vehicle = new Bus("中型","2t","220");
        vehicle.show();

        /**
         * 以下为错误用法
         * [交通工具]是[公交车] 错误
         */
        //Bus bus=new Vehicle();
    }
}

面向对象及其三大特性至此已介绍完毕,预备工作已完成,下面就真正进入到设计模式的学习中去。

一、创建型1——单例模式

单例模式指在某个系统中一个类只存在一个实例,同时提供集中、统一的访问接口,以使系统行为保持协调一致。

1.饿汉模式(常用)

饿汉模式,即在初始阶段就主动进行实例化,并时刻保持一种渴求的状态,无论此单例是否有人使用。

下面我以上帝God为例,看看如何对世间唯一(假设唯一)存在的God进行饿汉模式的实现。

  • God类解析
  • private关键字确保God实例的私有性。

  • static关键字确保God的静态性,在类加载的时候就实例化了,在内存中永生且唯一,即使是内存垃圾收集器也不会对其进行回收。

  • final关键字则确保这个God是常量、恒量,引用一旦被赋值就不能再修改;

  • new关键字初始化God类的静态实例,并赋予静态常量god。

  • 最后,getInstance()方法用来提供给外界唯一获取这个god单例的接口。

package com.wang.design.chapter1;

/**
 * @author Tracy
 *
 * 1-1 创建型——单例模式
 */
public class God {
    private static final God god=new God();

    private God(){}

    private static God getInstance(){
        return god;
    }
}

2.懒汉模式

虽说在一开始就对god单例进行饿汉模式的加载具有一定的方便性,但如果加载之后很长一段时间都没有人访问god,那对上帝或者内存来说这恐怕是一种没有必要的消耗。基于这种考虑,于是就有了懒汉模式,也就是在需要的时候再对单例进行实例化。

  • 懒汉模式的God类
  • 去掉final关键字:由于god不是在声明时进行初始化的,因此将声明语句中的final关键字去掉了,否则后续真正需要初始化时就无法对其进行初始化(属于一种修改操作)。
  • 好处与坏处:好处是在一定程度上节省了内存,坏处是临时初始化实例会消耗CPU资源使得程序有些许的延迟(尽管看上去也许不太明显)。
  • synchronized关键字保证线程同步。
/**
 * @author Tracy
 *
 * 1-1 创建型——单例模式——懒汉模式
 */
public class God {
    private static God god;

    private God(){}

    private static synchronized God getInstance(){
        if(god==null)god=new God();
        return god;
    }
}
  • 懒汉模式的God类——优化线程同步处理

上面的代码其实有一些不足,当有多个方法访问getInstance()方法时,在还没有进入这个方法时就开始了排队,会造成线程提前阻塞,其实这是没有必要的。下面进行了优化:首先,进入方法不排队,而是当god判空才开始排队;在同步块中进行二重检测(双检锁),是为了排除在进入getInstance的同时已经有线程获取到了god。

懒加载模式的“双检锁”:外层放宽入口,保证线程并发的高效性;内层加锁同步,保证实例化的单次运行。如此里应外合,不仅达到了单例模式的效果,还完美地保证了构建过程的运行效率,一举两得。

/**
 * @author Tracy
 *
 * 1-1 创建型——单例模式——懒汉模式
 */
public class God {
    private volatile static God god;

    private God(){}

    private static God getInstance(){//进入方法不排队
        if(god==null){
            synchronized(Sun.class){//判空才开始排队
                if(god==null)god=new God();//二重检测是为了排除在进入getInstance的同时已经有线程获取到了god
            }
        }
        return god;
    }
}

相比“懒汉模式”,其实在大多数情况下我们通常会更多地使用“饿汉模式”,原因在于这个单例迟早是要被实例化占用内存的,延迟懒加载的意义并不大,加锁解锁反而是一种资源浪费,同步更是会降低CPU的利用率,使用不当的话反而会带来不必要的风险。越简单的包容性越强,而越复杂的反而越容易出错。

二、创建型2——原型模式

我们可以关注一下实例化与克隆之间的区别,二者都是在造对象,原型模式的目的是从原型实例克隆出新的实例,应对那些有非常复杂的初始化过程的对象或者是需要耗费大量资源的情况时具有一定的性能优势。

在这里插入图片描述

1.克隆对象

究其本质,克隆操作时Java虚拟机会进行内存操作,直接拷贝原型对象数据流生成新的副本对象,绝不会拖泥带水地触发一些多余的复杂操作(如类加载、实例化、初始化等),所以其效率远远高于“new”关键字所触发的实例化操作。

我以路人甲类Passerby为例,通过实现java.lang包中的克隆接口Cloneable,并在实现的clone()方法中调用了父类Object的克隆方法,如此一来外部就能够对本类的实例进行克隆操作了,省去了由类而生的再造过程。注意,对于属性中的引用类型需要深拷贝才能完全彻底地克隆这个实例的所有。

  • 可被克隆的Passerby类
package com.wang.design.chapter2;

/**
 * @author Tracy
 *
 * 2-1 创建型——原型模式
 */
public class Passerby implements Cloneable{
    // x坐标和y坐标
    private int x;
    private int y;
    private Equipment equipment;

    public Passerby(int x, int y) {
        this.x = x;
        this.y = y;
    }

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

    public void setEquipment(Equipment equipment) {
        this.equipment = equipment;
    }

    public int getY() {
        return y;
    }

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

    //行走
    public void walk(){
        ++y;
    }

    //奔跑
    public void run(){
        y+=5;
    }

    //重写克隆方法
    @Override
    protected Passerby clone() throws CloneNotSupportedException {
        Passerby passerby=(Passerby)super.clone();
        passerby.setEquipment(this.equipment.clone());//深拷贝
        return passerby;
    }
}

class Equipment implements Cloneable{
    @Override
    protected Equipment clone() throws CloneNotSupportedException {
        return (Equipment)super.clone();
    }
}

2.克隆工厂

定义克隆工厂类可以更方便地生产对象。

package com.wang.design.chapter2;

/**
 * @author Tracy
 *
 * 2-2 创建型——原型模式——克隆工厂
 */
public class PasserbyFactory {
    //单例饿汉模式先创建一个Passerby原型
    private static Passerby passerby=new Passerby(0,0);

    //获取克隆实例
    public static Passerby getInstance(){
        try{
            Passerby one=passerby.clone();
            return one;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

三、创建型3——工厂模式

程序设计中的工厂类往往是对对象构造、实例化、初始化过程的封装,而工厂方法(FactoryMethod)则可以升华为一种设计模式,它对工厂制造方法进行接口规范化,以允许子类工厂决定具体制造哪类产品的实例,最终降低系统耦合,使系统的可维护性、可扩展性等得到提升。

我们都知道,传统的实例构造方式就是使用new关键字,但这样会导致客户端与实例化对象这个过程产生耦合,但如果我们把生产过程交给一个工厂类,由工厂类来完成实例化、初始化等过程,那么我们就能摆脱传统生产方式带给我们的束缚。

实现工厂模式并不是简单地把生产对象这个过程换一个位置,如果这样做,当工厂类需要扩展的时候必然会需要不断地重写它,这样做事实上也并没有太多优越性。相反,我们力求创建一个便于管理的、可扩展的工厂体系。

1.实体类

  • 乘客实体类
package com.wang.design.chapter3;

import java.util.Random;

/**
 * @author tracy
 * 3——工厂模式——乘客实体类
 */
public class Passenger {
    private int x,y;
    private int gender;//0表示女性 1表示男性

    public Passenger(int x,int y){
        this.x=x;
        this.y=y;
        this.gender=new Random().nextInt(2);
    }

    public void show(){
        System.out.println("乘客 坐标["+x+","+y+"]");
    }
}

2.工厂类

下面的Factory接口是工厂模式的核心,对于每个实体类都可以创建一个专门的工厂类来生产它,这样就能使代码具有低耦合、可扩展性。

package com.wang.design.chapter3;

/**
 * @author tracy
 * 3——工厂模式——工厂类
 */
public interface Factory {
    Passenger create(int x,int y);
}

class PassengerFactory implements Factory{
    @Override
    public Passenger create(int x,int y) {
        return new Passenger(x,y);
    }
}
  • 客户端
package com.wang.design.chapter3;

/**
 * 3-工厂模式-客户端
 */
public class Client {
    public static void main(String[] args) {
        System.out.println("begin");
        
		//批量生产10个乘客
        Factory factory=new PassengerFactory();
        for(int i=0;i<10;++i){
            factory.create(0,0).show();
        }

        System.out.println("end");
    }
}

//执行结果
//begin
//乘客 坐标[0,0]
//乘客 坐标[0,0]
//乘客 坐标[0,0]
//乘客 坐标[0,0]
//乘客 坐标[0,0]
//乘客 坐标[0,0]
//乘客 坐标[0,0]
//乘客 坐标[0,0]
//乘客 坐标[0,0]
//乘客 坐标[0,0]
//end

在工厂方法模式中,不仅产品需要分类,工厂同样需要分类,与其把所有生产方式堆积在一个简单工厂类中,不如把生产方式放在具体的子类工厂中去实现,这样做对工厂的抽象化与多态化有诸多好处,避免了由于新加入产品类而反复修改同一个工厂类所带来的困扰,使后期的代码维护以及扩展更加直观、方便。

四、创建型4——建造者模式

建造者模式的主要目的在于把烦琐的构建过程从不同对象中抽离出来,使其脱离并独立于产品类与工厂类,最终实现用同一套标准的制造工序能够产出不同的产品。

这一章的代码参考自《秒懂设计模式》。

  • 明确角色

我们以房屋施工为例。开发商找了施工方来为一块规划地修建别墅和公寓,由施工方提供两个施工队来对不同的建筑进行施工。由于开发商不是完全放心将任务全权交给施工方,因此又聘请了一位工程总监来对施工队的施工工作进行指导和监督。角色关系如下所示。

在这里插入图片描述

我们将建筑物简化为三个模块:地基、墙体、屋顶,施工队需要分别对三个模块进行施工和组装。

1.建筑物

package com.wang.design.chapter4;

import java.util.ArrayList;
import java.util.List;

/**
 * @author tracy
 * 4-建造者模式-建筑物类
 */
public class Building {
    private List<String> buildingComponents=new ArrayList<>();//组装组件

    public void setBasement(String basement){//修建地基
        buildingComponents.add(basement);
    }

    public void setWall(String wall){//修建墙体
        buildingComponents.add(wall);
    }

    public void setRoof(String roof){//修建屋顶
        buildingComponents.add(roof);
    }

    @Override
    public String toString() {
        String str="";
        for(int i = buildingComponents.size()-1;i>=0;--i){
            str += buildingComponents.get(i);
        }
        return str;
    }

}

2.施工队

package com.wang.design.chapter4;

/**
 * @author tracy
 * 3-建造者模式-施工队
 */
public interface Builder {
    void buildBasement();
    void buildWall();
    void buildRoof();
    Building getBuilding();
}

/**
 * 别墅施工队
 */
class HouseBuilder implements Builder{
    private Building house;

    public HouseBuilder(){
        house=new Building();
    }

    /**
     * 施工三步曲
     */
    @Override
    public void buildBasement() {
        System.out.println("建造地基");
        house.setBasement("╬╬╬╬╬╬╬╬╬╬\n");
    }

    @Override
    public void buildWall() {
        System.out.println("建造墙体");
        house.setWall("|田|田 田|\n");
    }

    @Override
    public void buildRoof() {
        System.out.println("建造屋顶");
        house.setRoof("╱◥███████◣\n");
    }

    @Override
    public Building getBuilding() {
        return house;
    }
}

/**
 * 公寓施工队
 */
class ApartmentBuilder implements Builder{
    private Building apartment;

    public ApartmentBuilder(){
        apartment=new Building();
    }

    /**
     * 施工三步曲
     */
    @Override
    public void buildBasement() {
        System.out.println("建造地基");
        apartment.setBasement("╚═════════╝\n");
    }

    @Override
    public void buildWall() {
        System.out.println("建造墙体");
        for (int i = 0; i < 8; i++) {// 8层
            apartment.setWall("║ □ □ □ □ ║\n");
        }
    }

    @Override
    public void buildRoof() {
        System.out.println("建造屋顶");
        apartment.setRoof("╔═════════╗\n");
    }

    @Override
    public Building getBuilding() {
        return apartment;
    }
}

3.工程监理

package com.wang.design.chapter4;

/**
 * 4-建造者模式-工程监理
 */
public class Director {
    private Builder builder;

    public void setBuilder(Builder builder) {
        this.builder = builder;
    }

    /**
     * 控制施工流程
     * @return
     */
    public Building direct(){
        System.out.println("=====工程项目启动=====");
        builder.buildBasement();
        builder.buildWall();
        builder.buildRoof();
        System.out.println("=====工程项目竣工=====");
        return builder.getBuilding();
    }
}

4.实际施工

package com.wang.design.chapter4;

/**
 * @author tracy
 * 4-建造者模式-实际施工
 */
public class Client {
    public static void main(String[] args) {
        Director director=new Director();
        //监理别墅施工队
        director.setBuilder(new HouseBuilder());
        System.out.println(director.direct());
        //监理公寓施工队
        director.setBuilder(new ApartmentBuilder());
        System.out.println(director.direct());
    }
}
  • 运行效果

在这里插入图片描述

五、结构型1——门面模式

这个模式的精髓就在于“封装”二字,把暴露在客户端的细节封装成一个大的系统,仅留给外部一个接口。只看文字描述可能不够直观,下面直接上代码。

  • 使用门面模式前

下面我以【上一门课】为例。我们都知道,一门课需要经过上课考勤、提问、作业、考试几个步骤,如果不使用门面模式,代码如下:

package com.wang.design.chapter5;
/**
 * 5-门面模式-不使用门面模式
 */
public class Client1 {
    public static void main(String[] args) {
        System.out.println("=====开始一门课程=====");
        new Check().handle();//考勤
        new Question().handle();//提问
        new Task().handle();//作业
        new Exam().handle();//考试
        System.out.println("=====结束一门课程=====");
    }
}

class Check{
    public void handle(){
        System.out.println("考勤......");
    }
}
class Question{
    public void handle(){
        System.out.println("提问......");
    }
}
class Task{
    public void handle(){
        System.out.println("交作业......");
    }
}
class Exam{
    public void handle(){
        System.out.println("期末考试......");
    }
}

执行过程

=====开始一门课程=====
考勤......
提问......
交作业......
期末考试......
=====结束一门课程=====

把大量代码都写在客户端的main()方法中看着非常的臃肿,很不优雅对吧?

  • 使用门面模式后

我们来把【上一门课】这个过程封装一下:

package com.wang.design.chapter5;

/**
 * @author tracy
 * 5-门面模式
 */
public class Facade {
    public void course(){
        new Check().handle();//考勤
        new Question().handle();//提问
        new Task().handle();//作业
        new Exam().handle();//考试
    }
}

然后再重写一下我们的客户端:

package com.wang.design.chapter5;

/**
 * @author tracy
 *
 * 5-门面模式
 */
public class Client2 {
    public static void main(String[] args) {
        System.out.println("=====开始一门课程=====");
        new Facade().course();
        System.out.println("=====结束一门课程=====");
    }
}

现在,我们通过门面模式将一些没必要暴露在外部的细节封装了起来,仅留给外界访问接口,代码相比之前更加简洁优雅,各部分之间的耦合也进一步降低了。这就是门面模式的魅力,你学废了吗?

六、结构型2——组合模式

不知道你有没有观察过一棵大树的结构,它是典型的的层次结构。首先,从整体来看它是一棵树,这棵树由许多一级树枝组成;然后,每一条大的树枝又由规模更小的树枝组成…最小的树枝由一些绿叶组成。

不管我们从哪个层次观看这一棵树,都不难发现,树、树枝、树叶这些元素之间具有一定的从属关系;且无论是处于哪一个级别的元素,它们之间都具备了一定的共同特征,除了处于最低层级的树叶,其他的元素都包含了多个次级子对象的结构特征。

在这里插入图片描述

1.分析需求

  • 善用设计模式,避免代码冗余

设想一下,如果我们为一棵树的每一个结点(树、树枝、树叶)都创建一个类,会造成什么样的结果——拥有成千上万个类,对于程序来说,这无疑是一种灾难。

为了写出优雅简洁的代码,我们完全可以将所有的树、树枝、树叶抽象为一种类,树相当于一种超级树枝,而树叶相当于一种没有子元素的树枝,通过组合不同的实例,就可以通过只设计一个类实现一整棵树。

下面请看我的代码示例。假设我们要打印这样一棵树。

在这里插入图片描述

2.结点类

  • 结点Node类
package com.wang.design.chapter6;

import java.util.*;

/**
 * @author tracy
 *
 * 6-组合模式
 */
public class Node {
    private final List<Node> children=new ArrayList<>();
    private int level=0;
    private String name;

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

    public void setLevel(int level){
        this.level=level;
    }

    public void addChild(Node child){
        child.setLevel(this.level+1);
        this.children.add(child);
    }
	
	//将层次可视化,并递归调用children的show()方法
    public void show(){
        System.out.println(this.name);
        if(this.children.size()==0)return;
        for(Node node:children){
            for(int i=0;i<=this.level;++i) System.out.print(" ");
            System.out.print("|_");
            for(int i=0;i<this.level;++i) System.out.print("_");
            node.show();
        }
    }
}

3.结点的组合

  • 在客户端中实例化树、树枝、树叶结点:
package com.wang.design.chapter6;

/**
 * @author tracy
 * 
 * 6-组合模式
 */
public class Client {
    public static void main(String[] args) {
        Node root=new Node("root");

        Node branch1=new Node("branch1");
        Node branch2=new Node("branch2");

        Node leaf1=new Node("leaf1");
        Node leaf2=new Node("leaf2");
        Node leaf3=new Node("leaf3");
        Node leaf4=new Node("leaf4");

        root.addChild(branch1);
        root.addChild(branch2);
        root.addChild(leaf4);

        branch1.addChild(leaf1);
        branch1.addChild(leaf2);
        branch2.addChild(leaf3);

        root.show();
    }
}

看一下运行效果,输出层次结构:

在这里插入图片描述

七、结构型3——装饰器模式

装饰器模式能够在运行时动态地为原始对象增加一些额外的功能,使其变得更加强大。从某种程度上讲,装饰器非常类似于“继承”,它们都是为了增强原始对象的功能,区别在于方式的不同,后者是在编译时静态地通过对原始类的继承完成,而前者则是在程序运行时通过对原始对象动态地“包装”完成,是对类实例(对象)“装饰”的结果。

我以包装礼物为例,进行代码演示。

在这里插入图片描述

1.实体类

  • Gift类和Showable接口
package com.wang.design.chapter7;

public interface Showable {
    void show();//展示礼物的方法
}

class Gift implements Showable{
    @Override
    public void show() {
        System.out.print("礼物");
    }
}

2.装饰器

  • 用来包装Gift的包装器体系
package com.wang.design.chapter7;

/**
 * Decorator为包装器父类
 */
public abstract class Decorator implements Showable{
    protected Showable show_obj;

    public Decorator(Showable show_obj){
        this.show_obj=show_obj;//注入被装饰对象
    }

    @Override
    public void show() {
        show_obj.show();//调用默认展示方法
    }
}

/**
 * 包装盒
 */
class BoxDecorator extends Decorator{
    public BoxDecorator(Showable show_obj){
        super(show_obj);
    }

    @Override
    public void show() {
        System.out.print("包装盒【");
        show_obj.show();
        System.out.print("】包装盒");
    }
}

/**
 * 蝴蝶结
 */
class BowknotDecorator extends Decorator{
    public BowknotDecorator(Showable show_obj){
        super(show_obj);
    }
    @Override
    public void show() {
        System.out.print("蝴蝶结【");
        show_obj.show();
        System.out.print("】蝴蝶结");
    }
}

  • 客户端展示
package com.wang.design.chapter7;

public class Client {
    public static void main(String[] args) {
        Showable gift=new BowknotDecorator(new BoxDecorator(new Gift()));
        gift.show();
    }
}

执行结果:

蝴蝶结【包装盒【礼物】包装盒】蝴蝶结

到这里,我们通过低耦合、高扩展的设计模式,成功实现了对一个类的包装。

八、结构型4——适配器模式

众所周知,人不能与牲口进行交流,原因就是人和牲口的语言系统并不能兼容。但如果我们非要和牲口交流呢,把人变成牲口或者把牲口变成人都是不现实的,更好的解决办法就是创造一个人畜交流大师(假设此人存在的话),这样一来,人还是原来的人,牲口也还是原来的牲口,不需要修改两个实体就能实现新的需求。即,通过低耦合的方式扩展了程序的功能。

在这里插入图片描述

下面,我们就来动手实现一下这个人畜交流大师。

1.实体类

  • Person类

这个类的对象只能与同类对象人进行交流。

package com.wang.design.chapter8;

public class Person {
    private String name;

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

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

    public void talk(Person anothor){
        System.out.println(this.name+"(人)正在与"+anothor.getName()+"(人)交流...");
    }
}
  • Cow类

这个类的对象只能与同类对象牛进行交流。

package com.wang.design.chapter8;

public class Cow {
    private String name;

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

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

    public void talk(Cow anothor){
        System.out.println(this.name+"(牛)正在与"+anothor.getName()+"(牛)交流...");
    }
}

2.适配器

  • 人畜交流大师

首先,这个大师肯定得是个人,因此他继承自Person类,并且也默认继承了Person类的talk()方法,具备与其他人类交流的能力;其次,他具有一个特别的方法talkWithCow(),这使得他也具备了与牛交流的能力。

package com.wang.design.chapter8;

public class PersonWithCowTranslator extends Person{

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

    public void talkWithCow(Cow cow){
        System.out.println(this.getName()+"(人)正在与"+cow.getName()+"(牛)交流...");
    }
}
  • 客户端演示
package com.wang.design.chapter8;

public class Client {
    public static void main(String[] args) {
        Person person=new Person("小明");
        Cow cow=new Cow("牛魔王");
        PersonWithCowTranslator translator=new PersonWithCowTranslator("动物语言学大师");

        person.talk(translator);//小明把想对牛魔王说的话告诉给大师
        translator.talkWithCow(cow);//大师把小明想说的话转告给牛魔王,并获得牛魔王对小明的回复
        translator.talk(person);//大师把牛魔王的回复转达给小明
    }
}
  • 执行结果
小明()正在与动物语言学大师()交流...
动物语言学大师()正在与牛魔王()交流...
动物语言学大师()正在与小明()交流...

至此,我们便在完全不修改Person类和Cow类的前提下扩展了我们需要的功能,这就是适配器模式的魅力。虽然我举的这个例子并不是那么实用,但其中体现的设计模式的思想完全可以应用到各种各样的实际情况中去。

九、结构型5——享元模式

享元模式的宗旨就是共享细粒度对象,将多个对同一对象的访问集中起来,不必为每个访问者创建一个单独的对象,以此来降低内存的消耗,享元模式属于结构型模式。

适用场景:当系统中多处需要用到一些公共信息时,可以把这些信息封装到一个对象实现享元模式,避免重复创建对象带来系统的开销。比如我们熟悉的线程池就是基于的这种设计模式的思想。

优点:减少对象的创建,降低了系统中对象的数量,故而可以降低系统的使用内存,提高效率。

在这里插入图片描述

我以买电影票为例,展示这种设计模式的代码实现。

1.实体类

  • Ticket接口

我们首先将所有买票实体类的共同行为抽取出来,形成一个Ticket接口,其中有两个方法需要实现类实现。setSeat()方法用于选择座位等级,info()方法用于获取电影票的信息。

public interface Ticket {
    void info();//获取电影票信息
    void setSeat(String seatType);//设置电影票座位等级:普通座、舒适座、豪华座
}
  • CinemaTicket类

然后,我们定义一个Ticket接口的实现类CinemaTicket。每个实例都应该有一个一一对应的name属性和seatType属性,表明电影名称和座位等级。并定义了4个需要的方法。

class CinemaTicket implements Ticket{
    private String name;//电影名称
    private String seatType="普通座";//座位等级,默认为普通座

    // 1 实例化
    public CinemaTicket(String name){
        this.name=name;
    }

    // 2 设置座位等级
    @Override
    public void setSeat(String seatType) {
        this.seatType=seatType;
    }

    // 3 获得票价
    public double getPrice(){
        switch (this.seatType){
            case "普通座":return 30.0;
            case "舒适座":return 40.0;
            default:return 50.0;//豪华座
        }
    }

    // 4 获得所有电影票相关信息
    @Override
    public void info() {
        System.out.println("当前影厅正在放映:"+this.name+",您购买的是:"+this.seatType+",票价为:"+this.getPrice()+"元。");
    }
}

2.享元思想

没学过享元模式的朋友可能会这么实现需求,买一张电影票new一个对象,买下一张再new一个…当你买了很多张电影票,不知不觉就new了很多个对象出来。当问题规模扩大到一定程度的时候,程序中会充斥着大量冗余的代码,性能下降,且难以维护。

享元模式的思想其实和单例模式有那么一点关系,单例模式是一个类只有一个对象,而享元模式是一个类只实例化需要的n(往往大于等于1)个对象。

  • 内部状态和外部状态

这里又不得不提到两个词——内部状态、外部状态。前者是指存储在享元对象内部并且不会随环境的改变而改变的信息,一般对象之间可以共享;后者是指无法被所有对象共享的信息,这部分信息往往会随着环境的变化而改变。

在我们的示例中,CinemaTicket类中的name属性可以被视为是内部状态,同一个电影的所有CinemaTicket对象可以共享这部分信息;seatType属性被视为是外部状态,不同的对象可以设置不同的座位等级。

  • CinemaTicketFactory工厂类

我们创建一个工厂类用来获取CinemaTicket对象。

public class CinemaTicketFactory {
    private static final Map<String,Ticket> cinema_pool = new HashMap<>();//缓冲池


    public static Ticket buyAndGetInfo(String name){
        //当缓冲池中不存在name电影对应的对象时,创建一个并放入池子里
        if(cinema_pool.get(name)==null){
            System.out.println("新建对象");
            cinema_pool.put(name,new CinemaTicket(name));
        }
        //获取需要的对象,而不必每次都重复new对象
        System.out.println("获取到了对象");
        return cinema_pool.get(name);
    }
}
  • Client客户端展示
public class Client {
    public static void main(String[] args) {
        //买一张票
        System.out.println("-----买票-----");
        Ticket ticket=CinemaTicketFactory.buyAndGetInfo("战狼");
        ticket.setSeat("普通座");
        ticket.info();
        //再买一张票
        System.out.println("-----买票-----");
        ticket=CinemaTicketFactory.buyAndGetInfo("战狼");
        ticket.setSeat("舒适座");
        ticket.info();
        //再买一张票
        System.out.println("-----买票-----");
        ticket=CinemaTicketFactory.buyAndGetInfo("四大名捕");
        ticket.setSeat("豪华座");
        ticket.info();
    }
}

执行结果

-----买票-----
新建对象
获取到了对象
当前影厅正在放映:战狼,您购买的是:普通座,票价为:30.0元。
-----买票-----
获取到了对象
当前影厅正在放映:战狼,您购买的是:舒适座,票价为:40.0元。
-----买票-----
新建对象
获取到了对象
当前影厅正在放映:四大名捕,您购买的是:豪华座,票价为:50.0元。

十、结构型6——代理模式

代理模式一般是指一个对象为另一个对象提供一种代理,从而通过代理对象来控制非代理对象的访问,代理对象在客户端和目标对象之间起到中介作用。静态代理是最基本的代理模式,更特殊的一种代理模式是动态代理模式。

在这里插入图片描述

1.静态代理

下面实现一个标准的静态代理模式,实际上就是一个代理对象操作被代理对象进行某种动作并在运行时添加一些额外的动作。还是以上一章的买电影票为例。

  • Ticket接口
public interface Ticket {
    void buyTicket(String name,String time,String seat);
}
  • CinemaTicket实体类
class CinemaTicket implements Ticket{
    @Override
    public void buyTicket(String name,String time,String seat){
        System.out.println(name+" 时间:"+time+" 座位:"+seat);
    }
}
  • CinemaTicketAgent代理类
class CinemaTicketAgent implements Ticket{
    private CinemaTicket cinemaTicket;

    public CinemaTicketAgent(CinemaTicket cinemaTicket){
        this.cinemaTicket=cinemaTicket;
    }

    @Override
    public void buyTicket(String name,String time,String seat){
        before();
        this.cinemaTicket.buyTicket(name,time,seat);
        after();
    }

    public void before(){
        System.out.println("-----选座位-----");
    }

    public void after(){
        System.out.println("-----付款-----");
    }
}
  • Client客户端展示
public class Client {
    public static void main(String[] args) {
        CinemaTicketAgent agent=new CinemaTicketAgent(new CinemaTicket());
        agent.buyTicket("战狼","13:00","0712");
    }
}

运行结果

-----选座位-----
战狼 时间:13:00 座位:0712
-----付款-----

虽然静态代理使用起来还是比较简单、直接,但实际上代理对象与被代理对象的耦合度仍旧较高,当需求发生变更时会涉及到较多源码上的修改。

2.动态代理

在动态代理模式中,实例化过程是动态完成的,而不是由我们手动实现的。我以JDK动态代理为例,将这一章的静态代理实现修改成动态代理实现。

  • 修改后的代理类CinemaTicketAgent

实现InvocationHandler接口 -> 根据注入的被代理对象获取代理对象 -> 调用代理对象方法

class CinemaTicketAgent implements InvocationHandler{
    private Object target;//被代理对象

    //根据注入的被代理对象获取代理对象
    public Object getAgent(Object target){
        this.target=target;
        Class<?> targetClass=target.getClass();
        //创建并返回代理对象
        return Proxy.newProxyInstance(targetClass.getClassLoader(),targetClass.getInterfaces(),this);
    }

    //调用代理对象方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object obj=method.invoke(this.target,args);
        after();
        return obj;
    }

    public void before(){
        System.out.println("-----选座位-----");
    }
    public void after(){
        System.out.println("-----付款-----");
    }
}
  • 客户端测试一下
public class Client {
    public static void main(String[] args) {
        //静态代理
        //CinemaTicketAgent agent=new CinemaTicketAgent(new CinemaTicket());
        //agent.buyTicket("战狼","13:00","0712");

        //动态代理
        Ticket ticket=(Ticket) new CinemaTicketAgent().getAgent(new CinemaTicket());
        ticket.buyTicket("爸爸去哪儿","14:20","0806");
    }
}

运行结果

-----选座位-----
爸爸去哪儿 时间:14:20 座位:0806
-----付款-----

至此,我们实现了耦合度更低的动态代理模式。

解析InvocationHandler

InvocationHandler 是 Java 标准库中的一个接口,它定义了一个可以代理方法调用的通用接口。它通常是在使用 Java 动态代理时使用的,也可以在其他一些场景下使用。

在 Java 动态代理中,InvocationHandler 是一个函数处理器,它负责处理代理对象上的方法调用。当我们使用动态代理时,我们会为代理对象指定一个 InvocationHandler,当代理对象的方法被调用时,InvocationHandlerinvoke 方法会被调用,代理对象的方法调用会被转发到该方法中进行处理。

InvocationHandler 接口只有一个方法:

public interface InvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

invoke 方法接收三个参数:

  • proxy:代理对象。
  • method:被调用的方法。
  • args:方法的参数数组。

invoke 方法返回一个 Object 类型的值,表示方法调用的返回值。

invoke 方法中,我们可以根据需要进行一些额外的操作,例如记录日志、执行一些其他操作等。最终,invoke 方法需要返回代理对象方法调用的结果,这就意味着我们需要在 invoke 方法中调用被代理对象的方法,并将其返回值返回给调用者。

总之,InvocationHandler 提供了一种通用的方式来代理对象的方法调用,并允许我们在代理对象的方法被调用时进行一些额外的操作。

解析Proxy.newProxyInstance()

Proxy.newProxyInstance() 是 Java 标准库中的一个静态方法,它用于创建一个动态代理对象。该方法的签名如下:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

该方法接收三个参数:

  • loader:一个 ClassLoader 对象,用于加载被代理类。
  • interfaces:一个 Class 类型的数组,表示被代理对象实现的接口。
  • h:一个 InvocationHandler 类型的对象,用于处理被代理对象方法的调用。

newProxyInstance() 方法返回一个代理对象,该对象实现了指定的接口,并将方法调用转发到 InvocationHandler 对象的 invoke() 方法。

例如,我们可以使用以下代码创建一个代理对象:

MyInterface myInterface = (MyInterface) Proxy.newProxyInstance(
    MyInterface.class.getClassLoader(),
    new Class<?>[] { MyInterface.class },
    new MyInvocationHandler());

上述代码中,我们要创建一个代理对象,该对象实现了 MyInterface 接口,并将方法调用转发到 MyInvocationHandler 对象的 invoke() 方法中。

十一、结构型7——桥接模式

桥接模式和前面的组合模式是有一些类似的,但各有侧重点。

桥接模式属于结构型模式,主要目的是通过聚合(组合)的方式建立两个类之间的关系,而不是通过继承来实现,从而达到解耦抽象和实现的目的。

在这里插入图片描述

设想一下,现在我们需要实现一个画画功能。首先画笔的颜色有三种,分别为红、绿、紫;其次,画笔的材质也有三种,分别为水彩、素描、油画;最后,画笔能画的形状也有三种,分别为三角形、矩形、圆形。如果是基于继承来实现这个需求,那么需要的具体的实现类为3x3x3=27种。对于这种小规模的问题或许还能勉强应付,但如果面对的是那种规模特别大、功能特别复杂的系统,那这种方法无疑是非常笨拙且效率低下的。

在这里插入图片描述

不如换一个思路,比如,我们可以设置一支画笔类Pen,然后在其中定义color属性、texture属性、shape形状,然后通过传参的方式设置具体的属性值,这样,就可以仅仅定义一支画笔而实现出27种不同的笔实例。而这个Pen类,就相当于一个桥梁,将三种颜色、三种材质、三种形状桥接起来。

1.实体类

  • 颜色
package com.wang.design.chapter11;

/**
 * @author tracy
 *
 * 11-桥接模式
 */
public interface Color {
    void showColor();
}

class Red implements Color{
    @Override
    public void showColor() {
        System.out.print("[红]");
    }
}

class Green implements Color{
    @Override
    public void showColor() {
        System.out.print("[绿]");
    }
}

class Purple implements Color{
    @Override
    public void showColor() {
        System.out.print("[紫]");
    }
}

  • 材质
package com.wang.design.chapter11;

/**
 * @author tracy
 *
 * 11-桥接模式
 */
public interface Texture {
    void showTexture();
}

class Water implements Texture{
    @Override
    public void showTexture() {
        System.out.print("[水彩]");
    }
}

class Sketch implements Texture{
    @Override
    public void showTexture() {
        System.out.print("[素描]");
    }
}

class Oil implements Texture{
    @Override
    public void showTexture() {
        System.out.print("[油画]");
    }
}
  • 形状
package com.wang.design.chapter11;

/**
 * @author tracy
 *
 * 11-桥接模式
 */
public interface Shape {
    void shape();
}

class Tri implements Shape{
    @Override
    public void shape() {
        System.out.print("△");
    }
}

class Rect implements Shape{
    @Override
    public void shape() {
        System.out.print("□");
    }
}

class Round implements Shape{
    @Override
    public void shape() {
        System.out.print("○");
    }
}

2.桥接器

  • 画笔类Pen
package com.wang.design.chapter11;

/**
 * @author tracy
 *
 * 11-桥接模式
 */
public class Pen {
    private Color color;
    private Texture texture;
    private Shape shape;

    public Pen(Color color, Texture texture, Shape shape) {
        this.color = color;
        this.texture = texture;
        this.shape = shape;
    }

    public void print(){
        this.color.showColor();
        this.texture.showTexture();
        this.shape.shape();
        System.out.println();
    }
}
  • 客户端展示
package com.wang.design.chapter11;

/**
 * @author tracy
 *
 * 11-桥接模式
 */
public class Client {
    public static void main(String[] args) {
        Pen pen=new Pen(new Red(),new Sketch(),new Rect());
        pen.print();

        pen=new Pen(new Green(),new Water(),new Tri());
        pen.print();
    }
}

运行效果

[][素描][绿][水彩]

至此,本博客的前二分之一内容已经更新完毕,后半部分我将介绍剩下的若干种行为类设计模式。


十二、行为1——模板方法模式

模板方法模式非常类似于定制表格,设计者先将所有需要填写的信息头(字段名)抽取出来,再将它们整合在一起成为一种既定格式的表格,最后让填表人按照这个标准化模板去填写自己特有的信息,而不必为书写内容、先后顺序、格式而感到困扰。

具体地说,就是把所有子类通用的信息和行为抽象出来放在父类中,建立抽象方法或非抽象的通用方法,然后由子类去继承和实现。下面我以[上数学课]和[上英语课]为例,展示模板方法模式的代码实现。

  • 实体类
package com.wang.design.chapter12;

public abstract class Course {
    abstract void register();//选课
    abstract void homework();//做作业
    abstract void exam();//考试

    public void show(){//通用方法
        this.register();
        this.homework();
        this.exam();
    }
}

class Math extends Course{
    @Override
    void register() {
        System.out.println("=====数学课开课了=====");
    }

    @Override
    void homework() {
        System.out.println("=====完成数学课平时=====");
    }

    @Override
    void exam() {
        System.out.println("=====进行数学期末考试=====");
    }
}

class English extends Course{
    @Override
    void register() {
        System.out.println("=====英语课开课了=====");
    }

    @Override
    void homework() {
        System.out.println("=====完成英语课平时=====");
    }

    @Override
    void exam() {
        System.out.println("=====进行英语期末考试=====");
    }
}
  • 客户端测试一下
package com.wang.design.chapter12;

public class Client {
    public static void main(String[] args) {
        Course course=new Math();
        course.show();

        course=new English();
        course.show();
    }
}

执行结果

=====数学课开课了=====
=====完成数学课平时=====
=====进行数学期末考试=====
=====英语课开课了=====
=====完成英语课平时=====
=====进行英语期末考试=====

十三、行为2——迭代器模式

迭代器满足了对集合迭代的需求,并向外部提供了一种统一的迭代方式,而不必暴露集合的内部数据结构。

相信很多写过代码的朋友对迭代器并不会陌生,像我们平时常见的一些集合基本上都实现了迭代器,用来对集合中的元素进行遍历。相比于普通的循环,迭代器提供的遍历可以让我们不必关注集合的总长度以及当前的索引位置。

下面我以实现一个[行车记录仪]为例。这个行车记录仪中以先进先出原则存放10条视频记录,当遍历它的时候,将按照最新记录的顺序遍历。(本章代码参考自《秒懂设计模式》)

  • 行车记录仪实现
package com.wang.design.chapter12;

import java.util.Iterator;

public class DriverRecorder implements Iterable<String>{
    private String[] records=new String[10];//最多存储10条记录
    private int index=-1;//记录最新一条记录存放的位置

    //插入一条记录
    public void append(String record){
        if(index==9){//records满了,覆盖第一条
            index=0;
        }else{
            ++index;
        }
        records[index]=record;
    }

    //获取迭代器
    @Override
    public Iterator<String> iterator(){
        return new Itr();
    }

    private class Itr implements Iterator<String>{
        int loopCount=0;//用来遍历计数
        int cursor=index;//用来记录遍历位置

        @Override
        public boolean hasNext() {
            return loopCount<10;
        }

        //按最新插入的顺序遍历元素
        @Override
        public String next() {
            int i=cursor;//用来记录需要返回的元素的位置
            if(cursor==0)cursor=9;
            else --cursor;
            ++loopCount;
            return records[i];
        }
    }
}

  • 客户端测试
package com.wang.design.chapter12;

import java.util.Iterator;

public class Client {
    public static void main(String[] args) {
        DriverRecorder recorder=new DriverRecorder();

        //插入13条视频
        for(int i=0;i<13;++i){
            recorder.append("--视频"+i+"--");
        }

        //遍历recorder中记录的视频
        Iterator<String> it= recorder.iterator();

        while(it.hasNext()){
            System.out.println(it.next());
        }
    }
}

执行代码

--视频12--
--视频11--
--视频10--
--视频9--
--视频8--
--视频7--
--视频6--
--视频5--
--视频4--
--视频3--

十四、行为3——责任链模式

在软件系统中,当一个业务需要经历一系列业务对象去处理时,我们可以把这些业务对象串联起来成为一条业务责任链,请求者可以直接通过访问业务责任链来完成业务的处理,最终实现请求者与响应者的解耦。

这一章以[报账审批]为例。假设审批流程中有三个级别的职位,分别是Staff、Manager、CFO,当金额小于等于1000元时交给Staff审批,当金额小于等于5000元时交给Manager审批,当金额小于等于10000元时交给CFO审批。当低权限的员工面临更高金额的审批时需要将审批单提交给更高级别的工作人员。

  • 实体类
package com.wang.design.chapter14;

public abstract class Approver {
    protected String name;
    protected Approver higherApprover;

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

    public void setHigherApprover(Approver higherApprover) {
        this.higherApprover = higherApprover;
    }

    public abstract void approve(int amount);//审批方法
}

class Staff extends Approver{
    public Staff(String name){
        super(name);
    }

    @Override
    public void approve(int amount) {
        System.out.println("-----开始审批-----");
        if(amount<=1000){
            System.out.println("staff"+this.name+"审批通过");
        }else if(this.higherApprover==null){
            System.out.println("staff"+this.name+"审批不通过");
        }else{
            System.out.println("staff"+this.name+"将流程提交给更高权限的工作人员");
            this.higherApprover.approve(amount);
        }
    }
}

class Manager extends Approver{
    public Manager(String name){
        super(name);
    }

    @Override
    public void approve(int amount) {
        if(amount<=5000){
            System.out.println("manager"+this.name+"审批通过");
        }else if(this.higherApprover==null){
            System.out.println("manager"+this.name+"审批不通过");
        }else{
            System.out.println("manager"+this.name+"将流程提交给更高权限的工作人员");
            this.higherApprover.approve(amount);
        }
    }
}

class CFO extends Approver{
    public CFO(String name){
        super(name);
    }

    @Override
    public void approve(int amount) {
        if(amount<=10000){
            System.out.println("cfo"+this.name+"审批通过");
        }else{
            System.out.println("cfo"+this.name+"审批不通过");
        }
    }
}
  • 客户端测试
package com.wang.design.chapter14;

public class Client {
    public static void main(String[] args) {
        Staff staff=new Staff("小王");
        Manager manager=new Manager("张冬良");
        CFO cfo=new CFO("程哥");

        staff.setHigherApprover(manager);
        manager.setHigherApprover(cfo);

        staff.approve(600);
        staff.approve(1200);
        staff.approve(6700);
        staff.approve(10020);
    }
}

测试结果

-----开始审批-----
staff小王审批通过
-----开始审批-----
staff小王将流程提交给更高权限的工作人员
manager张冬良审批通过
-----开始审批-----
staff小王将流程提交给更高权限的工作人员
manager张冬良将流程提交给更高权限的工作人员
cfo程哥审批通过
-----开始审批-----
staff小王将流程提交给更高权限的工作人员
manager张冬良将流程提交给更高权限的工作人员
cfo程哥审批不通过

十五、行为4——策略模式

策略模式强调的是行为的灵活切换,比如一个类的多个方法有着类似的行为接口,可以将它们抽离出来作为一系列策略类,在运行时灵活对接,变更其算法策略,以适应不同的场景。

本章以[玻璃杯]为例。一个玻璃杯,可以装啤酒、咖啡、纯净水、牛奶等等,如果每装一种饮料就创建一个新的杯子,长此以往,系统会变得臃肿不堪。比起不断地创建不同的杯子,不如创建一套灵活的策略,用一个杯子通过策略的运用装入不同的饮料,实现可插拔的功能。

  • 玻璃杯
package com.wang.design.chapter15;

public class Glass {
    private Drinks drinks;

    public void setDrinks(Drinks drinks) {
        this.drinks = drinks;
    }

    public void drinking(){
        System.out.println("玻璃杯中装入了"+drinks.name);
    }
}
  • 饮品
package com.wang.design.chapter15;

public abstract class Drinks {
    protected String name;

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

class Milk extends Drinks{
    public Milk(String name) {
        super(name);
    }
}

class Tea extends Drinks{
    public Tea(String name) {
        super(name);
    }
}
  • 客户端测试
package com.wang.design.chapter15;

public class Client {
    public static void main(String[] args) {
        Glass glass=new Glass();

        glass.setDrinks(new Milk("纯牛奶"));
        glass.drinking();

        glass.setDrinks(new Tea("乌龙茶"));
        glass.drinking();
    }
}

测试结果

玻璃杯中装入了纯牛奶
玻璃杯中装入了乌龙茶

十六、行为5——状态模式

状态指事物基于所处的状况、形态表现出的不同的行为特性。状态模式构架出一套完备的事物内部状态转换机制,并将内部状态包裹起来且对外部不可见,使其行为能随其状态的改变而改变,同时简化了事物的复杂的状态变化逻辑。

这一章以[红灯、黄灯、绿灯的切换]为例。首先为我们的信号灯切换制定出规则,红灯亮60s,接着黄灯亮10s,然后绿灯亮30s,绿灯亮完红灯亮,开始新一轮的循环。

  • 实体类
package com.wang.design.chapter16;

public abstract class Light {
    protected static int counter=0;//用来读秒
    protected abstract void on();
}

class Red extends Light{
    @Override
    public void on() {
        System.out.println(counter+"s 红灯...");
    }
}

class Yellow extends Light{
    @Override
    public void on() {
        System.out.println(counter+"s 黄灯...");
    }
}

class Green extends Light{
    @Override
    public void on() {
        System.out.println(counter+"s 绿灯...");
    }
}

  • 状态切换器
package com.wang.design.chapter16;

import java.util.Timer;
import java.util.TimerTask;

public class Switcher {
    private Light light;

    public void lightSwitch(){
        TimerTask timerTask=new TimerTask() {
            @Override
            public void run() {
                if(Light.counter<60){
                    light=new Red();
                    light.on();
                    Light.counter+=5;
                }else if(Light.counter<70){
                    light=new Yellow();
                    light.on();
                    Light.counter+=5;
                }else{
                    light=new Green();
                    light.on();
                    Light.counter+=5;
                    if(Light.counter==100) Light.counter=0;
                }
            }
        };
        new Timer().schedule(timerTask,0,5000);//设置定时任务,延迟0s,每过5s执行一次
    }
}

  • 客户端测试
package com.wang.design.chapter16;

public class Client {
    public static void main(String[] args) {
        new Switcher().lightSwitch();
    }
}

执行结果

0s 红灯...
5s 红灯...
10s 红灯...
15s 红灯...
20s 红灯...
25s 红灯...
30s 红灯...
35s 红灯...
40s 红灯...
45s 红灯...
50s 红灯...
55s 红灯...
60s 黄灯...
65s 黄灯...
70s 绿灯...
75s 绿灯...
80s 绿灯...
85s 绿灯...
90s 绿灯...
95s 绿灯...
0s 红灯...
5s 红灯...
......

十七、行为6——备忘录模式

备忘录用来记录曾经发生过的事情,使回溯历史变得切实可行。备忘录模式则可以在不破坏元对象封装性的前提下捕获其在某些时刻的内部状态,并像历史快照一样将它们保留在元对象之外,以备恢复之用。

这一章以实现一个[备忘录类]为例。这个类有几个重要功能:1以行为单位插入内容;2以行为单位删除内容;3一键清空内容;4备份功能。下面就来实现它。

  • 备忘录
package com.wang.design.chapter17;


import java.util.LinkedList;

public class Remember {
    private String title;
    private LinkedList<String> content;
    private LinkedList<LinkedList<String>> historyList;//最多备份5条

    public Remember(String title){
        this.title=title;
        this.content=new LinkedList<>();
        this.historyList=new LinkedList<>();
    }

    // 1以行为单位插入内容,每插入一行自动备份
    public void append(String row){
        System.out.println("添加:"+row);
        this.content.addLast(row);
        this.backup();
        this.show();
    }

    // 2以行为单位删除内容
    public void delete(){
        if(this.content.size()==0){
            System.out.println("内容为空,无法删除!");
            return;
        }
        System.out.println("删除:"+this.content.removeLast());
        this.show();
    }

    // 3一键清空内容
    public void clear(){
        this.content=new LinkedList<>();
        System.out.println("已清空内容");
        this.show();
    }

    // 4展示最新备份内容
    public void latestHistory(){
        System.out.println("正在获取最新备份内容:");
        if(this.historyList.size()==0){
            System.out.println("没有备份!");
            return;
        }
        for(String row:this.historyList.getLast()){
            System.out.println(row);
        }
        System.out.println();
    }

    // 5退回到最新备份内容
    public void recovery(){
        System.out.println("正在恢复...");
        this.content=this.historyList.getLast();
        System.out.println("备忘录恢复成功:");
        this.show();
    }

    // 私有方法:展示所有已输入内容
    private void show(){
        System.out.println("------<"+this.title+">------");
        if(this.content.size()==0) {
            System.out.println("null");
            System.out.println();
            return;
        }
        for(String row:this.content){
            System.out.println(row);
        }
        System.out.println();
    }

    // 私有方法:备份功能
    private void backup(){
        this.historyList.addLast(new LinkedList<>(this.content));
        if(this.historyList.size()==6)this.historyList.removeFirst();
    }
}

  • 客户端测试
package com.wang.design.chapter17;

public class Client {
    public static void main(String[] args) {
        // 新建备忘录
        Remember remember=new Remember("待办事项");
        
        // 插入文本
        remember.append("算法刷题");
        remember.append("买菜");
        remember.append("背单词");
        
        // 删除一行
        remember.delete();
        
        // 获取最新备份记录
        remember.latestHistory();
        
        // 恢复
        remember.recovery();
        
        // 情况
        remember.clear();

        // 恢复
        remember.recovery();
    }
}

测试结果

添加:算法刷题
------<待办事项>------
算法刷题

添加:买菜
------<待办事项>------
算法刷题
买菜

添加:背单词
------<待办事项>------
算法刷题
买菜
背单词

删除:背单词
------<待办事项>------
算法刷题
买菜

正在获取最新备份内容:
算法刷题
买菜
背单词

正在恢复...
备忘录恢复成功:
------<待办事项>------
算法刷题
买菜
背单词

已清空内容
------<待办事项>------
null

正在恢复...
备忘录恢复成功:
------<待办事项>------
算法刷题
买菜
背单词

十八、行为7——中介模式

中介是在事物之间传播信息的中间媒介。中介模式为对象构架出一个互动平台,通过减少对象间的依赖程度以达到解耦的目的。

本章以实现一个[简易的聊天室]为例,通过聊天室这个平台,实现用户与用户之间的解耦。

  • 用户类
package com.wang.design.chapter18;

import java.util.ArrayList;
import java.util.Objects;

public class User {
    private String name;
    private ChatRoom chatRoom;
    private ArrayList<String> msgBox;//接收消息的容器

    public User(String name){
        this.name=name;
        this.msgBox=new ArrayList<>();
    }

    public String getName() {
        return name;
    }

    //加入聊天室
    public void setChatRoom(ChatRoom chatRoom) {
        this.chatRoom = chatRoom;
        chatRoom.register(this);//在聊天室中登记注册用户信息
    }

    //发言
    public void talk(String words,User to){
        System.out.println(this.name+"正在发言...");
        //不指定用户,则默认为广播
        if(to==null){
            chatRoom.broadcast(this,words);
        }
        //指定向特定用户发送消息
        else chatRoom.toSomeone(this,to,words);
        System.out.println();
    }

    //接收消息
    public void receive(String msg){
        this.msgBox.add(msg);
    }

    //获取消息
    public void listen(){
        System.out.println("用户["+this.name+"]正在收听消息:");
        for (String str:this.msgBox){
            System.out.println(str);
        }
        System.out.println();
    }

    //判断两个用户是否为同一个
    @Override
    public boolean equals(Object obj) {
        if(obj==null||this.getClass()!=obj.getClass())return false;
        User u=(User)obj;
        return Objects.equals(this.name,u.getName());
    }
}

  • 聊天室类
package com.wang.design.chapter18;

import java.util.HashMap;

public class ChatRoom {
    private String name;//群聊名称
    private HashMap<String,User> members;//群聊成员

    public ChatRoom(String name){
        this.name=name;
        members=new HashMap<>();
        System.out.println("聊天群["+this.name+"]已创建成功");
        System.out.println();
    }

    //新成员加入
    public void register(User user){
        members.put(user.getName(),user);
        System.out.println("用户["+user.getName()+"]加入聊天群");
        System.out.println();
    }

    //广播消息
    public void broadcast(User from,String words){
        if(this.members.size()!=0){
            for(User user:this.members.values()){
                user.receive(from.getName()+":"+words);
            }
        }
    }

    //给特定用户发消息
    public void toSomeone(User from,User to,String words){
        if(to.equals(this.members.getOrDefault(to.getName(),null))){
            to.receive(from.getName()+"(私):"+words);
        }
    }
}

  • 客户端测试
package com.wang.design.chapter18;

public class Client {
    public static void main(String[] args) {
        //创建三个用户
        User user1=new User("小明");
        User user2=new User("小花");
        User user3=new User("团团");

        //创建聊天室
        ChatRoom chatRoom=new ChatRoom("卷王交流群");
        
        //用户加入聊天室
        user1.setChatRoom(chatRoom);
        user2.setChatRoom(chatRoom);
        user3.setChatRoom(chatRoom);

        //用户发言
        user1.talk("你们做作业了吗",null);
        user1.talk("帮我签一下到",user2);//私聊
        user3.talk("后天考试",null);

        //用户收听消息
        user1.listen();
        user2.listen();
        user3.listen();
    }
}

运行结果

聊天群[卷王交流群]已创建成功

用户[小明]加入聊天群

用户[小花]加入聊天群

用户[团团]加入聊天群

小明正在发言...

小明正在发言...

团团正在发言...

用户[小明]正在收听消息:
小明:你们做作业了吗
团团:后天考试

用户[小花]正在收听消息:
小明:你们做作业了吗
小明():帮我签一下到
团团:后天考试

用户[团团]正在收听消息:
小明:你们做作业了吗
团团:后天考试


Process finished with exit code 0

十九、行为8——命令模式

命令是一个对象向另一个或多个对象发送的指令信息。命令的发送方负责下达指令,接收方则根据命令触发相应的行为。命令模式能够将指令信息封装成一个对象,并将此对象作为参数发送给接收方去执行,以使命令的请求方与执行方解耦,双方只通过传递各种命令过象来完成任务。

这个设计模式简单来说就是,我有一个类A,现在我想通过类B操控A的一些行为,但是我又不想直接在类B中出现A中的实例化过程之类的(不想耦合),所以我就再创建一个Commond类C来控制A,这样B和A不就解耦了吗。

下面我以[开/关空调]为例来实现这种设计模式。

  • 空调类
package com.wang.design.chapter19;

/**
 * @author tracy
 *
 * 19-命令模式
 */
public class AirConditioner {
    private String name;
    private boolean on=false;

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

    //开
    public void turnOn(){
        System.out.println(this.name+"打开了");
        if(!on)this.on=true;
    }

    //关
    public void turnOff(){
        System.out.println(this.name+"关闭了");
        if(on)this.on=false;
    }
}
  • Command类
package com.wang.design.chapter19;

public class AirCommand {
    private AirConditioner airConditioner;

    public AirCommand(AirConditioner airConditioner) {
        this.airConditioner = airConditioner;
    }

    public void onExecute(){
        this.airConditioner.turnOn();
    }

    public void offExecute(){
        this.airConditioner.turnOff();
    }
}

  • 遥控器Switcher
package com.wang.design.chapter19;

public class Switcher {
    private AirCommand airCommand;

    public Switcher(AirCommand airCommand) {
        this.airCommand = airCommand;
    }

    public void buttonPush(){
        System.out.println("按下开关");
        this.airCommand.onExecute();
        System.out.println();
    }

    public void buttonPop(){
        System.out.println("放下开关");
        this.airCommand.offExecute();
        System.out.println();
    }
}

  • 客户端测试
package com.wang.design.chapter19;

public class Client {
    public static void main(String[] args) {
        AirCommand airCommand=new AirCommand(new AirConditioner("美的空调"));
        Switcher switcher=new Switcher(airCommand);
        switcher.buttonPush();
        switcher.buttonPop();
    }
}

执行结果

按下开关
美的空调打开了

放下开关
美的空调关闭了

二十、行为9——访问者模式

访问者模式主要解决的是数据与算法的耦合问题,尤其是在数据结构比较稳定,而算法多变的情况下。为了不「污染」数据本身,访问者模式会将多种算法独立归类,并在访问数据时根据数据类型自动切换到对应的算法,实现数据的自动响应机制,并且确保算法的自由扩展。

简单来说,访问者模式就是,将数据本身与数据的访问过程分开实现,使二者解耦,从而保证数据不受污染。

本章以[超市商品计价为例]。我们都知道,超市有着琳琅满目的商品,每件商品都有着不同的计价方式和优惠方式,但是无论计价方式多么复杂,最后都交给收银员(访问者)统一处理。

  • 自动派发机制

当我们仅考虑一件商品的结算时是很容易实现的。

商品类定义

package com.wang.design.chapter20;
import java.time.LocalDate;

/**
 * @author tracy
 * 20-访问者模式
 */

// 商品实体类继承体系
public abstract class Product {
    private String name;//商品名称
    private LocalDate productedDate;//生产日期,后面打折会用到
    private float price;//单价

    public Product(String name, LocalDate productedDate, float price) {
        this.name = name;
        this.productedDate = productedDate;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public LocalDate getProductedDate() {
        return productedDate;
    }

    public float getPrice() {
        return price;
    }
}

//第一种商品:袋装果干
class DriedFruit extends Product{
    public DriedFruit(String name, LocalDate productedDate, float price) {
        super(name, productedDate, price);
    }
}

//第二种商品:散装水果
class Fruit extends Product{
    private float weight;//散装商品需要称重,定义一个新的属性

    public float getWeight() {
        return weight;
    }

    public Fruit(String name, LocalDate productedDate, float price,float weight) {
        super(name, productedDate, price);
        this.weight=weight;
    }
}

//第三种商品:瓶装饮料
class Drink extends Product{
    public Drink(String name, LocalDate productedDate, float price) {
        super(name, productedDate, price);
    }
}

计价类(访问者)

package com.wang.design.chapter20;

import java.time.LocalDate;

/**
 * @author tracy
 * 20-访问者模式
 */
public interface Visitor {
    //为每一种商品实现一种计价方式
    void visit(DriedFruit driedFruit);
    void visit(Fruit fruit);
    void visit(Drink drink);
}

//打折计价类的实现
class DiscountVisitor implements Visitor{
    private LocalDate checkDate;//定义一个结账日期
    private float total;//定义一个结账总金额

    public DiscountVisitor(LocalDate checkDate) {
        this.checkDate = checkDate;
        this.total=0f;
        System.out.println("结账日期:"+checkDate);
    }

    @Override
    public void visit(DriedFruit driedFruit) {
        System.out.println("=====果干【"+driedFruit.getName()+"】=====");
        float rate=1;//打折率
        long days=checkDate.toEpochDay()-driedFruit.getProductedDate().toEpochDay();
        if(days>180){
            rate=0;
            System.out.println("果干已过期,请勿食用!");
        }else if(days>60){//超过两个月,8折
            rate=0.8f;
        }else{//默认9折
            rate=0.9f;
        }
        float bill=driedFruit.getPrice()*rate;
        System.out.println(driedFruit.getName()+"1件共计"+bill+"元");
        this.total+=bill;
        System.out.println("当前消费总金额"+this.total+"元");
    }

    @Override
    public void visit(Fruit fruit) {
        System.out.println("=====水果【"+fruit.getName()+"】=====");
        float rate=1;//打折率
        long days=checkDate.toEpochDay()-fruit.getProductedDate().toEpochDay();
        if(days>7){
            rate=0;
            System.out.println("水果已过期,请勿食用!");
        }else if(days>3){//超过3天,5折
            rate=0.5f;
        }else{//默认9折
            rate=0.9f;
        }
        float bill=fruit.getPrice()*rate;
        System.out.println(fruit.getName()+fruit.getWeight()+"斤共计"+bill+"元");
        this.total+=bill;
        System.out.println("当前消费总金额"+this.total+"元");
    }

    @Override
    public void visit(Drink drink) {
        System.out.println("=====饮料【"+drink.getName()+"】=====");
        System.out.println(drink.getName()+"1件共计"+drink.getPrice()+"元");
        this.total+=drink.getPrice();//不打折
        System.out.println("当前消费总金额"+this.total+"元");
    }
}

客户端测试一下

package com.wang.design.chapter20;

import java.time.LocalDate;

/**
 * @author tracy
 * 20-访问者模式
 */

public class Client {
    public static void main(String[] args) {
        //一袋芒果干,单价15元
        DriedFruit driedFruit=new DriedFruit("芒果干", LocalDate.of(2022,3,1),15.0f);
        //两斤苹果,单价5.2元
        Fruit apple=new Fruit("苹果",LocalDate.of(2022,6,3),5.2f,2.0f);
        //一瓶红酒,单价125元
        Drink wine=new Drink("红酒",LocalDate.of(2020,4,3),125.0f);
        
        Visitor discountVisitor=new DiscountVisitor(LocalDate.now());
        discountVisitor.visit(driedFruit);//计价
        discountVisitor.visit(wine);//计价
        discountVisitor.visit(apple);//计价
    }
}

执行结果

结账日期:2022-06-05
=====果干【芒果干】=====
芒果干1件共计12.0元
当前消费总金额12.0=====饮料【红酒】=====
红酒1件共计125.0元
当前消费总金额137.0=====水果【苹果】=====
苹果2.0斤共计4.68元
当前消费总金额141.68Process finished with exit code 0

  • 批量计价

单价商品的计价实现还是比较简单的,现在我们考虑实现批量计价。

一种很容易想到的实现方式是将商品类型处理为泛型:

package com.wang.design.chapter20;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author tracy
 * 20-访问者模式
 */

public class Client {
    public static void main(String[] args) {
        //一袋芒果干,单价15元
        //两斤苹果,单价5.2元
        //一瓶红酒,单价125元
        List<Product> products= Arrays.asList(
                new DriedFruit("芒果干", LocalDate.of(2022,3,1),15.0f),
                new Fruit("苹果",LocalDate.of(2022,6,3),5.2f,2.0f),
                new Drink("红酒",LocalDate.of(2020,4,3),125.0f)
        );

        //批量结算
        Visitor discountVisitor=new DiscountVisitor(LocalDate.now());
        for(Product product:products){
            discountVisitor.visit(product);//这里会报错
        }
    }
}

但由于传入的类型统一被视为是Product类型,系统并不知道具体是哪一种商品类,因此这样的修改并不能达到批量计价的目的。

  • 双派发机制

为了实现批量计价的功能,这里引入一种双派发机制。

增加Acceptable接口,让每个商品类实现此接口,主动接待访问者(相当于将商品类型放入购物车),向visitor派发自己。

public interface Acceptable {
    void accept(Visitor visitor);
}
package com.wang.design.chapter20;

import java.time.LocalDate;

/**
 * @author tracy
 * 20-访问者模式
 */

// 商品实体类继承体系
public abstract class Product{
    private String name;//商品名称
    private LocalDate productedDate;//生产日期,后面打折会用到
    private float price;//单价

    public Product(String name, LocalDate productedDate, float price) {
        this.name = name;
        this.productedDate = productedDate;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public LocalDate getProductedDate() {
        return productedDate;
    }

    public float getPrice() {
        return price;
    }
}

//第一种商品:袋装果干
class DriedFruit extends Product implements Acceptable{
    public DriedFruit(String name, LocalDate productedDate, float price) {
        super(name, productedDate, price);
    }

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

//第二种商品:散装水果
class Fruit extends Product implements Acceptable{
    private float weight;//散装商品需要称重,定义一个新的属性

    public float getWeight() {
        return weight;
    }

    public Fruit(String name, LocalDate productedDate, float price,float weight) {
        super(name, productedDate, price);
        this.weight=weight;
    }

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

//第三种商品:瓶装饮料
class Drink extends Product implements Acceptable{
    public Drink(String name, LocalDate productedDate, float price) {
        super(name, productedDate, price);
    }

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

客户端测试

package com.wang.design.chapter20;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author tracy
 * 20-访问者模式
 */

public class Client {
    public static void main(String[] args) {
        //一袋芒果干,单价15元
        //两斤苹果,单价5.2元
        //一瓶红酒,单价125元
        List<Acceptable> products= Arrays.asList(
                new DriedFruit("芒果干", LocalDate.of(2022,3,1),15.0f),
                new Fruit("苹果",LocalDate.of(2022,6,3),5.2f,2.0f),
                new Drink("红酒",LocalDate.of(2020,4,3),125.0f)
        );

        //批量结算
        Visitor discountVisitor=new DiscountVisitor(LocalDate.now());
        for(Acceptable product:products){
            product.accept(discountVisitor);
        }
    }
}

执行过程

结账日期:2022-06-05
=====果干【芒果干】=====
芒果干1件共计12.0元
当前消费总金额12.0=====水果【苹果】=====
苹果2.0斤共计4.68元
当前消费总金额16.68=====饮料【红酒】=====
红酒1件共计125.0元
当前消费总金额141.68

至此,访问者模式已完全实现。

二十一、行为10——观察者模式

观察者模式可以针对被观察对象与观察者对象之间一对多的依赖关系建立起一种行为自动触发机制,当被观察对象状态发生变化时主动对外发起广播,以通知所有观察者做出响应。

在一般情况下,观察者总是处于非常忙碌的状态,因为它总是在一遍又一遍地轮询被观察者,直到被观察者的状态发生变化。这样做无疑会给系统带来很多额外的负担。观察者模式则反其道而行之,与其让观察者无休止地询问,不如让被观察者在状态发生变化之后,主动通知观察者前来访问。

本章以[商店卖货]为例。

  • 杂货铺
package com.wang.design.chapter21;

import java.util.ArrayList;
import java.util.List;

/**
 * @author tracy
 * 21-观察者模式
 */
public class Shop {
    private String name;//商店名
    private List<String> handicraft,stationary,phone;//售卖种类:手工艺品、文具、手机
    private List<Buyer> buyers;//买家预定

    public Shop(String name){
        this.name=name;
        this.handicraft=new ArrayList<>();
        this.stationary=new ArrayList<>();
        this.phone=new ArrayList<>();
        buyers=new ArrayList<>();
        System.out.println(this.name+"开张了!");
    }

    public List<String> getHandicraft() {
        return handicraft;
    }
    public List<String> getStationary() {
        return stationary;
    }
    public List<String> getPhone() {
        return phone;
    }

    //商家登记
    public void register(Buyer buyer){
        this.buyers.add(buyer);
    }

    //进货
    public void purchaseHandicraft(String product){
        this.handicraft.add(product);
        notifyBuyers();
    }
    public void purchaseStationary(String product){
        this.stationary.add(product);
        notifyBuyers();
    }
    public void purchasePhone(String product){
        this.phone.add(product);
        notifyBuyers();
    }

    //通知买家
    public void notifyBuyers(){
        this.buyers.stream().forEach(b->b.inform(this));
    }
}

  • 买家类
package com.wang.design.chapter21;

import java.util.List;

/**
 * @author tracy
 * 21-观察者模式
 */
public abstract class Buyer {
    protected String name;

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

    //来自商家的到货通知
    public abstract void inform(Shop shop);
}

class HandicraftBuyer extends Buyer{
    public HandicraftBuyer(String name) {
        super(name);
    }

    @Override
    public void inform(Shop shop) {
        List<String> products=shop.getHandicraft();
        if(products.size()!=0){
            System.out.println("====="+this.name+"买入手工艺品=====");
            for (String str:products){
                System.out.println(str);
            }
            products.clear();
        }
    }
}

class StatonaryBuyer extends Buyer{
    public StatonaryBuyer(String name) {
        super(name);
    }

    @Override
    public void inform(Shop shop) {
        List<String> products=shop.getStationary();
        if(products.size()!=0){
            System.out.println("====="+this.name+"买入文具=====");
            for (String str:products){
                System.out.println(str);
            }
            products.clear();
        }
    }
}

class PhoneBuyer extends Buyer{
    public PhoneBuyer(String name) {
        super(name);
    }

    @Override
    public void inform(Shop shop) {
        List<String> products=shop.getPhone();
        if(products.size()!=0){
            System.out.println("====="+this.name+"买入手机=====");
            for (String str:products){
                System.out.println(str);
            }
            products.clear();
        }
    }
}
  • 客户端测试
package com.wang.design.chapter21;

/**
 * @author tracy
 * 21-观察者模式
 */
public class Client {
    public static void main(String[] args) {
        Shop shop=new Shop("解忧杂货铺");
        Buyer handyBuyer=new HandicraftBuyer("手工艺品买手");
        Buyer phoneBuyer=new PhoneBuyer("手机买手");
        Buyer stationaryBuyer=new StatonaryBuyer("文具买手");

        //买家登记
        shop.register(handyBuyer);
        shop.register(phoneBuyer);
        shop.register(stationaryBuyer);

        //到货了
        shop.purchaseHandicraft("兵马俑摆件");
        shop.purchaseHandicraft("海螺捕梦网");
        shop.purchaseHandicraft("古风沙漏");
        shop.purchaseStationary("直尺");
        shop.purchaseStationary("书立");
        shop.purchaseStationary("订书机");
        shop.purchasePhone("华为荣耀20");
        shop.purchasePhone("华为荣耀20s");
    }
}

执行结果

解忧杂货铺开张了!
=====手工艺品买手买入手工艺品=====
兵马俑摆件
=====手工艺品买手买入手工艺品=====
海螺捕梦网
=====手工艺品买手买入手工艺品=====
古风沙漏
=====文具买手买入文具=====
直尺
=====文具买手买入文具=====
书立
=====文具买手买入文具=====
订书机
=====手机买手买入手机=====
华为荣耀20
=====手机买手买入手机=====
华为荣耀20s

Process finished with exit code 0

参考资料

  • 《秒懂设计模式》
  • 蓝桥云课https://www.lanqiao.cn/courses/3031
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【设计模式】用Java手写21种常见设计模式 的相关文章

  • java.lang.NoClassDefFoundError:org.apache.batik.dom.svg.SVGDOMImplementation

    我在链接到我的 Android LibGDX 项目的 Apache Batik 库时遇到了奇怪的问题 但让我们从头开始 在 IntelliJ Idea 中我有一个项目 其中包含三个模块 Main Android 和 Desktop 我强调的
  • Java Swing:从 JOptionPane 获取文本值

    我想创建一个用于 POS 系统的新窗口 用户输入的是客户拥有的金额 并且窗口必须显示兑换金额 我是新来的JOptionPane功能 我一直在使用JAVAFX并且它是不同的 这是我的代码 public static void main Str
  • Java中反射是如何实现的?

    Java 7 语言规范很早就指出 本规范没有详细描述反射 我只是想知道 反射在Java中是如何实现的 我不是问它是如何使用的 我知道可能没有我正在寻找的具体答案 但任何信息将不胜感激 我在 Stackoverflow 上发现了这个 关于 C
  • 给定两个 SSH2 密钥,我如何检查它们是否属于 Java 中的同一密钥对?

    我正在尝试找到一种方法来验证两个 SSH2 密钥 一个私有密钥和一个公共密钥 是否属于同一密钥对 我用过JSch http www jcraft com jsch 用于加载和解析私钥 更新 可以显示如何从私钥 SSH2 RSA 重新生成公钥
  • 使用 Android 发送 HTTP Post 请求

    我一直在尝试从 SO 和其他网站上的大量示例中学习 但我无法弄清楚为什么我编写的示例不起作用 我正在构建一个小型概念验证应用程序 它可以识别语音并将其 文本 作为 POST 请求发送到 node js 服务器 我已确认语音识别有效 并且服务
  • 制作一个交互式Windows服务

    我希望我的 Java 应用程序成为交互式 Windows 服务 用户登录时具有 GUI 的 Windows 服务 我搜索了这个 我发现这样做的方法是有两个程序 第一个是服务 第二个是 GUI 程序并使它们进行通信 服务将从 GUI 程序获取
  • Final字段的线程安全

    假设我有一个 JavaBeanUser这是从另一个线程更新的 如下所示 public class A private final User user public A User user this user user public void
  • 操作错误不会显示在 JSP 上

    我尝试在 Action 类中添加操作错误并将其打印在 JSP 页面上 当发生异常时 它将进入 catch 块并在控制台中打印 插入异常时出错 请联系管理员 在 catch 块中 我添加了它addActionError 我尝试在jsp页面中打
  • 我可以使用 HSQLDB 进行 junit 测试克隆 mySQL 数据库吗

    我正在开发一个 spring webflow 项目 我想我可以使用 HSQLDB 而不是 mysql 进行 junit 测试吗 如何将我的 mysql 数据库克隆到 HSQLDB 如果您使用 spring 3 1 或更高版本 您可以使用 s
  • Mockito when().thenReturn 不必要地调用该方法

    我正在研究继承的代码 我编写了一个应该捕获 NullPointerException 的测试 因为它试图从 null 对象调用方法 Test expected NullPointerException class public void c
  • 在两个活动之间传输数据[重复]

    这个问题在这里已经有答案了 我正在尝试在两个不同的活动之间发送和接收数据 我在这个网站上看到了一些其他问题 但没有任何问题涉及保留头等舱的状态 例如 如果我想从 A 类发送一个整数 X 到 B 类 然后对整数 X 进行一些操作 然后将其发送
  • JRE 系统库 [WebSphere v6.1 JRE](未绑定)

    将项目导入 Eclipse 后 我的构建路径中出现以下错误 JRE System Library WebSphere v6 1 JRE unbound 谁知道怎么修它 右键单击项目 特性 gt Java 构建路径 gt 图书馆 gt JRE
  • 仅将 char[] 的一部分复制到 String 中

    我有一个数组 char ch 我的问题如下 如何将 ch 2 到 ch 7 的值合并到字符串中 我想在不循环 char 数组的情况下实现这一点 有什么建议么 感谢您花时间回答我的问题 Use new String value offset
  • Java执行器服务线程池[关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 如果我使用 Executor 框架在
  • 如何从泛型类调用静态方法?

    我有一个包含静态创建方法的类 public class TestClass public static
  • 有没有办法为Java的字符集名称添加别名

    我收到一个异常 埋藏在第 3 方库中 消息如下 java io UnsupportedEncodingException BIG 5 我认为发生这种情况是因为 Java 没有定义这个名称java nio charset Charset Ch
  • 使用 JMF 创建 RTP 流时出现问题

    我正处于一个项目的早期阶段 需要使用 RTP 广播DataStream创建自MediaLocation 我正在遵循一些示例代码 该代码目前在rptManager initalize localAddress 出现错误 无法打开本地数据端口
  • 按日期对 RecyclerView 进行排序

    我正在尝试按日期对 RecyclerView 进行排序 但我尝试了太多的事情 我不知道现在该尝试什么 问题就出在这条线上适配器 notifyDataSetChanged 因为如果我不放 不会显示错误 但也不会更新 recyclerview
  • 如何实现仅当可用内存较低时才将数据交换到磁盘的写缓存

    我想将应用程序生成的数据缓存在内存中 但如果内存变得稀缺 我想将数据交换到磁盘 理想情况下 我希望虚拟机通知它需要内存并将我的数据写入磁盘并以这种方式释放一些内存 但我没有看到任何方法以通知我的方式将自己挂接到虚拟机中before an O
  • 使用 xpath 和 vtd-xml 以字符串形式获取元素的子节点和文本

    这是我的 XML 的一部分

随机推荐