Java设计模式(23种详细介绍)

Java设计模式(23种详细介绍)

文章目录

1.工厂模式

工厂模式(Factory Pattern)是一种创建型模式,它提供了一种创建对象的最佳方式。它的主要思想是定义一个用于创建对象的接口,但让子类决定实例化哪个类。工厂模式让一个类的实例化延迟到其子类。


在工厂模式中,有四个核心角色:

  1. 抽象产品(Product):定义了产品的规范,描述了产品的属性和方法,所有具体的产品都必须实现这些规范。
  2. 具体产品(Concrete Product):实现了抽象产品所定义的规范,由具体工厂类创建,每个具体工厂只能创建一个具体产品类的实例。
  3. 抽象工厂(Factory):用来声明创建抽象产品的方法,它是工厂方法模式的核心,在Java中它通常是一个接口。
  4. 具体工厂(Concrete Factory):实现抽象工厂接口中声明的创建产品的方法,返回一个具体产品实例。

工厂模式的优点:

  • 通过工厂类接口与实现分离,可以在不修改客户端代码的情况下更换具体工厂和产品对象实现。
  • 客户端无需关心产品的创建细节和逻辑,只需要通过工厂类获得需要的产品即可。
  • 工厂模式符合单一职责原则和开闭原则,增加新的产品时只需添加新的产品类和对应的工厂类即可。

工厂模式的缺点:

  • 工厂类集中了所有产品创建逻辑,一旦出现问题整个系统将受到影响。
  • 工厂方法模式需要定义很多工厂类和产品类,增加代码复杂度。

示例:

在实际应用中,根据具体的业务需求和代码组织结构,可以选择工厂模式的不同变种,例如简单工厂模式、工厂方法模式、抽象工厂模式等来完成对象的创建。

一个典型的简单工厂模式的示例是创建不同类型的汽车。假设我们有三种汽车类型:轿车、越野车和卡车。每个车型有它们自己的特点,例如:轿车通常用于城市行驶,越野车用于复杂地形行驶,卡车用于物流运输等。

首先,我们需要定义一个抽象汽车类,包含了所有汽车都具有的属性和方法:

public abstract class Car {
    // 汽车名称
    public String name;
    // 汽车型号
    public String model;

    public Car(String name, String model) {
        this.name = name;
        this.model = model;
    }

    // 获取汽车名称
    public String getName() {
        return name;
    }

    // 获取汽车型号
    public String getModel() {
        return model;
    }

    // 启动汽车
    public void start() {
        System.out.println("启动" + name);
    }

    // 停止汽车
    public void stop() {
        System.out.println("停止" + name);
    }
}

然后,我们需要创建各个具体汽车类:

public class SedanCar extends Car {
    public SedanCar(String name, String model) {
        super(name, model);
    }
}

public class OffRoadCar extends Car {
    public OffRoadCar(String name, String model) {
        super(name, model);
    }
}

public class TruckCar extends Car {
    public TruckCar(String name, String model) {
        super(name, model);
    }
}

接着,我们需要创建一个汽车工厂类,该类提供了一个静态方法根据汽车类型创建相应的汽车:

public class CarFactory {
    // 创建汽车类型枚举
    public enum CarType {
        SEDAN, OFF_ROAD, TRUCK
    }

    public static Car createCar(CarType type) {
        switch (type) {
            case SEDAN:
                return new SedanCar("轿车", "C200");
            case OFF_ROAD:
                return new OffRoadCar("越野车", "SUV");
            case TRUCK:
                return new TruckCar("卡车", "10吨");
            default:
                throw new IllegalArgumentException("Unsupported car type.");
        }
    }
}

最后,客户端可以通过调用工厂类的静态方法来获取不同类型的汽车实例:

Car sedanCar = CarFactory.createCar(CarFactory.CarType.SEDAN);
sedanCar.start();
sedanCar.stop();

Car offRoadCar = CarFactory.createCar(CarFactory.CarType.OFF_ROAD);
offRoadCar.start();
offRoadCar.stop();

Car truckCar = CarFactory.createCar(CarFactory.CarType.TRUCK);
truckCar.start();
truckCar.stop();

以上示例中,简单工厂模式通过工厂类方法的静态调用和参数传递实现了不同类型汽车对象的创建过程,隐藏了对象的具体创建过程,并降低了客户端与具体汽车对象的耦合性。

2.抽象工厂模式

抽象工厂模式 (Abstract Factory Pattern)是工厂方法模式的升级版,它提供了一种创建一系列相关或独立对象的方法。其核心思想是为每个产品族提供一个抽象工厂类,可以定义多个产品等级结构。因此,它可以在不修改客户端代码的情况下,动态切换产品族和产品等级结构。


在抽象工厂模式中,有四个主要角色:

  1. 抽象工厂(Abstract Factory):用来声明一组创建抽象产品的方法,每个方法对应一个具体产品族。
  2. 具体工厂(Concrete Factory):实现抽象工厂接口中声明的一组创建抽象产品的方法,返回一个具体产品实例。
  3. 抽象产品(Abstract Product):定义了产品族的通用规范,描述了产品的属性和方法,所有具体的产品都必须实现这些规范。
  4. 具体产品(Concrete Product):实现了抽象产品所定义的规范,由具体工厂类创建,每个具体工厂只能创建一种具体产品类的实例。

抽象工厂模式的优点:

  • 抽象工厂模式通过引入抽象层,实现了客户端与具体实现的分离,可以随时更换不同的工厂来生产不同的产品。
  • 抽象工厂模式可以保证创建的产品之间的兼容性,避免出现与业务不相关的产品组合。
  • 抽象工厂模式可以简化客户端代码,避免直接调用具体产品类,提高代码可维护性和扩展性。

抽象工厂模式的缺点:

  • 抽象工厂模式中每新增一个产品族就需要增加一组新的工厂和产品类,增加了代码复杂度。
  • 抽象工厂模式注重于一系列相关的产品对象创建,如果产品之间不相关或者产品等级结构较为固定,则不建议使用抽象工厂模式。

示例:

下面以一个具体示例来展开学习抽象工厂模式

假设我们要开发一个游戏,该游戏有两个职业:魔法师和战士,每个职业有自己的装备组合。具体的产品族如下:

等级1魔法师装备:魔法棒、魔法披风
等级1战士装备:钢剑、钢甲
等级2魔法师装备:神器法杖、羽翼披风
等级2战士装备:龙牙剑、龙骨铠甲
首先我们需要定义两个抽象产品族:魔法装备和战士装备。

/**

魔法装备接口
*/
public interface MagicEquipment {
public void showMagicEquipment();
}

/**

战士装备接口
*/
public interface WarriorEquipment {
public void showWarriorEquipment();
}

然后,我们定义四个具体产品类实现这两个抽象产品族:

/**

等级1魔法师装备
*/
public class Level1MagicEquipment implements MagicEquipment{
@Override
public void showMagicEquipment() {
    System.out.println("等级1魔法师使用了魔法棒和魔法披风。");
}
}

/**

等级1战士装备
*/
public class Level1WarriorEquipment implements WarriorEquipment{
@Override
public void showWarriorEquipment() {
    System.out.println("等级1战士使用了钢剑和钢甲。");
}
}

/**

等级2魔法师装备
*/
public class Level2MagicEquipment implements MagicEquipment{
@Override
public void showMagicEquipment() {
    System.out.println("等级2魔法师使用了神器法杖和羽翼披风。");
}
}

/**

等级2战士装备
*/
public class Level2WarriorEquipment implements WarriorEquipment{
@Override
public void showWarriorEquipment() {
    System.out.println("等级2战士使用了龙牙剑和龙骨铠甲。");
}
}

接下来,我们定义一个抽象工厂类,该类提供了两个创建不同职业装备的方法:

/**

抽象工厂类
*/
public abstract class AbstractEquipmentFactory {
/**

创建魔法师装备
*/
public abstract MagicEquipment createMagicEquipment();

/**

创建战士装备
*/
public abstract WarriorEquipment createWarriorEquipment();
}

然后,我们需要创建两个具体工厂类,分别实现抽象工厂类中的方法用于创建魔法装备和战士装备:

/**

等级1工厂类
*/
public class Level1EquipmentFactory extends AbstractEquipmentFactory{
@Override
public MagicEquipment createMagicEquipment() {
    return new Level1MagicEquipment();
}

@Override
public WarriorEquipment createWarriorEquipment() {
    return new Level1WarriorEquipment();
}
}

/**

等级2工厂类
*/
public class Level2EquipmentFactory extends AbstractEquipmentFactory{
@Override
public MagicEquipment createMagicEquipment() {
    return new Level2MagicEquipment();
}

@Override
public WarriorEquipment createWarriorEquipment() {
    return new Level2WarriorEquipment();
}
}

最后,客户端可以通过调用具体工厂类创建对应的职业和等级的装备:

public class Client {
    public static void main(String[] args) {
        // 选择等级1工厂
        AbstractEquipmentFactory level1Factory = new Level1EquipmentFactory();
        // 魔法师使用等级1魔法装备
        MagicEquipment level1MagicEquipment = level1Factory.createMagicEquipment();
        level1MagicEquipment.showMagicEquipment();
        // 战士使用等级1战士装备
        WarriorEquipment level1WarriorEquipment = level1Factory.createWarriorEquipment();
        level1WarriorEquipment.showWarriorEquipment();

   	 // 选择等级2工厂
    	AbstractEquipmentFactory level2Factory = new Level2EquipmentFactory();
   	 // 魔法师使用等级2魔法装备
    	MagicEquipment level2MagicEquipment = level2Factory.createMagicEquipment();
   	 level2MagicEquipment.showMagicEquipment();
   	 // 战士使用等级2战士装备
    	WarriorEquipment level2WarriorEquipment = level2Factory.createWarriorEquipment();
   	 level2WarriorEquipment.showWarriorEquipment();
	}

}

在以上示例中,抽象工厂模式通过定义抽象产品族和工厂类实现了职业和装备对象的创建过程,并且可以动态切换装备等级结构

3.单例模式

单例模式 (Singleton Pattern)是一种创建型设计模式,它能够确保一个类只有一个实例,并且提供了一个全局访问点。

在单例模式中,类的构造函数需要声明为私有,以防止外部代码创建该类的实例。同时,类内部需要保存一个静态实例变量,用于存储该类的唯一实例。对外提供一个公共的静态方法,用于获取该实例,如果该实例不存在,则创建一个并返回,否则直接返回已有的实例。

单例模式是一种非常实用的设计模式,在很多场景下都能够起到重要的作用,但是也需要在使用时注意其局限性,结合实际情况进行判断和运用。


单例模式的特点:

  • 单例模式只允许一个实例存在,可以避免多实例所带来的资源浪费和实例不一致性问题。
  • 单例模式提供了一个全局唯一的访问点,使得外部应用可以方便地访问该实例。
  • 单例模式可以控制实例化的时机,延迟实例化可以提高应用程序的启动速度。
  • 单例模式在某些情况下可能会造成单点故障,因此需要注意线程安全和并发访问问题。

单例模式的优点:

  • 单例模式可以保证在一个jvm中,该实例只有一个存在。这样就可以避免由于一些资源的过多占用而导致的内存浪费问题。

  • 单例模式提供了一种全局访问方式,可以很方便地进行数据共享和通信。

  • 单例模式可以避免对资源的多重占用,例如对文件操作、数据库连接池等资源的管理都可以通过单例模式来解决,避免同时写操作。

  • 单例模式可以减少系统的开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决。

单例模式的缺点:

  • 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为它既充当了工厂角色,提供了工厂方法,又充当了产品角色,包含了一些公共业务方法,将一些没有关系的逻辑融合在一个类中。

  • 单例模式对测试的支持不太友好,因为可能会造成单元测试时的“依赖其他对象”问题。如果单例没有完成,那么单元测试可能会失败。

  • 单例模式在某些情况下会造成全局状态的不利影响,从而使得程序的调试过程变得困难。此外,单例模式也无法在多线程并发访问中有效地保护数据。在一些特殊情况下,多个实例共同操作一个状态时,这种单例还可能会导致冲突。


示例:

假设我们要实现一个日志打印工具类,由于日志文件是共享资源,在同一时间只允许一个线程写入,为了保证写入的正确性,需要将日志打印工具类实现为单例模式。

首先,我们需要将日志打印工具类的构造函数声明为私有:

public class Logger {
    private Logger() {
        // 私有化构造函数,防止外部类直接创建实例
    }
}

然后,在该类中声明一个静态私有成员变量instance,用于存储唯一实例:

public class Logger {
    private static Logger instance;
    private Logger() {
        // 私有化构造函数,防止外部类直接创建实例
    }
}

接下来,我们需要提供一个公共静态方法getInstance(),用于获取该实例。在该方法中判断实例是否已经存在,如果不存在则创建一个新的实例并返回,否则直接返回已有的实例:

public class Logger {
    private static Logger instance;
    private Logger() {
        // 私有化构造函数,防止外部类直接创建实例
    }
    
    public static Logger getInstance() {
        if (instance == null) {
            instance = new Logger();
        }
        return instance;
    }
}

最后,我们可以在该类中添加一个输出日志的方法log():

public class Logger {
    private static Logger instance;
    private Logger() {
        // 私有化构造函数,防止外部类直接创建实例
    }

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

    public void log(String message) {
        System.out.println(message);
    }

}

现在,我们就可以直接调用该类的getInstance()方法获取唯一实例,并通过该实例调用log()方法输出日志信息了:

Logger logger1 = Logger.getInstance();
logger1.log("Write log message 1.");

Logger logger2 = Logger.getInstance();
logger2.log("Write log message 2.");

在以上示例中,单例模式确保了只有一个Logger实例存在,并提供了一个全局的访问点getInstance(),来获取该实例,避免了多实例所带来的资源浪费和实例不一致性问题。

4.建造者模式

