offer来了(原理篇)学习笔记-第9章设计模式

设计模式

  • 设计模式简介
    • 单一职责原则
    • 开闭原则
    • 里氏代换原则
    • 依赖倒转原则
    • 接口隔离原则
    • 合成/聚合复用原则
    • 迪米特法则
  • 1.工厂模式的概念及Java实现
  • 2.抽象工厂模式的概念及Java实现
  • 3.单例模式的概念及Java实现
    • 懒汉模式(线程安全)
    • 饿汉模式
    • 静态内部类
    • 双重校验锁
  • 4.建造者模式的概念及Java实现
  • 5.原型模式的概念及Java实现
  • 6.适配器模式的概念及Java实现
    • 类适配器模式
    • 对象适配器模式
    • 接口适配器模式
  • 7.装饰者模式的概念及Java实现
  • 8.代理模式的概念及Java实现
  • 9.外观模式的概念及Java实现
  • 10.桥接模式的概念及Java实现
  • 11.组合模式的概念及Java实现
  • 12.享元模式的概念及Java实现
  • 13.策略模式的概念及Java实现
  • 14.模板方法模式的概念及Java实现
  • 15.观察者模式的概念及Java实现
  • 16.迭代器模式的概念及Java实现
  • 17.责任链模式的概念及Java实现
  • 18.命令模式的概念及Java实现
  • 19.备忘录模式的概念及Java实现
  • 20.状态模式的概念及Java实现
  • 21.访问者模式的概念及Java实现
  • 22.中介者模式的概念及Java实现
  • 23.解释器模式的概念及Java实现
  • 本书结束

设计模式(Design Pattern)是经过高度抽象化的在编程中可以被反复使用的代码设计经验的总结。

正确使用设计模式能有效提高代码的可读性、可重用性和可靠性,编写符合设计模式规范的代码不但有利于自身系统的稳定、可靠,还有利于外部系统的对接。在使用了良好的设计模式的系统工程中,无论是对满足当前的需求,还是对适应未来的需求,无论是对自身系统间模块的对接,还是对外部系统的对接,都有很大的帮助。

设计模式简介

设计模式是人们经过长期编程经验总结出来的一种编程思想。随着软件工程的不断演进,针对不同的需求,新的设计模式不断被提出(比如大数据领域中这些年不断被大家认可的数据分片思想),但设计模式的原则不会变。基于设计模式的原则,我们可以使用已有的设计模式,也可以根据产品或项目的开发需求在现有的设计模式基础上组合、改造或重新设计自身的设计模式。

设计模式有7个原则:单一职责原则、开闭原则、里氏代换原则、依赖倒转原则、接口隔离原则、合成/聚合复用原则、迪米特法则。

单一职责原则

单一职责原则又称单一功能原则,它规定一个类只有一个职责。如果有多个职责(功能)被设计在一个类中,这个类就违反了单一职责原则。

开闭原则

开闭原则规定软件中的对象(类、模块、函数等)对扩展开放,对修改封闭,这意味着一个实体允许在不改变其源代码的前提下改变其行为,该特性在产品化的环境下是特别有价值的,在这种环境下,改变源代码需要经过代码审查、单元测试等过程,以确保产品的使用质量。遵循这个原则的代码在扩展时并不发生改变,因此不需要经历上述过程。

里氏代换原则

里氏代换原则是对开闭原则的补充,规定了在任意父类可以出现的地方,子类都一定可以出现。实现开闭原则的关键就是抽象化,父类与子类的继承关系就是抽象化的具体表现,所以里氏代换原则是对实现抽象化的具体步骤的规范。(子类可以扩展父类的功能,但不能改变父类原有的功能)

依赖倒转原则

依赖倒转原则指程序要依赖于抽象(Java中的抽象类和接口),而不依赖于具体的实现(Java中的实现类)。简单地说,就是要求对抽象进行编程,不要求对实现进行编程,这就降低了用户与实现模块之间的耦合度。

接口隔离原则

接口隔离原则指通过将不同的功能定义在不同的接口中来实现接口的隔离,这样就避免了其他类在依赖该接口(接口上定义的功能)时依赖其不需要的接口,可减少接口之间依赖的冗余性和复杂性。

合成/聚合复用原则

合成/聚合复用原则指通过在一个新的对象中引入(注入)已有的对象以达到类的功能复用和扩展的目的。它的设计原则是要尽量使用合成或聚合而不要使用继承来扩展类的功能。

迪米特法则

迪米特法则指一个对象尽可能少地与其他对象发生相互作用,即一个对象对其他对象应该有尽可能少的了解或依赖。其核心思想在于降低模块之间的耦合度,提高模块的内聚性。迪米特法则规定每个模块对其他模块都要有尽可能少的了解和依赖,因此很容易使系统模块之间功能独立,这使得各个模块的独立运行变得更简单,同时使得各个模块之间的组合变得更容易。

设计模式按照其功能和使用场景可以分为三大类:创建型模式(Creational Pattern)、结构型模式(Structural Pattern)和行为型模式(Behavioral Pattern)。

offer来了(原理篇)学习笔记-第9章设计模式_第1张图片

1.工厂模式的概念及Java实现

工厂模式(Factory Pattern)是最常见的设计模式,该模式设属于创建型模式,它提供了一种简单、快速、高效而安全地创建对象的方式。工厂模式在接口中定义了创建对象的方法,而将具体的创建对象的过程在子类中实现,用户只需通过接口创建需要的对象即可,不用关注对象的具体创建过程。同时,不同的子类可根据需求灵活实现创建对象的不同方法。

通俗地讲,工厂模式的本质就是用工厂方法代替new操作创建一种实例化对象的方式,以提供一种方便地创建有同种类型接口的产品的复杂对象。

如下代码通过new关键字实例化类Class的一个实例class,但如果Class类在实例化时需要一些初始化参数,而这些参数需要其他类的信息,则直接通过new关键字实例化对象会增加代码的耦合度,不利于维护,因此需要通过工厂模式将创建实例和使用实例分开。将创建实例化对象的过程封装到工厂方法中,我们在使用时直接通过调用工厂来获取,不需要关心具体的负载实现过程:Class class = new Class();

以创建手机为例,假设手机的品牌有华为和苹果两种类型,我们要实现的是根据不同的传入参数实例化不同的手机,则其具体的UML设计如图所示。

offer来了(原理篇)学习笔记-第9章设计模式_第2张图片
  1. 定义接口,并在接口中定义了brand(),用来返回手机的品牌。
interface Phone {
    String brand();
}
  1. 定义实现类,Iphone和HuaWei来表示两个品牌的手机,两个品牌的手机通过实现brand()打印自己的商标。
class HuaWei implements Phone{
    @Override
    public String brand() {
        return "this is a huawei phone";
    }
}

class Iphone implements Phone{
    @Override
    public String brand() {
        return "this is a apple phone";
    }
}
  1. 定义工厂类,工厂类有一个方法createPhone(),用来根据不同的参数实例化不同品牌的手机类并返回。在createPhone()的参数为“HuaWei”时,工厂类为我们实例化一个HuaWei类的实例并返回;在createPhone()的参数为“Apple”时,工厂类为我们实例化一个Iphone类的实例并返回。这样便实现了工厂类根据不同的参数创建不同的实例,对调用者来说屏蔽了实例化的细节。
class Factory {
    public Phone createPhone(String phoneName){
        if (phoneName.equals("offer_设计模式.工厂模式.HuaWei")){
            return new HuaWei();
        }
        else if (phoneName.equals("Apple")){
            return new Iphone();
        }
        else {
            return null;
        }
    }
}
  1. 使用工厂模式,定义了一个Factory的实例,并调用createPhone()根据不同的参数创建了名为huawei的实例和名为iphone的实例,并分别调用其brand()打印不同的品牌信息
public class FactoryPattern {
    public static void main(String[] args) {
        Factory factory = new Factory();
        Phone huawei = factory.createPhone("offer_设计模式.工厂模式.HuaWei");
        Phone iphone = factory.createPhone("Apple");
        System.out.println(huawei.brand());
        System.out.println(iphone.brand());
    }
}

2.抽象工厂模式的概念及Java实现

抽象工厂模式(Abstract Factory Pattern)在工厂模式上添加了一个创建不同工厂的抽象接口(抽象类或接口实现),该接口可叫作超级工厂。在使用过程中,我们首先通过抽象接口创建出不同的工厂对象,然后根据不同的工厂对象创建不同的对象。

我们可以将工厂模式理解为针对一个产品维度进行分类,比如上述工厂模式下的苹果手机和华为手机;而抽象工厂模式针对的是多个产品维度分类,比如苹果公司既制造苹果手机也制造苹果笔记本电脑,同样,华为公司既制造华为手机也制造华为笔记本电脑。

在同一个厂商有多个维度的产品时,如果使用工厂模式,则势必会存在多个独立的工厂,这样的话,设计和物理世界是不对应的。正确的做法是通过抽象工厂模式来实现,我们可以将抽象工厂类比成厂商(苹果、华为),将通过抽象工厂创建出来的工厂类比成不同产品的生产线(手机生成线、笔记本电脑生产线),在需要生产产品时根据抽象工厂生产。

工厂模式定义了工厂方法来实现不同厂商手机的制造。可是问题来了,我们知道苹果公司和华为公司不仅制造手机,还制造电脑。如果使用工厂模式,就需要实现两个工厂类,并且这两个工厂类没有多大关系,这样的设计显然不够优雅,那么如何实现呢?使用抽象工厂就能很好地解决上述问题。我们定义一个抽象工厂,在抽象工厂中定义好要生产的产品(手机或者电脑),然后在抽象工厂的实现类中根据不同类型的产品和产品规格生产不同的产品返回给用户,UML的设计如图所示。

offer来了(原理篇)学习笔记-第9章设计模式_第3张图片
  1. 第1类产品的手机接口及实现类的定义如下
interface Phone {
    String call();
}
class PhoneApple implements Phone{
    @Override
    public String call() {
        return "call by apple phone";
    }
}
class PhoneHuaWei implements Phone{
    @Override
    public String call() {
        return "call by huawei phone";
    }
}

以上代码定义了Phone的接口及其实现类PhoneApple和PhoneHwaiWei。在该接口中定义了一个打电话的方法call(),实现类根据其品牌打印相关信息。

  1. 第1类产品的手机工厂类的定义如下
class PhoneFactory extends AbstractFactory {
    @Override
    Phone createPhone(String brand) {
        if (brand.equals("HuaWei")){
            return new PhoneHuaWei();
        }
        else if (brand.equals("Apple")){
            return new PhoneApple();
        }
        else {
            return null;
        }
    }
    @Override
    Computer createComputer(String brand) {
        return null;
    }
}

以上代码定义了PhoneFactory的手机工厂类,该类继承了AbstractFactory并实现了方法createPhone(),createPhone()根据不同的参数实例化不同品牌的手机类并返回。在createPhone()的参数为“HuaWei”时,工厂类为我们实例化一个PhoneHwaiWei类的实例并返回;在createPhone()的参数为“Apple”时,工厂类为我们实例化一个PhoneApple类的实例并返回,这样便满足了工厂根据不同参数生产不同产品的需求。

  1. 第2类产品的电脑接口及实现类的定义如下