建造者模式 (Builder Pattern)是一种创建型设计模式,它可以将一个复杂对象的构建过程与其表示相分离,使得同样的构建过程可以生成不同的表示。换句话说,使用建造者模式,我们可以向客户端隐藏对象的具体构建过程,使得用户无需知道对象的内部结构即可创建出复杂的对象。

建造者模式在构建复杂对象时非常有用,能够灵活地控制对象的构建过程,并且简化了客户端的代码。


建造者模式由以下几部分组成:

  • 产品(Product):需要被构建的复杂对象,通常包含多个组成部分。

  • 抽象建造者(Builder):抽象出对象的各个部分的构建方法,以及一个返回构建完成后的产品的方法。

  • 具体建造者(Concrete Builder):实现抽象建造者所定义的所有方法,并且返回一个最终的产品。

  • 指挥者(Director):引用一个建造者对象,它知道建造者的具体顺序和过程,控制建造过程最终完成后构建出产品。


建造者模式的优点:

  • 隐藏复杂对象的构建过程,使得客户端代码可以不关心具体的构建细节。

  • 将构建过程与对象的表示相分离,使得同样的构建过程可以生成不同的对象表示。

  • 可以方便地改变产品的内部表示,对客户端代码没有影响。

  • 可以更加精细地控制构建过程,创建对象时可以灵活选择需要的组件和属性。

  • 增强了代码的可读性和可维护性,规范了对象构建的流程。


建造者模式的缺点:

  • 由于必须额外定义多个类(包括产品类、抽象建造者类和具体建造者类),因此可能导致代码量增加。

  • 构造函数参数过多时,会导致建造者类中的方法过于繁琐,影响程序可读性。

  • 如果产品类的属性发生变化,那么需要同时修改Builder类和Director类,可能会导致维护成本上升。


示例:

下面以一个示例来展开学习建造者模式:

假设我们要创建一个电脑类(Computer),它由CPU、内存、硬盘等多个部分组成,其中CPU有品牌和价格两个属性,内存有容量和价格两个属性,硬盘有大小和价格两个属性。现在我们要使用建造者模式构建这个电脑类。

首先,我们需要定义一个产品类Computer,该类由多个组成部分构成,例如CPU、内存、硬盘等:

public class Computer {
    private String cpuBrand;
    private double cpuPrice;
    private int memorySize;
    private double memoryPrice;
    private int diskSize;
    private double diskPrice;
    

    public void setCpu(String brand, double price) {
        this.cpuBrand = brand;
        this.cpuPrice = price;
    }
    
    public void setMemory(int size, double price) {
        this.memorySize = size;
        this.memoryPrice = price;
    }
    
    public void setDisk(int size, double price) {
        this.diskSize = size;
        this.diskPrice = price;
    }
    
    // 其他方法

}

然后,我们需要定义一个抽象的建造者类Builder,它包含了创建CPU、创建内存和创建硬盘等多个方法,以及一个返回构建完成后的电脑对象的方法getComputer()。这里需要注意的是,由于我们使用返回值的方式来构建对象,因此需要将Builder类设计为链式调用的形式:

public abstract class Builder {
    public abstract Builder createCpu(String brand, double price);
    public abstract Builder createMemory(int size, double price);
    public abstract Builder createDisk(int size, double price);
    public abstract Computer getComputer();
}

接下来,我们需要实现具体的建造者类ConcreteBuilder,该类继承自抽象建造者类Builder,并且实现了抽象建造者中所定义的所有方法。在每个创建方法中,我们需要调用产品类Computer中所定义的对应方法来完成相应部分的创建:

public class ConcreteBuilder extends Builder {
    private Computer computer = new Computer();

    @Override
    public Builder createCpu(String brand, double price) {
        computer.setCpu(brand, price);
        return this;
    }
    
    @Override
    public Builder createMemory(int size, double price) {
        computer.setMemory(size, price);
        return this;
    }
    
    @Override
    public Builder createDisk(int size, double price) {
        computer.setDisk(size, price);
        return this;
    }
    
    @Override
    public Computer getComputer() {
        return computer;
    }

}

最后,我们需要定义一个指挥者类Director,它引用一个建造者对象,控制建造过程最终完成后构建出电脑对象。在Director类中,我们可以定义一个构建电脑的方法buildComputer(),该方法通过一系列的建造者方法来构建电脑对象:

public class Director {
    private Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }
    
    public void buildComputer() {
        builder.createCpu("Intel", 3000.00)
            .createMemory(8, 800.00)
            .createDisk(512, 1500.00);
    }

}

现在,我们就可以在客户端代码中通过以下方式来构建一个电脑对象:

Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
director.buildComputer();
Computer computer = builder.getComputer();

在以上示例中,建造者模式将电脑类的构建过程与其具体表示相分离,使得用户无需知道对象内部结构即可创建复杂的电脑对象。这样做的好处是,可以很方便地复用构建逻辑,并且灵活地改变对象的内部表示方式。

5.原型模式

原型模式(Prototype Pattern)是一种对象创建型设计模式,它可以通过复制或克隆已有对象来创建新的对象,而不是通过调用构造函数来创建。原型模式不仅能够提高对象的创建效率,同时还可以隐藏对象的创建细节,使得客户端代码可以直接通过已有对象来创建新对象。

原型模式适用于创建复杂对象的场景,特别是当对象的创建过程比较耗时或者属性较多时,使用原型模式可以提高代码效率和可维护性。但是,在应用原型模式时需要注意克隆过程中可能会引发对象共享、垃圾回收等问题,需要对这些问题做好处理。


建造者模式的主要特点:

  • 将对象创建的步骤分解为多个独立的部分,从而可以更加灵活地组合和配置这些部分,并且可以复用相同的构建逻辑来构建不同的对象。

  • 可以对相同的构建过程进行不同的表达和表示,从而生成不同的对象形式和结构。

  • 可以将复杂对象的构建步骤屏蔽在背后,使客户端代码与对象的内部结构解耦,让客户端更专注于完成自己的业务逻辑。

  • 可以实现高阶构建功能,例如动态地根据实际需要来调整构建顺序或添加/删除构建步骤。

  • 可以通过指挥者类(Director)来控制产品的构建过程,并且可以灵活地改变构建顺序和步骤。

  • 可以提供一个统一的接口来构建不同种类的产品,从而提高代码的可读性和可维护性。

  • 可以通过建造者模式来实现构建复杂对象的线程安全,因为建造者模式中每个对象都是独立的,不会产生多个线程之间的竞争和冲突。

总的来说,建造者模式是一种非常有用和灵活的设计模式,它能够帮助我们更好地控制对象的创建过程,提高代码的可维护性和可扩展性,同时减少了客户端代码中的冗余代码。


原型模式的优点:

  • 可以更加方便地创建复杂对象,提高对象创建效率。

  • 可以动态添加或删除原型对象,使得客户端代码不需要改变。

  • 隐藏了对象的创建细节,客户端代码只需要关心如何复制或克隆对象。


原型模式的缺点:

原型模式需要对每个对象分别进行克隆,可能会消耗大量内存和处理时间。

克隆过程中需要处理深复制和浅复制等问题,可能会增加代码的复杂度。


示例:

在原型模式中,需要定义一个抽象的原型类(Prototype),该类包含了一个克隆自身的方法clone(),以及一个用于设置或获取原型对象属性的方法。然后,具体的原型类(ConcretePrototype)继承自抽象原型类,并实现其clone()方法。在使用原型模式时,我们可以先创建一个原型对象,然后通过调用它的clone()方法来创建新的对象,新对象的属性与原型对象相同。

以下是一个简单的示例代码:

// 定义抽象原型类
public abstract class Prototype implements Cloneable {
    public abstract void setAttr(String attr);
    public abstract String getAttr();
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

// 定义具体原型类
public class ConcretePrototype extends Prototype {
    private String attr;
    public ConcretePrototype(String attr) {
        this.attr = attr;
    }
    public void setAttr(String attr) {
        this.attr = attr;
    }
    public String getAttr() {
        return attr;
    }
}

// 测试代码
public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        ConcretePrototype prototype = new ConcretePrototype("prototype");
        ConcretePrototype clone = (ConcretePrototype) prototype.clone();
        System.out.println(clone.getAttr()); // 输出 "prototype"
    }
}

在以上示例中,首先定义了一个抽象原型类Prototype,该类包含了两个抽象方法setAttr()和getAttr(),用于设置或获取对象属性。然后,定义了具体原型类ConcretePrototype,该类继承自抽象原型类Prototype,并实现了其抽象方法。

在测试代码中,首先创建了一个原型对象prototype,然后通过调用它的clone()方法来创建新对象clone。由于克隆方法是从Object类继承而来的,因此需要将返回值强制转换为具体原型类型。最后输出新对象的属性值,可以看到与原型对象相同。

6.适配器模式

适配器模式(Adapter Pattern)是一种结构型设计模式,它通过将一个类的接口转换为另一个客户端所期望的接口来使得原本不兼容的类可以协同工作。适配器模式的核心思想就是改变已有的接口,使其与客户端所需的接口匹配。

适配器模式是一种非常灵活的设计模式,它可以方便地对已有的系统进行封装和重用,但同时也带来了一定的复杂性和额外的工作量。在应用适配器模式时,我们需要根据具体的场景评估其优缺点,从而做出最佳的设计和实现决策。


适配器模式通常由以下几个部分组成:

  • 目标(Target)接口:客户端所期望的接口,它定义了客户端代码所能调用的方法。

  • 源(Adaptee)类:已经存在的、需要被适配的类,其中包含了客户端代码所不能直接调用的一些方法。

  • 适配器(Adapter)类:将源类中的方法适配到目标接口中的方法,让客户端可以像使用目标接口一样使用源类。


适配器模式的特点:

  • 通过将一个类的接口转换为另一个客户端所期望的接口,使得原本不兼容的类可以协同工作。

  • 改变已有的接口,使其与客户端所需的接口匹配。

  • 可以对已有的系统进行封装,提供更易于使用的接口。

  • 适配器可以重复使用,提高了代码的复用性和可维护性。


适配器模式的优点:

  • 可以让客户端无需关心具体的实现细节,只需要关注目标接口,从而降低了客户端代码的复杂度。

  • 可以增加代码的可扩展性,当需要添加新的功能时,只需要添加相应的适配器即可。

  • 可以提高代码的复用性,一个适配器可以被多个客户端使用,减少了重复代码的编写。

  • 可以隔离客户端和源类之间的耦合关系,当源类发生变化时,只需要修改适配器即可,不会影响到客户端。


适配器模式的缺点:

  • 适配器模式增加了系统的复杂度,引入了额外的类/接口,可能会造成代码更难阅读和维护。

  • 适配器模式需要花费额外的工作量来进行设计和实现。

  • 如果适配器的设计不够好,可能会导致系统的性能下降,因为需要增加额外的方法调用或逻辑判断。

  • 适配器模式有时并不能完全避免修改原有代码的情况,因为某些情况下需要对源类进行一定程度上的修改才能完成适配过程。


示例:

下面以一个示例来展开学习适配器模式:

假设我们有一个音频播放器程序,现在需要支持播放MP3格式的音频文件,但是原本只支持播放WAV格式的音频文件。现在我们就要使用适配器模式来适配这两种不同的音频文件格式。

首先,我们需要定义一个目标接口AudioPlayer,该接口定义了播放MP3格式音频文件的方法playMp3():

public interface AudioPlayer {
    void playMp3(String fileName);
}

然后,我们需要定义一个源类WavPlayer,该类用于播放WAV格式音频文件,其中包含了客户端代码所不能直接调用的方法playWav():

public class WavPlayer {
    public void playWav(String fileName) {
        System.out.println("Playing WAV file: " + fileName);
    }
}

如果我们将WavPlayer类直接提供给客户端使用,那么客户端需要调用的方法是playWav()而不是playMp3(),这与客户端所期望的接口是不兼容的。因此,我们需要定义一个适配器类,将WavPlayer的方法适配到AudioPlayer接口中:

public class WavToMp3Adapter implements AudioPlayer {
    private WavPlayer wavPlayer;

    public WavToMp3Adapter(WavPlayer wavPlayer) {
        this.wavPlayer = wavPlayer;
    }
    
    @Override
    public void playMp3(String fileName) {
        // 将MP3文件转换为WAV文件,并播放
        String wavFileName = convertMp3ToWav(fileName);
        wavPlayer.playWav(wavFileName);
    }
    
    // 将MP3文件转换为WAV文件
    private String convertMp3ToWav(String mp3FileName) {
        System.out.println("Converting MP3 to WAV: " + mp3FileName);
        // 省略具体的转换逻辑
        return mp3FileName.replace(".mp3", ".wav");
    }
}

在上面的适配器类WavToMp3Adapter中,我们通过构造函数将WavPlayer对象注入到适配器中,并且实现了目标接口AudioPlayer中的方法playMp3()。在playMp3()方法中,我们将MP3文件转换为WAV文件,并调用WavPlayer类中的方法playWav()来播放音频。

最后,我们可以在客户端代码中通过以下方式来播放MP3格式的音频文件:

AudioPlayer player = new WavToMp3Adapter(new WavPlayer());
player.playMp3("song.mp3");

在以上示例中,客户端需要播放MP3格式的音频文件,但是原本只支持播放WAV格式的音频文件,因此我们使用适配器模式将WavPlayer类适配到了AudioPlayer接口中,使得客户端无需关心WAV与MP3格式之间的转换和适配问题,并且可以像调用目标接口一样来调用WavPlayer中的方法。

总之,适配器模式使得原本不兼容的类可以协同工作,从而提高了代码的复用性和可维护性。它减少了客户端代码中的冗余逻辑,并且让我们可以更加灵活地控制外部类库的使用。不过,在使用适配器模式时需要注意接口的设计,尽可能地让接口具备普适性和可扩展性,从而避免过度耦合和重构麻烦。

7.桥接模式

桥接模式(Bridge Pattern)是一种结构型设计模式,它将抽象部分与实现部分分离,以便二者可以独立变化,从而提高系统的灵活性和可扩展性。通俗点说,桥接模式就是让抽象和实现解耦,通过封装两者之间的关系来达到针对接口编程、而不针对具体实现的目的。

桥接模式是一种非常有用的设计模式,它能够将抽象部分和实现部分分离开来,提高系统的灵活性和可扩展性。


桥接模式主要由以下几个角色组成:

  • 抽象部分(Abstraction):定义了抽象部分的接口,并维护一个指向实现部分的引用。

  • 具体抽象部分(Refined Abstraction):扩展抽象部分接口,增加新的方法或属性。

  • 实现部分(Implementor):定义了实现部分的接口,但不负责具体的实现。

  • 具体实现部分(Concrete Implementor):实现部分接口的具体实现。


桥接模式的特点:

  • 桥接模式将抽象部分和实现部分分离开来,使得它们可以独立地变化和扩展,具有很好的灵活性和可扩展性。
  • 桥接模式通过在抽象部分中维护对实现部分的引用,使得可以在运行时动态地选择不同的实现部分,从而实现更加灵活的设计。
  • 桥接模式提倡面向接口编程,而不是面向具体实现编程,能够避免过度依赖具体实现,从而提高代码的可读性和可维护性。

桥接模式的优点:

  • 分离抽象和实现部分,使得系统更容易扩展。
  • 隐藏了实现细节,使得系统更加稳定。
  • 面向接口编程,降低了系统的耦合度,提高了代码的可读性和可维护性。

桥接模式的缺点:

  • 增加了系统的复杂度,需要更多的类和接口来实现这种分离。
  • 在设计时需要仔细考虑抽象部分和实现部分之间的关系,否则可能会导致系统设计不当。

示例:

下面通过一个简单的例子来介绍桥接模式的原理:

假设我们正在开发一个多媒体播放器程序,该程序需要支持播放不同格式的音频和视频文件。其中,抽象部分是多媒体播放器,实现部分是文件格式。下面我们来分别定义它们的接口:

// 抽象部分的接口
public abstract class MediaPlayer {
    protected MediaFile mediaFile;
    public MediaPlayer(MediaFile mediaFile) {
        this.mediaFile = mediaFile;
    }
    public abstract void play();
}

// 实现部分的接口
public interface MediaFile {
    void decode(String fileName);
}

在上面的代码中,MediaPlayer是抽象部分,定义了播放音频/视频文件的方法play(),并维护了一个指向实现部分MediaFile的引用。MediaFile是实现部分,它定义了解码媒体文件的接口。

接下来,我们定义不同类型的媒体文件(即具体实现部分):

// 具体实现部分:MP3音频文件
public class Mp3File implements MediaFile {
    @Override
    public void decode(String fileName) {
        System.out.println("Decoding MP3 file: " + fileName);
    }
}

// 具体实现部分:WAV音频文件
public class WavFile implements MediaFile {
    @Override
    public void decode(String fileName) {
        System.out.println("Decoding WAV file: " + fileName);
    }
}

// 具体实现部分:MP4视频文件
public class Mp4File implements MediaFile {
    @Override
    public void decode(String fileName) {
        System.out.println("Decoding MP4 file: " + fileName);
    }
}

// 具体实现部分:MKV视频文件
public class MkvFile implements MediaFile {
    @Override
    public void decode(String fileName) {
        System.out.println("Decoding MKV file: " + fileName);
    }
}

在上面的代码中,不同的媒体文件实现了MediaFile接口,并实现了其中的解码方法decode()。

最后,我们可以定义具体抽象部分MediaPlayer的不同子类来支持不同类型的媒体文件:

// 播放MP3音频文件
public class Mp3Player extends MediaPlayer {
    public Mp3Player(MediaFile mediaFile) {
        super(mediaFile);
    }
    @Override
    public void play() {
        mediaFile.decode("song.mp3");
        System.out.println("Playing MP3 file...");
    }
}

// 播放WAV音频文件
public class WavPlayer extends MediaPlayer {
    public WavPlayer(MediaFile mediaFile) {
        super(mediaFile);
    }
    @Override
    public void play() {
        mediaFile.decode("song.wav");
        System.out.println("Playing WAV file...");
    }
}

// 播放MP4视频文件
public class Mp4Player extends MediaPlayer {
    public Mp4Player(MediaFile mediaFile) {
        super(mediaFile);
    }
    @Override
    public void play() {
        mediaFile.decode("movie.mp4");
        System.out.println("Playing MP4 file...");
    }
}

// 播放MKV视频文件
public class MkvPlayer extends MediaPlayer {
    public MkvPlayer(MediaFile mediaFile) {
        super(mediaFile);
    }
    @Override
    public void play() {
        mediaFile.decode("movie.mkv");
        System.out.println("Playing MKV file...");
    }
}

在上面的代码中,不同类型的MediaPlayer通过传入不同类型的MediaFile来调用其对应媒体文件的解码方法,并实现了自身的播放方法play()。

最后,我们可以在客户端代码中通过以下方式来播放不同类型的媒体文件:

MediaPlayer mp3Player = new Mp3Player(new Mp3File());
mp3Player.play();

MediaPlayer wavPlayer = new WavPlayer(new WavFile());
wavPlayer.play();

MediaPlayer mp4Player = new Mp4Player(new Mp4File());
mp4Player.play();

MediaPlayer mkvPlayer = new MkvPlayer(new MkvFile());
mkvPlayer.play();

在以上示例中,我们首先通过传入不同类型的MediaFile来实例化不同类型的MediaPlayer,然后调用其播放方法play()。

总之,桥接模式通过将抽象部分和实现部分分离,使得它们可以独立演化,从而提高了系统的灵活性和可扩展性。通过适当地设计和使用抽象类和接口,我们可以轻松地扩展系统中的功能,并且降低了耦合度,提高了代码的可读性和可维护性。

8.过滤器模式

过滤器模式(Filter Pattern)是一种结构型设计模式,它可以将一个对象集合按照特定的条件(即过滤器)进行筛选,从而得到想要的结果集合。该模式主要由过滤器接口、具体过滤器、数据源和客户端组成,它能够很好地支持复杂查询、排序和过滤等操作。


以下是过滤器模式的相关角色:

  • 过滤器接口(Filter):定义了一个用于筛选数据的方法,所有具体过滤器都必须实现该接口。

  • 具体过滤器(Concrete Filter):实现了过滤器接口的具体类,负责对数据进行筛选和过滤。

  • 数据源(Data Source):表示包含大量数据的对象集合,由客户端来传递给具体过滤器进行筛选。

  • 客户端(Client):负责创建和配置具体过滤器,然后调用其筛选方法对数据进行处理,并最终得到筛选后的结果。


过滤器模式的特点:

  • 灵活性好:过滤器模式可以根据需要动态组合不同的过滤器,从而实现对数据的多重筛选和排序等操作。

  • 可扩展性好:通过新增具体过滤器类,可以方便地扩展过滤器模式。

  • 条件复杂度高:过滤器模式适用于对条件复杂、查询条件多变的数据进行筛选和过滤。

  • 性能较低:由于每个过滤器都要处理一遍数据,所以在数据量较大时,可能会影响系统性能。


过滤器模式的优点:

  • 可以提高系统的灵活性和可维护性,降低系统的复杂度。

  • 可以实现对数据的多重筛选和排序等操作,满足不同的需求。

  • 可以根据需要动态组合不同的过滤器,具有较好的扩展性。

  • 可以将筛选算法和数据源解耦,提高系统的模块化程度。


过滤器模式的缺点:

  • 在数据量较大时,由于每个过滤器都要处理一遍数据,可能会影响系统性能。

  • 过多的过滤器可能会让程序变得复杂化,增加代码的维护难度。

  • 过多的具体过滤器实现类可能会让程序变得庞大化,增加系统的复杂度。

综上所述,过滤器模式是一种非常有用的设计模式,它可以提高系统的灵活性和可维护性,降低系统的复杂度。但是,在大数据量的情况下,可能会影响系统的性能。因此,在使用过滤器模式时,需要根据实际情况进行权衡和选择。


示例:

假设我们要开发一个商品管理系统,需要对商品进行筛选和过滤,以便查询价格在一定范围内的商品。该系统中,商品对象包含名称、价格、颜色等属性,数据源是一个包含多个商品对象的集合。为了实现对商品的筛选和过滤,我们可以定义一个过滤器接口Filter,并且在其中声明一个用于筛选商品的方法filter():

// 过滤器接口
public interface Filter {
    List<Product> filter(List<Product> productList);
}

接下来,我们可以定义两个具体过滤器分别实现该接口,以支持不同的筛选算法:

// 筛选价格大于等于minPrice的商品
public class MinPriceFilter implements Filter {
    private double minPrice;
    public MinPriceFilter(double minPrice) {
        this.minPrice = minPrice;
    }
    @Override
    public List<Product> filter(List<Product> productList) {
        List<Product> result = new ArrayList<>();
        for (Product product : productList) {
            if (product.getPrice() >= minPrice) {
                result.add(product);
            }
        }
        return result;
    }
}

// 筛选价格小于等于maxPrice的商品
public class MaxPriceFilter implements Filter {
    private double maxPrice;
    public MaxPriceFilter(double maxPrice) {
        this.maxPrice = maxPrice;
    }
    @Override
    public List<Product> filter(List<Product> productList) {
        List<Product> result = new ArrayList<>();
        for (Product product : productList) {
            if (product.getPrice() <= maxPrice) {
                result.add(product);
            }
        }
        return result;
    }
}

在上面的代码中,MinPriceFilter和MaxPriceFilter分别实现了Filter接口的filter()方法,用于筛选价格大于等于minPrice或价格小于等于maxPrice的商品。它们都是具体过滤器,负责实现筛选算法并返回筛选后的商品列表。

接下来,我们可以定义一个Product类用于表示商品对象:

// 商品类
public class Product {
    private String name;
    private double price;
    private String color;
    public Product(String name, double price, String color) {
        this.name = name;
        this.price = price;
        this.color = color;
    }
    // getters and setters
}

在上面的代码中,Product类包含名称、价格和颜色等属性,并提供了相应的getter和setter方法。

最后,我们可以在客户端代码中使用具体过滤器对商品进行筛选和过滤:

List<Product> products = new ArrayList<>();
products.add(new Product("iPhone", 5999.99, "White"));
products.add(new Product("MacBook Pro", 14888.00, "Space Gray"));
products.add(new Product("iPad", 3299.00, "Silver"));
products.add(new Product("AirPods Max", 6499.00, "Sky Blue"));
Filter minPriceFilter = new MinPriceFilter(5000.00);
Filter maxPriceFilter = new MaxPriceFilter(10000.00);
List<Product> filteredProducts = minPriceFilter.filter(maxPriceFilter.filter(products));
for (Product product : filteredProducts) {
    System.out.println(product.getName() + " - " + product.getPrice() + " - " + product.getColor());
}

在以上示例中,我们首先创建了一个包含多个商品对象的集合,然后通过具体过滤器MinPriceFilter和MaxPriceFilter对商品进行筛选和过滤,最终得到了价格在5000.00~10000.00之间的商品列表。

总之,过滤器模式是一种非常有用的设计模式,它可以将一个对象集合按照特定条件进行筛选,从而得到想要的结果集合。通过定义不同的具体过滤器,我们可以方便地支持不同的筛选算法,并且可以根据需求动态地组合这些过滤器。在实际的软件开发中,过滤器模式被广泛应用于复杂查询、排序和过滤等操作,它能够提高系统的灵活性和可维护性,降低系统的复杂度。

9.组合模式

组合模式(Composite Pattern)是一种结构型设计模式,它将对象组合成树形结构以表示“整体/部分”层次结构。通过这种方式,客户端可以将单个对象和组合对象统一看待,从而简化了客户端的代码,并且具有良好的扩展性和透明性。


组合模式由以下几个核心角色组成:

  • 抽象组件(Component):定义所有对象共有的方法和属性,通常包括添加、删除、获取子节点等方法。

  • 叶子节点(Leaf):表示树中无子节点的对象,实现抽象组件接口。

  • 组合节点(Composite):表示树中有子节点的对象,实现抽象组件接口,并在内部管理子节点。

在组合模式中,通常采用递归的方式来遍历整个树形结构。客户端可以调用根节点的方法,然后该方法会递归地调用子节点的方法,从而实现整个树形结构的遍历。


组合模式的优点:

  • 简化客户端代码:客户端可以将单个对象和组合对象看作同一类对象,并使用相同的方法进行操作,从而简化了客户端的代码。

  • 透明性好:由于组合对象和叶子对象实现了相同的接口,因此客户端不需要区分它们,只需要调用相应的方法即可。

  • 易于扩展:可以方便地添加新的组件,只需要继承抽象组件即可。


组合模式的缺点:

  • 难以限制组合中的类型:由于所有组件都实现了相同的接口,因此难以限制组合中的类型。

  • 可能会导致设计过于抽象化:过度使用组合模式可能会导致设计过于抽象化,从而使代码变得难以理解和维护。


示例:

下面是一个简单的Java代码示例,演示了如何使用组合模式来构建树形结构:

// 定义抽象组件
public abstract class Component {
    protected String name;

    public Component(String name) {
        this.name = name;
    }
    
    // 添加子节点
    public abstract void add(Component c);
    
    // 移除子节点
    public abstract void remove(Component c);
    
    // 遍历子节点
    public abstract void display(int depth);

}

// 定义叶子节点
public class Leaf extends Component {
    public Leaf(String name) {
        super(name);
    }

    // 添加子节点
    public void add(Component c) {
        System.out.println("Leaf不能添加子节点");
    }
    
    // 移除子节点
    public void remove(Component c) {
        System.out.println("Leaf不能移除子节点");
    }
    