interface Computer {
    String internet();
}
class ComputerApple implements Computer{

    @Override
    public String internet() {
        return "internet by apple computer";
    }
}
class ComputerHuaWei implements Computer{

    @Override
    public String internet() {
        return "internet by huawei computer";
    }
}

以上代码定义了Computer的电脑接口及其实现类ComputerApple和ComputerHwaiWei。在该接口中定义了一个上网的方法internet(),实现类根据其品牌打印相关信息。

  1. 第2类产品的电脑工厂类的定义如下
class ComputerFactory extends AbstractFactory{

    @Override
    Phone createPhone(String brand) {
        return null;
    }

    @Override
    Computer createComputer(String brand) {
        if (brand.equals("HuaWei")){
            return new ComputerHuaWei();
        }
        else if (brand.equals("Apple")){
            return new ComputerApple();
        }
        else {
            return null;
        }
    }
}

以上代码定义了ComputerFactory的电脑工厂类,该类继承了AbstractFactory并实现了方法createComputer(),createComputer()根据不同的参数实例化不同品牌的电脑类并返回。在createComputer()的参数为“HuaWei”时,工厂类为我们实例化一个ComputerHwaiWei类的实例并返回;在createComputer()的参数为“Apple”时,工厂类为我们实例化一个ComputerApple类的实例并返回,这样便实现了工厂根据不同参数生产不同产品的需求。

  1. 抽象工厂的定义如下
abstract class AbstractFactory {
    abstract Phone createPhone(String brand);
    abstract Computer createComputer(String brand);
}

以上代码定义了抽象类AbstractFactory,这个类便是抽象工厂的核心类,它定义了两个方法createPhone()和createComputer(),用户在需要手机时调用其createPhone()构造一个手机(华为或者苹果品牌)即可,用户在需要电脑时调用其createComputer()构造一个电脑(华为或者苹果品牌)即可。

  1. 使用抽象工厂
public class AbstractFactoryPattern {
    public static void main(String[] args) {
        AbstractFactory phoneFactory = new PhoneFactory();
        Phone phoneHuawei = phoneFactory.createPhone("HuaWei");
        Phone phoneApple = phoneFactory.createPhone("Apple");
        System.out.println(phoneHuawei.call());
        System.out.println(phoneApple.call());
        AbstractFactory ComputerFactory = new ComputerFactory();
        Computer computerHuawei = ComputerFactory.createComputer("HuaWei");
        Computer computerApple = ComputerFactory.createComputer("Apple");
        System.out.println(computerHuawei.internet());
        System.out.println(computerApple.internet());
    }
}

以上代码使用了我们定义好的抽象工厂,在需要生产产品时,首先需要定义一个抽象的工厂类AbstractFactory,然后使用抽象的工厂类生产不同的工厂类,最终根据不同的工厂生产不同的产品。

3.单例模式的概念及Java实现

单例模式是保证系统实例唯一性的重要手段。单例模式首先通过将类的实例化方法私有化来防止程序通过其他方式创建该类的实例,然后通过提供一个全局唯一获取该类实例的方法帮助用户获取类的实例,用户只需也只能通过调用该方法获取类的实例。

单例模式的设计保证了一个类在整个系统中同一时刻只有一个实例存在,主要被用于一个全局类的对象在多个地方被使用并且对象的状态是全局变化的场景下。同时,单例模式为系统资源的优化提供了很好的思路,频繁创建和销毁对象都会增加系统的资源消耗,而单例模式保障了整个系统只有一个对象能被使用,很好地节约了资源。

单例模式的实现很简单,每次在获取对象前都先判断系统是否已经有这个单例对象,有则返回,没有则创建。需要注意的是,单例模型的类构造函数是私有的,只能由自身创建和销毁对象,不允许除了该类的其他程序使用new关键字创建对象及破坏单例模式。

单例模式的常见写法有懒汉模式(线程安全)饿汉模式静态内部类双重校验锁,下面一一解释这些写法。

懒汉模式(线程安全)

懒汉模式很简单:定义一个私有的静态对象instance,之所以定义instance为静态,是因为静态属性或方法是属于类的,能够很好地保障单例对象的唯一性;然后定义一个加锁的静态方法获取该对象,如果该对象为null,则定义一个对象实例并将其赋值给instance,这样下次再获取该对象时便能够直接获取了。

懒汉模式在获取对象实例时做了加锁操作,因此是线程安全的,代码如下:

class LazySingleton{
    private static LazySingleton instance;
    private LazySingleton(){}

    public static synchronized LazySingleton getInstance(){
        if (instance == null){
            instance = new LazySingleton();
        }
        return instance;
    }
}

饿汉模式

汉模式指在类中直接定义全局的静态对象的实例并初始化,然后提供一个方法获取该实例对象。懒汉模式和饿汉模式的最大不同在于,懒汉模式在类中定义了单例但是并未实例化,实例化的过程是在获取单例对象的方法中实现的,也就是说,在第一次调用懒汉模式时,该对象一定为空,然后去实例化对象并赋值,这样下次就能直接获取对象了;而饿汉模式是在定义单例对象的同时将其实例化的,直接使用便可。也就是说,在饿汉模式下,在Class Loader完成后该类的实例便已经存在于JVM中了,代码如下:

class HungrySingleton{
    private static HungrySingleton instance = new HungrySingleton();

    private HungrySingleton() {
    }
    
    public static HungrySingleton getInstance(){
        return instance;
    }
}

静态内部类

静态内部类通过在类中定义一个静态内部类,将对象实例的定义和初始化放在内部类中完成,我们在获取对象时要通过静态内部类调用其单例对象。之所以这样设计,是因为类的静态内部类在JVM中是唯一的,这很好地保障了单例对象的唯一性,代码如下:

public class Singleton {
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton(){}
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

双重校验锁

双锁模式指在懒汉模式的基础上做进一步优化,给静态对象的定义加上volatile锁来保障初始化时对象的唯一性,在获取对象时通过synchronized (Singleton.class)给单例类加锁来保障操作的唯一性。代码如下:

public class Lock2Singleton{
    private volatile static Lock2Singleton singleton;
    //1 对象锁
    private Lock2Singleton(){}
    public static Lock2Singleton getInstance(){
        if (singleton==null){
            synchronized (Singleton.class){
                // synchronized方法锁
                if (singleton==null){
                    singleton = new Lock2Singleton();
                }
            }
        }
        return singleton;
    }
}

4.建造者模式的概念及Java实现

建造者模式(Builder Pattern)使用多个简单的对象创建一个复杂的对象,用于将一个复杂的构建与其表示分离,使得同样的构建过程可以创建不同的表示,然后通过一个Builder类(该Builder类是独立于其他对象的)创建最终的对象。

建造者模式主要用于解决软件系统中复杂对象的创建问题,比如有些复杂对象的创建需要通过各部分的子对象用一定的算法构成,在需求变化时这些复杂对象将面临很大的改变,这十分不利于系统的稳定。但是,使用建造者模式能将它们各部分的算法包装起来,在需求变化后只需调整各个算法的组合方式和顺序,能极大提高系统的稳定性。建造者模式常被用于一些基本部件不会变而其组合经常变化的应用场景下。

注意,建造者模式与工厂模式的最大区别是,建造者模式更关注产品的组合方式和装配顺序,而工厂模式关注产品的生产本身。

建造者模式在设计时有以下几种角色。

  • Builder:创建一个复杂产品对象的抽象接口。
  • ConcreteBuilder:Builder接口的实现类,用于定义复杂产品各个部件的装配流程。
  • Director:构造一个使用Builder接口的对象。
  • Product:表示被构造的复杂对象。ConcreteBuilder定义了该复杂对象的装配流程,而Product定义了该复杂对象的结构和内部表示。

以生产一个电脑为例,电脑的生产包括CPU、Memory、Disk等生产过程,这些生产过程对顺序不敏感,这里的Product角色就是电脑。我们还需要定义生产电脑的Builder、ConcreteBuilder和Director。

offer来了(原理篇)学习笔记-第9章设计模式_第4张图片
  1. 定义需要生产的产品Computer
class Computer{
    private String cpu;
    private String memory;
    private String disk;
    //省略了getter和setter
}

以上代码定义了一个Computer类来描述我们要生产的产品,具体的一个Computer包括CPU、内存(memory)和磁盘(disk),当然,还包括显示器、键鼠等,这里作为demo,为简单起见就不一一列举了。

  1. 定义抽象接口ComputerBuilder来描述产品构造和装配的过程:
interface ComputerBuilder{
    void buildCpu();
    void buildMemory();
    void buildDisk();
    Computer buildComputer();
}

以上代码定义了ComputerBuilder接口来描述电脑的组装过程,具体包括组装CPU的方法buildcpu()、组装内存的方法buildemory()和组装磁盘的方法buildDisk(),等这些都生产和组装完成后,就可以调用buildComputer()组装一台完整的电脑了。

  1. 定义ComputerBuilder接口实现类ComputerConcreteBuilder以实现构造和装配该产品的各个组件:
class ComputerConcreteBuilder implements ComputerBuilder{
    Computer computer;

    public ComputerConcreteBuilder(){
    	computer = new Computer();
	}

    @Override
    public void buildCpu() {
        System.out.println("build cpu");
        computer.setCpu("8core");
    }

    @Override
    public void buildMemory() {
        System.out.println("build memory");
        computer.setCpu("16GB");
    }

    @Override
    public void buildDisk() {
        System.out.println("build Disk");
        computer.setCpu("1TB");
    }

    @Override
    public Computer buildComputer() {
        return computer;
    }
}

以上代码定义了ComputerConcreteBuilder来完成具体电脑的组装,其中Computer的实例在构造函数中进行了定义。

  1. 定义ComputerDirector使用Builder接口实现产品的装配:
class ComputerDirector{
    public Computer constructComputer(ComputerBuilder computerBuilder){
        computerBuilder.buildMemory();
        computerBuilder.buildCpu();
        computerBuilder.buildDisk();
        return computerBuilder.buildComputer();
    }
}

以上代码定义了ComputerDirector来调用ComputerBuilder接口实现电脑的组装,具体组装顺序为buildemory、buildcpu、buildDisk和buildComputer。该类是建造者模式对产品生产过程的封装,在需求发生变化且需要先装配完磁盘再装配CPU时,只需调整Director的执行顺序即可,每个组件的装配都稳定不变。

  1. 构建Computer:
public static void main(String[] args) {
        ComputerDirector computerDirector = new ComputerDirector();
        ComputerBuilder computerConcreteBuilder = new ComputerConcreteBuilder();
        Computer computer = computerDirector.constructComputer(computerConcreteBuilder);
        System.out.println(computer.getCpu());
        System.out.println(computer.getDisk());
        System.out.println(computer.getMemory());
    }

以上代码首先定义了一个ComputerDirector和ComputerBuilder,为构建Computer做好准备,然后通过调用ComputerDirector的constructComputer()实现产品Computer的构建。

5.原型模式的概念及Java实现

原型模式指通过调用原型实例的Clone方法或其他手段来创建对象。

原型模式属于创建型设计模式,它以当前对象为原型(蓝本)来创建另一个新的对象,而无须知道创建的细节。原型模式在Java中通常使用Clone技术实现,在JavaScript中通常使用对象的原型属性实现。

原型模式的Java实现很简单,只需原型类实现Cloneable接口并覆写clone方法即可。Java中的复制分为浅复制和深复制。