    // 遍历子节点
    public void display(int depth) {
        System.out.println("-".repeat(depth) + name);
    }

}

// 定义组合节点
public class Composite extends Component {
    private List<Component> children = new ArrayList<>();

    public Composite(String name) {
        super(name);
    }
    
    // 添加子节点
    public void add(Component c) {
        children.add(c);
    }
    
    // 移除子节点
    public void remove(Component c) {
        children.remove(c);
    }
    
    // 遍历子节点
    public void display(int depth) {
        System.out.println("-".repeat(depth) + name);
    
        for (Component c : children) {
            c.display(depth + 2);
        }
    }

}

// 测试代码
public class Test {
    public static void main(String[] args) {
        Component root = new Composite("A");
        root.add(new Leaf("A-1"));
        root.add(new Leaf("A-2"));

        Component b = new Composite("B");
        b.add(new Leaf("B-1"));
        b.add(new Leaf("B-2"));
    
        Component c = new Composite("C");
        c.add(new Leaf("C-1"));
        c.add(new Leaf("C-2"));
    
        b.add(c);
        root.add(b);
    
        root.display(0);
    }

}

这个代码示例中,我们定义了一个抽象组件(Component)类,它包含了一些共有方法和属性,例如添加子节点、移除子节点、遍历子节点等。然后我们定义了两个具体的子类叶子节点(Leaf)和组合节点(Composite),它们分别表示树形结构中的叶子节点和具有子节点的节点。

在测试代码中,我们创建了一个根节点A,并向它添加了两个叶子节点(A-1和A-2)和一个组合节点B。节点B又包含了两个叶子节点(B-1和B-2)和一个组合节点C,节点C包含了两个叶子节点(C-1和C-2)。最后我们遍历整个树形结构,并将结果输出到控制台。

这个示例展示了如何使用组合模式来构建树形结构,并且通过递归遍历整个树形结构。通过组合模式,我们可以将单个对象和组合对象看作同一类对象,并使用相同的方法进行操作,从而简化了客户端的代码。

10.装饰器模式

装饰器模式(Decorator Pattern)是一种结构型设计模式,可以动态地将责任附加到对象上,从而在不改变其行为的情况下扩展对象功能。它允许向一个现有对象添加新的功能,同时又不改变其结构。

总之,装饰器模式是一种非常有用的设计模式,可以帮助我们动态地扩展对象功能,并且具有良好的灵活性和可维护性。但是,在使用装饰器模式时,需要权衡好扩展和复杂度之间的关系,确保程序易于理解和维护。


装饰器模式由以下几个角色组成:

  • 抽象组件(Component):定义了具体组件和装饰器的共同接口,可以是抽象类或接口。

  • 具体组件(ConcreteComponent):实现了抽象组件接口,并且是被装饰的原始对象。

  • 抽象装饰器(Decorator):继承了抽象组件,并且内部包含一个指向抽象组件的引用,可以通过调用这个引用来实现对具体组件的操作,并且可以动态地增加新的装饰器。

  • 具体装饰器(ConcreteDecorator):继承了抽象装饰器,并且可以在具体组件上添加新的行为或者改变现有行为。

在装饰器模式中,抽象装饰器和具体装饰器可以包含多个具体组件作为其成员,并且可以在运行时动态地将这些组件包裹起来,从而形成一个链式结构。客户端代码只需要使用最外层的装饰器对象即可,而不需要考虑内部组件的结构。


装饰器模式的优点:

  • 扩展性好:可以在不改变现有代码和类层次结构的情况下动态地添加新的行为或者修改现有行为。

  • 开闭原则:通过装饰器模式,扩展对象功能时只需要添加新的装饰器,而不需要修改已有代码,从而符合开闭原则。

  • 灵活性好:可以通过不同方式组合装饰器对象,形成不同的行为组合,从而具有很高的灵活性。

  • 可以避免类爆炸:使用装饰器模式可以将多个单一职责的类组合起来,避免了由于类之间的直接关系导致的类爆炸问题。


装饰器模式的缺点:

  • 需要创建许多小对象:每个装饰器对象都会增加一个额外的开销,当需要组合多个装饰器时,可能会产生大量的小对象。

  • 可能会导致设计变得复杂:过度使用装饰器模式可能会导致设计变得复杂,并且难以理解和维护。


示例:

下面是一个简单的 Java 代码示例,演示了如何使用装饰器模式实现对食品订单的计价:

// 抽象组件
public interface FoodOrder {
    double getPrice();
}

// 具体组件-汉堡
public class Hamburger implements FoodOrder {
    public double getPrice() {
        return 12.0;
    }
}

// 具体组件-薯条
public class FrenchFries implements FoodOrder {
    public double getPrice() {
        return 8.0;
    }
}

// 抽象装饰器
public abstract class OrderDecorator implements FoodOrder {
    protected FoodOrder foodOrder;

    public OrderDecorator(FoodOrder foodOrder) {
        this.foodOrder = foodOrder;
    }
    
    public double getPrice() {
        return foodOrder.getPrice();
    }

}

// 具体装饰器-加热
public class HeatingDecorator extends OrderDecorator {
    public HeatingDecorator(FoodOrder foodOrder) {
        super(foodOrder);
    }

    public double getPrice() {
        return super.getPrice() + 1.0;
    }

}

// 具体装饰器-加冰
public class IcedDecorator extends OrderDecorator {
    public IcedDecorator(FoodOrder foodOrder) {
        super(foodOrder);
    }

    public double getPrice() {
        return super.getPrice() + 0.5;
    }

}

// 测试代码
public class Test {
    public static void main(String[] args) {
        // 订购汉堡
        FoodOrder burgerOrder = new Hamburger();

        // 订购薯条并加热
        FoodOrder friesOrder = new HeatingDecorator(new FrenchFries());
    
        // 订购汉堡、薯条并加热
        FoodOrder mixedOrder = new HeatingDecorator(new Hamburger());
        mixedOrder = new HeatingDecorator(new FrenchFries());
    
        System.out.println("汉堡的价格为:" + burgerOrder.getPrice());
        System.out.println("薯条加热后的价格为:" + friesOrder.getPrice());
        System.out.println("汉堡、薯条加热后的价格为:" + mixedOrder.getPrice());
    }

}

在这个示例中,我们定义了抽象组件 FoodOrder 和两个具体组件 Hamburger 和 FrenchFries,它们分别表示食品订单里的汉堡和薯条。

然后我们定义了抽象装饰器 OrderDecorator,它也实现了 FoodOrder 接口,并在内部包含了一个指向抽象组件的引用。我们还定义了两个具体装饰器 HeatingDecorator 和 IcedDecorator,它们分别表示食品订单里的加热和加冰服务,并且都继承自 OrderDecorator。

在测试代码中,我们按照以下方式订购食品:

订购汉堡;
订购薯条并加热;
订购汉堡和薯条并加热。
每次订购食品时,我们可以通过装饰器来动态地添加额外的服务,从而改变其价格。最后,我们将价格输出到控制台。

这个示例展示了如何使用装饰器模式来动态地为对象添加新的行为,并且在不改变现有代码的情况下扩展其功能。

11.外观模式

外观模式(Facade Pattern)是一种结构型设计模式,它提供了一个简单的接口,隐藏了一系列复杂的子系统操作,使得客户端能够轻松地使用这些子系统。

在外观模式中,客户端只需要和外观对象进行交互,而不需要直接和子系统进行交互。外观对象可以根据客户端的需要,将多个子系统的操作组合成一个简单的接口,从而隐藏了子系统的复杂性。


外观模式由以下几个角色组成:

  • 外观(Facade):提供了一个简单的接口,用于访问底层的子系统,客户端通过这个接口来和子系统进行交互。

  • 子系统(SubSystem):实现了相关功能,并且被外观对象所封装。


外观模式的优点:

  • 简化了客户端的操作:客户端只需要和外观对象进行交互,不需要直接和子系统进行交互,从而可以减少客户端的代码量,降低了客户端的复杂度。

  • 提高了灵活性:由于外观对象封装了子系统的操作,因此可以更加灵活地修改子系统,而不会影响到客户端的操作。

  • 降低了耦合度:由于客户端只需要和外观对象进行交互,而不需要直接和子系统进行交互,因此可以降低客户端与子系统之间的耦合度。

  • 提高了系统的安全性:在外观对象中可以实现访问控制,从而提高了系统的安全性。


外观模式的缺点:

  • 可能会造成代码膨胀:如果外观对象过于庞大或者功能过于复杂,可能会导致代码膨胀,难以维护。

  • 不符合开闭原则:如果需要增加新的功能,可能需要修改外观对象的代码,从而违反了开闭原则。


示例:

下面是一个简单的 Java 代码示例,演示了如何使用外观模式来简化客户端的操作:

// 外观类
public class Computer {
    private CPU cpu;
    private Memory memory;
    private HardDrive hardDrive;

    public Computer() {
        this.cpu = new CPU();
        this.memory = new Memory();
        this.hardDrive = new HardDrive();
    }
    
    public void start() {
        cpu.freeze();
        memory.load(BOOT_ADDRESS, hardDrive.read(BOOT_SECTOR, SECTOR_SIZE));
        cpu.jump(BOOT_ADDRESS);
        cpu.execute();
    }

}

// 子系统类-处理器
public class CPU {
    public void freeze() { ... }
    public void jump(long position) { ... }
    public void execute() { ... }
}

// 子系统类-内存
public class Memory {
    public void load(long position, byte[] data) { ... }
}

// 子系统类-硬盘
public class HardDrive {
    public byte[] read(long lba, int size) { ... }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.start();
    }
}

在这个示例中,我们定义了一个外观类 Computer,它包含了三个子系统对象(CPU, Memory, HardDrive)的实例,并且封装了这些子系统的方法。外观类 Computer 中的 start 方法封装了一系列启动计算机所需的步骤,包括:冻结 CPU、从硬盘读取引导扇区、加载数据到内存并跳转到引导扇区开始执行程序。

客户端代码只需要创建一个 Computer 对象,调用它的 start 方法就可以启动计算机了。客户端不需要了解内部子系统的具体细节,也不需要直接和子系统进行交互。

这个示例演示了如何使用外观模式来简化客户端操作,客户端只需要跟外观类交互,而无需直接与底层子系统交互。

12.享元模式

享元模式(Flyweight Pattern)是一种结构型设计模式,它旨在减少对象的创建和内存消耗。享元模式通过共享对象来降低系统中对象的数量和内存占用。

在享元模式中,如果需要创建一个新的享元对象,首先需要判断当前是否已经存在相同的享元对象。如果已经存在,则直接返回已有的享元对象;如果不存在,则创建新的享元对象并将其缓存起来。通过这种方式,可以复用已经存在的对象,从而减少对象的创建和内存使用量。


享元模式由以下几个角色组成:

  • 抽象享元(Flyweight):定义了享元对象的接口,并且包含了所有可以被共享的操作。在 Java 中可以是一个接口或者抽象类。

  • 具体享元(Concrete Flyweight):实现了抽象享元中定义的接口,并且表示可以被共享的具体对象。

  • 享元工厂(Flyweight Factory):负责管理所有享元对象的创建和缓存,它的主要作用是确保共享的享元对象能够被正确地复用。


享元模式的优点:

  • 减少对象的创建:通过共享对象,减少了系统中对象的数量,降低了内存的使用量。

  • 提高系统的性能:由于减少了对象的创建和销毁,大大提高了系统的性能。

  • 提高代码的复用度:共享对象可以被多个客户端复用,从而提高了代码的复用度。

  • 提高系统的可维护性:由于共享对象可以被统一管理,可以更加方便地对系统进行维护和升级。


享元模式的缺点:

  • 对象的状态共享问题:由于享元对象需要被多个客户端共享,因此在使用时需要注意对对象状态的修改。如果一个客户端修改了共享对象的状态,可能会影响其他客户端的操作。

  • 系统复杂度增加:由于引入了享元工厂来管理对象的创建和缓存,系统的复杂度会增加,需要额外的开发时间和成本。


示例:

下面是一个简单的 Java 代码示例,演示了如何使用享元模式来缓存字符串:

// 抽象享元类
public interface Flyweight {
    public void operation(String extrinsicState);
}

// 具体享元类
public class ConcreteFlyweight implements Flyweight {
    private String intrinsicState;

    public ConcreteFlyweight(String intrinsicState) {
        this.intrinsicState = intrinsicState;
    }
    
    public void operation(String extrinsicState) {
        System.out.println("Intrinsic state: " + intrinsicState);
        System.out.println("Extrinsic state: " + extrinsicState);
    }

}

// 享元工厂类
public class FlyweightFactory {
    private Map<String, Flyweight> flyweights = new HashMap<>();

    public Flyweight getFlyweight(String key) {
        if (flyweights.containsKey(key)) {
            return flyweights.get(key);
        } else {
            Flyweight flyweight = new ConcreteFlyweight(key);
            flyweights.put(key, flyweight);
            return flyweight;
        }
    }

}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        FlyweightFactory factory = new FlyweightFactory();

        Flyweight flyweight1 = factory.getFlyweight("Hello");
        flyweight1.operation("World");
    
        Flyweight flyweight2 = factory.getFlyweight("Hello");
        flyweight2.operation("Flyweight");
    
        Flyweight flyweight3 = factory.getFlyweight("Java");
        flyweight3.operation("Design Pattern");
    }

}

在这个示例中,我们定义了一个抽象享元类 Flyweight,其中定义了一个 operation 方法用于展示共享对象的状态。然后我们定义了一个具体享元类 ConcreteFlyweight,它实现了抽象享元类中的方法,表示可以被共享的具体对象。

我们同时定义了一个享元工厂类 FlyweightFactory,负责管理所有享元对象的创建和缓存。在客户端代码中,我们创建了一个 FlyweightFactory 对象,并从中获取不同的享元对象。当获取一个新的享元对象时,我们首先从缓存中查找是否已经存在相同的对象,如果存在则返回已有的享元对象,否则创建新的享元对象并缓存起来。

享元模式通过共享对象来减少系统中对象的数量和内存占用,从而提高系统的性能和可维护性。通过合理地应用享元模式,可以大大提高系统的性能和扩展性。

13.代理模式

代理模式 (Proxy Pattern)是一种结构型设计模式,它为其他对象提供了一种代理以控制对这个对象的访问。代理对象可以在不影响原始对象的情况下增加一些额外的功能,例如缓存、安全性检查等。

在使用代理模式时需要根据具体的情况来判断其是否适合使用。如果需要控制对某个对象的访问,并且希望增加一些额外的功能,那么代理模式是一个不错的选择。但如果系统中的对象较少,或者需要快速响应客户端请求,那么代理模式可能会带来一定的负面影响。


代理模式中包含以下几种角色:

  • 抽象对象接口:定义了代理对象和原始对象的公共接口,客户端代码通过这个接口访问原始对象或代理对象。

  • 原始对象:实现了抽象对象接口,是代理对象所代表的真实对象。

  • 代理对象:实现了抽象对象接口并维护了对原始对象的引用,可以在客户端访问原始对象时执行某些额外操作,例如安全性检查、缓存等。

  • 代理模式主要解决了一些常见的问题,比如:

  • 远程代理:在客户端和服务端之间建立一个远程代理,使得客户端可以访问服务端提供的远程服务。

  • 虚拟代理:当需要创建开销很大的对象时,先使用一个代理对象代表这个对象,等到真正需要使用它的时候再创建。

  • 安全代理:在访问原始对象之前进行安全性检查,确保只有被授权的用户可以访问该对象。

  • 缓存代理:为了加快访问速度,将一些频繁访问的对象缓存起来,当客户端需要访问这个对象时,先从缓存中获取。


代理模式有以下优点:

代理对象和原始对象实现了相同的接口,因此对客户端代码来说是透明的,客户端不需要知道真正的业务逻辑对象是谁。

代理对象可以在不影响原始对象的情况下增加一些额外的功能,例如安全性检查、缓存等,可以提高系统的灵活性和可扩展性。

代理模式可以实现远程代理、虚拟代理、安全代理、缓存代理等多种应用场景。

代理模式可以控制对原始对象的访问,例如在真正需要使用原始对象之前执行某些操作,从而提高系统的性能和效率。


但代理模式也有一些缺点:

代理模式会增加系统的复杂度,因为需要增加额外的接口和类。

由于代理对象需要维护对原始对象的引用,所以会增加内存的开销。

代理模式可能会降低系统的响应速度,因为增加了一层间接访问的过程。


示例:

下面是一个简单的 Java 代码示例,演示了如何使用代理模式来控制对某个对象的访问:

// 抽象对象接口
public interface Subject {
    public void request();
}

// 原始对象
public class RealSubject implements Subject {
    public void request() {
        System.out.println("RealSubject handles request.");
    }
}

// 代理对象
public class Proxy implements Subject {
    private RealSubject realSubject;

    public void request() {
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
    
        preRequest();
        realSubject.request();
        postRequest();
    }
    
    public void preRequest() {
        System.out.println("Proxy takes some pre-request actions.");
    }
    
    public void postRequest() {
        System.out.println("Proxy takes some post-request actions.");
    }

}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        Subject subject = new Proxy();
        subject.request();
    }
}

在这个示例中,我们定义了一个抽象对象接口 Subject,其中定义了一个 request 方法用于处理客户端请求。然后我们定义了一个原始对象 RealSubject,它实现了抽象对象接口中的方法,并且表示真正的业务逻辑。

我们同时定义了一个代理对象 Proxy,它实现了抽象对象接口中的方法,并且维护了对真正的业务逻辑对象的引用。当客户端调用代理对象的 request 方法时,代理对象会先执行一些预处理操作,然后再调用真正的业务逻辑对象的 request 方法,最后再执行一些后处理操作。

通过使用代理模式,我们可以在不影响原始对象的情况下增加一些额外的功能,例如安全性检查、缓存等。同时,代理模式也能够提高系统的灵活性和可扩展性,使得我们能够更加方便地对系统进行改进和维护。

14.责任链模式

责任链模式 (Chain of Responsibility Pattern)是一种行为型设计模式,它可以将请求的发送者和接收者解耦,并且请求沿着一条链传递直到被处理。在责任链模式中,每个处理对象都有一个对下一个处理对象的引用,如果当前处理对象无法处理请求,那么它会将请求传递给下一个处理对象,直到有一个处理对象能够处理该请求。

责任链模式主要解决的问题是7W原则中的"类与类之间的耦合度过高"、"请求的处理流程需要灵活处理时"等问题。责任链模式适用于处理对象的数量不确定,或者处理请求的顺序不确定的情况。如果责任链过长或者处理对象判断不清楚,会导致系统性能下降和维护难度增加。因此,在使用责任链模式时,需要谨慎设计和选择处理对象链。


责任链模式中涉及以下几个角色:

  • 抽象处理者(Handler):定义了请求的接收方法,并且持有对下一个处理对象的引用。

  • 具体处理者(Concrete Handler):实现了抽象处理者接口中的方法,并且能够处理请求。如果当前处理对象无法处理请求,那么它会将请求传递给下一个处理对象。

  • 客户端(Client):创建请求,并将其发送到第一个处理对象。客户端只需要知道第一个处理对象即可,不需要知道请求将如何被处理或者哪个处理对象来处理请求。


责任链模式的优点包括:

  • 可以将请求的发送者和接收者解耦,降低对象之间的耦合度。发送者不需要知道请求将由哪个接收者来处理,而接收者也不需要知道请求的发送者是谁。

  • 可以灵活地增加或修改处理对象,使得系统可以动态地选择处理对象,而不需要修改源代码。

  • 可以让多个处理对象都有机会处理请求,避免了请求烦琐的分支结构。


责任链模式的缺点包括:

  • 系统性能可能受到影响,因为链中每个对象都需要处理请求,会导致一定的系统开销。

  • 如果链过长,会导致调试和维护困难。

  • 请求没有明确的接收者,且处理对象链的创建需要仔细考虑,否则可能会导致循环调用等问题。

  • 对于某些请求,责任链可能无法处理,需要在系统中加入默认处理函数进行处理,而这可能会降低系统的可靠性。


示例:

下面是一个简单的 Java 代码示例,演示了如何使用责任链模式来处理请求:

// 抽象处理者
public abstract class Handler {
    protected Handler successor;

    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }
    
    public abstract void handleRequest(Request request);

}

// 具体处理者
public class ConcreteHandler1 extends Handler {
    public void handleRequest(Request request) {
        if (request.getType() == RequestType.TYPE1) {
            System.out.println("ConcreteHandler1 handles request.");
        } else {
            if (successor != null) {
                successor.handleRequest(request);
            }
        }
    }
}

// 具体处理者
public class ConcreteHandler2 extends Handler {
    public void handleRequest(Request request) {
        if (request.getType() == RequestType.TYPE2) {
            System.out.println("ConcreteHandler2 handles request.");
        } else {
            if (successor != null) {
                successor.handleRequest(request);
            }
        }
    }
}

// 请求类
public class Request {
    private RequestType type;

    public Request(RequestType type) {
        this.type = type;
    }
    
    public RequestType getType() {
        return type;
    }

}

// 请求类型枚举
public enum RequestType {
    TYPE1, TYPE2
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        Handler handler1 = new ConcreteHandler1();
        Handler handler2 = new ConcreteHandler2();
        handler1.setSuccessor(handler2);

        Request request1 = new Request(RequestType.TYPE1);
        Request request2 = new Request(RequestType.TYPE2);
    
        handler1.handleRequest(request1);
        handler1.handleRequest(request2);
    }

}

在这个示例中,我们定义了一个抽象处理者类 Handler,其中定义了一个设置下一个处理对象的方法 setSuccessor 和一个处理请求的抽象方法 handleRequest,由具体的处理对象来实现该方法。客户端只需要创建一个处理对象链,然后将请求发送到链的第一个对象即可。

我们同时定义了两个具体的处理对象 ConcreteHandler1 和 ConcreteHandler2,它们根据请求类型来判断是否能够处理该请求。如果当前处理对象无法处理该请求,那么它会将请求传递给下一个处理对象,直到有一个处理对象能够处理该请求。

通过使用责任链模式,我们可以将请求的发送者和接收者解耦,并且可以灵活地处理请求。但在实际开发中,需要注意责任链中对象的数量和顺序,过多或过少的处理对象都会影响系统的性能和效率。

15.命令模式

命令模式 (Command Pattern)是一种行为型设计模式,它将请求封装为对象,从而可以使请求的发送者和接收者解耦。在命令模式中,请求被封装为一个或多个命令对象,并将这些命令对象作为参数传递给调用者,调用者只需要知道如何执行命令对象即可,而无需知道命令对象背后的具体实现。

命令模式的核心思想是将请求封装为对象,从而将请求的发送者和接收者解耦。

命令模式适用于需要将请求封装为对象,或需要支持撤销、重做等操作的场景。如果系统中有很多命令,并且需要与操作者或接收者进行交互,那么命令模式可能不是最好的选择。在实际应用中,我们需要根据具体的场景来选择是否使用命令模式。


在命令模式中,涉及以下几个角色:

  • 命令接口(Command):定义了执行命令的接口方法。
  • 具体命令类(Concrete Command):实现了命令接口中的方法,并且能够执行对应的业务逻辑。
  • 调用者(Invoker):将命令对象作为参数传递给调用者,并负责执行命令。调用者不需要知道命令背后的具体实现。
  • 接收者(Receiver):负责执行具体的业务逻辑。

命令模式的优点:

  • 降低系统耦合度:命令模式将请求封装为对象,使得发送者和接收者之间的关系被解耦。发送者和接收者不再直接交互,而是通过命令对象进行交互,从而降低系统的耦合度。

  • 可扩展性:由于命令模式将请求封装为对象,因此可以方便地添加新的命令类,从而扩展系统的功能。

  • 容易实现撤销、重做等操作:由于命令对象包含执行和撤销操作,因此可以轻松地实现撤销、重做等操作。

  • 易于实现日志和事务处理:由于命令对象包含执行和撤销操作,因此可以轻松地实现日志和事务处理。


命令模式的缺点:

  • 系统中会增加大量的具体命令类,增加了系统的复杂度。

  • 如果命令很多,可能会导致系统的性能受到影响。

  • 命令将操作者和接收者分开,如果需要与这些对象进行交互,就需要使用额外的机制来实现。


示例:

下面是一个简单的 Java 代码示例,演示了如何使用命令模式来实现一个电视机控制器:

// 命令接口
public interface Command {
    void execute();
}

// 具体命令类
public class TurnOnCommand implements Command {
    private TV tv;

    public TurnOnCommand(TV tv) {
        this.tv = tv;
    }

    public void execute() {
        tv.turnOn();
    }
}

// 具体命令类
public class TurnOffCommand implements Command {
    private TV tv;

    public TurnOffCommand(TV tv) {
        this.tv = tv;
    }

    public void execute() {
        tv.turnOff();
    }
}

// 接收者
public class TV {
    public void turnOn() {
        System.out.println("Turn on the TV");
    }

    public void turnOff() {
        System.out.println("Turn off the TV");
    }
}

// 调用者
public class Controller {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public void pressButton() {
        command.execute();
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        TV tv = new TV();

        Command turnOnCommand = new TurnOnCommand(tv);
        Command turnOffCommand = new TurnOffCommand(tv);

        Controller controller = new Controller();
        controller.setCommand(turnOnCommand);
        controller.pressButton();

        controller.setCommand(turnOffCommand);
        controller.pressButton();
    }
}

在这个示例中,我们定义了一个命令接口 Command,其中定义了一个 execute 方法,由具体的命令类来实现该方法。我们同时定义了两个具体的命令类 TurnOnCommandTurnOffCommand,它们分别执行打开和关闭电视机的操作。

我们还定义了一个接收者 TV,它负责执行具体的业务逻辑,即打开和关闭电视机。

最后,我们定义了一个调用者 Controller,将命令对象作为参数传递给调用者,并负责执行命令。在客户端代码中,我们创建了一个电视机和两个命令对象,并将命令对象设置到控制器中。通过调用控制器的 pressButton 方法来执行具体的命令操作。

通过使用命令模式,我们可以将请求封装为对象,从而可以使请求的发送者和接收者解耦。但在实际开发中,需要注意命令对象的数量和复杂度,过多或过少的命令对象都会影响系统的性能和效率。另外,需要考虑如何对命令对象进行持久化等问题。

16.解释器模式

解释器模式 (Interpreter Pattern)是一种行为型设计模式,它定义了一种语言和该语言的解释器,用于对特定问题领域中的语言进行解释和求值。

解释器模式的核心思想是将语言的解释器封装成对象,这些对象可以相互组合形成复杂的语法树,以实现对语言的解释和求值。在解释器模式中,问题领域中的每一个符号和句子都可以被抽象成一个抽象语法树中的节点。该模式中包含两类对象:终结符和非终结符。终结符表示语言中的基本符号,例如数字、字符串等,而非终结符则表示语言中复杂的句子或语法规则。


在解释器模式中,通常包含以下几个角色:

  • 抽象表达式(Abstract Expression):定义了一个抽象的解释方法,通常是一个抽象的语法树节点接口。

  • 终结符表达式(Terminal Expression):实现了抽象表达式接口,并且表示语言中的终结符。

  • 非终结符表达式(Nonterminal Expression):实现了抽象表达式接口,并且表示语言中的非终结符。

  • 上下文(Context):保存解释器所解释的语言信息。

  • 客户端(Client):负责创建抽象语法树,并且调用解释器进行求值。