  • 浅复制:Java中的浅复制是通过实现Cloneable接口并覆写其Clone方法实现的。在浅复制的过程中,对象的基本数据类型的变量值会重新被复制和创建,而引用数据类型仍指向原对象的引用。也就是说,浅复制不复制对象的引用类型数据。
  • 深复制:在深复制的过程中,不论是基本数据类型还是引用数据类型,都会被重新复制和创建。简而言之,深复制彻底复制了对象的数据(包括基本数据类型和引用数据类型),浅复制的复制却并不彻底(忽略了引用数据类型)。

浅复制的代码实现如下

class Computer implements Cloneable{
    private String cpu;
    private String memory;
    private String disk;
    public Computer(String cpu, String memory, String disk){
        this.cpu= cpu;
        this.memory = memory;
        this.disk = disk;
    }
    public Object clone(){
        // 浅复制
        try{
            return (Computer)super.clone();
        } catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
    @Override
    public String toString() {
        return "Computer{" +
                "cpu='" + cpu + '\'' +
                ", memory='" + memory + '\'' +
                ", disk='" + disk + '\'' +
                '}';
    }
}

以上代码定义了Computer类,要使该类支持浅复制,只需实现Cloneable接口并覆写clone()即可。

深复制的代码实现如下

class ComputerDetail implements Cloneable{
    private String cpu;
    private String memory;
    private Disk disk;
    public ComputerDetail(String cpu, String memory, Disk disk){
        this.cpu= cpu;
        this.memory = memory;
        this.disk = disk;
    }
    public Object clone(){
        // 深复制
        try{
            ComputerDetail computerDetail = (ComputerDetail)super.clone();
            computerDetail.disk = (Disk) this.disk.clone();
            return computerDetail;
        } catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
    @Override
    public String toString() {
        return "ComputerDetail{" +
                "cpu='" + cpu + '\'' +
                ", memory='" + memory + '\'' +
                ", disk=" + disk +
                '}';
    }
}

//应用对象深复制
class Disk implements Cloneable{
    private String ssd;
    private String hhd;
    public Disk(String ssd, String hhd){
        this.ssd = ssd;
        this.hhd = hhd;
    }
    public Object clone(){
        try {
            return (Disk)super.clone();
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
    @Override
    public String toString() {
        return "Disk{" +
                "ssd='" + ssd + '\'' +
                ", hhd='" + hhd + '\'' +
                '}';
    }
}

以上代码定义了ComputerDetail和Disk两个类,其中ComputerDetail的disk属性是一个引用对象,要实现这种对象的复制,就要使用深复制技术,具体操作是引用对象类需要实现Cloneable接口并覆写clone(),然后在复杂对象中声明式地将引用对象复制出来赋值给引用对象的属性,具体代码如下:computerDetail.disk = (Disk) this.disk.clone();

使用原型模型

public static void main(String[] args) {
        // 浅复制
        Computer computer = new Computer("8core","16GB", "1TB");
        System.out.println("before simple clone:" + computer.toString());
        Computer computerClone = (Computer)computer.clone();
        System.out.println("after simple clone:" + computerClone.toString());
        // 深复制
        Disk disk = new Disk("208G","2TB");
        ComputerDetail computerDetail = new ComputerDetail("12core","64GB",disk);
        System.out.println("before simple clone:" + computerDetail.toString());
        ComputerDetail computerDetailClone = (ComputerDetail)computerDetail.clone();
        System.out.println("after simple clone:" + computerDetailClone.toString());
    }

以上代码先定义了一个简单对象computer,并利用浅复制技术复制出一个新的对象computerClone,然后定义了复制对象computerDetail,并使用深复制技术复制出一个新的对象computerDetailClone,最后分别打印出复制前和复制后的对象。

6.适配器模式的概念及Java实现

我们常常在开发中遇到各个系统之间的对接问题,然而每个系统的数据模型或多或少均存在差别,因此可能存在修改现有对象模型的情况,这将影响到系统的稳定。若想在不修改原有代码结构(类的结构)的情况下完成友好对接,就需要用到适配器模式。

适配器模式(Adapter Pattern)通过定义一个适配器类作为两个不兼容的接口之间的桥梁,将一个类的接口转换成用户期望的另一个接口,使得两个或多个原本不兼容的接口可以基于适配器类一起工作。

适配器模式主要通过适配器类实现各个接口之间的兼容,该类通过依赖注入或者继承实现各个接口的功能并对外统一提供服务,可形象地使用图9-4来表示适配器模式。

offer来了(原理篇)学习笔记-第9章设计模式_第5张图片

在适配器模式的实现中有三种角色:Source、Targetable、Adapter。Source是待适配的类Targetable是目标接口Adapter是适配器。我们在具体应用中通过Adapter将Source的功能扩展到Targetable,以实现接口的兼容。适配器的实现主要分为三类:类适配器模式、对象适配器模式、接口适配器模式。

类适配器模式

在需要不改变(或者由于项目原因无法改变)原有接口或类结构的情况下扩展类的功能以适配不同的接口时,可以使用类的适配器模式。适配器模式通过创建一个继承原有类(需要扩展的类)并实现新接口的适配器类来实现。具体的UML设计如图所示。

offer来了(原理篇)学习笔记-第9章设计模式_第6张图片
  1. 定义Source类:
class Source{
    public void editTextFile(){
        // text文件编辑
        System.out.println("a text file editing");
    }
}

以上代码定义了待适配的Source类,在该类中实现了一个编辑文本文件的方法editTextFile()。

  1. 定义Targetable接口:
interface Targetable{
    void editTextFile();
    void editWordFile();
}

以上代码定义了一个Targetable接口,在该接口中定义了两个方法,editTextFile和editWordFile,其中editTextFile是Source中待适配的方法。

  1. 定义Adapter继承Source类并实现Targetable接口:
class Adapter extends Source implements Targetable{
    @Override
    public void editWordFile() {
        System.out.println("a word file editing");
    }
}

以上代码定义了一个Adapter类并继承了Source类实现Targetable接口,以完成对Source类的适配。适配后的类既可以编辑文本文件,也可以编辑Word文件。

  1. 使用类的适配器:
public static void main(String[] args) {
		// 创建的是Adapter!
        Targetable targetable = new Adapter();
        targetable.editTextFile();
        targetable.editWordFile();
    }

在使用适配器时只需定义一个实现了Targetable接口的Adapter类并调用target中适配好的方法即可。从运行结果可以看出,我们的适配器不但实现了编辑Word文件的功能,还实现了编辑文本文件的功能

对象适配器模式

对象适配器模式的思路和类适配器模式基本相同,只是修改了Adapter类。Adapter不再继承Source类,而是持有Source类的实例,以解决兼容性问题。具体的UML设计如图所示。

offer来了(原理篇)学习笔记-第9章设计模式_第7张图片
  1. 适配器类的定义如下:
class ObjectAdapter implements Targetable{
    private Source source;

    public ObjectAdapter(Source source){
        super();
        this.source =source;
    }

    @Override
    public void editTextFile() {
        this.source.editTextFile();
    }

    @Override
    public void editWordFile() {
        System.out.println("a word file editing");
    }
}

以上代码定义了一个名为ObjectAdapter的适配器,该适配器实现了Targetable接口并持有Source实例,在适配editTextFile()的方法时调用Source实例提供的方法即可。

  1. 使用对象适配器模式:
public class AdapterPattern {
    public static void main(String[] args) {
        Source source = new Source();
        Targetable targetable = new ObjectAdapter(source);
        targetable.editTextFile();
        targetable.editWordFile();
    }
}

在使用对象适配器时首先需要定义一个Source实例,然后在初始化ObjectAdapter时将Source实例作为构造函数的参数传递进去,这样就实现了对象的适配。

接口适配器模式

在不希望实现一个接口中所有的方法时,可以创建一个抽象类AbstractAdapter实现所有方法,在使用时继承该抽象类按需实现方法即可。具体的UML设计如图所示。

offer来了(原理篇)学习笔记-第9章设计模式_第8张图片
  1. 定义公共接口Sourceable:
interface Sourceable{
    void editTextFile();
    void editWordFile();
}

以上代码定义了Sourceable接口,并在接口中定义了两个方法,editTextFile()和editWordFile()。

  1. 定义抽象类AbstractAdapter并实现公共接口的方法:
abstract class AbstractAdapter implements Sourceable{
    @Override
    public void editTextFile() {
    }

    @Override
    public void editWordFile() {
    }
}

以上代码定义了Sourceable的抽象实现类AbstractAdapter,该类对Sourceable进行了重写,但是不做具体实现。

  1. 定义SourceSub1类按照需求实现editTextFile():
class SourceSub1 extends AbstractAdapter{
    @Override
    public void editTextFile() {
        System.out.println("a text file editing");
    }
}

以上代码定义了SourceSub1类并继承了AbstractAdapter,由于继承父类的子类可以按需实现自己关心的方法,因此适配起来更加灵活,这里SourceSub1类实现了editTextFile()。

  1. 定义SourceSub2类按照需求实现editWordFile():
class SourceSub2 extends AbstractAdapter{
    @Override
    public void editWordFile() {
        System.out.println("a word file editing");
    }
}

以上代码定义了SourceSub2类,继承了AbstractAdapter并实现了editWordFile()。

  1. 使用接口适配器:
public class AdapterPattern {
    public static void main(String[] args) {
        Sourceable source1 = new SourceSub1();
        Sourceable source2 = new SourceSub2();
        source1.editTextFile();
        source2.editWordFile();
    }
}

使用接口适配器时按照需求实例化不同的子类并调用实现好的方法即可。

7.装饰者模式的概念及Java实现

装饰者模式(Decorator Pattern)指在无须改变原有类及类的继承关系的情况下,动态扩展一个类的功能。它通过装饰者来包裹真实的对象,并动态地向对象添加或者撤销功能。

装饰者模式包括Source和Decorator两种角色,Source是被装饰者,Decorator是装饰者。装饰者模式通过装饰者可以为被装饰者Source动态添加一些功能。具体的UML设计如图所示。

offer来了(原理篇)学习笔记-第9章设计模式_第9张图片
  1. 定义Sourceable接口:
interface Sourceable{
    public void createComputer();
}

以上代码定义了一个Sourceable接口,该接口定义了一个生产电脑的方法createComputer()。

  1. 定义Sourceable接口的实现类Source:
class Source implements Sourceable{
    @Override
    public void createComputer() {
        System.out.println("create computer by Source");
    }
}

以上代码定义了Sourceable接口的实现类Source并实现了其createComputer()。

  1. 定义装饰者类Decorator:
class Decorator implements Sourceable{
    private Sourceable source;
    public Decorator(Sourceable source){
        super();
        this.source = source;
    }
    @Override
    public void createComputer() {
        source.createComputer();
        System.out.println("make system");
    }
}

以上代码定义了装饰者类Decorator,装饰者类通过构造函数将Sourceable实例初始化到内部,并在其方法createComputer()中调用原方法后加上了装饰者逻辑,这里的装饰指在电脑创建完成后给电脑装上相应的系统。注意,之前的Sourceable没有给电脑安装系统的步骤,我们引入装饰者为Sourceable扩展了安装系统的功能。

  1. 使用装饰者模式:
public class DecoratorPattern {
    public static void main(String[] args) {
        Sourceable source = new Source();
        Sourceable decorator = new Decorator(source);
        decorator.createComputer();
    }
}

在使用装饰者模式时,需要先定义一个待装饰的Source类的source对象,然后初始化构造器Decorator并在构造函数中传入source对象,最后调用createComputer(),程序在创建完电脑后还为电脑安装了系统。

8.代理模式的概念及Java实现

代理模式指为对象提供一种通过代理的方式来访问并控制该对象行为的方法。在客户端不适合或者不能够直接引用一个对象时,可以通过该对象的代理对象来实现对该对象的访问,可以将该代理对象理解为客户端和目标对象之间的中介者

在现实生活也能看到代理模式的身影,比如企业会把五险一金业务交给第三方人力资源公司去做,因为人力资源公司对这方面的业务更加熟悉,等等。

在代理模式下有两种角色,一种是被代理者,一种是代理(Proxy),在被代理者需要做一项工作时,不用自己做,而是交给代理做。比如企业在招人时,不用自己去市场上找,可以通过代理(猎头公司)去找,代理有候选人池,可根据企业的需求筛选出合适的候选人返回给企业。具体的UML设计如图所示。

offer来了(原理篇)学习笔记-第9章设计模式_第10张图片
  1. 定义Company接口及其实现类HR:
interface Company{
    void findWorker(String title);
}
class HR implements Company{
    @Override
    public void findWorker(String title) {
        System.out.println("I need find a worker, title is:" + title);
    }
}

以上代码定义了一个名为Company的接口,在该接口中定义了方法findWorker(),然后定义了其实现类HR,实现findWorker()以负责公司的具体招聘工作。

  1. 定义Proxy:
class Proxy implements Company{
    private HR hr;
    public Proxy(){
        super();
        this.hr = new HR();
    }
    //代理方法
    @Override
    public void findWorker(String title) {
        hr.findWorker(title);
        //通过猎头找候选人
        String worker = getWorker(title);
        System.out.println("find a worker by proxy, worker name is :"+ worker);
    }
    private String getWorker(String title){
        Map<String, String> workerList = new HashMap<String, String>(){
            { put("Java","张三");
            put("Python","李四");
            put("Php","王五"); }
        };
       return workerList.get(title);
    }
}

以上代码定义了一个代理类Proxy,用来帮助企业寻找合适的候选人。其中Proxy继承了Company并持有HR对象,在其HR发出招人指令(findWorker)后,由代理完成具体的寻找候选人工作并将找到的候选人提供给公司。

  1. 使用代理模式:
public class ProxyPattern {
    public static void main(String[] args) {
        Company company = new Proxy();
        company.findWorker("Java");
    }
}

在使用代理模式时直接定义一个代理对象并调用其代理的方法即可。

9.外观模式的概念及Java实现

外观模式(Facade Pattern)也叫作门面模式,通过一个门面(Facade)向客户端提供一个访问系统的统一接口,客户端无须关心和知晓系统内部各子模块(系统)之间的复杂关系,其主要目的是降低访问拥有多个子系统的复杂系统的难度,简化客户端与其之间的接口。外观模式将子系统中的功能抽象成一个统一的接口,客户端通过这个接口访问系统,使得系统使用起来更加容易。具体的使用场景如图所示。

offer来了(原理篇)学习笔记-第9章设计模式_第11张图片

简单来说,外观模式就是将多个子系统及其之间的复杂关系和调用流程封装到一个统一的接口或类中以对外提供服务。这种模式涉及3种角色。

  • 子系统角色:实现了子系统的功能。
  • 门面角色:外观模式的核心,熟悉各子系统的功能和调用关系并根据客户端的需求封装统一的方法来对外提供服务。
  • 客户角色:通过调用Facade来完成业务功能。

以汽车的启动为例,用户只需按下启动按钮,后台就会自动完成引擎启动、仪表盘启动、车辆自检等过程。我们通过外观模式将汽车启动这一系列流程封装到启动按钮上,对于用户来说只需按下启动按钮即可,不用太关心具体的细节。具体的UML设计如图所示。

offer来了(原理篇)学习笔记-第9章设计模式_第12张图片
  1. 定义Dashboard类:
class Dashboard{
    public void startup(){
        System.out.println("dashboard startup...");
    }
    public void shutdown(){
        System.out.println("dashboard shutdown...");
    }
}

以上代码定义了Dashboard类来代表仪表盘,并定义了startup()和shutdown()来控制仪表盘的启动和关闭。

  1. 定义Engine类:
class Engine{
    public void startup(){
        System.out.println("engine startup...");
    }
    public void shutdown(){
        System.out.println("engine shutdown...");
    }
}

以上代码定义了Engine类来代表发动机,并定义了startup()和shutdown()来控制发动机的启动和关闭。

  1. 定义SelfCheck类:
class SelfCheck{
    public void startupCheck(){
        System.out.println("startup check finished...");
    }
    public void shutdownCheck(){
        System.out.println("shutdown check finished...");
    }
}

以上代码定义了SelfCheck类来代表汽车自检器,并定义了startupCheck()和shutdowncheck()来控制汽车启动后的自检和关闭前的自检。

  1. 定义门面类Starter:
class Starter{
    private Dashboard dashboard;
    private Engine engine;
    private SelfCheck selfCheck;
    public Starter(){
        this.dashboard = new Dashboard();
        this.engine = new Engine();
        this.selfCheck = new SelfCheck();
    }
    public void startup(){
        System.out.println("car startup");
        engine.startup();
        dashboard.startup();
        selfCheck.startupCheck();
        System.out.println("car startup finished");
    }
    public void shutdown(){
        System.out.println("car shutdown");
        engine.shutdown();
        dashboard.shutdown();
        selfCheck.shutdownCheck();
        System.out.println("car shutdown finished");
    }
}

以上代码定义了门面类Starter,在Starter中定义了startup方法,该方法先调用engine的启动方法启动引擎,再调用dashboard的启动方法启动仪表盘,最后调用selfCheck的启动自检方法完成启动自检。

  1. 使用外观模式:
public class FacadePattern {
    public static void main(String[] args) {
        Starter starter = new Starter();
        starter.startup();
        starter.shutdown();
    }
}

在使用外观模式时,用户只需定义门面类的实例并调用封装好的方法或接口即可。这里调用starter的startup()完成启动。

10.桥接模式的概念及Java实现

桥接模式(Bridge Pattern)通过将抽象及其实现解耦,使二者可以根据需求独立变化。这种类型的设计模式属于结构型模式,通过定义一个抽象和实现之间的桥接者来达到解耦的目的。

桥接模型主要用于解决在需求多变的情况下使用继承造成类爆炸的问题,扩展起来不够灵活。可以通过桥接模式将抽象部分与实现部分分离,使其能够独立变化而相互之间的功能不受影响。具体做法是通过定义一个桥接接口,使得实体类的功能独立于接口实现类,降低它们之间的耦合度。

我们常用的JDBC和DriverManager就使用了桥接模式,JDBC在连接数据库时,在各个数据库之间进行切换而不需要修改代码,因为JDBC提供了统一的接口,每个数据库都提供了各自的实现,通过一个叫作数据库驱动的程序来桥接即可。下面以数据库连接为例介绍桥接模式,具体的UML设计如图所示。

offer来了(原理篇)学习笔记-第9章设计模式_第13张图片
  1. 定义Driver接口:
interface Driver{
    void executeSQL();
}

以上代码定义了Driver接口,在该接口中定义了一个执行SQL语句的方法,用于处理不同数据库的SQL语句。

  1. 定义Driver接口的MySQL实现类MysqlDriver:
class MysqlDriver implements Driver{
    @Override
    public void executeSQL() {
        System.out.println("execute sql by mysql driver");
    }
}

以上代码定义了Driver的实现类MysqlDriver,并基于MySQL实现了其执行SQL语句的方法。

  1. 定义Driver接口的Oracle实现类OracleDriver:
class OracleDriver implements Driver{
    @Override
    public void executeSQL() {
        System.out.println("execute sql by oracle driver");
    }
}

以上代码定义了Driver的实现类OracleDriver,并基于Oracle实现了其执行SQL语句的方法。

  1. 定义DriverManagerBridge:
abstract class DriverManagerBridge{
    private Driver driver;
    public void execute(){
        this.driver.executeSQL();
    }
    public Driver getDriver(){
        return driver;
    }
    public void setDriver(Driver driver){
        this.driver = driver;
    }
}

以上代码定义了抽象类DriverManagerBridge,用于实现桥接模式,该类定义了Driver的注入,用户注入不同的驱动器便能实现不同类型的数据库的切换。

  1. 定义MyDriverBridge:
class MyDriverBridge extends DriverManagerBridge{
    public void execute(){
        getDriver().executeSQL();
    }
}

在以上代码中,MyDriverBridge用于实现用户自定义的功能,也可以直接使用DriverManagerBridge提供的功能。

  1. 使用桥接模式:
public class BridgePattern {
    public static void main(String[] args) {
        DriverManagerBridge driverManagerBridge = new MyDriverBridge();
        // 设置Mysql驱动
        driverManagerBridge.setDriver(new MysqlDriver());
        driverManagerBridge.execute();
        // 切换到Oracle驱动
        driverManagerBridge.setDriver(new OracleDriver());
        driverManagerBridge.execute();
    }
}

在以上代码中使用了桥接模式,定义了一个DriverManagerBridge,然后注入不同的驱动器,以实现在不同类型的数据库中实现驱动的切换和数据库SQL语句的执行。

11.组合模式的概念及Java实现

组合模式(Composite Pattern)又叫作部分整体模式,主要用于实现部分和整体操作的一致性。组合模式常根据树形结构来表示部分及整体之间的关系,使得用户对单个对象和组合对象的操作具有一致性。

组合模式通过特定的数据结构简化了部分和整体之间的关系,使得客户端可以像处理单个元素一样来处理整体的数据集,而无须关心单个元素和整体数据集之间的内部复杂结构。

组合模式以类似树形结构的方式实现整体和部分之间关系的组合。下面以实现一个简单的树为例介绍组合模式。具体的UML设计如图所示。

offer来了(原理篇)学习笔记-第9章设计模式_第14张图片
  1. 定义TreeNode:
class TreeNode{
    private String name;
    private TreeNode parent;
    private Vector<TreeNode> children = new Vector<>();
    public TreeNode(String name){
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public TreeNode getParent() {
        return parent;
    }
    public void setParent(TreeNode parent) {
        this.parent = parent;
    }
    //添加子节点
    public void add(TreeNode node){
        children.add(node);
    }
    //删除子节点
    public void remove(TreeNode node){
        children.remove(node);
    }
    //获取子节点
    public Enumeration<TreeNode> getChildren(){
        return children.elements();
    }
    @Override
    public String toString() {
        return "TreeNode{" +
                "name='" + name + '\'' +
                ", parent=" + parent +
                ", children=" + children +
                '}';
    }
}

以上代码定义了TreeNode类来表示一个树形结构,并定义了children来存储子类,定义了方法add()和remove()来向树中添加数据和从树中删除数据。

  1. 使用TreeNode:
public class CompositePattern {
    public static void main(String[] args) {
        TreeNode nodeA = new TreeNode("A");
        TreeNode nodeB = new TreeNode("B");
        nodeA.add(nodeB);
        System.out.println(nodeA.toString());
    }
}

以上代码演示了TreeNode的使用过程,定义了nodeA和nodeB,并将nodeB作为nodeA的子类

12.享元模式的概念及Java实现

享元模式(Flyweight Pattern)主要通过对象的复用来减少对象创建的次数和数量,以减少系统内存的使用和降低系统的负载。享元模式属于结构型模式,在系统需要一个对象时享元模式首先在系统中查找并尝试重用现有的对象,如果未找到匹配的对象,则创建新对象并将其缓存在系统中以便下次使用。

享元模式主要用于避免在有大量对象时频繁创建和销毁对象造成系统资源的浪费,把其中共同的部分抽象出来,如果有相同的业务请求,则直接返回内存中已有的对象,避免重新创建。

下面以内存的申请和使用为例介绍享元模式的使用方法,创建一个MemoryFactory作为内存管理的工厂,用户通过工厂获取内存,在系统内存池有可用内存时直接获取该内存,如果没有则创建一个内存对象放入内存池,等下次有相同的内存请求过来时直接将该内存分配给用户即可。具体的UML设计如图所示。

offer来了(原理篇)学习笔记-第9章设计模式_第15张图片
  1. 定义Memory:
class Memory{
    // 内存大小,单位为MB
    private int size;
    // 内存是否在被使用
    private boolean isused;
    // 内存id
    private String id;
    public Memory(int size, boolean isused, String id){
        this.size = size;
        this.isused = isused;
        this.id = id;
    }
    public int getSize() {
        return size;
    }
    public void setSize(int size) {
        this.size = size;
    }
    public boolean isIsused() {
        return isused;
    }
    public void setIsused(boolean isused) {
        this.isused = isused;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
}
  1. 定义MemoryFactory工厂:
class MemoryFactory {
    // 内存对象列表
    private static List<Memory> memoryList = new ArrayList<>();
    public static Memory getMemory(int size){
        Memory memory = null;
        for (int i =0;i<memoryList.size();i++){
            memory = memoryList.get(i);
            // 如果存在和需求size一样大小并且未使用的内存块,则直接返回
            if (memory.getSize()==size && memory.isIsused()==false){
                memory.setIsused(true);
                memoryList.set(i,memory);
                System.out.println("get memory from memoryList" + memory.toString());
                break;
            }
        }
        // 如果内存不存在,则从系统中申请新的内存返回,并将该内存放入内存对象列表中
        if (memory ==null){
            memory = new Memory(32,false, UUID.randomUUID().toString());
            System.out.println("create a new memory for system and add to memoryList" + memory.toString());
            memoryList.add(memory);
        }
        return memory;
    }
    //释放内存
    public static void releaseMemory(String id){
        for (int i =0;i<memoryList.size();i++){
            Memory memory = memoryList.get(i);
            // 如果存在和需求size一样大小并且未使用的那内存块直接返回
            if (memory.getId().equals(id)){
                memory.setIsused(false);
                memoryList.set(i,memory);
                System.out.println("release memory:" + id);
                break;
            }
        }
    }
}

以上代码定义了工厂类MemoryFactory,在该类中定义了memoryList用于存储从系统中申请到的内存,该类定义了getMemory,用于从memoryList列表中获取内存,如果在内存中有空闲的内存,则直接取出来返回,并将该内存的使用状态设置为已使用,如果没有,则创建内存并放入内存列表;还定义了releaseMemory来释放内存,具体做法是将内存的使用状态设置为false。

  1. 使用享元模式:
public class FlyweightPattern {
    public static void main(String[] args) {
        // 首次获取内存,将创建一个内存
        Memory memory = MemoryFactory.getMemory(32);
        // 在使用后释放内存
        MemoryFactory.releaseMemory(memory.getId());
        // 重新获取内存
        MemoryFactory.getMemory(32);
    }
}

在使用享元模式时,直接从工厂类MemoryFactory中获取需要的数据Memory,在使用完成后释放即可。

13.策略模式的概念及Java实现

策略模式(Strategy Pattern)为同一个行为定义了不同的策略,并为每种策略都实现了不同的方法。在用户使用的时候,系统根据不同的策略自动切换不同的方法来实现策略的改变。同一个策略下的不同方法是对同一功能的不同实现,因此在使用时可以相互替换而不影响用户的使用。

策略模式的实现是在接口中定义不同的策略,在实现类中完成了对不同策略下具体行为的实现,并将用户的策略状态存储在上下文(Context)中来完成策略的存储和状态的改变。

我们在现实生活中常常碰到实现目标有多种可选策略的情况,比如下班后可以通过开车、坐公交、坐地铁、骑自行回家,在旅行时可以选择火车、飞机、汽车等交通工具,在淘宝上购买指定商品时可以选择直接减免部分钱、送赠品、送积分等方式。

对于上述情况,使用多重if …else条件转移语句也可实现,但属于硬编码方式,这样做不但会使代码复杂、难懂,而且在增加、删除、更换算法时都需要修改源代码,不易维护,违背了开闭原则。通过策略模式就能优雅地解决这些问题。

下面以旅游交通工具的选择为例实现策略模式,具体的UML设计如图所示。

offer来了(原理篇)学习笔记-第9章设计模式_第16张图片
  1. 定义TravelStrategy:
interface TravelStrategy{
    void travelMode();
}

以上代码定义了策略模式接口TravelStrategy,并在该接口中定义了方法travelMode()来表示出行方式。

  1. 定义TravelStrategy的两种实现方式TravelByAirStrategy和TravelByCarStrategy:
class TravelByAirStrategy implements TravelStrategy{
    @Override
    public void travelMode() {
        System.out.println("travel by air");
    }
}

class TravelByCarStrategy implements TravelStrategy{
    @Override
    public void travelMode() {
        System.out.println("travel by car");
    }
}

以上代码定义了TravelStrategy的两个实现类TravelByAirStrategy和TravelByCarStrategy,分别表示基于飞机的出行方式和基于开车自驾的出行方式,并实现了方法travelMode()。

  1. 定义Context实现策略模式:
class Context{
    private TravelStrategy travelStrategy;
    public TravelStrategy getTravelStrategy() {
        return travelStrategy;
    }
    public void setTravelStrategy(TravelStrategy travelStrategy) {
        this.travelStrategy = travelStrategy;
    }
    public void travelMode(){
        this.travelStrategy.travelMode();
    }
}

以上代码定义了策略模式实现的核心类Context,在该类中持有TravelStrategy实例并通过setTravelStrategy()实现了不同策略的切换。

  1. 使用策略模式:
public class StrategyPattern {
    public static void main(String[] args) {
        Context context = new Context();
        TravelStrategy travelByAirStrategy = new TravelByAirStrategy();
        // 设置出行策略为飞机
        context.setTravelStrategy(travelByAirStrategy);
        context.travelMode();
        System.out.println("change strategy");
        // 设置出行策略为开车自驾
        TravelStrategy travelByCarStrategy = new TravelByCarStrategy();
        context.setTravelStrategy(travelByCarStrategy);
        context.travelMode();
    }
}

在使用策略模式时,首先需要定义一个Context,然后定义不同的策略实现并将其注入Context中实现不同策略的切换。

14.模板方法模式的概念及Java实现

模板方法(Template Method)模式定义了一个算法框架,并通过继承的方式将算法的实现延迟到子类中,使得子类可以在不改变算法框架及其流程的前提下重新定义该算法在某些特定环节的实现,是一种类行为型模式。

该模式在抽象类中定义了算法的结构并实现了公共部分算法,在子类中实现可变的部分并根据不同的业务需求实现不同的扩展。模板方法模式的优点在于其在父类(抽象类)中定义了算法的框架以保障算法的稳定性,同时在父类中实现了算法公共部分的方法来保障代码的复用;将部分算法部分延迟到子类中实现,因此子类可以通过继承的方式来扩展或重新定义算法的功能而不影响算法的稳定性,符合开闭原则。

模板方法模式需要注意抽象类与具体子类之间的协作,在具体使用时包含以下主要角色。

  • 抽象类(Abstract Class):定义了算法的框架,由基本方法和模板方法组成。基本方法定义了算法有哪些环节,模板方法定义了算法各个环节执行的流程。
  • 具体子类(Concrete Class):对在抽象类中定义的算法根据需求进行不同的实现。

下面以银行办理业务为例实现一个模板方法模式,我们去银行办理业务都要经过抽号、排队、办理业务和评价,其中的业务流程是固定的,但办理的具体业务比较多,比如取钱、存钱、开卡等。其中,办理业务的固定流程就是模板算法中的框架,它常常是不变的,由抽象类定义和实现,而具体办理的业务是可变的部分,通常交给子类去做具体的实现。具体的UML设计如图所示。

offer来了(原理篇)学习笔记-第9章设计模式_第17张图片
  1. 定义AbstractTemplate模板类:
abstract class AbstractTemple {
    public void templateMethod(){
        //模板方法,用于核心流程和算法的定义
        checkNumber();
        queueUp();
        handleBusiness();
        serviceEvaluation();
    }
    public void checkNumber(){
        // 1.抽号
        System.out.println("check number");
    }
    public void queueUp(){
        // 2.排队
        System.out.println("queue up");
    }
    // 3.办理业务
    public abstract void handleBusiness();
    public void serviceEvaluation(){
        // 4. 服务评价
        System.out.println("business finished, service evaluation");
    }
}

以上代码定义了抽象类AbstractTemplate,用于实现模板方法模式,其中定义了checkNumber()表示抽号过程,queueUp()表示排队过程,handleBusiness()表示需要办理的具体业务,serviceEvaluation()表示在业务办理完成后对服务的评价,templateMethod()定义了银行办理业务的核心流程,即取号、排队、办理业务和评价。抽象类实现了取号、排队、办理业务这些公共方法,而将办理业务的具体方法交给具体的业务类实现。

  1. 定义SaveMoney的业务实现:
class SaveMoney extends AbstractTemple{
    @Override
    public void handleBusiness() {
        System.out.println("save money");
    }
}

以上代码定义了SaveMoney并实现了handleBusiness(),以完成存钱的业务逻辑。

  1. 定义TakeMoney的业务实现:
class TakeMoney extends AbstractTemple{
    @Override
    public void handleBusiness() {
        System.out.println("take money");
    }
}

以上代码定义了TakeMoney并实现了handleBusiness(),以完成取钱的业务逻辑。

  1. 使用模板模式:
public class TemplatePattern {
    public static void main(String[] args) {
        // 办理取钱业务
        AbstractTemple take = new TakeMoney();
        take.templateMethod();
        // 办理存钱业务
        AbstractTemple save = new SaveMoney();
        save.templateMethod();
    }
}

在使用模板模式时只需按照需求定义具体的模板类实例并调用其模板方法即可

15.观察者模式的概念及Java实现

观察者(Observer)模式指在被观察者的状态发生变化时,系统基于事件驱动理论将其状态通知到订阅其状态的观察者对象中,以完成状态的修改和事件传播。这种模式有时又叫作发布-订阅模式或者模型-视图模式

观察者模式是一种对象行为型模式,观察者和被观察者之间的关系属于抽象耦合关系,主要优点是在观察者与被观察者之间建立了一套事件触发机制,以降低二者之间的耦合度。

观察者模式的主要角色如下:

  • 抽象主题(Subject):持有订阅了该主题的观察者对象的集合,同时提供了增加、删除观察者对象的方法和主题状态发生变化后的通知方法。
  • 具体主题(Concrete Subject):实现了抽象主题的通知方法,在主题的内部状态发生变化时,调用该方法通知订阅了主题状态的观察者对象。
  • 抽象观察者(Observer):观察者的抽象类或接口,定义了主题状态发生变化时需要调用的方法。
  • 具体观察者(Concrete Observer):抽象观察者的实现类,在收到主题状态变化的信息后执行具体的触发机制。

观察者模式具体的UML设计如图所示。

offer来了(原理篇)学习笔记-第9章设计模式_第18张图片
  1. 定义抽象主题Subject:
abstract class Subject{
    protected List<Observer> observers = new ArrayList<>();
    // 增加观察这
    public void add(Observer observer){
        observers.add(observer);
    }
    // 删除观察这
    public void remove(Observer observer){
        observers.remove(observer);
    }
    // 通过观察者的抽象方法
    public abstract void notifyObserver(String message);
}

以上代码定义了抽象主题Subject类,并定义和实现了方法add()、remove()来向Subject添加观察者和删除观察者,定义了抽象方法notifyObserver()来实现在消息发生变化时将变化后的消息发送给观察者。

  1. 定义具体的主题ConcreteSubject:
class ConcreteSubject extends Subject{
    @Override
    public void notifyObserver(String message) {
        for (Object obs:observers){
            System.out.println("notify observer" + message + "change....");
            ((Observer)obs).dataChange(message);
        }
    }
}

以上代码定义了ConcreteSubject类,该类继承了Subject并实现了notifyObserver(),用于向观察者发送消息。

  1. 定义抽象观察者Observer:
interface Observer{
    // 接受数据
    void dataChange(String message);
}

以上代码定义了观察者Observer接口并定义了messageReceive(),用于接收ConcreteSubject发送的通知。

  1. 定义具体的观察者ConcreteObserver:
class ConcreteObserver implements Observer {
    @Override
    public void dataChange(String message) {
        System.out.println("receive message:" + message);
    }
}

以上代码定义了具体的观察者ConcreteObserver类,用于接收Observer发送过来的通知并做具体的消息处理。(原书错误,应该是implements Observer)

  1. 使用观察者模式:
public class ObserverPattern {
    public static void main(String[] args) {
        Subject subject = new ConcreteSubject();
        Observer obs = new ConcreteObserver();
        subject.add(obs);
        subject.notifyObserver("data");
    }
}

在使用观察者模式时首先要定义一个Subject主题,然后定义需要接收通知的观察者,接着将观察者加入主题的监控列表中,在有数据发生变化时,Subject(主题)会将变化后的消息发送给观察者,最后调用subject的方法notifyObserver()发送一个数据变化的通知

16.迭代器模式的概念及Java实现

迭代器(Iterator)模式提供了顺序访问集合对象中的各种元素,而不暴露该对象内部结构的方法

Java中的集合就是典型的迭代器模式,比如HashMap,在我们需要遍历HashMap时,通过迭代器不停地获取Next元素就可以循环遍历集合中的所有元素。

迭代器模式将遍历集合中所有元素的操作封装成迭代器类,其目的是在不暴露集合对象内部结构的情况下,对外提供统一访问集合的内部数据的方法。迭代器的实现一般包括一个迭代器,用于执行具体的遍历操作;以及一个Collection,用于存储具体的数据。我们以Collection集合的迭代器设计为例介绍迭代器模式的设计思路。具体的UML设计如图所示。

观察者模式具体的UML设计如图所示。

offer来了(原理篇)学习笔记-第9章设计模式_第19张图片
  1. 定义名为Collection的集合接口:
interface Collection{
    // 对集合元素的迭代
    Iterator iterator();
    // 取得集合元素
    Object get(int i);
    // 向集合添加元素
    boolean add(Object object);
    // 取得集合大小
    int size();
}

以上代码定义了名为Collection的接口,用于制定集合操作的规范。在该接口中定义了iterator()用于集合接口的遍历,定义了get()用于获取集合中的元素,定义了add()用于向集合中添加元素,定义了size()用于获取集合的大小。

  1. 定义Collection接口实现类ListCollection:
class ListCollection implements Collection{
    // 用于储存数据
    public List list = new ArrayList<>();
    @Override
    public Iterator iterator() {
        return new ConcreteIterator(this);
    }
    @Override
    public Object get(int i) {
        return list.get(i);
    }
    @Override
    public boolean add(Object object) {
        list.add(object);
        return true;
    }
    @Override
    public int size() {
        return list.size();
    }
}

以上代码定义了Collection接口的实现类ListCollection,ListCollection类用于存储具体的数据并实现数据操作方法,其中,list用于存储数据,iterator()用于构造集合迭代器。

  1. 定义迭代器接口Iterator:
interface Iterator{
    //指针前移
    public Object previous();
    //指针后移
    public Object next();
    public boolean hasNext();
}

以上代码定义了迭代器接口Iterator,在该接口中规范了迭代器应该实现的方法,其中,previous()用于访问迭代器中的上一个元素,next()用于访问迭代器中的下一个元素,hasNext()用于判断在迭代器中是否还有元素。

  1. 定义迭代器接口Iterator的实现类ConcreteIterator:
class ConcreteIterator implements Iterator{
    private Collection collection;
    // 当前迭代器遍历到的元素位置
    private int pos = -1;

    public ConcreteIterator(Collection collection) {
        this.collection = collection;
    }
    @Override
    public Object previous() {
        if (pos>0){
            pos--;
        }
        return collection.get(pos);
    }
    @Override
    public Object next() {
        if (pos<collection.size()-1){
            pos++;
        }
        return collection.get(pos);
    }
    @Override
    public boolean hasNext() {
        return pos < collection.size() - 1;
    }
}

以上代码定义了迭代器接口Iterator的实现类ConcreteIterator,在ConcreteIterator中定义了Collection用于访问集合中的数据,pos用于记录当前迭代器遍历到的元素位置,同时实现了在Iterator接口中定义的方法previous()、next()和hasNext(),以完成具体的迭代器需要实现的基础功能。

  1. 使用迭代器:
public class IteratorPattern {
    public static void main(String[] args) {
        //定义集合
        Collection collection = new ListCollection();
        //向集合中添加数据
        collection.add("object1");
        //使用迭代器遍历
        Iterator it = collection.iterator();
        while (it.hasNext()){
            System.out.println(it.next());
        }
    }
}

迭代器的使用方法比较简单:首先需要定义一个集合并向集合中加入数据,然后获取集合的Iterator迭代器并通过循环遍历集合中的数据。

17.责任链模式的概念及Java实现

责任链(Chain of Responsibility)模式也叫作职责链模式,用于避免请求发送者与多个请求处理者耦合在一起,让所有请求的处理者持有下一个对象的引用,从而将请求串联成一条链,在有请求发生时,可将请求沿着这条链传递,直到遇到该对象的处理器。

在责任链模式下,用户只需将请求发送到责任链上即可,无须关心请求的处理细节和传递过程,所以责任链模式优雅地将请求的发送和处理进行了解耦。

责任链模式在Web请求中很常见,比如我们要为客户端提供一个REST服务,服务端要针对客户端的请求实现用户鉴权、业务调用、结果反馈流程,就可以使用责任链模式实现。

责任链模式包含以下三种角色。

  • Handler接口:用于规定在责任链上具体要执行的方法。
  • AbstractHandler抽象类:持有Handler实例并通过setHandler()和getHandler()将各个具体的业务Handler串联成一个责任链,客户端上的请求在责任链上执行。
  • 业务Handler:用户根据具体的业务需求实现的业务逻辑。

例如,用户鉴权、业务调用、结果反馈流程的责任链实现的具体UML设计如图所示。

offer来了(原理篇)学习笔记-第9章设计模式_第20张图片
  1. 定义Handler接口:
interface Handler {
    void operator();
}

以上代码定义了Handler接口,该接口用于规定责任链上各个环节的操作,这里定义了operator(),用于在责任链上各个环节处理任务时进行调用。

  1. 定义AbstractHandler类:
abstract class AbstractHandler{
    private Handler handler;
    public Handler getHandler() {
        return handler;
    }
    public void setHandler(Handler handler) {
        this.handler = handler;
    }
}

以上代码定义了抽象类AbstractHandler来将责任链上的各个组件连接起来,具体操作是通过setHandler()设置下一个环节的组件,通过getHandler()获取下一个环节的组件。

  1. 定义用户授权类AuthHandler:
class AuthHandler extends AbstractHandler implements Handler{
    private String name;
    public AuthHandler(String name) {
        this.name = name;
    }
    @Override
    public void operator() {
        System.out.println("user auth");
        if (getHandler()!=null){
            //执行下一个流程
            getHandler().operator();
        }
    }
}

以上代码定义了用户授权类AuthHandler并实现了operator(),该方法首先调用当前环节的业务流程,即用户授权,然后通过getHandler()获取下一个组件并调用其operator(),使其执行下一个责任链流程。

  1. 定义业务处理类BusinessHandler:
class BusinessHandler extends AbstractHandler implements Handler{
    private String name;
    public BusinessHandler(String name) {
        this.name = name;
    }
    @Override
    public void operator() {
        System.out.println("business handler");
        if (getHandler()!=null){
            //执行下一个流程
            getHandler().operator();
        }
    }
}

以上代码定义了用户授权类BusinessHandler并实现了方法operator(),该方法首先调用当前环节的业务流程,即业务处理流程,然后通过getHandler()获取下一个组件并调用其operator(),使其执行责任链的下一个流程。

  1. 定义请求反馈类ResponseHandler:
class ResponseHandler extends AbstractHandler implements Handler{
    private String name;
    public ResponseHandler(String name) {
        this.name = name;
    }
    @Override
    public void operator() {
        System.out.println("message response");
        if (getHandler()!=null){
            //执行下一个流程
            getHandler().operator();
        }
    }
}

以上代码定义了用户授权类ResponseHandler并实现了operator(),该方法首先调用当前环节的业务流程,这里的业务流程主要是判断业务流程执行的结果并做出相应的反馈,然后通过getHandler()获取下一个组件并调用其operator(),使其执行下一个责任链流程。

  1. 使用责任链模式:
public class ResponsibilityChainPattern {
    public static void main(String[] args) {
        AuthHandler authHandler = new AuthHandler("auth");
        BusinessHandler businessHandler = new BusinessHandler("business");
        ResponseHandler responseHandler = new ResponseHandler("response");
        authHandler.setHandler(businessHandler);
        businessHandler.setHandler(responseHandler);
        authHandler.operator();
    }
}

在使用责任链模式时,首先要定义各个责任链的组件,然后将各个组件通过setHandler()串联起来,最后调用第一个责任链上的operator(),接着程序就像多米诺骨牌一样在责任链上执行下去。

18.命令模式的概念及Java实现

命令(Command)模式指将请求封装为命令基于事件驱动异步地执行,以实现命令的发送者和命令的执行者之间的解耦,提高命令发送、执行的效率和灵活度。

**命令模式将命令调用者与命令执行者解耦,有效降低系统的耦合度。**同时,由于命令调用者和命令执行者进行了解耦,所以增加和删除(回滚)命令变得非常方便。

命令模式包含以下主要角色。

  • 抽象命令类(Command):执行命令的接口,定义执行命令的抽象方法execute()。
  • 具体命令类(Concrete Command):抽象命令类的实现类,持有接收者对象,并在接收到命令后调用命令执行者的方法action()实现命令的调用和执行。
  • 命令执行者(Receiver):命令的具体执行者,定义了命令执行的具体方法action()。
  • 命令调用者(Invoker):接收客户端的命令并异步执行。
offer来了(原理篇)学习笔记-第9章设计模式_第21张图片
  1. 定义Command接口:
interface Command{
    public void exe(String command);
}

以上代码定义了Command接口,并在该接口中定义了Command的执行方法exe()。

  1. 定义Command接口的实现类ConcreteCommand:
class ConcreteCommand implements Command{
    private Receiver receiver;
    public ConcreteCommand(Receiver receiver) {
        this.receiver = receiver;
    }
    @Override
    public void exe(String command) {
        receiver.action(command);
    }
}

以上代码定义了Command接口的实现类ConcreteCommand,该类持有命令接收和执行者Receiver的实例,并实现了Command接口中的exe(),具体操作是在ConcreteCommand接收到命令后,调用Receiver的action()将命令交给Receiver执行。

  1. 定义命令调用者类Invoker:
class Invoker{
    private Command command;
    public Invoker(Command command){
        this.command = command;
    }
    public void action(String commandMessage){
        System.out.println("command sending...");
        command.exe(commandMessage);
    }
}

以上代码定义了命令调用者类Invoker,该类持有Command实例并在action()中实现了对命令的调用,具体做法是在action()中执行Command的exe()。

  1. 定义命令的接收和执行者类Receiver:
class Receiver{
    public void action(String command){
        //接受并执行命令
        System.out.println("command received, now execute command: " + command);
    }
}

以上代码定义了命令的接收和执行者类Receiver,并在action()中接收和执行命令。(改进原书中代码,新增command显示更清楚)

  1. 使用命令模式:
public class CommandPattern {
    public static void main(String[] args) {
        //定义命令的接收和执行者
        Receiver receiver = new Receiver();
        //定义命令实现类
        Command cmd = new ConcreteCommand(receiver);
        //定义命令调用者
        Invoker invoker = new Invoker(cmd);
        //命令调用
        invoker.action("command11111");
    }
}

在使用命令模式时首先要定义一个命令接收和执行者Receiver,接着定义一个具体的命令ConcreteCommand实例,并将命令接收者实例设置到实例中,然后定义一个命令的调用者Invoker实例,并将命令实例设置到实例中,最后调用命令调用者的action(),将命令发送出去,在命令接收者收到数据后会执行相关命令,这样就完成了命令的调用。

19.备忘录模式的概念及Java实现

备忘录(Memento)模式又叫作快照模式,该模式将当前对象的内部状态保存到备忘录中,以便在需要时能将该对象的状态恢复到原先保存的状态。

备忘录模式提供了一种保存和恢复状态的机制,常用于快照的记录和状态的存储,在系统发生故障或数据发生不一致时能够方便地将数据恢复到某个历史状态。

备忘录模式的核心是设计备忘录类及用于管理备忘录的管理者类,其主要角色如下。

  • 发起人(Originator):记录当前时刻对象的内部状态,定义创建备忘录和恢复备忘录数据的方法。
  • 备忘录(Memento):负责存储对象的内部状态。
  • 状态管理者(Storage):对备忘录的历史状态进行存储,定义了保存和获取备忘录状态的功能。注意,备忘录只能被保存或恢复,不能进行修改。
offer来了(原理篇)学习笔记-第9章设计模式_第22张图片
  1. 定义原始数据Original:
class Original{
    private String value;
    public Original(String value) {
        this.value = value;
    }
    public String getValue() {
        return value;
    }
    public void setValue(String value) {
        this.value = value;
    }
    public Memento createMemento(){
        return new Memento(value);
    }
    public void restoreMemento(Memento memento){
        this.value = memento.getValue();
    }
}

以上代码定义了原始数据Original,在原始数据中定义了createMemento()和restoreMemento()分别用于创建备忘录和从备忘录中恢复数据。

  1. 定义备忘录Memento:
class Memento {
    private String value;
    public Memento(String value) {
        this.value = value;
    }
    public String getValue() {
        return value;
    }
    public void setValue(String value) {
        this.value = value;
    }
}

以上代码定义了备忘录Memento,其中value为备忘录具体的数据内容。

  1. 定义备忘录管理者Storage:
class Storage{
    private Memento memento;
    public Storage(Memento memento){
        this.memento = memento;
    }
    public Memento getMemento() {
        return memento;
    }
    public void setMemento(Memento memento) {
        this.memento = memento;
    }
}

以上代码定义了备忘录管理者Storage,持有备忘录实例,并提供了setMemento()和getMemento()分别用来设置和获取一个备忘录数据。

  1. 使用备忘录:
public class MementoPattern {
    public static void main(String[] args) {
        //创建原始类
        Original original = new Original("张三");
        Storage storage = new Storage(original.createMemento());
        //修改原始类的状态
        System.out.println("original value " + original.getValue());
        original.setValue("李四");
        System.out.println("update " + original.getValue());
        //恢复原始类状态
        original.restoreMemento(storage.getMemento());
        System.out.println("restore value " + original.getValue());
    }
}

备忘录的使用方法比较简单:先定义一个原始数据,然后将数据存储到Storage,这时我们可以修改数据,在我们想把数据回滚到之前的状态时调用Original的restoreMemento()便可将存储在Storage中上次数据的状态恢复。其实,备忘录简单来说就是把原始数据的状态在Storage中又重新存储一份,在需要时可以恢复数据。

上面的例子只存储了数据的上一次状态,如果想存储多个状态,就可以在Storage中使用列表记录多个状态的数据。

20.状态模式的概念及Java实现

状态模式指给对象定义不同的状态,并为不同的状态定义不同的行为,在对象的状态发生变换时自动切换状态的行为。

状态模式是一种对象行为型模式,它将对象的不同行为封装到不同的状态中,遵循了“单一职责”原则。同时,状态模式基于对象的状态将对象行为进行了明确的界定,减少了对象行为之间的相互依赖,方便系统的扩展和维护。

状态模式在生活中很常见,比如日常生活有工作状态、休假状态;钉钉有出差、会议、工作中等状态。每种状态都对应不同的操作,比如工作状态对应的行为有开会、写PPT、写代码、做设计等,休假状态对应的行为有旅游、休息、陪孩子等。

状态模式把受环境改变的对象行为包装在不同的状态对象里,用于让一个对象在其内部状态改变时,行为也随之改变。具体的角色如下。

  • 环境(Context):也叫作上下文,用于维护对象当前的状态,并在对象状态发生变化时触发对象行为的变化。
  • 抽象状态(AbstractState):定义了一个接口,用于定义对象中不同状态所对应的行为。
  • 具体状态(Concrete State):实现抽象状态所定义的行为。
offer来了(原理篇)学习笔记-第9章设计模式_第23张图片
  1. 定义AbstractState:
abstract class AbstractState{
    public abstract void action(Context context);
}

以上代码定义了AbstractState抽象类,在类中定义了action()用于针对不同的状态执行不同的动作。

  1. 定义AbstractState的子类HolidayState:
class HolidayState extends AbstractState{
    @Override
    public void action(Context context) {
        System.out.println("state change to holiday state");
        System.out.println("holiday state action is travel, shopping, watch tv....");
    }
}

以上代码定义了AbstractState的子类HolidayState并实现了action(), HolidayState中的action()的主要动作是旅行(travel)、购物(shopping)、看电视(watchtelevision)等。

  1. 定义AbstractState的子类WorkState:
class WorkState extends AbstractState{
    @Override
    public void action(Context context) {
        System.out.println("state change to work state");
        System.out.println("holiday state action is meeting, design, coding....");
    }
}

以上代码定义了AbstractState的子类WorkState并实现了action(), WorkState中action()的主要动作是开会(meeting)、设计(design)、写代码(coding)等。

  1. 定义Context用于存储状态和执行不同状态下的行为:
class Context{
    private AbstractState state;
    public Context(AbstractState state){
        this.state = state;
    }
    public AbstractState getState() {
        return state;
    }
    public void setState(AbstractState state) {
        this.state = state;
    }
    public void action(){
        this.state.action(this);
    }
}

以上代码定义了Context类,该类用于设置上下文环境中的状态,并根据不同的状态执行不同的action()。这里状态的设置通过setState()完成,具体的动作执行通过action()完成。

  1. 使用状态模式:
public class StatePattern {
    public static void main(String[] args) {
        //定义当前状态为工作状态
        Context context = new Context(new WorkState());
        context.action();
        //切换当前状态为休假状态
        context.setState(new HolidayState());
        context.action();
    }
}

在使用状态模式时,只需定义一个上下文Context,并设置Context中的状态,然后调用Context中的行为方法即可。以上代码首先通过Context的构造函数将状态设置为WorkState,接着通过setState()将状态设置为HolidayState,两种不同的状态将对应不同的行为。

21.访问者模式的概念及Java实现

访问者(Visitor)模式指将数据结构和对数据的操作分离开来,使其在不改变数据结构的前提下动态添加作用于这些元素上的操作。它将数据结构的定义和数据操作的定义分离开来,符合“单一职责”原则。访问者模式通过定义不同的访问者实现对数据的不同操作,因此在需要给数据添加新的操作时只需为其定义一个新的访问者即可。

访问者模式是一种对象行为型模式,主要特点是将数据结构和作用于结构上的操作解耦,使得集合的操作可自由地演化而不影响其数据结构。它适用于数据结构稳定但是数据操作方式多变的系统中。

访问者模式实现的关键是将作用于元素的操作分离出来封装成独立的类,包含以下主要角色。

  • 抽象访问者(Visitor):定义了一个访问元素的接口,为每类元素都定义了一个访问操作visit(),该操作中的参数类型对应被访问元素的数据类型。
  • 具体访问者(ConcreteVisitor):抽象访问者的实现类,实现了不同访问者访问到元素后具体的操作行为。
  • 抽象元素(Element):元素的抽象表示,定义了访问该元素的入口的accept()方法,不同的访问者类型代表不同的访问者。
  • 具体元素(Concrete Element):实现抽象元素定义的accept()操作,并根据访问者的不同类型实现不同的业务逻辑。

比如,我们有个项目计划需要上报,项目计划的数据结构是稳定的,包含项目名称和项目内容,但项目的访问者有多个,比如项目经理、CEO和CTO。类似的数据结构稳定但对数据的操作多变的情况很适合只用访问者模式实现。下面以项目的访问为例介绍访问者模式。具体的UML设计如图所示。

offer来了(原理篇)学习笔记-第9章设计模式_第24张图片
  1. 定义抽象Visitor接口:
interface Visitor{
    void visit(ProjectElement element);
}

以上代码定义了Visitor接口,并在接口中定义了visit()用于指定要访问的数据。

  1. 定义Visitor实现类CEOVisitor:
class CEOVistor implements Visitor{
    @Override
    public void visit(ProjectElement element) {
        System.out.println("CEO visitor");
        element.signature("CEO", new Date());
        System.out.println(element.toString());
    }
}

以上代码定义了Visitor实现类CEOVisitor,并实现了其方法visit(),该方法在接收到具体的元素时,访问该元素并调用signature()签名方法表示CEOVisitor已经访问和审阅了该项目。

  1. 定义Visitor实现类CTOVisitor:
class CTOVistor implements Visitor{
    @Override
    public void visit(ProjectElement element) {
        System.out.println("CTO visitor");
        element.signature("CTO", new Date());
        System.out.println(element.toString());
    }
}

以上代码定义了Visitor实现类CTOVisitor,并实现了其方法visit(),该方法在接收到具体的元素时,会访问该元素并调用signature()签名方法表示CTOVisitor已经访问和审阅了该项目。

  1. 定义抽象元素Element的接口:
interface Element{
    void accept(Visitor visitor);
}

以上代码定义了抽象元素Element,并定义了accept()用于接收访问者对象。

  1. 定义具体元素ProjectElement的类:
class ProjectElement implements Element{
    private String projectName;
    private String projectContext;
    private String visitorName;
    private Date visitorTime;
    public ProjectElement(String projectName, String projectContext) {
        this.projectName = projectName;
        this.projectContext = projectContext;
    }
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    public void signature(String visitorName, Date visitorTime){
        this.visitorName = visitorName;
        this.visitorTime = visitorTime;
    }
    @Override
    public String toString() {
        return "ProjectElement{" +
                "projectName='" + projectName + '\'' +
                ", projectContext='" + projectContext + '\'' +
                ", visitorName='" + visitorName + '\'' +
                ", visitorTime=" + visitorTime +
                '}';
    }
}

以上代码定义了ProjectElement用于表示一个具体的元素,该元素表示一个项目信息,包含项目名称projectName、项目内容projectContent、项目访问者visitorName和项目访问时间,还定义了signature()用于记录访问者的签名,以及accept()用于接收具体的访问者。

  1. 使用访问者模式:
public class VisitorPattern {
    public static void main(String[] args) {
        Element element = new ProjectElement("ele", "饿了吗");
        element.accept(new CTOVistor());
        element.accept(new CEOVistor());
    }
}

在使用访问者模式时,首先需要定义一个具体的元素,然后通过accept()为元素添加访问者即可。

22.中介者模式的概念及Java实现

中介者(Mediator)模式指对象和对象之间不直接交互,而是通过一个名为中介者的角色来实现对象之间的交互,使原有对象之间的关系变得松散,且可以通过定义不同的中介者来改变它们之间的交互。中介者模式又叫作调停模式,是迪米特法则的典型应用。

中介者模式属于对象行为型模式,其主要特点是将对象与对象之间的关系变为对象和中介者之间的关系,降低了对象之间的耦合性,提高了对象功能的复用性和系统的灵活性,使得系统易于维护和扩展。

中介者模式包含以下主要角色。

  • 抽象中介者(Mediator):中介者接口,定义了注册同事对象方法和转发同事对象信息的方法。
  • 具体中介者(Concrete Mediator):中介者接口的实现类,定义了一个List来保存同事对象,协调各个同事角色之间的交互关系。
  • 抽象同事类(Colleague):定义同事类的接口,持有中介者对象,并定义同事对象交互的抽象方法,同时实现同事类的公共方法和功能。
  • 具体同事类(Concrete Colleague):抽象同事类的实现者,在需要与其他同事对象交互时,通过中介者对象来完成。

下面以租房场景为例来介绍中介者模式。我们知道,在租房时会找房屋中介,把自己的租房需求告知中介,中介再把需求告知房东,在整个过程中租房者和房东不产生直接关系(也不能产生关系,不然中介就没钱赚了),而是通过中介来完成信息交互,这样就完成了对象之间的解耦,也就是租户和房东的解耦,房东不用关心具体有哪些房客、房客有哪些需求,租房者也不用辛苦寻找房东及房子的信息。具体的UML设计如图所示。

offer来了(原理篇)学习笔记-第9章设计模式_第25张图片
  1. 定义抽象的Colleague类:
abstract class Colleague{
    protected Mediator mediator;
    public void setMediator(Mediator mediator){
        this.mediator = mediator;
    }
    //同事类的操作
    public abstract boolean operation(String message);
}

以上代码定义了抽象同事类Colleague,该类持有中介者对象并定义了同事类的具体操作方法operation()。

  1. 定义Colleague实现类ColleagueLandlord以代表房东:
class ColleagueLandlord extends Colleague{
    @Override
    public boolean operation(String message) {
        System.out.println("landlord receive a message from mediator" + message);
        return true;
    }
}

以上代码定义了Colleague实现类ColleagueLandlord以代表房东,并实现了方法operation(),该方法用来接收中介者传递的房客需求并做出具体响应。

  1. 定义Colleague实现类ColleagueTenant以代表租户:
class ColleagueTenant extends Colleague{
    @Override
    public boolean operation(String message) {
        System.out.println("tenant receive a message from mediator" + message);
        return true;
    }
}

以上代码定义了Colleague实现类ColleagueTenant以代表租户,并实现了方法operation(),该方法用来接收中介者传递的房东的房源信息并做出具体的响应。

  1. 定义抽象中介者Mediator类:
abstract class Mediator{
    protected Colleague colleagueTenant;
    protected Colleague colleagueLandlord;
    public Mediator(Colleague colleagueTenant, Colleague colleagueLandlord) {
        this.colleagueTenant = colleagueTenant;
        this.colleagueLandlord = colleagueLandlord;
    }
    public abstract boolean notifyColleagueTenant(String message);
    public abstract boolean notifyColleagueLandlord(String message);
}

以上代码定义了抽象中介者Mediator类,该类持有租客和房东类的实例,并定义了notifyColleagueTenant()和notifyColleagueLandlord()分别向房客和房东传递信息。

  1. 定义Mediator实现类ConcreteMediator代表一个具体的中介:
class ConcreteMediator extends Mediator{
    public ConcreteMediator(Colleague colleagueTenant,Colleague colleagueLandlord){
        super(colleagueTenant,colleagueLandlord);
    }
    @Override
    public boolean notifyColleagueTenant(String message) {
        if (colleagueTenant!=null){
            return colleagueTenant.operation(message);
        }
        return false;
    }
    @Override
    public boolean notifyColleagueLandlord(String message) {
        if (colleagueLandlord!=null){
            return colleagueLandlord.operation(message);
        }
        return false;
    }
}

以上代码定义了Mediator实现类ConcreteMediator来代表一个具体的中介,该中介实现了notifyColleagueTenant()和notifyColleagueLandlord()来完成房客和房东直接、具体的消息传递。

  1. 使用中介者模式:
public class MediatorPattern {
    public static void main(String[] args) {
        //定义房客同事类
        Colleague colleagueTenant = new ColleagueTenant();
        //定义房东同事类
        Colleague ColleagueLandlord = new ColleagueLandlord();
        //创建中间者
        ConcreteMediator concreteMediator = new ConcreteMediator(colleagueTenant, ColleagueLandlord);
        boolean resoult = concreteMediator.notifyColleagueTenant("想租2室1厅嘛?");
        if (resoult){
            concreteMediator.notifyColleagueLandlord("租客对面积满意");
        }else{
            concreteMediator.notifyColleagueLandlord("租客对面积不满意");
        }
    }
}

在使用中介者模式时,首先要定义同事类,然后定义中介者并通过中介者完成对象之间的交互。以上代码首先定义了房客类和房东类,然后定义了中介者,最后通过中介者的notifyColleagueTenant()和notifyColleagueLandlord()完成房客和中间者之间的交互。以上代码的流程是中介者首先向房客询问对方对房屋面积的需求,然后将需求反馈给房东。

23.解释器模式的概念及Java实现

解释器(Interpreter)模式给定一种语言,并定义该语言的语法表示,然后设计一个解析器来解释语言中的语法,这种模式常被用于SQL解析、符号处理引擎等。

解释器模式包含以下主要角色。

  • 抽象表达式(Abstract Expression):定义解释器的接口,约定解释器所包含的操作,比如interpret()方法。
  • 终结符表达式(Terminal Expression):抽象表达式的子类,用来定义语法中和终结符有关的操作,语法中的每一个终结符都应有一个与之对应的终结表达式。
  • 非终结符表达式(Nonterminal Expression):抽象表达式的子类,用来定义语法中和非终结符有关的操作,语法中的每条规则都有一个非终结符表达式与之对应。
  • 环境(Context):定义各个解释器需要的共享数据或者公共的功能。

解释器模式主要用于和语法及表达式有关的应用场景,例如正则表达式解释器等。具体的UML设计如图所示。

offer来了(原理篇)学习笔记-第9章设计模式_第26张图片
  1. 定义Expression接口:
interface Expression{
    public void interpret(Context ctx);
}

以上代码定义了Expression接口,并定义了解释器方法。

  1. 定义NonterminalExpression类:
class NonterminalExpression implements Expression{
    private Expression left;
    private Expression right;
    public NonterminalExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }
    public void interpret(Context ctx){
        //递归调用每一个组成成分的interpret();
        //在递归调用时指定组成部分的连接方式,即非终结符的功能
    }
}

以上代码定义了Expression的实现类NonterminalExpression, NonterminalExpression类主要用于对非终结元素的处理。NonterminalExpression定义了left和right的操作元素。

  1. 定义TerminalExpression:
class TerminalExpression implements Expression{
    @Override
    public void interpret(Context ctx) {
        //终结符表达式的解释操作
    }
}

以上代码定义了Expression的实现类TerminalExpression,TerminalExpression类主要用于对终结元素的处理,表示该元素是整个语法表达式的最后一个元素。

  1. 定义Context:
class Context{
    private HashMap map = new HashMap();
    public void assign(String key, String value){
        //在环境类中设值
    }
    public String get(String key){
        //获取储存再环境类中的值
        return "";
    }
}

以上代码定义了Context的全局类用于存储表达式解析出来的值,并提供查询和解析后表达式的结果,以便其他表达式进一步使用。

本书结束

你可能感兴趣的:(学习总结,设计模式,java)