解释器模式的优点:

  • 可扩展性好:由于抽象语法树节点对象对应的是语言中的语法规则,因此可以非常容易地向该模式中添加新的语法规则,从而扩展该语言的能力。

  • 易于实现:解释器模式的实现相对简单,只需要定义好抽象表达式接口、终结符表达式、非终结符表达式、上下文和客户端即可。

  • 可以灵活地变化语法规则:由于解释器模式使用抽象语法树来表示语言的句子,所以非常适合需要经常调整语法规则的场景,只需要修改语法树节点的子类,即可更改语言的语法规则。

  • 容易实现解释器的自定义:由于解释器模式中的解释器可以自定义,所以可以根据需要实现不同类型的解释器,例如,在编写代码时可以自定义一些快捷键的解释器,帮助提升编码效率。


解释器模式的缺点:

  • 语法规则复杂:如果语言的语法规则非常复杂,那么构建抽象语法树的过程会变得非常复杂和耗时。

  • 可读性差:由于解释器模式的实现需要创建大量的语法树节点对象,所以其代码可读性较差,在阅读解释器模式代码时需要对抽象语法树的结构有深入的理解。

  • 执行效率低:由于解释器模式需要递归遍历整个抽象语法树,因此其执行效率较低,特别是在处理大型语言时。

  • 不易维护:如果语法规则发生较大变化,则需要重新设计和构建整个抽象语法树,这可能会导致解释器模式的维护成本增加。


示例:

下面是一个简单的 Java 代码示例,演示了如何使用解释器模式来实现一个加减乘除计算器:

// 抽象表达式
interface Expression {
    int interpret(Context context);
}

// 终结符表达式
class NumberExpression implements Expression {
    private int number;

    public NumberExpression(int number) {
        this.number = number;
    }
    
    public int interpret(Context context) {
        return number;
    }

}

// 非终结符表达式
class AddExpression implements Expression {
    private Expression left;
    private Expression right;

    public AddExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }
    
    public int interpret(Context context) {
        return left.interpret(context) + right.interpret(context);
    }

}

// 非终结符表达式
class SubtractExpression implements Expression {
    private Expression left;
    private Expression right;

    public SubtractExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }
    
    public int interpret(Context context) {
        return left.interpret(context) - right.interpret(context);
    }

}

// 非终结符表达式
class MultiplyExpression implements Expression {
    private Expression left;
    private Expression right;

    public MultiplyExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }
    
    public int interpret(Context context) {
        return left.interpret(context) * right.interpret(context);
    }

}

// 非终结符表达式
class DivideExpression implements Expression {
    private Expression left;
    private Expression right;

    public DivideExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }
    
    public int interpret(Context context) {
        int divisor = right.interpret(context);
        if (divisor == 0) {
            throw new ArithmeticException("Divide by zero");
        }
        return left.interpret(context) / divisor;
    }

}

// 上下文
class Context {
    private String[] tokens;
    private int index;
    private int result;

    public Context(String expression) {
        this.tokens = expression.split(" ");
        this.index = 0;
    }
    
    public String getToken() {
        if (index < tokens.length) {
            return tokens[index++];
        }
        return null;
    }
    
    public void setResult(int result) {
        this.result = result;
    }
    
    public int getResult() {
        return result;
    }

}

// 客户端
class Client {
    public static void main(String[] args) {
        String expression = "3 + 4 * 2 - 1 / 5";
        Context context = new Context(expression);

        Expression exp = null;
    
        while ((exp = getNextExpression(context)) != null) {
            int value = exp.interpret(context);
            context.setResult(value);
        }
    
        System.out.println(expression + " = " + context.getResult());
    }
    
    private static Expression getNextExpression(Context context) {
        String token = context.getToken();
        if (token == null) {
            return null;
        } else if (token.equals("+")) {
            return new AddExpression(getNextExpression(context), getNextExpression(context));
        } else if (token.equals("-")) {
            return new SubtractExpression(getNextExpression(context), getNextExpression(context));
        } else if (token.equals("*")) {
            return new MultiplyExpression(getNextExpression(context), getNextExpression(context));
        } else if (token.equals("/")) {
            return new DivideExpression(getNextExpression(context), getNextExpression(context));
        } else {
            return new NumberExpression(Integer.parseInt(token));
        }
    }

}

在这个示例中,我们定义了一个抽象表达式接口 Expression,其中包含了一个 interpret 方法用于解释和求值语言中的句子。同时我们还定义了四个非终结符表达式类 AddExpression、SubtractExpression、MultiplyExpression 和 DivideExpression,它们分别表示加、减、乘、除四种运算符。我们还定义了一个终结符表达式类 NumberExpression,表示语言中的数字。

客户端代码中,我们将输入的字符串转化为一个上下文对象,并通过 getNextExpression 方法逐个解析表达式,创建出对应的语法树节点,并进行求值。最后输出求值结果。

通过使用解释器模式,我们可以方便地实现一种特定领域的语言解释器,从而可以解释和求值该语言中的句子。但是,在实际应用中,由于解释器模式的实现需要创建大量的语法树节点对象,因此需要合理控制其复杂度以避免影响系统性能和效率。

17.迭代器模式 (Iterator Pattern)

迭代器模式是一种行为型设计模式,它提供了一种方法来访问一个聚合对象中的各个元素,而不暴露其内部结构。该模式可以使得聚合对象的客户端代码与其元素结构相互独立,从而更加简单灵活。

迭代器模式适用于需要遍历聚合对象中元素的场景,能够使得代码更加简洁和灵活。但是在处理庞大的聚合对象时需要注意效率问题,同时也存在一定的空间复杂度问题。


在迭代器模式中,包含以下几个角色:

  • 抽象聚合对象(Abstract Aggregate):定义一个接口,用于创建一个迭代器对象。

  • 具体聚合对象(Concrete Aggregate):实现抽象聚合对象接口,返回一个迭代器对象。

  • 抽象迭代器(Abstract Iterator):定义一个接口,用于遍历聚合对象中的元素。

  • 具体迭代器(Concrete Iterator):实现抽象迭代器接口,对聚合对象进行遍历,并跟踪当前位置。

迭代器模式的核心思想是将聚合对象和迭代器分离,使得聚合对象只需要负责存储和管理元素,而迭代器则负责访问和遍历元素。这样,当聚合对象结构发生变化时,只需要调整迭代器的实现代码,而不需要修改聚合对象的代码。


迭代器模式的优点包括:

  • 单一职责原则:由于迭代器对象负责遍历聚合对象中的元素,因此可以将聚合对象和迭代器对象分离开来,使得聚合对象只需要负责存储和管理元素,而迭代器对象只需要负责访问和遍历元素。这样,当聚合对象结构发生变化时,只需要调整迭代器的实现代码,而不需要修改聚合对象的代码。

  • 简化客户端代码:使用迭代器模式可以使得客户端代码更加简洁直观,无需了解聚合对象的具体实现细节,只需要通过迭代器对象来遍历聚合对象中的元素。

  • 支持多种遍历方式:迭代器模式可以支持多种遍历方式,例如顺序遍历、倒序遍历、跳跃遍历等,且每种遍历方式都可以封装在一个具体迭代器对象中。

  • 可扩展性:可以通过定义新的迭代器对象来扩展迭代器的功能,从而使得聚合对象的遍历更加灵活和可定制化。


迭代器模式的缺点包括:

  • 空间复杂度高:迭代器对象需要维护自己的状态,因此可能会占用较多的内存空间。

  • 效率问题:在某些情况下,使用迭代器模式可能会影响程序的效率,例如需要遍历一个庞大的聚合对象时。

  • 一次性遍历:由于迭代器对象只能往前遍历,无法回溯,因此如果需要反复遍历聚合对象,则需要重新创建迭代器对象。


示例:

下面是一个简单的 Java 代码示例,演示了如何使用迭代器模式来遍历一个列表:
// 抽象聚合对象

interface Aggregate<T> {
    Iterator<T> createIterator();
}

// 具体聚合对象
class ListAggregate<T> implements Aggregate<T> {
    private List<T> list = new ArrayList<>();

    public void add(T item) {
        list.add(item);
    }
    
    public T get(int index) {
        return list.get(index);
    }
    
    public int size() {
        return list.size();
    }
    
    public Iterator<T> createIterator() {
        return new ListIterator<>(this);
    }

}

// 抽象迭代器
interface Iterator<T> {
    boolean hasNext();

    T next();

}

// 具体迭代器
class ListIterator<T> implements Iterator<T> {
    private ListAggregate<T> aggregate;
    private int index;

    public ListIterator(ListAggregate<T> aggregate) {
        this.aggregate = aggregate;
        this.index = 0;
    }
    
    public boolean hasNext() {
        return index < aggregate.size();
    }
    
    public T next() {
        if (hasNext()) {
            return aggregate.get(index++);
        }
        return null;
    }

}

// 客户端
class Client {
    public static void main(String[] args) {
        ListAggregate<String> aggregate = new ListAggregate<>();
        aggregate.add("John");
        aggregate.add("Mary");
        aggregate.add("Alice");
        aggregate.add("Bob");

        Iterator<String> iterator = aggregate.createIterator();
        while (iterator.hasNext()) {
            String item = iterator.next();
            System.out.println(item);
        }
    }

}

在这个示例中,我们定义了一个抽象聚合对象接口 Aggregate,其中包含了一个 createIterator 方法用于创建迭代器对象。我们还定义了一个具体聚合对象类 ListAggregate,实现了抽象聚合对象接口,并且包含了一个列表用于存储元素。该类还实现了 createIterator 方法,返回一个具体迭代器对象。

我们还定义了一个抽象迭代器接口 Iterator,其中包含了 hasNext 和 next 两个方法,用于遍历聚合对象中的元素。最后,我们定义了一个具体迭代器类 ListIterator,实现了该接口,并且包含了一个指向聚合对象的引用和当前索引。

客户端代码中,我们创建了一个具体聚合对象 ListAggregate,并向其中添加了一些元素。接着,我们使用 createIterator 方法来创建一个具体迭代器对象,并通过 while 循环遍历聚合对象中的元素。

通过使用迭代器模式,我们可以将聚合对象的遍历和元素访问功能独立出来,从而提供了一种更加灵活和可扩展的方式来处理聚合对象中的元素。迭代器模式还可以使得聚合对象的实现更加简单,同时也可以提高代码的可读性和可维护性。

18.中介者模式 (Mediator Pattern)

中介者模式(Mediator Pattern)是一种行为型设计模式,它用于降低多个对象之间的耦合关系。中介者模式通过把多个对象之间的直接通信转换为通过一个中介者对象进行间接通信来实现。

中介者模式的核心思想是通过引入一个中介者对象来管理多个对象之间的通信,使得对象之间的耦合性降低,系统更加灵活和可扩展。具体来说,中介者模式可以使得各个同事对象彼此独立,只需要与中介者对象进行通信,而无需了解其他同事对象的存在。


在中介者模式中,包含以下几个角色:

  • 抽象中介者(Abstract Mediator):定义了各个同事对象之间通信的接口,负责协调各个同事对象的行为。

  • 具体中介者(Concrete Mediator):实现抽象中介者接口,负责协调各个同事对象之间的通信,从而达到降低耦合度的目的。

  • 抽象同事类(Abstract Colleague):定义了各个同事类之间通信的接口,包括向中介者发送消息和接收消息。

  • 具体同事类(Concrete Colleague):实现抽象同事类接口,向中介者发送消息和接收消息,并且可以和其他同事对象进行直接通信。


中介者模式的优点:

  • 简化了对象之间的交互:中介者模式将对象之间的直接交互转换为通过中介者进行间接交互,从而简化了对象之间的交互关系。
  • 减少了类的耦合:对象之间的交互被封装在中介者对象内部,各个对象之间只需要和中介者对象进行通信,从而降低了对象之间的耦合度。
  • 更容易扩展和维护:由于各个对象之间的交互被集中管理,因此更容易对系统进行扩展和维护。
  • 可以降低系统的复杂度:通过封装对象之间的交互关系,中介者模式可以降低系统的复杂度,使得系统更加易于理解和使用。

中介者模式的缺点:

  • 中介者对象会膨胀:随着系统中对象之间关系的复杂度增加,中介者对象的职责也会变得越来越复杂,从而导致中介者对象的代码也会变得越来越复杂。
  • 中介者对象的存在会影响到系统的性能:由于所有的对象之间的交互都需要通过中介者对象进行,因此可能会导致系统的性能下降。
  • 中介者模式会增加系统的复杂度:虽然中介者模式可以对系统进行简化,但是它也会引入中介者对象,从而增加了系统的复杂度。

示例:

下面是一个简单的 Java 代码示例,演示了如何使用中介者模式来实现多个对象之间的通信:

// 抽象中介者
interface Mediator {
    void send(String message, Colleague colleague);
}

// 具体中介者
class ConcreteMediator implements Mediator {
    private ColleagueA colleagueA;
    private ColleagueB colleagueB;

    public void setColleagueA(ColleagueA colleagueA) {
        this.colleagueA = colleagueA;
    }
    
    public void setColleagueB(ColleagueB colleagueB) {
        this.colleagueB = colleagueB;
    }
    
    public void send(String message, Colleague colleague) {
        if (colleague == colleagueA) {
            colleagueB.receive(message);
        } else {
            colleagueA.receive(message);
        }
    }

}

// 抽象同事类
abstract class Colleague {
    protected Mediator mediator;

    public Colleague(Mediator mediator) {
        this.mediator = mediator;
    }
    
    public abstract void send(String message);
    
    public abstract void receive(String message);

}

// 具体同事类 A
class ColleagueA extends Colleague {
    public ColleagueA(Mediator mediator) {
        super(mediator);
    }

    public void send(String message) {
        mediator.send(message, this);
    }
    
    public void receive(String message) {
        System.out.println("ColleagueA received message: " + message);
    }

}

// 具体同事类 B
class ColleagueB extends Colleague {
    public ColleagueB(Mediator mediator) {
        super(mediator);
    }

    public void send(String message) {
        mediator.send(message, this);
    }
    
    public void receive(String message) {
        System.out.println("ColleagueB received message: " + message);
    }

}

// 客户端
class Client {
    public static void main(String[] args) {
        ConcreteMediator mediator = new ConcreteMediator();

        ColleagueA colleagueA = new ColleagueA(mediator);
        ColleagueB colleagueB = new ColleagueB(mediator);
    
        mediator.setColleagueA(colleagueA);
        mediator.setColleagueB(colleagueB);
    
        colleagueA.send("Hello, I'm ColleagueA.");
        colleagueB.send("Hi, I'm ColleagueB.");
    }

}

在这个示例中,我们定义了一个抽象中介者接口 Mediator,其中包含了一个 send 方法用于向其他同事对象发送消息。我们还定义了一个具体中介者类 ConcreteMediator,实现了抽象中介者接口,并且持有了两个具体同事对象的引用。

我们还定义了一个抽象同事类 Colleague,其中包含了一个中介者对象的引用,以及 send 和 receive 两个方法。该类是所有具体同事类的父类。我们还定义了两个具体同事类 ColleagueA 和 ColleagueB,它们继承自抽象同事类,并且实现了 send 和 receive 两个方法。

客户端代码中,我们首先创建了一个具体中介者对象 ConcreteMediator,并且创建了两个具体同事对象 ColleagueA 和 ColleagueB,并且将它们注册到中介者对象中。最后,我们分别向两个具体同事对象发送消息,并且打印出了每个同事对象接收到的消息。

通过使用中介者模式,我们可以将多个对象之间的通信集中管理,从而降低对象之间的耦合度,使得系统更加灵活和可扩展。同时,中介者模式也可以使得各个同事对象之间彼此独立,从而提高代码的可维护性。

19.备忘录模式

备忘录模式(Memento Pattern)是一种行为型设计模式,它允许将对象的内部状态保存到外部并在需要时恢复该状态。备忘录模式分离出状态的保存和恢复操作,从而使得对象本身不必关注状态的保存和恢复,同时也确保了状态的封装性。


在备忘录模式中,包含以下几个角色:

发起人(Originator):发起人是需要保存状态的对象。它将自身状态存储到备忘录中,并且可以从备忘录中恢复自身状态。

备忘录(Memento):备忘录是用于存储发起人状态的对象。它可以记录发起人的内部状态,并且可以防止发起人之外的其他对象访问备忘录内容。

管理者(Caretaker):管理者负责存储备忘录,并且能够对备忘录进行管理。它可以存储多个备忘录,并且可以按照一定的规则进行管理。

备忘录模式的核心思想是通过将对象的状态保存到备忘录中,从而使得对象在需要时可以恢复到之前的状态。具体来说,备忘录模式将状态保存和恢复的逻辑封装到了备忘录和发起人对象中,从而将这些逻辑与其他对象彻底分离开来,提高了系统的可维护性和可扩展性。


备忘录模式的优点包括:

  • 备忘录模式可以使发起人对象的状态保存和恢复逻辑与其他对象完全解耦,提高了系统的可维护性和可扩展性。
  • 备忘录模式可以允许发起人对象恢复到之前的某个状态,实现了撤销和恢复功能。
  • 备忘录模式可以支持多个历史状态的存储和管理,从而提供更加灵活的状态恢复策略。

备忘录模式的缺点包括:

  • 如果发起人对象频繁地改变状态,而备忘录对象的保存操作较为耗时,那么备忘录模式可能会影响系统的性能。
  • 如果需要备份的状态很多,那么备忘录对象的存储和管理可能会变得比较复杂。
  • 备忘录模式可能需要占用大量的存储空间,这需要开发者在设计时进行合理的权衡和优化。

示例:

下面是一个简单的 Java 代码示例,演示了如何使用备忘录模式来实现状态的保存和恢复:

// 备忘录类
class Memento {
    private String state;

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

}

// 发起人类
class Originator {
    private String state;

    public void setState(String state) {
        this.state = state;
    }
    
    public String getState() {
        return state;
    }
    
    public Memento saveStateToMemento() {
        return new Memento(state);
    }
    
    public void getStateFromMemento(Memento memento) {
        state = memento.getState();
    }

}

// 管理者类
class Caretaker {
    private List<Memento> mementoList = new ArrayList<>();

    public void addMemento(Memento memento) {
        mementoList.add(memento);
    }
    
    public Memento getMemento(int index) {
        return mementoList.get(index);
    }

}

// 客户端类
public class Client {
    public static void main(String[] args) {
        Originator originator = new Originator();
        Caretaker caretaker = new Caretaker();

        originator.setState("State 1");
        originator.setState("State 2");
        caretaker.addMemento(originator.saveStateToMemento());
        originator.setState("State 3");
        caretaker.addMemento(originator.saveStateToMemento());
        originator.setState("State 4");
    
        System.out.println("Current state: " + originator.getState());
    
        originator.getStateFromMemento(caretaker.getMemento(1));
        System.out.println("Restored state: " + originator.getState());
    }

}

在这个示例中,我们定义了一个备忘录类 Memento,它用于存储发起人对象的状态。我们还定义了一个发起人类 Originator,它包含了一个状态属性和两个方法:

  • setState 用于设置状态。
  • getState 用于获取状态。
  • saveStateToMemento 用于将自身状态保存到备忘录中。
  • getStateFromMemento 用于从备忘录中恢复自身状态。

我们还定义了一个管理者类 Caretaker,它用于存储备忘录,并且提供了添加备忘录和获取备忘录的方法。

在客户端代码中,我们首先创建了一个发起人对象 Originator 和一个管理者对象 Caretaker。接着,我们分别设置四个不同的状态,每次设置完状态后,将当前状态保存到备忘录中。最后,我们输出了当前状态和恢复之后的状态。

通过使用备忘录模式,我们可以实现对对象状态的保存和恢复,从而使得对象可以在某个时间点恢复到之前的状态。备忘录模式将状态的保存和恢复逻辑封装到了备忘录和发起人对象中,与其他对象完全解耦,提高了系统的可维护性和可扩展性。

20.观察者模式 (Observer Pattern)

观察者模式(Observer Pattern)是一种行为型设计模式,它定义了一种一对多的依赖关系,当一个对象状态发生改变时,所有依赖于它的对象都将得到通知并自动更新。在观察者模式中,被观察者对象也被称为主题(Subject),而观察者对象也被称为订阅者(Subscriber)。

观察者模式是一种非常有用的设计模式,它可以实现松耦合的通信模式,提高系统的灵活性和可扩展性。但是,在使用观察者模式时需要注意内存泄漏和同步问题等缺点。


观察者模式包含以下几个角色:

抽象主题(Subject):抽象主题定义了被观察者对象的接口,包括添加、删除和通知订阅者的方法。

具体主题(Concrete Subject):具体主题实现了抽象主题的接口,维护了订阅者列表,并且在状态发生改变时通知订阅者。

抽象订阅者(Subscriber):抽象订阅者定义了订阅者接口,包括接收主题通知和更新数据的方法。

具体订阅者(Concrete Subscriber):具体订阅者实现了订阅者接口,收到主题通知之后可以更新自己的状态。

观察者模式的核心思想是将主题对象和订阅者对象解耦,降低对象之间的耦合度。具体来说,观察者模式将订阅者对象和主题对象分离开来,让它们之间通过一个中介对象进行通信,从而实现了一种松耦合的设计。当主题对象状态发生改变时,所有已注册的订阅者对象都可以得到通知,并且可以根据需要更新自己的状态。


观察者模式有以下优点:

  • 可以实现松耦合的设计。观察者模式将订阅者和主题对象解耦,降低了它们之间的依赖关系,使得系统更加灵活和可扩展。
  • 可以实现一对多的通信模式。观察者模式可以让一个主题对象同时通知多个订阅者对象,极大地提高了系统的灵活性和可扩展性。
  • 可以支持广播通信机制。观察者模式允许订阅者对象接收广播通知,提高了通信效率和数据安全性。
  • 可以支持事件模式。观察者模式可以将事件处理封装在具体订阅者中,同时也可以支持异步处理和多线程并发执行。

观察者模式的缺点包括:

  • 可能导致内存泄漏。当订阅者对象一直存在,而主题对象不断更新状态时,如果没有正确移除订阅者对象,就会导致内存泄漏问题。
  • 同步问题。如果在多线程环境下使用观察者模式,需要考虑同步问题。如果没有正确处理同步,就可能产生数据竞争和死锁等问题。

示例:

下面是一个简单的 Java 代码示例,演示了如何使用观察者模式来实现状态的更新:

// 抽象主题
interface Subject {
    void attach(Observer observer);
    void detach(Observer observer);
    void notifyObservers();
}

// 具体主题
class ConcreteSubject implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private int state;

    public int getState() {
        return state;
    }
    
    public void setState(int state) {
        this.state = state;
        notifyObservers();
    }
    
    @Override
    public void attach(Observer observer) {
        observers.add(observer);
    }
    
    @Override
    public void detach(Observer observer) {
        observers.remove(observer);
    }
    
    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }

}

// 抽象订阅者
interface Observer {
    void update();
}

// 具体订阅者
class ConcreteObserver implements Observer {
    private ConcreteSubject subject;

    public ConcreteObserver(ConcreteSubject subject) {
        this.subject = subject;
        this.subject.attach(this);
    }
    
    @Override
    public void update() {
        System.out.println("State updated: " + subject.getState());
    }

}

// 客户端类
public class Client {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        new ConcreteObserver(subject);
        new ConcreteObserver(subject);

        subject.setState(1);
        subject.setState(2);
    }

}

在这个示例中,我们定义了抽象主题接口 Subject 和具体主题类 ConcreteSubject。具体主题类维护了一个订阅者列表和一个状态属性,并且实现了 Subject 接口的添加、删除和通知订阅者的方法。

我们还定义了抽象订阅者接口 Observer 和具体订阅者类 ConcreteObserver。具体订阅者类包含了一个主题对象,并且当它创建时,自动将自己注册到主题对象的订阅者列表中。

在客户端代码中,我们创建了一个具体主题对象 subject,并且创建了两个具体订阅者对象。接着,我们修改了具体主题对象的状态,发现所有已注册的订阅者对象都能够得到通知,并且可以根据需要更新自己的状态。

通过使用观察者模式,我们可以实现松耦合的设计,降低对象之间的耦合度,提高系统的可维护性和可扩展性。观察者模式适用于一对多的场景,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知并自动更新。

21.状态模式

状态模式(State Pattern)是一种行为型设计模式,它将对象的行为和状态分离开来,使得状态转换变得容易和灵活。在状态模式中,一个对象在不同的状态下具有不同的行为,并且可以根据需要动态地切换状态。

状态模式的核心思想是将状态封装成对象,每个状态对象都具有不同的行为,可以根据需要相互切换。环境对象持有一个状态对象的引用,通过委托实现具体的操作。状态模式将状态转换的处理交给状态对象自身处理,同时也避免了使用大量的条件语句判断对象状态的问题。

状态模式是一种有效的行为型设计模式,它将状态封装成对象,可以根据需要相互切换,并且可以在状态转换过程中做一些处理,提高了代码的可读性和可维护性。但是,状态模式也存在一些缺点,可能导致系统的复杂度增加,具体实现需要根据实际的应用场景进行权衡和选择。


状态模式包含以下几个角色:

  • 抽象状态(State):抽象状态定义了状态接口,包括对应状态的行为方法和切换到其他状态的方法。

  • 具体状态(Concrete State):具体状态实现了抽象状态的接口,定义该状态的行为和切换规则。

  • 环境(Context):环境维护了一个对抽象状态对象的引用,并且在需要时可以切换状态。环境会将具体的操作委托给当前状态对象处理。


状态模式的优点包括:

  • 将状态转换逻辑封装到具体状态对象中,使得状态转换变得容易和灵活。
  • 将状态对象和环境对象分离开来,避免了使用大量的条件语句判断对象状态的问题,提高了代码可读性和可维护性。
  • 优化了系统结构,将各个状态分开处理,符合开闭原则并且易于扩展。
  • 可以让多个环境对象共享同一个状态对象,减少对象数量,节省系统资源。
  • 状态模式将状态转换显示化,在状态转换过程中可以做一些动画效果,增强用户体验。

状态模式的缺点包括:

  • 状态模式增加了系统的复杂度,增加了代码的数量和抽象层次。
  • 如果状态数目太多,会导致具体状态类的数量急剧增加,引起程序的混乱。
  • 状态模式需要实现抽象状态和具体状态之间的切换,可能导致系统的性能降低。
  • 可能会让系统变得更加分散和难以控制,不利于统一管理

示例:

下面是一个简单的 Java 代码示例,演示了如何使用状态模式来实现状态的更新:

// 抽象状态
interface State {
    void handle(Context context);
}

// 具体状态 1
class ConcreteState1 implements State {
    @Override
    public void handle(Context context) {
        System.out.println("Current state is 1");
        context.setState(new ConcreteState2());
    }
}

// 具体状态 2
class ConcreteState2 implements State {
    @Override
    public void handle(Context context) {
        System.out.println("Current state is 2");
        context.setState(new ConcreteState1());
    }
}

// 环境类
class Context {
    private State state;

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

}

// 客户端类
public class Client {
    public static void main(String[] args) {
        Context context = new Context(new ConcreteState1());
        context.request();
        context.request();
        context.request();
    }
}

在这个示例中,我们定义了抽象状态接口 State 和两个具体状态类 ConcreteState1 和 ConcreteState2。每个具体状态类都实现了处理该状态下的行为方法和切换到其他状态的方法。

我们还定义了环境类 Context,它持有一个状态对象的引用,并且在需要时可以切换状态。环境类将具体的操作委托给当前状态对象处理。客户端代码中,我们创建一个环境对象 context 并设置初始状态为 ConcreteState1。接着,我们多次调用 request 方法,让环境对象切换状态并执行相应的操作。

通过使用状态模式,我们可以将复杂的状态转换逻辑封装到具体状态对象中,降低了系统的耦合度和复杂性,提高了系统的灵活性和可扩展性。状态模式适用于状态数目较少的场景,当一个对象的行为取决于它的状态时,状态模式是一种很好的选择。

22.策略模式

策略模式(Strategy Pattern)是一种行为型设计模式,它定义了算法簇(或者说是“策略”)的家族,并且让它们之间可以相互替换。策略模式可以让算法的变化独立于使用算法的客户端,从而降低了系统的耦合度。

策略模式的核心思想是将算法的实现和使用分离开来,将算法封装成独立的策略类,并且让这些策略类可以相互替换。在使用策略模式时,客户端只需要知道有哪些策略类可供选择即可,无需知道具体的算法实现细节。

策略模式适用于需求变化频繁的场景,使得系统具有更好的灵活性和可扩展性。但是,如果策略类数量过多或各策略类之间差别不大,就应该考虑是否有必要使用策略模式


策略模式包含以下几个角色:

  • 抽象策略(Strategy):抽象策略声明了算法家族的接口,所有具体策略都需要实现这个接口。

  • 具体策略(Concrete Strategy):具体策略实现了抽象策略定义的接口,包含了具体的算法实现。

  • 环境类(Context):环境类持有一个策略类的引用,并且在需要时将算法的执行委托给策略对象处理。


策略模式的优点:

  • 算法的实现和使用分离:策略模式将算法簇封装成独立的策略类,使其能够在不改变客户端代码的情况下随时替换算法实现,从而降低了系统的耦合度。
  • 易于扩展:添加新的策略非常方便,只需要增加一个新的策略类即可,无需修改其他代码。
  • 可以提高代码复用率:使用策略模式可以将公共的代码抽离到抽象策略类中,减少重复代码的出现。
  • 算法可以自由切换:客户端可以根据需要随时选择不同的算法,而不必关心具体实现细节,提高了系统的灵活性和可维护性。

策略模式的缺点:

  • 增加了类的个数:每个具体策略类都需要一个单独的类来实现,因此策略模式会增加系统中类的数量。
  • 客户端必须知道所有的策略类:客户端需要了解所有的具体策略类,才能做出正确的选择,这增加了客户端代码的复杂度。
  • 策略模式使用的条件比较苛刻:策略模式只适用于能够将算法簇封装成独立的类,并且这些算法之间可以自由替换的情况。如果算法之间的差异很小,或者算法涉及到其他方面的复杂逻辑,那么策略模式可能会显得不太适用。

示例:

下面是一个简单的 Java 代码示例,演示了如何使用策略模式来实现算法的替换:

// 抽象策略类
interface Strategy {
    void execute();
}

// 具体策略类 1
class ConcreteStrategy1 implements Strategy {
    @Override
    public void execute() {
        System.out.println("Execute strategy 1");
    }
}

// 具体策略类 2
class ConcreteStrategy2 implements Strategy {
    @Override
    public void execute() {
        System.out.println("Execute strategy 2");
    }
}

// 环境类
class Context {
    private Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }
    
    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }
    
    public void executeStrategy() {
        strategy.execute();
    }

}

// 客户端类
public class Client {
    public static void main(String[] args) {
        Context context = new Context(new ConcreteStrategy1());
        context.executeStrategy();

        context.setStrategy(new ConcreteStrategy2());
        context.executeStrategy();
    }

}

在这个示例中,我们定义了抽象策略接口 Strategy 和两个具体策略类 ConcreteStrategy1 和 ConcreteStrategy2。每个具体策略类都实现了执行相应算法的方法。

我们还定义了环境类 Context,它持有一个策略对象的引用,并且将算法的执行委托给策略对象处理。

客户端代码中,我们创建一个环境对象 context 并设置初始策略为 ConcreteStrategy1。接着,我们调用 executeStrategy 方法,让环境对象执行相应的算法。又因为策略是可替换的,在需要时我们可以通过调用 setStrategy 方法将策略更改为 ConcreteStrategy2,并重新执行算法。

通过使用策略模式,我们可以将算法的实现和使用分离开来,而且可以根据需要随时替换算法实现。这种做法具有很大的灵活性和可扩展性,能够提高代码的复用性和可维护性。同时,策略模式也遵循了开闭原则,允许增加新的算法簇而不影响已有算法的使用。

23.模板方法模式 (Template Method Pattern)

模板方法模式是一种行为型设计模式,它定义了一个算法的骨架,并允许子类为一个或多个步骤提供具体实现。通过模板方法模式,我们可以在不改变算法结构的情况下,定制化地修改算法的某些步骤,以满足不同客户端的需求。

模板方法模式的核心思想是将算法的不变部分封装在父类中,将算法的可变部分交给子类实现。这样做的好处在于,算法的模板结构不容易发生变化,从而提高了代码的复用性和可维护性;同时,子类的扩展也变得很容易,只需要按照模板方法的要求实现相应的抽象方法即可。

模板方法模式在设计上具有很好的灵活性和可扩展性,适用于需要按照固定流程完成一系列相似操作的场景,但应该在使用时注意抽象程度和自由度的平衡。


模板方法模式包含以下几个角色:

  • 抽象类(Abstract Class):抽象类定义了算法的骨架结构,其中包含若干抽象方法和非抽象方法。抽象方法由子类实现,而非抽象方法会直接完成某些操作,也可能调用抽象方法。

  • 具体类(Concrete Class):具体类继承自抽象类,并且提供了抽象方法的具体实现。


模板方法模式的优点:

  • 提高代码复用:将算法的骨架结构封装在抽象类中,子类只需要实现具体的操作即可,可以大幅提高代码复用性。
  • 易于扩展:由于模板方法模式使用了抽象类和抽象方法,因此易于扩展。通过添加新的子类,可以改变算法的具体实现方式。
  • 便于维护:将算法的核心部分放在父类中,而将变化的部分放在子类中,使得系统更加易于维护和修改。
  • 符合开闭原则:模板方法模式将算法的骨架固定下来,而将具体实现留给子类,遵循了开闭原则,即对扩展开放,对修改关闭。

模板方法模式的缺点:

  • 可能带来过多的抽象:由于模板方法模式使用了抽象类和抽象方法,因此可能会增加代码的抽象程度,使得代码难以理解。
  • 可能会限制子类的自由:由于模板方法模式将算法的结构固定下来,子类必须按照模板方法的要求来实现具体操作,这可能会限制子类的自由度。

示例:

下面是一个简单的 Java 代码示例,演示了如何使用模板方法模式来实现一个打印日志的例子:

// 抽象类,定义了日志打印的骨架结构
abstract class Logger {
    // 通用操作,创建日志
    public void log(String message) {
        // 这里使用了钩子方法(hook),需要子类进行重写
        String formattedMessage = formatMessage(message);
        // 子类必须实现的方法
        writeLog(formattedMessage);
    }

    // 钩子方法,子类可以按需重写
    protected String formatMessage(String message) {
        return message;
    }
    
    // 抽象方法,子类必须实现
    protected abstract void writeLog(String formattedMessage);

}

// 具体类,实现了抽象方法
class ConsoleLogger extends Logger {
    @Override
    protected void writeLog(String formattedMessage) {
        System.out.println("[Console] " + formattedMessage);
    }
}

// 具体类,实现了抽象方法和钩子方法
class FileLogger extends Logger {
    private String fileName;

    public FileLogger(String fileName) {
        this.fileName = fileName;
    }
    
    @Override
    protected void writeLog(String formattedMessage) {
        System.out.println("[File] Writing to file " + fileName + ": " + formattedMessage);
    }
    
    @Override
    protected String formatMessage(String message) {
        return "[" + java.time.LocalDateTime.now() + "] " + message;
    }

}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        Logger consoleLogger = new ConsoleLogger();
        consoleLogger.log("This is a console log.");

        Logger fileLogger = new FileLogger("test.log");
        fileLogger.log("This is a file log.");
    }

}

在这个示例中,我们定义了一个抽象类 Logger,其中包括了日志打印的骨架结构。具体来说,Logger 包含了一个公共的方法 log,它调用了两个私有方法 formatMessage 和 writeLog,其中 writeLog 是一个抽象方法,需要子类来实现,而 formatMessage 是一个钩子方法,子类可以按需重写。

我们还定义了两个具体类 ConsoleLogger 和 FileLogger,它们分别实现了抽象方法 writeLog。ConsoleLogger 将日志信息输出到控制台,而 FileLogger 将日志信息写入到文件中。同时,FileLogger 重写了钩子方法 formatMessage,将日志消息格式化为特定格式。

在客户端代码中,我们实例化了 ConsoleLogger 和 FileLogger 两个子类,并分别使用它们来打印日志。由于它们都继承自 Logger 抽象类,因此都可以调用 log 方法以完成日志打印操作。

通过使用模板方法模式,我们可以将算法的不变部分(如日志打印的骨架结构)封装在父类中,将可变部分(如日志的具体输出方式)留给子类实现。这样可以提高代码的复用性和可维护性,并且使得系统更加灵活易扩展。

用。

23.模板方法模式 (Template Method Pattern)

模板方法模式是一种行为型设计模式,它定义了一个算法的骨架,并允许子类为一个或多个步骤提供具体实现。通过模板方法模式,我们可以在不改变算法结构的情况下,定制化地修改算法的某些步骤,以满足不同客户端的需求。

模板方法模式的核心思想是将算法的不变部分封装在父类中,将算法的可变部分交给子类实现。这样做的好处在于,算法的模板结构不容易发生变化,从而提高了代码的复用性和可维护性;同时,子类的扩展也变得很容易,只需要按照模板方法的要求实现相应的抽象方法即可。

模板方法模式在设计上具有很好的灵活性和可扩展性,适用于需要按照固定流程完成一系列相似操作的场景,但应该在使用时注意抽象程度和自由度的平衡。


模板方法模式包含以下几个角色:

  • 抽象类(Abstract Class):抽象类定义了算法的骨架结构,其中包含若干抽象方法和非抽象方法。抽象方法由子类实现,而非抽象方法会直接完成某些操作,也可能调用抽象方法。

  • 具体类(Concrete Class):具体类继承自抽象类,并且提供了抽象方法的具体实现。


模板方法模式的优点:

  • 提高代码复用:将算法的骨架结构封装在抽象类中,子类只需要实现具体的操作即可,可以大幅提高代码复用性。
  • 易于扩展:由于模板方法模式使用了抽象类和抽象方法,因此易于扩展。通过添加新的子类,可以改变算法的具体实现方式。
  • 便于维护:将算法的核心部分放在父类中,而将变化的部分放在子类中,使得系统更加易于维护和修改。
  • 符合开闭原则:模板方法模式将算法的骨架固定下来,而将具体实现留给子类,遵循了开闭原则,即对扩展开放,对修改关闭。

模板方法模式的缺点:

  • 可能带来过多的抽象:由于模板方法模式使用了抽象类和抽象方法,因此可能会增加代码的抽象程度,使得代码难以理解。
  • 可能会限制子类的自由:由于模板方法模式将算法的结构固定下来,子类必须按照模板方法的要求来实现具体操作,这可能会限制子类的自由度。

示例:

下面是一个简单的 Java 代码示例,演示了如何使用模板方法模式来实现一个打印日志的例子:

// 抽象类,定义了日志打印的骨架结构
abstract class Logger {
    // 通用操作,创建日志
    public void log(String message) {
        // 这里使用了钩子方法(hook),需要子类进行重写
        String formattedMessage = formatMessage(message);
        // 子类必须实现的方法
        writeLog(formattedMessage);
    }

    // 钩子方法,子类可以按需重写
    protected String formatMessage(String message) {
        return message;
    }
    
    // 抽象方法,子类必须实现
    protected abstract void writeLog(String formattedMessage);

}

// 具体类,实现了抽象方法
class ConsoleLogger extends Logger {
    @Override
    protected void writeLog(String formattedMessage) {
        System.out.println("[Console] " + formattedMessage);
    }
}

// 具体类,实现了抽象方法和钩子方法
class FileLogger extends Logger {
    private String fileName;

    public FileLogger(String fileName) {
        this.fileName = fileName;
    }
    
    @Override
    protected void writeLog(String formattedMessage) {
        System.out.println("[File] Writing to file " + fileName + ": " + formattedMessage);
    }
    
    @Override
    protected String formatMessage(String message) {
        return "[" + java.time.LocalDateTime.now() + "] " + message;
    }

}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        Logger consoleLogger = new ConsoleLogger();
        consoleLogger.log("This is a console log.");

        Logger fileLogger = new FileLogger("test.log");
        fileLogger.log("This is a file log.");
    }

}

在这个示例中,我们定义了一个抽象类 Logger,其中包括了日志打印的骨架结构。具体来说,Logger 包含了一个公共的方法 log,它调用了两个私有方法 formatMessage 和 writeLog,其中 writeLog 是一个抽象方法,需要子类来实现,而 formatMessage 是一个钩子方法,子类可以按需重写。

我们还定义了两个具体类 ConsoleLogger 和 FileLogger,它们分别实现了抽象方法 writeLog。ConsoleLogger 将日志信息输出到控制台,而 FileLogger 将日志信息写入到文件中。同时,FileLogger 重写了钩子方法 formatMessage,将日志消息格式化为特定格式。

在客户端代码中,我们实例化了 ConsoleLogger 和 FileLogger 两个子类,并分别使用它们来打印日志。由于它们都继承自 Logger 抽象类,因此都可以调用 log 方法以完成日志打印操作。

通过使用模板方法模式,我们可以将算法的不变部分(如日志打印的骨架结构)封装在父类中,将可变部分(如日志的具体输出方式)留给子类实现。这样可以提高代码的复用性和可维护性,并且使得系统更加灵活易扩展。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小将lcz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值