目录
[创建型模式(Creational Patterns)](#创建型模式(Creational Patterns))
[单例模式(Singleton Pattern)](#单例模式(Singleton Pattern))
[1. 饿汉式(Eager Initialization)](#1. 饿汉式(Eager Initialization))
[2. 懒汉式(Lazy Initialization, 线程不安全)](#2. 懒汉式(Lazy Initialization, 线程不安全))
[3. 懒汉式(线程安全)](#3. 懒汉式(线程安全))
[4. 双重检查锁定(Double-Checked Locking)](#4. 双重检查锁定(Double-Checked Locking))
[5. 静态内部类方式(推荐)](#5. 静态内部类方式(推荐))
[工厂方法模式(Factory Method Pattern)](#工厂方法模式(Factory Method Pattern))
[抽象工厂模式(Abstract Factory Pattern)](#抽象工厂模式(Abstract Factory Pattern))
[建造者模式(Builder Pattern)](#建造者模式(Builder Pattern))
[原型模式(Prototype Pattern)](#原型模式(Prototype Pattern))
[结构型模式(Structural Patterns)](#结构型模式(Structural Patterns))
[适配器模式(Adapter Pattern)](#适配器模式(Adapter Pattern))
[代理模式(Proxy Pattern)](#代理模式(Proxy Pattern))
[行为型模式(Behavioral Patterns)](#行为型模式(Behavioral Patterns))
设计模式(Design Patterns)是软件开发人员在软件设计过程中面临常见问题时,总结出来的一系列可复用的解决方案。这些模式并不是代码,而是描述了在特定上下文中,如何组织类和对象以解决常见的设计问题。
在Java中,设计模式被广泛应用,以提高代码的可维护性、可读性和可扩展性。
创建型模式(Creational Patterns)
单例模式(Singleton Pattern)
描述
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取该实例。这个模式在多种场景下都非常有用,比如当你想控制资源的访问,或者确保一个类只有一个实例来协调系统的操作时。
单例模式的主要特点包括:
- 单一实例:确保一个类只有一个实例。
- 全局访问:提供一个全局访问点来获取该实例。
- 线程安全:在多线程环境中,确保只有一个实例被创建(可选,但通常很重要)。
实现单例模式有多种方式,包括饿汉式、懒汉式(线程不安全、线程安全)、双重检查锁定(Double-Checked Locking)、静态内部类方式等。下面将展示几种常见的实现方式。
示例代码
1. 饿汉式(Eager Initialization)
public class Singleton {
// 在类加载时就创建实例
private static final Singleton INSTANCE = new Singleton();
// 私有构造函数,防止外部实例化
private Singleton() {}
// 提供全局访问点
public static Singleton getInstance() {
return INSTANCE;
}
}
2. 懒汉式(Lazy Initialization, 线程不安全)
public class Singleton {
// 延迟加载,在第一次调用getInstance()时创建实例
private static Singleton instance;
// 私有构造函数,防止外部实例化
private Singleton() {}
// 提供全局访问点,线程不安全
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
上面的懒汉式实现是线程不安全的,因为在多线程环境中,可能会有多个线程同时进入if (instance == null)
判断,导致创建多个实例。
3. 懒汉式(线程安全)
public class Singleton {
// 延迟加载,在第一次调用getInstance()时创建实例
private static Singleton instance;
// 私有构造函数,防止外部实例化
private Singleton() {}
// 提供全局访问点,线程安全
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
虽然上面的线程安全懒汉式实现是有效的,但它在每次调用getInstance()
时都会进行同步,这可能会导致性能问题。
4. 双重检查锁定(Double-Checked Locking)
public class Singleton {
// 使用volatile确保instance的可见性和有序性
private static volatile Singleton instance;
// 私有构造函数,防止外部实例化
private Singleton() {}
// 提供全局访问点,双重检查锁定
public static Singleton getInstance() {
if (instance == null) { // 第一次检查,无需同步
synchronized (Singleton.class) { // 同步块
if (instance == null) { // 第二次检查,需要同步
instance = new Singleton();
}
}
}
return instance;
}
}
5. 静态内部类方式(推荐)
public class Singleton {
// 私有构造函数,防止外部实例化
private Singleton() {}
// 静态内部类,负责创建实例
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
// 提供全局访问点
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
静态内部类方式是一种推荐的实现方式,因为它具有如下优点:
- 线程安全:由于类加载机制保证了静态内部类只会被加载一次,因此是线程安全的。
- 延迟加载 :只有在第一次调用
getInstance()
时才会加载和创建实例。 - 高效:没有额外的同步开销。
选择哪种实现方式取决于具体需求,比如是否需要延迟加载、是否关心性能等。
工厂方法模式(Factory Method Pattern)
描述
工厂方法模式是一种创建型设计模式,它定义了一个用于创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法使一个类的实例化延迟到其子类。其核心思想是将对象的创建与使用解耦,客户端无需知道具体产品的创建细节。
工厂方法模式的主要角色包括:
- 产品(Product):定义了工厂方法所创建对象的接口,是创建对象的超类型。
- 具体产品(Concrete Product):实现了产品接口的具体类,由工厂方法创建。
- 工厂(Creator):声明了工厂方法,用于返回一个产品对象,工厂方法的返回类型是产品类型。
- 具体工厂(Concrete Creator):实现了工厂方法,返回具体产品的实例。
工厂方法模式的优点包括:
- 符合开闭原则:新增一种产品时,只需要增加相应的具体产品类和相应的工厂子类即可,无需修改现有代码。
- 提高了可扩展性:客户端只需要知道具体产品对应的工厂,无需关心创建细节。
- 封装了对象的创建过程:将对象的创建与使用解耦,使得代码更加清晰和易于维护。
然而,工厂方法模式也存在一些缺点:
- 类的个数容易过多:每新增一种产品,都需要增加一个新的产品类和对应的工厂子类,这在一定程度上增加了系统的复杂度。
- 增加了系统的抽象性和理解难度:由于引入了抽象层,客户端代码需要使用抽象层进行定义,这可能会增加系统的抽象性和理解难度。
示例代码
以下是一个使用工厂方法模式创建日志记录器的示例:
// 产品接口:Logger
public interface Logger {
void writeLog();
}
// 具体产品:FileLogger
public class FileLogger implements Logger {
@Override
public void writeLog() {
System.out.println("文件日志记录。");
}
}
// 具体产品:DatabaseLogger
public class DatabaseLogger implements Logger {
@Override
public void writeLog() {
System.out.println("数据库日志记录。");
}
}
// 工厂接口:LoggerFactory
public abstract class LoggerFactory {
public abstract Logger createLogger();
}
// 具体工厂:FileLoggerFactory
public class FileLoggerFactory extends LoggerFactory {
@Override
public Logger createLogger() {
return new FileLogger();
}
}
// 具体工厂:DatabaseLoggerFactory
public class DatabaseLoggerFactory extends LoggerFactory {
@Override
public Logger createLogger() {
return new DatabaseLogger();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
LoggerFactory factory;
Logger logger;
// 通过配置或参数决定使用哪种日志方式
factory = new FileLoggerFactory(); // 可替换为 DatabaseLoggerFactory
logger = factory.createLogger();
logger.writeLog();
}
}
在这个示例中,定义了一个Logger
接口作为产品,并实现了两个具体产品FileLogger
和DatabaseLogger
。然后,我们定义了一个LoggerFactory
接口作为工厂,并实现了两个具体工厂FileLoggerFactory
和DatabaseLoggerFactory
。客户端代码通过配置或参数决定使用哪种日志方式,并调用相应的工厂来创建日志记录器对象。这样,客户端代码与具体产品解耦,符合开放-关闭原则,易于扩展新的日志方式。
抽象工厂模式(Abstract Factory Pattern)
描述
抽象工厂模式是一种创建型设计模式,它提供了一种创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。
抽象工厂模式的核心思想是将客户端与具体产品的创建过程解耦,使得客户端可以通过工厂接口来创建一族产品。它通常涉及一族相关的产品,每个具体工厂类负责创建该族中的具体产品。这样,当需要增加新的产品族时,只需增加新的具体工厂和产品类,而无需修改现有代码,从而提高了系统的可扩展性和灵活性。
抽象工厂模式的主要角色包括:
- 抽象工厂(Abstract Factory):声明了一组用于创建产品对象的方法,每个方法对应一种产品类型。抽象工厂可以是接口或抽象类。
- 具体工厂(Concrete Factory):实现了抽象工厂接口,负责创建具体产品对象的实例。
- 抽象产品(Abstract Product):定义了一组产品对象的共同接口或抽象类,描述了产品对象的公共方法。
- 具体产品(Concrete Product):实现了抽象产品接口,定义了具体产品的特定行为和属性。
抽象工厂模式的优点包括:
- 封装了产品族的创建过程:客户端只需要通过抽象工厂接口来创建产品对象,而无需关心具体产品的创建细节。
- 提高了系统的可扩展性:当需要增加新的产品族时,只需增加新的具体工厂和产品类,而无需修改现有代码。
- 确保了产品族的一致性:由于同一产品族的对象由同一个具体工厂创建,因此它们之间具有很好的兼容性和一致性。
然而,抽象工厂模式也存在一些缺点:
- 增加了系统的复杂性:由于引入了抽象层和产品族的概念,系统的结构和代码可能会变得更加复杂。
- 限制了产品族的扩展:虽然增加新的产品族相对容易,但增加新的产品等级结构(即在同一产品族中增加新的产品类型)却比较困难,因为需要修改抽象工厂和所有具体工厂的代码。
示例代码
以下是一个使用抽象工厂模式创建不同品牌电器的示例:
// 抽象产品:冰箱
public abstract class Fridge {
public abstract void produce();
}
// 具体产品:美的冰箱
public class MideaFridge extends Fridge {
@Override
public void produce() {
System.out.println("生产美的冰箱");
}
}
// 具体产品:格力冰箱
public class GreeFridge extends Fridge {
@Override
public void produce() {
System.out.println("生产格力冰箱");
}
}
// 抽象产品:空调
public abstract class AirConditioner {
public abstract void produce();
}
// 具体产品:美的空调
public class MideaAirConditioner extends AirConditioner {
@Override
public void produce() {
System.out.println("生产美的空调");
}
}
// 具体产品:格力空调
public class GreeAirConditioner extends AirConditioner {
@Override
public void produce() {
System.out.println("生产格力空调");
}
}
// 抽象工厂
public interface Factory {
Fridge getFridge();
AirConditioner getAirConditioner();
}
// 具体工厂:美的工厂
public class MideaFactory implements Factory {
@Override
public Fridge getFridge() {
return new MideaFridge();
}
@Override
public AirConditioner getAirConditioner() {
return new MideaAirConditioner();
}
}
// 具体工厂:格力工厂
public class GreeFactory implements Factory {
@Override
public Fridge getFridge() {
return new GreeFridge();
}
@Override
public AirConditioner getAirConditioner() {
return new GreeAirConditioner();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Factory factory;
Fridge fridge;
AirConditioner airConditioner;
// 通过配置或参数决定使用哪个品牌的工厂
factory = new MideaFactory(); // 可替换为GreeFactory
fridge = factory.getFridge();
airConditioner = factory.getAirConditioner();
fridge.produce();
airConditioner.produce();
}
}
在这个示例中,定义了两个抽象产品Fridge
和AirConditioner
,以及它们的具体产品MideaFridge
、GreeFridge
、MideaAirConditioner
和GreeAirConditioner
。然后,定义了一个抽象工厂接口Factory
,并实现了两个具体工厂MideaFactory
和GreeFactory
。客户端代码通过配置或参数决定使用哪个品牌的工厂,并调用相应的工厂来创建冰箱和空调对象。这样,客户端代码与具体产品解耦,符合开放-关闭原则,易于扩展新的品牌或产品类型。
建造者模式(Builder Pattern)
描述
建造者模式是一种创建型设计模式,它通过将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。建造者模式一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,而无需知道内部的具体构建细节。
建造者模式主要包含以下几个角色:
- 产品(Product):要创建的复杂对象,它由多个部件组成。产品的内部结构通常由多个部分组成,而具体的部分可以由抽象建造者和具体建造者来定义。
- 抽象建造者(Builder):定义了创建产品的抽象接口,包括创建产品的各个部件的方法。抽象建造者中的方法通常对应产品的各个部分,但这些方法通常是空的或者有默认实现。
- 具体建造者(Concrete Builder):实现抽象建造者接口,负责实际构建产品的各个部件。具体建造者包含了具体的构建逻辑,它知道如何组装产品的各个部分,并负责最终返回构建好的产品。
- 指导者(Director):负责使用建造者接口构建产品。指导者通常包含一个构建方法,该方法通过调用建造者的方法按照一定的顺序来构建产品。指导者知道构建的步骤和顺序,但它并不知道具体的产品是如何构建的。
- 客户端(Client):客户端通过指导者来构建产品,而不需要知道产品的具体构建过程。客户端创建一个指导者,并将具体的建造者传递给指导者。
建造者模式的优点包括:
- 封装性好:有效地封装了建造过程(主要业务逻辑),使得系统整体的稳定性得到了一定保证。
- 解耦:产品本身和建造过程解耦,相同的建造过程可以创建出不同的产品。
- 产品建造过程精细化:该模式注重产品创建的整个过程,将复杂的步骤拆解得到多个相对简单的步骤,使得系统流程更清晰,且对细节的把控更精准。
- 易于扩展:如果有新产品需求,只需要添加一个建造者类即可,不需要改动之前的代码,符合开闭原则。
然而,建造者模式也存在一些缺点,例如产品的组成部分和构建过程要一致,这可能会限制产品的多样性。若产品内部有结构上的变化,则整个系统都要进行大改,增加了后期维护成本。
示例代码
以下是一个使用建造者模式创建不同类型电脑的示例:
// 产品:电脑
public class Computer {
private String CPU;
private String GPU;
private String mainBoard;
private String RAM;
// 省略getter和setter方法
@Override
public String toString() {
return "Computer{" +
"CPU='" + CPU + '\'' +
", GPU='" + GPU + '\'' +
", mainBoard='" + mainBoard + '\'' +
", RAM='" + RAM + '\'' +
'}';
}
}
// 抽象建造者
public abstract class Builder {
protected Computer computer = new Computer();
public abstract void addCPU();
public abstract void addGPU();
public abstract void addMainBoard();
public abstract void addRAM();
public Computer getComputer() {
return computer;
}
}
// 具体建造者:联想建造者
public class LenovoBuilder extends Builder {
@Override
public void addCPU() {
computer.setCPU("Intel 酷睿i7-8700K");
}
@Override
public void addGPU() {
computer.setGPU("RTX 4050");
}
@Override
public void addMainBoard() {
computer.setMainBoard("B760");
}
@Override
public void addRAM() {
computer.setRAM("三星DDR4 2666mhz 16G");
}
}
// 具体建造者:惠普建造者
public class HPBuilder extends Builder {
@Override
public void addCPU() {
computer.setCPU("Intel 酷睿i5-6300HQ");
}
@Override
public void addGPU() {
computer.setGPU("GTX 1060");
}
@Override
public void addMainBoard() {
computer.setMainBoard("B660");
}
@Override
public void addRAM() {
computer.setRAM("金士顿DDR4 2666mhz 16G");
}
}
// 指导者
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public void setBuilder(Builder builder) {
this.builder = builder;
}
public Computer construct() {
builder.addCPU();
builder.addGPU();
builder.addMainBoard();
builder.addRAM();
return builder.getComputer();
}
}
// 客户端
public class Client {
public static void main(String[] args) {
Builder lenovoBuilder = new LenovoBuilder();
Director director = new Director(lenovoBuilder);
Computer lenovoComputer = director.construct();
System.out.println(lenovoComputer);
Builder hpBuilder = new HPBuilder();
director.setBuilder(hpBuilder);
Computer hpComputer = director.construct();
System.out.println(hpComputer);
}
}
在这个示例中,定义了一个Computer
类作为产品,然后定义了抽象建造者Builder
和两个具体建造者LenovoBuilder
和HPBuilder
。Director
类作为指导者,负责使用建造者接口构建产品。客户端代码通过创建指导者和具体建造者来构建不同类型的电脑,并打印出它们的配置信息。这样,客户端代码与具体产品的构建过程解耦,符合建造者模式的设计原则。
原型模式(Prototype Pattern)
描述
原型模式是一种创建型设计模式,它通过复制现有对象来创建新对象,而不是使用构造函数。其核心思想是利用一个已经创建好的对象(即原型)作为模板,通过复制这个模板来生成新的对象。这种方式可以避免对象的重复初始化,提高性能,并使对象的创建更加灵活和动态。
原型模式包含以下几个主要角色:
- 抽象原型(Prototype):这是一个抽象类或接口,声明了一个克隆方法(通常命名为clone()),用于复制当前对象并返回一个新的副本。所有具体原型类都必须实现这个接口或继承这个抽象类,以确保它们能够被克隆。
- 具体原型(Concrete Prototype):这是实现了抽象原型接口或继承了抽象原型的具体类,它包含了实际的对象数据,并实现了克隆方法。当客户端需要创建新对象时,它们通过调用具体原型的克隆方法来复制现有对象。
- 客户端(Client):客户端代码是使用原型模式的地方,它通过调用具体原型类的克隆方法来创建新对象。客户端不需要了解对象的具体构造方式,只需知道如何复制对象。
原型模式的优点包括:
- 性能优势:当需要创建大量相似对象时,通过复制已有对象而非重新初始化新对象,可以显著提高性能。
- 简化创建过程:对于复杂的对象创建过程,原型模式提供了简化的创建结构。它不需要专门的工厂类来创建产品,而是通过封装在原型类中的克隆方法来实现对象的复制。
- 可扩展性:原型模式提供了抽象原型类,客户端可以针对抽象原型类进行编程。这样,在增加或减少产品类时,对原有系统没有影响,提高了系统的可扩展性。
- 支持深拷贝:可以使用深拷贝的方式保存对象的状态,以便在需要的时候使用,如恢复到某一历史状态,有助于实现撤销操作等。
然而,原型模式也存在一些缺点:
- 需要为每个类配备克隆方法:这可能会导致代码量增加,并且需要对类的功能进行通盘考虑。对于已有的类进行改造时,需要修改源代码,这违背了"开闭原则"。
- 深拷贝实现复杂:当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,这可能会增加实现的复杂性。
- 潜在的线程安全问题:在多线程环境下,如果没有采取适当的同步措施,原型模式可能会导致竞态条件等问题。
示例代码
以下是一个使用Java语言实现的原型模式示例:
// 抽象原型接口
interface Prototype {
Prototype clone();
}
// 具体原型类
class ConcretePrototype implements Prototype {
private String field;
public ConcretePrototype(String field) {
this.field = field;
}
@Override
public Prototype clone() {
return new ConcretePrototype(this.field);
}
public void setField(String field) {
this.field = field;
}
public String getField() {
return field;
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
// 创建原型对象
Prototype original = new ConcretePrototype("Original Field");
// 克隆原型对象来创建新对象
Prototype clone = original.clone();
// 验证新对象的属性与原始对象相同
System.out.println("Original Field: " + original.getField());
System.out.println("Clone Field: " + clone.getField());
}
}
在这个示例中,定义了一个抽象原型接口Prototype
,并创建了一个具体原型类ConcretePrototype
来实现这个接口。客户端代码通过调用具体原型类的克隆方法来创建新对象,并验证新对象的属性与原始对象相同。
需要注意的是,在Java中,实现克隆通常需要让类实现Cloneable
接口并重写clone()
方法。但是在这个示例中,为了简化代码,我们直接在ConcretePrototype
类中实现了克隆方法,而没有显式地实现Cloneable
接口。在实际应用中,如果类需要支持克隆,最好还是实现Cloneable
接口并处理可能的CloneNotSupportedException
异常。
此外,上述示例中的克隆方法是浅拷贝。如果对象包含引用类型的成员变量,并且需要实现深拷贝,那么需要在克隆方法中手动复制这些引用对象的副本。
结构型模式( Structural Patterns )
适配器模式(Adapter Pattern)
详细描述
适配器模式是一种结构型设计模式,它允许接口不兼容的类一起工作,通过将一个类的接口转换成客户端所期望的另一个接口,使原本由于接口不兼容而不能一起工作的类能够协同工作。适配器模式的主要目的是提高代码的可重用性和可维护性,它涉及到一个适配器类,该类负责将源接口转换为目标接口,使客户端可以与目标接口交互,而无需了解源接口的具体实现。
适配器模式包含以下几个主要角色:
- 目标接口(Target):定义客户端所期望的接口。这个接口是客户端代码所依赖的,它包含了客户端期望调用的方法。
- 适配者类(Adaptee):已经存在的接口,这个接口需要被适配以便与客户端协同工作。适配者类通常包含了一些与客户端需求相似但又不完全相同的方法。
- 适配器类(Adapter):适配器类是适配器模式的核心,它实现了目标接口,并通过组合或继承的方式调用适配者类中的方法,从而实现了目标接口。适配器类将客户端的请求转换为适配者类可以理解的命令,并执行相应的操作。
根据实现方式的不同,适配器模式可以分为类适配器模式和对象适配器模式:
- 类适配器模式:通过继承适配者类并实现目标接口来实现适配。由于Java等语言不支持多重类继承,因此类适配器模式在这些语言中可能受到限制。
- 对象适配器模式:通过组合适配者对象并实现目标接口来达到适配的目的。这种方式更加灵活,因为它不需要继承适配者类,只需要将适配者对象作为适配器类的一个成员变量即可。
适配器模式的优点包括:
- 提高了代码的复用性和灵活性:通过适配器,可以将已有的类集成到新的系统中,而无需修改其源代码。
- 降低了系统的复杂性:适配器模式使得客户端代码与适配者类解耦,客户端只需要与目标接口交互,而无需了解适配者类的具体实现。
- 符合开闭原则:适配器模式允许在不修改现有代码的情况下,通过添加新的适配器类来扩展系统的功能。
然而,适配器模式也存在一些潜在的缺点:
- 代码量增加:引入适配器类可能会增加系统的代码量,特别是在需要适配多个类或多个接口时。
- 性能损耗:在某些情况下,适配器模式可能会导致性能损耗,因为适配器类需要在客户端请求和适配者类之间进行额外的转换。
示例代码
以下是一个使用Java语言实现的适配器模式示例,该示例展示了如何将一个只能播放MP3文件的音频播放器适配为可以播放VLC和MP4文件的音频播放器:
// 目标接口:定义一个可以播放多种格式文件的音频播放器接口
interface AdvancedMediaPlayer {
void playVlc(String fileName);
void playMp4(String fileName);
}
// 适配者类:现有的音频播放器,只能播放MP3文件
class AudioPlayer implements MediaPlayer {
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("mp3")) {
System.out.println("Playing mp3 file. Name: " + fileName);
} else {
System.out.println("Invalid media. " + audioType + " format not supported");
}
}
}
// 适配器类:创建一个新的类,实现目标接口,并在内部使用适配者类来播放MP3文件,同时添加对VLC和MP4文件的支持
class MediaAdapter implements AdvancedMediaPlayer {
private MediaPlayer mediaPlayer;
public MediaAdapter(String audioType) {
if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
mediaPlayer = new AudioPlayer();
}
}
@Override
public void playVlc(String fileName) {
// 通过调用适配者类的play方法并传递MP3格式来模拟VLC文件的播放(这里仅为示例,实际应处理VLC文件)
mediaPlayer.play("mp3", fileName); // 注意:这里传递"mp3"只是为了演示,实际应处理VLC文件的播放逻辑
// 实际情况下,这里应该调用一个能够播放VLC文件的播放器或方法
}
@Override
public void playMp4(String fileName) {
// 同理,模拟MP4文件的播放
mediaPlayer.play("mp3", fileName); // 注意:同样,这里传递"mp3"只是为了演示
// 实际情况下,应该调用一个能够播放MP4文件的播放器或方法
}
}
// 客户端代码:使用适配器来播放VLC和MP4文件
public class AdapterPatternDemo {
public static void main(String[] args) {
AdvancedMediaPlayer vlcPlayer = new MediaAdapter("vlc");
AdvancedMediaPlayer mp4Player = new MediaAdapter("mp4");
vlcPlayer.playVlc("sample.vlc");
mp4Player.playMp4("sample.mp4");
}
}
上述示例中的MediaAdapter
类在playVlc
和playMp4
方法中直接调用了AudioPlayer
的play
方法,并传递了"mp3"作为音频类型。这仅是为了演示适配器模式的工作原理,并不代表实际的处理逻辑。在实际应用中,应该根据VLC和MP4文件的播放需求,调用相应的播放器或方法来实现播放功能。
此外,为了更准确地模拟实际情况,可以创建一个能够播放VLC和MP4文件的真实播放器类,并在MediaAdapter
类中调用这些类的播放方法。
代理模式(Proxy Pattern)
描述
代理模式是一种结构型设计模式,它提供一个代理对象来控制对另一个对象的访问。代理对象具备真实对象的功能,并代替真实对象完成相应操作,同时可以在操作执行的前后,对操作进行增强处理。代理模式的核心思想是控制对象访问,即通过代理对象间接访问目标对象,以实现一些特定的功能,如权限控制、延迟加载、日志记录等。
代理模式通常包含以下三种角色:
- 抽象主题角色(Subject):通过接口或抽象类声明真实角色实现的业务方法。
- 代理主题角色(Proxy):实现抽象角色,是真实角色的代理(访问层),通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
- 真实主题角色(RealSubject):实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
根据代理类生成时机的不同,代理模式又可以分为静态代理和动态代理。静态代理的代理类在编译期就生成,而动态代理的代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。
代理模式的主要优点包括:
- 保护目标对象:通过代理对象对访问进行控制,可以保护目标对象不被非法访问。
- 增强目标对象:在不修改目标对象代码的前提下,通过代理对象可以为目标对象增加额外的功能。
- 降低系统耦合度:代理模式将客户端与目标对象分离,使得系统结构更加灵活,降低了耦合度。
然而,代理模式也存在一些潜在的缺点:
- 增加系统复杂性:引入代理模式可能会使系统设计更复杂,需要维护额外的代理类。
- 可能导致系统变慢:因为增加了额外的层次和处理,可能会导致系统响应时间增加。
示例代码
以下是一个使用Java语言实现的静态代理和动态代理的示例。
静态代理示例:
// 抽象主题角色:定义一个接口
interface Image {
void display();
}
// 真实主题角色:实现接口的真实对象
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("Loading " + filename);
try {
Thread.sleep(2000); // 模拟加载时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void display() {
System.out.println("Displaying " + filename);
}
}
// 代理主题角色:实现接口的代理对象
class ProxyImage implements Image {
private RealImage realImage;
private String filename;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Image image = new ProxyImage("test_image.jpg");
// 图像加载会在display方法被调用时发生,实现了延迟加载
image.display();
}
}
在这个静态代理示例中,ProxyImage
类充当了RealImage
类的代理,通过延迟加载图像来优化资源的加载过程。
动态代理示例:
// 抽象主题角色:定义一个接口
interface Service {
void add();
void del();
}
// 真实主题角色:实现接口的真实对象
class RealService implements Service {
@Override
public void add() {
System.out.println("成功添加");
}
@Override
public void del() {
System.out.println("成功删除");
}
}
// 动态代理处理器
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
class DynamicProxyHandler implements InvocationHandler {
private Object target;
public DynamicProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("在代理中,记录日志,方法:" + method.getName() + "开始执行");
Object result = method.invoke(target, args);
System.out.println("在代理中,记录日志,方法:" + method.getName() + "执行完毕");
return result;
}
}
// 客户端代码
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
Service realService = new RealService();
Service proxyService = (Service) Proxy.newProxyInstance(
realService.getClass().getClassLoader(),
realService.getClass().getInterfaces(),
new DynamicProxyHandler(realService)
);
proxyService.add();
proxyService.del();
}
}
在这个动态代理示例中,DynamicProxyHandler
类实现了InvocationHandler
接口,通过反射机制在调用真实对象的方法之前和之后添加了日志记录功能。客户端通过Proxy.newProxyInstance
方法创建了一个动态代理对象,并调用了其方法,实现了对真实对象方法的增强处理。
装饰器模式
描述
装饰器模式(Decorator Pattern),也称为包装模式(Wrapper Pattern),是GoF(四人帮)提出的23种设计模式中的一种结构型设计模式。其核心思想是在不改变原有对象的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案来扩展原有对象的功能。通过装饰器模式,可以透明且动态地扩展类的功能,而无需修改类的定义。
构成
装饰器模式一般包含以下四种角色:
- 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件(ConcreteComponent)角色:实现抽象构件,是装饰器模式中被装饰的原始对象。
- 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。抽象装饰角色通常会有一个构造函数,接收一个抽象构件作为参数,以便在装饰器内部持有被装饰对象的引用。
- 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。具体装饰角色会在其方法实现中调用被装饰对象的相应方法,并在调用前后添加自己的逻辑。
优点
- 功能扩展灵活:装饰器模式允许在不修改原有类的情况下,通过添加新的装饰器类来扩展功能。
- 组合灵活:可以根据需要灵活地组合不同的装饰器,形成不同的功能组合。
- 符合开闭原则:对扩展开放,对修改封闭。通过添加新的装饰器类而不是修改原有类来扩展功能。
缺点
- 复杂性增加:引入装饰器后,系统的层次可能变得复杂,尤其是当有多个装饰器相互组合时,维护性较差。
- 依赖过多对象:装饰器类的数量过多时,会导致系统中的类和对象过多,增加管理和理解的难度。
应用场景
装饰器模式适用于以下场景:
- 当需要动态地给一个对象添加一些额外的职责时,而不想使用继承的方式来实现。
- 当不能采用继承的方式对系统进行扩展时,例如系统定义不允许继承或者使用多继承会导致类爆炸等问题。
- 当希望将对象的职责划分为多个部分,并允许用户以灵活的方式来组合这些职责时。
示例
以下是一个简单的装饰器模式示例,用于展示如何动态地为对象添加新的功能。
示例代码:
// 抽象构件角色
interface Component {
void operation();
}
// 具体构件角色
class ConcreteComponent implements Component {
@Override
public void operation() {
System.out.println("ConcreteComponent: Basic operation.");
}
}
// 抽象装饰角色
abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}
// 具体装饰角色A
class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
addedBehaviorA();
}
private void addedBehaviorA() {
System.out.println("ConcreteDecoratorA: Added behavior A.");
}
}
// 具体装饰角色B
class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
addedBehaviorB();
}
private void addedBehaviorB() {
System.out.println("ConcreteDecoratorB: Added behavior B.");
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
// 创建基础组件对象
Component component = new ConcreteComponent();
// 使用装饰器A装饰组件
Component decoratorA = new ConcreteDecoratorA(component);
// 使用装饰器B继续装饰
Component decoratorB = new ConcreteDecoratorB(decoratorA);
// 调用最终装饰后的对象
decoratorB.operation();
}
}
输出结果
ConcreteComponent: Basic operation.
ConcreteDecoratorA: Added behavior A.
ConcreteDecoratorB: Added behavior B.
在这个示例中,ConcreteComponent
执行了基础操作,之后通过 ConcreteDecoratorA
和 ConcreteDecoratorB
为该组件对象动态添加了新的行为。最终的输出显示了基础操作以及两个装饰器的新增行为。
通过装饰器模式,在不修改原有类定义的情况下,灵活地扩展对象的功能,从而满足不同的需求。
桥接模式
描述
桥接模式(Bridge Pattern)是一种结构型设计模式,其定义是将抽象部分与它的实现部分分离,使它们都可以独立地变化。桥接模式的核心思想是通过组合的方式建立两个类之间的联系,而不是继承,从而实现了抽象和实现部分的解耦,提高了系统的灵活性和可扩展性。
-
目的:桥接模式的主要目的是通过组合的方式建立两个类之间的联系,使它们可以独立地变化。这有助于减少类之间的耦合度,提高系统的可扩展性和可维护性。
-
应用场景:桥接模式适用于抽象和具体实现之间需要增加更多灵活性的场景,以及一个类存在两个或多个独立变化的维度,且这些维度都需要独立进行扩展的场景。
-
角色组成:
- 抽象(Abstraction):持有一个对实现角色的引用,抽象角色中的方法需要实现角色来实现。抽象角色一般为抽象类(构造函数规定子类要传入一个实现对象)。
- 修正抽象(RefinedAbstraction):Abstraction的具体实现,对Abstraction的方法进行完善和扩展。
- 实现(Implementor):确定实现维度的基本操作,提供给Abstraction使用。该类一般为接口或者抽象类。
- 具体实现(ConcreteImplementor):Implementor的具体实现。
-
优点:
- 分离了抽象部分及具体实现部分,提高了系统的扩展性。
- 符合开闭原则、合成复用原则。
-
缺点:
- 增加了系统的理解与设计难度。
- 需要正确地识别系统中两个独立变化的维度。
示例
以下是一个关于手机品牌和样式的桥接模式示例:
定义接口 :Brand
接口:表示手机品牌的抽象实现,包含开机、关机和打电话等方法。
public interface Brand {
void open();
void close();
void call();
}
具体实现 :XM
类:实现Brand
接口,表示小米手机的具体实现。
public class XM implements Brand {
@Override
public void open() {
System.out.println("小米手机开机");
}
@Override
public void close() {
System.out.println("小米手机关机");
}
@Override
public void call() {
System.out.println("小米手机打电话");
}
}
HW
类 :实现Brand
接口,表示华为手机的具体实现。
public class HW implements Brand {
@Override
public void open() {
System.out.println("华为手机开机");
}
@Override
public void close() {
System.out.println("华为手机关机");
}
@Override
public void call() {
System.out.println("华为手机打电话");
}
}
抽象类:
Phone
类:充当桥接类,聚合Brand
接口,调用Brand
的方法。
public abstract class Phone {
private Brand brand;
public Phone(Brand brand) {
this.brand = brand;
}
protected void open() {
this.brand.open();
}
protected void close() {
this.brand.close();
}
protected void call() {
this.brand.call();
}
}
具体实现类:
FolderPhone
类:继承Phone
抽象类,实现折叠样式手机与品牌结合。
public class FolderPhone extends Phone {
public FolderPhone(Brand brand) {
super(brand);
}
@Override
protected void open() {
super.open();
System.out.println("折叠样式手机");
}
@Override
protected void close() {
super.close();
System.out.println("折叠样式手机");
}
@Override
protected void call() {
super.call();
System.out.println("折叠样式手机");
}
}
UpRightPhone
类:继承Phone
抽象类,实现直立样式手机与品牌结合。
public class UpRightPhone extends Phone {
public UpRightPhone(Brand brand) {
super(brand);
}
@Override
protected void open() {
super.open();
System.out.println("直立样式手机");
}
@Override
protected void close() {
super.close();
System.out.println("直立样式手机");
}
@Override
protected void call() {
super.call();
System.out.println("直立样式手机");
}
}
客户端代码:
Client
类:桥接模式的调用者。
public class Client {
public static void main(String[] args) {
// 获取折叠式小米手机(样式+品牌)
FolderPhone folderPhone = new FolderPhone(new XM());
folderPhone.open();
folderPhone.call();
folderPhone.close();
System.out.println("--------------------------------------");
// 获取直立式华为手机(样式+品牌)
UpRightPhone upRightPhone = new UpRightPhone(new HW());
upRightPhone.open();
upRightPhone.call();
upRightPhone.close();
}
}
桥接模式通过将抽象部分与实现部分分离,使得它们可以独立地变化,从而提高了系统的灵活性和可扩展性。然而,它也需要开发者在设计阶段正确地识别系统中两个独立变化的维度,并合理地设计抽象层次结构。
外观模式
描述
外观模式(Facade Pattern)是一种结构型设计模式,它通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问。该模式定义了一个高层接口,使得这一子系统更加容易使用。外观模式的核心思想是通过创建一个外观类,将复杂系统的内部实现细节隐藏起来,只暴露出一个简化的接口给客户端。客户端只需要与外观类进行交互,而不需要直接与子系统的组件进行交互,从而降低了系统的复杂度,提高了系统的可维护性和可扩展性。
结构
外观模式主要包含以下几个角色:
- 外观(Facade)角色:这是外观模式的核心部分,也被称为门面角色。外观类为多个子系统对外提供一个共同的接口,使得调用端能够更容易地与系统进行交互。通过外观类,调用端无需了解子系统的内部细节和复杂性,只需与外观类进行交互即可。
- 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。它们处理由外观类指派的任务,是系统功能的实际提供者。虽然子系统之间可能存在复杂的交互和依赖关系,但外观类为调用端屏蔽了这些复杂性。
- 客户(Client)角色:这是外观接口的调用者。调用者通过外观类与各个子系统交互,无需了解子系统的具体实现和细节。
特点
- 简化接口:外观模式为子系统提供了一个简化的接口,使得调用者无需关心子系统的内部细节和复杂性,降低了系统的学习成本和使用难度。
- 降低耦合度:通过引入外观类,子系统与调用者之间的耦合度得到了降低。外观类作为中间层,屏蔽了子系统的具体实现,使得子系统内部的修改不会影响到调用者。
- 提高灵活性:由于外观类提供了统一的接口,调用者可以更加灵活地使用子系统,而无需关心子系统的具体实现细节。这有助于增加系统的可扩展性和可维护性。
- 易于维护:当子系统内部发生变更时,只需要修改外观类即可,而无需修改所有调用者的代码。这大大降低了系统的维护成本。
缺点
- 可能违背开闭原则:当需要添加新的子系统功能时,可能需要修改外观类或者增加新的外观类。这在一定程度上违背了开闭原则(即对扩展开放,对修改封闭),可能导致系统的稳定性和可维护性受到影响。
- 可能增加不必要的复杂性:在某些情况下,如果子系统本身就很简单或者调用者需要直接访问子系统的某些功能,引入外观模式可能会增加不必要的复杂性。
示例
以下是一个简单的外观模式示例,用于演示如何在一个电脑系统中使用外观模式来简化对多个子系统的访问。
假设有一个电脑系统,包含了多个子系统,如音乐功能、视频功能和联网功能。我们可以创建一个外观类来封装对这些子系统的调用,并提供一个简化的接口给客户端。
子系统类:
// 音乐功能子系统
public class Music {
public void open() {
System.out.println("加载音乐");
}
public void stop() {
System.out.println("关闭音乐");
}
}
// 视频功能子系统
public class Video {
public void open() {
System.out.println("打开视频");
}
public void stop() {
System.out.println("关闭视频");
}
}
// 联网功能子系统
public class Internet {
public void open() {
System.out.println("连接网络");
}
public void stop() {
System.out.println("断开网络");
}
}
外观类:
// 外观类 - 电脑系统
public class Computer {
private Music music;
private Video video;
private Internet internet;
public Computer() {
this.music = new Music();
this.video = new Video();
this.internet = new Internet();
}
// 提供简化的接口给客户端
public void openVideo() {
internet.open(); // 连接网络
music.open(); // 加载音乐
video.open(); // 打开视频
}
public void stopVideo() {
video.stop(); // 关闭视频
music.stop(); // 关闭音乐
internet.stop(); // 断开网络
}
}
客户端代码
// 客户端
public class Client {
public static void main(String[] args) {
Computer computer = new Computer();
System.out.println("播放视频步骤:");
computer.openVideo(); // 播放视频
System.out.println("关闭视频步骤:");
computer.stopVideo(); // 关闭视频
}
}
在这个示例中,Music
、Video
和Internet
是子系统类,分别实现了音乐、视频和联网功能。Computer
是外观类,它封装了对这些子系统的调用,并提供了openVideo
和stopVideo
两个简化的接口给客户端。在客户端中,我们只需要与Computer
类进行交互,而无需关心Music
、Video
和Internet
子系统的具体实现和细节。
通过使用外观模式,简化了电脑系统的使用,降低了系统的复杂度,提高了系统的可维护性和可扩展性。
享元模式
描述
享元模式(Flyweight Pattern)是一种结构型设计模式,它通过共享已经存在的对象实例来减少对象的创建数量,从而提高系统性能。享元模式的核心思想是将对象的状态分为内部状态和外部状态,内部状态是对象可以共享的部分,而外部状态是对象不能共享的部分,需要在使用时通过参数传递等方式传递给对象。通过共享内部状态的对象,享元模式可以显著减少对象的数量,从而降低内存消耗和提高系统性能。
结构
享元模式主要包含以下几个角色:
- Flyweight(享元)接口:定义享元对象的接口,这个接口规定了可以共享的状态和行为。
- ConcreteFlyweight(具体享元)类:实现了Flyweight接口,并包含了内部状态。具体享元类可以存储并共享对象的内部状态,但不允许存储外部状态。
- UnsharedConcreteFlyweight(非共享具体享元)类:这是Flyweight接口的另一种实现,但它不需要被共享。通常,非共享具体享元类代表了那些不能或不应该被共享的对象。
- FlyweightFactory(享元工厂)类:负责创建和管理享元对象。它维护了一个享元对象的池,并根据需要向客户端提供享元对象。享元工厂类通过检查池中是否已存在具有相同内部状态的享元对象来决定是创建新对象还是返回现有对象。
- Client(客户端)代码:客户端代码通过享元工厂类获取享元对象,并与之进行交互。客户端代码负责维护外部状态,并在需要时将其传递给享元对象。
特点
- 减少对象数量:享元模式通过共享对象来减少对象的创建数量,从而降低了内存消耗。
- 提高性能:由于减少了对象的创建和销毁操作,享元模式可以提高系统的运行效率。
- 分离内外状态:享元模式将对象的状态分为内部状态和外部状态,内部状态可以共享,而外部状态需要在使用时通过参数传递等方式传递给对象。这种分离使得享元模式更加灵活和可扩展。
- 依赖于外部状态:由于享元对象本身不包含完整的状态信息(缺少外部状态),因此它们的使用需要依赖于外部状态的传递。这在一定程度上增加了系统的复杂性。
示例
以下是一个简单的享元模式示例,用于演示如何在文本编辑器中使用享元模式来共享字符对象。
Flyweight接口:
// Flyweight接口
public interface CharacterFlyweight {
void display(char externalState);
}
ConcreteFlyweight类:
import java.util.HashMap;
import java.util.Map;
// ConcreteFlyweight类
public class UnicodeCharacter implements CharacterFlyweight {
private final char internalState;
// 使用一个静态的Map来存储已经创建的享元对象
private static final Map<Character, UnicodeCharacter> flyweightPool = new HashMap<>();
// 私有构造函数,防止外部直接创建对象
private UnicodeCharacter(char internalState) {
this.internalState = internalState;
}
// 工厂方法,用于获取享元对象
public static UnicodeCharacter getFlyweight(char internalState) {
UnicodeCharacter flyweight = flyweightPool.get(internalState);
if (flyweight == null) {
flyweight = new UnicodeCharacter(internalState);
flyweightPool.put(internalState, flyweight);
}
return flyweight;
}
@Override
public void display(char externalState) {
// 在这里,我们假设externalState是用于表示字符在文本中的位置或样式等外部信息
// 而internalState则是字符本身的编码信息,是可以共享的
System.out.print("内部状态: " + internalState + ", 外部状态: " + externalState + " ");
}
}
Client代码:
// 客户端代码
public class TextEditor {
public static void main(String[] args) {
// 假设我们要显示一段文本 "Hello, World!"
char[] text = {'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!'};
for (char ch : text) {
// 通过工厂方法获取享元对象,并传递外部状态(在这里,我们可以简单地将字符本身作为外部状态传递)
UnicodeCharacter flyweight = UnicodeCharacter.getFlyweight(ch);
flyweight.display(ch); // 显示字符,同时打印其内部状态和外部状态(虽然在这个例子中,外部状态和内部状态是相同的字符)
}
// 注意:在这个例子中,我们并没有真正地使用到外部状态的概念,因为字符本身就是内部状态。
// 在实际应用中,外部状态可能是字符的样式、位置、颜色等信息,这些信息在需要时才传递给享元对象。
// 输出结果应该会显示每个字符一次,即使字符在文本中多次出现,也只会有一个享元对象与之对应。
}
}
在这个示例中,UnicodeCharacter
类实现了CharacterFlyweight
接口,并作为具体享元类。它使用了一个静态的Map
来存储已经创建的享元对象,并通过工厂方法getFlyweight
来获取享元对象。客户端代码TextEditor
通过工厂方法获取享元对象,并调用其display
方法来显示字符。由于享元对象被共享,因此即使字符在文本中多次出现,也只会有一个享元对象与之对应。
需要注意的是,在这个示例中,我们并没有真正地使用到外部状态的概念,因为字符本身就是内部状态。在实际应用中,外部状态可能是字符的样式、位置、颜色等信息,这些信息在需要时才传递给享元对象。通过这种方式,享元模式可以在保持对象共享的同时,灵活地处理不同的外部状态。
组合模式
描述
组合模式(Composite Pattern),也称为整体-部分模式(Part-Whole),是一种结构型设计模式。它通过将单个对象(叶子节点)和组合对象(树枝节点)用相同的接口进行表示,使得客户对单个对象和组合对象的使用具有一致性。组合模式的主要目的是让用户以一致的方式处理个别对象以及组合对象,从而简化客户端代码,提高系统的可维护性和可扩展性。
结构
组合模式主要包含以下几个角色:
-
抽象根节点(Component):定义了系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性。它是所有叶子节点和树枝节点的公共父类或接口。
-
树枝节点(Composite):定义了树枝节点的行为,存储子节点,并组合树枝节点和叶子节点形成一个树形结构。它实现了Component接口,并添加了管理子节点的方法(如添加、删除、获取子节点等)。
-
叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位。它也实现了Component接口,但通常不实现管理子节点的方法(这些方法在叶子节点中通常抛出异常或返回空值)。
特点
-
一致性:组合模式使得客户端能够以一致的方式处理单个对象和组合对象,无需区分它们的类型。
-
灵活性:通过组合不同的叶子节点和树枝节点,可以灵活地构建复杂的树形结构。
-
可扩展性:由于使用了接口或抽象类,组合模式具有良好的可扩展性,可以方便地添加新的叶子节点或树枝节点类型。
-
递归性:组合模式中的操作通常具有递归性,即对于组合对象,其操作会递归地应用于其子节点。
示例
以下是一个简单的组合模式示例,用于演示如何构建一个表示公司组织架构的树形结构。
抽象根节点(Component):
// 抽象根节点接口
public interface OrganizationComponent {
void add(OrganizationComponent component);
void remove(OrganizationComponent component);
OrganizationComponent getChild(int index);
void printStructure(String prefix);
}
树枝节点(Composite):
import java.util.ArrayList;
import java.util.List;
// 树枝节点类
public class Department implements OrganizationComponent {
private String name;
private List<OrganizationComponent> subDepartments = new ArrayList<>();
public Department(String name) {
this.name = name;
}
@Override
public void add(OrganizationComponent component) {
subDepartments.add(component);
}
@Override
public void remove(OrganizationComponent component) {
subDepartments.remove(component);
}
@Override
public OrganizationComponent getChild(int index) {
return subDepartments.get(index);
}
@Override
public void printStructure(String prefix) {
System.out.println(prefix + name);
for (OrganizationComponent component : subDepartments) {
component.printStructure(prefix + "--");
}
}
}
叶子节点(Leaf):
// 叶子节点类
public class Employee implements OrganizationComponent {
private String name;
public Employee(String name) {
this.name = name;
}
@Override
public void add(OrganizationComponent component) {
throw new UnsupportedOperationException("Employee cannot have subordinates");
}
@Override
public void remove(OrganizationComponent component) {
throw new UnsupportedOperationException("Employee cannot have subordinates");
}
@Override
public OrganizationComponent getChild(int index) {
throw new UnsupportedOperationException("Employee cannot have subordinates");
}
@Override
public void printStructure(String prefix) {
System.out.println(prefix + name);
}
}
客户端代码:
public class Client {
public static void main(String[] args) {
// 创建公司根节点
OrganizationComponent company = new Department("Company");
// 创建部门节点
OrganizationComponent hrDepartment = new Department("HR Department");
OrganizationComponent itDepartment = new Department("IT Department");
// 创建员工节点
Employee employee1 = new Employee("John Doe");
Employee employee2 = new Employee("Jane Smith");
// 构建公司组织架构
hrDepartment.add(employee1);
itDepartment.add(employee2);
company.add(hrDepartment);
company.add(itDepartment);
// 打印公司组织架构
company.printStructure("");
}
}
在这个示例中,定义了一个OrganizationComponent
接口作为抽象根节点,它包含了添加、删除、获取子节点以及打印结构的方法。Department
类实现了OrganizationComponent
接口,并作为树枝节点,它包含了子节点的列表,并实现了这些方法。Employee
类也实现了OrganizationComponent
接口,但它是叶子节点,因此这些方法在Employee
类中抛出异常或返回空值。客户端代码通过创建和组合不同的Department
和Employee
对象来构建公司组织架构,并调用printStructure
方法来打印整个架构的结构。
行为型模式( Behavioral Patterns )
策略模式
描述
策略模式(Strategy Pattern),又称为政策模式(Policy),是一种对象行为型设计模式。它定义了一系列算法,将每一个算法封装起来,并让它们可以相互替换,策略模式让算法独立于使用它的客户而变化。简而言之,策略模式通过把一组算法分别封装起来,使得它们可以互换,从而使得算法的变化独立于使用算法的客户。
结构
策略模式包含以下三个主要角色:
- Context(环境类):环境类是使用算法的角色,它在解决某个问题(即实现某个功能)时可以采用多种策略。环境类维持一个对抽象策略类的引用实例,用于定义所采用的策略。
- Strategy(抽象策略类):抽象策略类为所支持的算法声明了抽象方法,是所有策略类的父类。环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。
- ConcreteStrategy(具体策略类):具体策略类实现了在抽象策略类中声明的算法。在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务功能。
特点
- 算法自由切换和扩展:策略模式允许用户从算法族中任选一个算法来解决某一问题,同时可以方便地更换算法和增加新的算法。
- 算法定义和算法使用分离:策略模式实现了算法定义和算法使用的分离,它通过继承和多态的机制实现对算法族的使用和管理。
- 灵活性:策略模式提供了一种可插入式(Pluggable)算法的实现方案,使得算法可以独立于使用它的客户而变化。
示例
以下是一个使用Java语言实现的策略模式示例,用于演示如何根据不同的折扣策略来计算商品的总价。
-
定义策略接口 :
// 折扣策略接口 public interface DiscountStrategy { double applyDiscount(double amount); }
-
创建具体策略类 :
// 百分比折扣策略类 public class PercentageDiscountStrategy implements DiscountStrategy { private double percentage; public PercentageDiscountStrategy(double percentage) { this.percentage = percentage; } @Override public double applyDiscount(double amount) { return amount - (amount * percentage / 100); } } // 固定金额折扣策略类 public class FixedAmountDiscountStrategy implements DiscountStrategy { private double discountAmount; public FixedAmountDiscountStrategy(double discountAmount) { this.discountAmount = discountAmount; } @Override public double applyDiscount(double amount) { return amount - discountAmount; } }
-
创建上下文类 :
// 购物车上下文类 public class ShoppingCart { private DiscountStrategy discountStrategy; public void setDiscountStrategy(DiscountStrategy discountStrategy) { this.discountStrategy = discountStrategy; } public double checkout(double totalAmount) { if (discountStrategy != null) { return discountStrategy.applyDiscount(totalAmount); } return totalAmount; } }
在这个示例中,定义了一个
DiscountStrategy
接口作为抽象策略类,它声明了一个applyDiscount
方法。然后,创建了两个具体策略类PercentageDiscountStrategy
和FixedAmountDiscountStrategy
,它们分别实现了百分比折扣和固定金额折扣的算法。ShoppingCart
类是环境类,它持有一个对DiscountStrategy
接口的引用,并在checkout
方法中调用所选策略的applyDiscount
方法。客户端代码通过创建ShoppingCart
对象,并设置不同的折扣策略来计算商品的总价。这个示例展示了策略模式如何使得算法的变化独立于使用算法的客户,从而提高了系统的灵活性和可维护性。
模板方法模式
描述
模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义了一个操作的算法骨架,而将一些步骤延迟到子类中实现。这样,子类可以在不改变算法结构的情况下,重新定义算法的某些特定步骤。这种设计方式将特定步骤的具体实现与操作流程分离开来,实现了代码的复用和扩展,从而提高代码质量和可维护性。
结构
模板方法模式通常由一个抽象类和多个具体子类组成。其主要角色包括:
-
抽象类(Abstract Class):
- 定义了算法的框架,包含模板方法和一些抽象方法。
- 模板方法定义了算法的骨架,按某种顺序调用其包含的方法。
- 抽象方法由具体子类实现,是算法中的可变部分。
-
具体子类(Concrete Class):
- 实现抽象类中的抽象方法,从而定义了算法的具体步骤。
- 可以重写抽象类中的具体方法,以改变算法中的某些不变部分(但通常不推荐这样做,以保持算法的一致性)。
特点
-
封装不变部分,扩展可变部分:
- 抽象类封装了算法的不变部分,具体子类实现了算法的可变部分。
- 这种方式使得子类可以在不改变算法结构的情况下,重新定义算法的某些特定步骤。
-
提高代码复用性和可扩展性:
- 抽象类中的具体方法可以被多个子类共享,减少了重复代码。
- 具体子类可以通过扩展方式增加相应的功能,符合开闭原则(对扩展开放,对修改关闭)。
-
提高代码的安全性:
- 把重要的逻辑放在抽象类中实现,避免了具体子类乱改重要逻辑的风险。
-
增加系统的抽象性和理解难度:
- 由于在抽象类中定义了算法框架,增加了系统的抽象性,可能导致理解难度增加。
示例
以下是一个使用Java语言实现的模板方法模式示例,用于演示如何发送短信验证码。
-
定义抽象类 :
// 发送短信模板抽象类 public abstract class SmsTemplate { // 模板方法,定义了发送短信的基本流程 public void send(String mobile) throws Exception { System.out.println("检查用户一分钟内是否发送过短信, mobile:" + mobile); if (checkUserReceiveInOneMinute(mobile)) { throw new Exception("请等待1分钟后重试"); } String code = genCode(); if (manufacturer(mobile, code)) { System.out.println("短信厂商发送短信成功, mobile:" + mobile + ", code=" + code); save2redis(mobile, code); } } // 抽象方法,由具体子类实现 protected abstract boolean manufacturer(String mobile, String code); // 具体方法,实现了检查用户一分钟内是否发送过短信的逻辑 protected boolean checkUserReceiveInOneMinute(String mobile) { // 此处省略具体实现逻辑 return false; // 示例返回false,表示用户一分钟内未发送过短信 } // 具体方法,生成6位验证码 protected String genCode() { return "123456"; // 示例生成固定的6位验证码 } // 具体方法,将手机号+验证码存进redis中 protected void save2redis(String mobile, String code) { // 此处省略具体实现逻辑 } }
-
创建具体子类
// 阿里云短信发送子类 public class AliyunSmsSend extends SmsTemplate { @Override protected boolean manufacturer(String mobile, String code) { System.out.println("读取阿里云短信配置"); System.out.println("创建阿里云发送短信客户端"); System.out.println("阿里云发送短信成功"); return true; } } // 腾讯云短信发送子类 public class TencentSmsSend extends SmsTemplate { @Override protected boolean manufacturer(String mobile, String code) { System.out.println("读取腾讯云短信配置"); System.out.println("创建腾讯云发送短信客户端"); System.out.println("腾讯云发送短信成功"); return true; } }
-
在客户端代码中使用
public class Main { public static void main(String[] args) throws Exception { SmsTemplate smsTemplate1 = new AliyunSmsSend(); smsTemplate1.send(""); // 传入空字符串作为手机号,仅用于演示 System.out.println("---------------------------"); SmsTemplate smsTemplate2 = new TencentSmsSend(); smsTemplate2.send(""); // 传入空字符串作为手机号,仅用于演示 } }
运行结果
检查用户一分钟内是否发送过短信, mobile: 读取阿里云短信配置 创建阿里云发送短信客户端 阿里云发送短信成功 短信厂商发送短信成功, mobile:, code=123456 --------------------------- 检查用户一分钟内是否发送过短信, mobile: 读取腾讯云短信配置 创建腾讯云发送短信客户端 腾讯云发送短信成功 短信厂商发送短信成功, mobile:, code=123456
通过上述示例,可以看到模板方法模式如何定义算法的框架,并将某些步骤延迟到子类中实现。子类可以在不改变算法结构的情况下,重新定义算法的某些特定步骤,从而实现了代码的复用和扩展。
观察者模式
描述
观察者模式(Observer Pattern)是一种行为型设计模式,它定义了一种对象间的一对多依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。这种模式通常被用来实现事件处理系统,能够显著降低对象之间的耦合度,提高系统的灵活性和可维护性。
结构
观察者模式主要包含两个核心角色:主题(Subject,也称为被观察者或可观察者)和观察者(Observer)。
-
主题(Subject):
- 是具有状态的对象,并维护着一个观察者列表。
- 提供了添加、删除和通知观察者的方法。
-
观察者(Observer):
- 是接收主题通知的对象。
- 需要实现特定的接口以接收通知。
特点
- 松耦合:通过将变化的部分分离出去,观察者模式实现了对象间的松耦合。被观察者和观察者之间并不直接依赖,而是通过接口或抽象类进行交互,这使得它们可以独立变化。
- 一对多:观察者模式定义了对象之间的一对多依赖关系,即一个对象状态发生改变时,其相关依赖对象都得到通知并被更新。
- 动态性:观察者可能会动态变化(新加入、删除),这使得系统更加灵活。
- 事件驱动:观察者模式通过事件驱动的方式,实现了实时通知和更新。
实现方式
观察者模式的实现方式有多种,但从根本上说,该模式必须包含两个角色:观察者和被观察对象。常见的实现方式包括推数据的模式和拉数据的模式。
- 推数据的模式:被观测对象向观测对象送出数据,即在被观测对象发生改变时,主动告知被观测对象。
- 拉数据的模式:观测对象主动去被观测对象查询是否发生了变化。
示例
以下是一个简单的Java语言实现的观察者模式示例,用于演示如何管理用户界面的更新。
-
定义接口 :
// 观察者接口 public interface Observer { void update(String message); } // 被观察者接口 public interface Subject { void registerObserver(Observer o); void removeObserver(Observer o); void notifyObservers(); }
-
实现被观察者 :
import java.util.ArrayList; import java.util.List; // 具体被观察者类 public class ConcreteSubject implements Subject { private List<Observer> observers; private String state; public ConcreteSubject() { observers = new ArrayList<>(); } @Override public void registerObserver(Observer o) { observers.add(o); } @Override public void removeObserver(Observer o) { observers.remove(o); } @Override public void notifyObservers() { for (Observer observer : observers) { observer.update(state); } } public void setState(String state) { this.state = state; notifyObservers(); } public String getState() { return state; } }
-
实现观察者 :
// 具体观察者类 public class ConcreteObserver implements Observer { private String name; public ConcreteObserver(String name) { this.name = name; } @Override public void update(String message) { System.out.println(name + " received message: " + message); } }
-
在客户端代码中使用 :
public class ObserverPatternDemo { public static void main(String[] args) { ConcreteSubject subject = new ConcreteSubject(); Observer observer1 = new ConcreteObserver("Observer 1"); Observer observer2 = new ConcreteObserver("Observer 2"); subject.registerObserver(observer1); subject.registerObserver(observer2); subject.setState("State has changed!"); subject.removeObserver(observer1); subject.setState("State has changed again!"); } }
-
运行结果
Observer 1 received message: State has changed! Observer 2 received message: State has changed! Observer 2 received message: State has changed again!
通过上述示例,可以看到观察者模式如何定义对象间的一对多依赖关系,并在对象状态发生改变时通知所有依赖的对象。这种模式在游戏开发、事件处理系统、UI界面更新等方面有着广泛的应用。
迭代器模式
描述
迭代器模式(Iterator Pattern)是一种对象行为型设计模式,它提供了一种方法来顺序访问聚合对象中的元素,而又不暴露该对象的内部表示。该模式的主要目的是将迭代逻辑封装在迭代器对象中,而不是将该逻辑嵌入到聚合对象中,从而实现了迭代逻辑与聚合对象实现的分离,增强了代码的可维护性和可扩展性。
结构
迭代器模式包含以下几个核心角色:
- 迭代器接口(Iterator):定义了访问和遍历集合元素的方法,为使用者提供了一种方便的方式来遍历集合中的元素,而不必关心集合的实现细节。
- 具体迭代器(ConcreteIterator):持有一个具体的集合,并且实现了迭代器接口,实际负责按照特定的顺序遍历集合中的元素,并将遍历结果反馈给使用者。
- 集合接口(Aggregate):定义了创建迭代器的抽象方法,隐藏了具体集合的表示和实现,具体的表示和实现由具体集合类来负责实现。
- 具体集合(ConcreteAggregate):该类实现了集合接口,创建出具体的迭代器对象,供使用者通过迭代器来访问和遍历集合元素。
特点
- 访问透明:迭代器模式使得外部代码可以透明地访问集合内部的数据,而不需要了解集合的内部结构。
- 多种遍历方式:迭代器模式支持以不同的方式遍历一个聚合对象,而无需修改聚合对象的内部实现。
- 简化聚合类:通过引入迭代器,可以将遍历的逻辑从聚合类中分离出来,从而简化了聚合类的设计。
- 扩展方便:在迭代器模式中,增加新的聚合类和迭代器类都很方便,无需修改原有代码,符合开闭原则。
实现方式
迭代器模式的实现通常包含以下几个步骤:
- 定义一个迭代器接口,该接口包含访问和遍历集合元素的方法,如
hasNext()
和next()
。 - 实现一个具体迭代器类,该类持有一个具体的集合对象,并实现了迭代器接口中的方法。
- 定义一个集合接口,该接口包含创建迭代器的抽象方法。
- 实现一个具体集合类,该类实现了集合接口,并提供了创建具体迭代器对象的方法。
示例
以下是一个简单的Java语言实现的迭代器模式示例,用于演示如何遍历一个包含字符串的集合。
-
定义迭代器接口 :
public interface Iterator<T> { boolean hasNext(); T next(); }
-
定义集合接口 :
public interface Container<T> { Iterator<T> getIterator(); }
-
实现具体集合类 :
import java.util.ArrayList; import java.util.List; public class NameRepository implements Container<String> { private List<String> names = new ArrayList<>(); public void addName(String name) { names.add(name); } @Override public Iterator<String> getIterator() { return new NameIterator(); } private class NameIterator implements Iterator<String> { private int index = 0; @Override public boolean hasNext() { return index < names.size(); } @Override public String next() { if (hasNext()) { return names.get(index++); } throw new RuntimeException("No more elements in the iteration"); } } }
-
在客户端代码中使用 :
public class IteratorPatternDemo { public static void main(String[] args) { NameRepository nameRepository = new NameRepository(); nameRepository.addName("Robert"); nameRepository.addName("John"); nameRepository.addName("Julie"); nameRepository.addName("Lora"); Iterator<String> iterator = nameRepository.getIterator(); while (iterator.hasNext()) { String name = iterator.next(); System.out.println("Name: " + name); } } }
-
运行结果
Name: Robert Name: John Name: Julie Name: Lora
通过上述示例,可以看到迭代器模式如何定义了一个统一的接口来访问和遍历集合中的元素,而无需了解集合的内部结构。同时,该模式还提供了多种遍历方式,并简化了聚合类的设计。这种模式在集合类、文件系统等需要遍历对象的场景中有着广泛的应用。
责任链模式
描述
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它允许多个对象按照顺序处理请求,并且每个对象可以选择自己是否处理该请求或将其传递给下一个对象。该模式通过将请求的发送者和接收者解耦,提供了更大的灵活性和可扩展性。
责任链模式的核心在于设计好一个请求链以及链结束的标识。它将多个处理请求的对象连接成一条链,并允许请求在这条链上传递,直到链上的某一个对象决定处理此请求。发出请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
责任链模式包含以下角色:
- 抽象处理者(Handler):定义出一个处理请求的接口。如果需要,接口可以定义出一个方法以设定和返回对下家的引用。这个角色通常由一个抽象类或者接口实现。
- 具体处理者(Concrete Handler):具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。
- 客户类(Client):创建处理链,并向链头的具体处理者对象提交请求。
责任链模式的优点包括:
- 多个对象共同对一个任务进行处理,如多级审批系统,根据审批者的权限和级别将审批请求传递给下一个级别的审批者,直到获得最终的审批结果。
- 动态组合处理流程,通过灵活配置责任链,可以动态地组合处理对象,实现不同的处理流程。
- 避免请求的发送者和接收者之间的直接耦合,通过将请求传递给责任链,请求发送者无需知道具体的处理对象,减少了对象之间的依赖关系。
- 降低耦合度,将请求的发送者和接收者解耦,请求只管发送,不管谁来处理。
- 增强给对象指派的灵活性,通过改变链内的成员或者调动他们的次序,允许动态地新增或删除责任。
- 简化对象,使得对象不需要知道链的结构。
- 增加新的请求处理类很方便,遵循开闭原则,新的处理者可以随时被加入到责任链中,不需要修改已有代码,提供了良好的扩展性。
同时,责任链模式也存在一些缺点:
- 不能保证请求一定被接收,如果责任链没有被正确配置,或者某个处理者没有正确处理请求,可能会导致请求无法被处理。
- 性能问题,当责任链过长或者请求在责任链中被频繁传递时,可能会对性能产生影响。
- 调试不方便,当责任链特别是链条比较长,环节比较多的时候,由于采用了类似递归的方式,调试的时候逻辑可能比较复杂。
示例
以下是一个简单的责任链模式实现示例,用于处理日志消息,根据日志级别(如DEBUG、INFO、WARN、ERROR)将消息传递给不同的处理者:
// 抽象处理者
abstract class LogHandler {
protected int level;
protected LogHandler nextHandler;
public void setNextHandler(LogHandler nextHandler) {
this.nextHandler = nextHandler;
}
// 除了处理自己的逻辑,还会调用nextHandler进行处理
public void logMessage(int level, String message) {
if (this.level <= level) {
write(message);
}
if (nextHandler != null) {
nextHandler.logMessage(level, message);
}
}
abstract protected void write(String message);
}
// 具体处理者: ErrorLogHandler
class ErrorLogHandler extends LogHandler {
public ErrorLogHandler(int level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("ErrorLogHandler: " + message);
}
}
// 具体处理者: WarnLogHandler
class WarnLogHandler extends LogHandler {
public WarnLogHandler(int level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("WarnLogHandler: " + message);
}
}
// 具体处理者: InfoLogHandler
class InfoLogHandler extends LogHandler {
public InfoLogHandler(int level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("InfoLogHandler: " + message);
}
}
// 客户端代码
public class ChainPatternDemo {
private static LogHandler getChainOfLoggers() {
LogHandler errorLogHandler = new ErrorLogHandler(3);
LogHandler warnLogHandler = new WarnLogHandler(2);
warnLogHandler.setNextHandler(errorLogHandler);
LogHandler infoLogHandler = new InfoLogHandler(1);
infoLogHandler.setNextHandler(warnLogHandler);
return infoLogHandler;
}
public static void main(String[] args) {
LogHandler loggerChain = getChainOfLoggers();
loggerChain.logMessage(1, "This is an informational message.");
loggerChain.logMessage(2, "This is a warning message.");
loggerChain.logMessage(3, "This is an error message.");
}
}
在这个例子中,定义了三个具体的日志处理者类(ErrorLogHandler、WarnLogHandler、InfoLogHandler),它们分别处理不同级别的日志消息。每个处理者都包含一个级别(level),用于确定是否应该处理该级别的消息。当客户端发出日志请求时,请求将沿着链传播,直到找到能够处理该级别日志的处理者为止。
另一个常见的示例是请假审批系统。在这个系统中,可以根据审批者的权限和级别将审批请求传递给下一个级别的审批者,直到获得最终的审批结果。例如,学生请假小于或等于2天,班主任可以批准;小于或等于7天,系主任可以批准;小于或等于10天,院长可以批准;其他情况不予批准。可以使用责任链模式来设计这个请假条审批模块。
命令模式
描述
命令模式(Command Pattern)是一种行为型设计模式,它将一个请求封装为一个对象,从而使请求发送者和请求接收者解耦,使得请求发送者可以用不同的请求对接收者进行参数化,而不需要知道接收者的具体实现。命令模式的核心思想是将请求(行为)封装为对象,这样执行请求的责任和实现请求的责任可以分离开来,从而降低了系统组件之间的耦合度。
命令模式包含以下主要角色:
- 抽象命令类(Command) :声明执行命令的接口,通常包含一个执行命令的抽象方法
execute()
。 - 具体命令类(Concrete Command):是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
- 接收者(Receiver):执行命令功能的相关操作,是具体命令对象业务的真正实现者。
- 调用者/请求者(Invoker):是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。
命令模式的优点包括:
- 降低系统耦合度:命令模式将调用操作的对象与实现该操作的对象解耦,使得系统更加灵活和可扩展。
- 增加或删除命令方便:采用命令模式增加与删除命令不会影响其他类,它满足"开闭原则",对扩展比较灵活。
- 可以实现宏命令:命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
- 方便实现Undo和Redo操作:命令模式可以与备忘录模式结合,实现命令的撤销与恢复。
同时,命令模式也存在一些缺点:
- 可能产生大量具体命令类:因为针对每一个具体操作都需要设计一个具体命令类,这将增加系统的复杂性。
- 命令对象的管理可能增加开销:封装命令和管理命令对象的生命周期可能会增加系统的复杂性和运行开销。
示例
以下是一个简单的命令模式示例,用于模拟一个电视遥控器的操作:
// 抽象命令接口
interface Command {
void execute();
}
// 具体命令类:打开电视命令
class TurnOnTvCommand implements Command {
private Television tv;
public TurnOnTvCommand(Television tv) {
this.tv = tv;
}
@Override
public void execute() {
tv.on();
}
}
// 具体命令类:关闭电视命令
class TurnOffTvCommand implements Command {
private Television tv;
public TurnOffTvCommand(Television tv) {
this.tv = tv;
}
@Override
public void execute() {
tv.off();
}
}
// 接收者类:电视
class Television {
public void on() {
System.out.println("电视已打开");
}
public void off() {
System.out.println("电视已关闭");
}
}
// 调用者类:遥控器
class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
if (command != null) {
command.execute();
}
}
}
// 客户端代码
public class CommandPatternDemo {
public static void main(String[] args) {
Television tv = new Television();
Command turnOnCommand = new TurnOnTvCommand(tv);
Command turnOffCommand = new TurnOffTvCommand(tv);
RemoteControl remote = new RemoteControl();
remote.setCommand(turnOnCommand);
remote.pressButton(); // 输出:电视已打开
remote.setCommand(turnOffCommand);
remote.pressButton(); // 输出:电视已关闭
}
}
在这个例子中,定义了两个具体的命令类(TurnOnTvCommand
和TurnOffTvCommand
),它们分别实现了Command
接口中的execute()
方法。Television
类作为接收者,包含了打开和关闭电视的具体操作。RemoteControl
类作为调用者,通过setCommand()
方法设置要执行的命令,并通过pressButton()
方法执行该命令。
通过这个示例,可以看出命令模式将请求封装为对象,使得请求发送者(遥控器)和请求接收者(电视)之间解耦,从而提高了系统的灵活性和可扩展性。同时,通过定义不同的命令类,可以轻松地增加或删除命令,而不需要修改现有的代码。
中介者模式
描述
中介者模式(Mediator Pattern)是一种行为型设计模式,它通过引入一个中介者对象来简化多个对象之间的交互,使这些对象之间不需要直接引用彼此,从而降低了对象之间的耦合度,提高了系统的灵活性和可维护性。
中介者模式包含以下主要角色:
- 中介者(Mediator):定义了与各个同事对象交互的接口。它可以是抽象类或接口,通常包含注册同事对象、转发同事对象信息等方法。
- 具体中介者(Concrete Mediator):实现了中介者接口,负责协调各个同事对象之间的通信。它通常持有一个包含所有同事对象的集合,并在同事对象发生变化时负责通知其他相关对象。
- 同事类(Colleague):所有参与交互的对象,它们不直接通信,而是通过中介者来交流。同事类通常只知道中介者,而不直接与其他同事对象进行交互。
- 具体同事类(Concrete Colleague):实现了同事类接口的具体类,负责执行实际的业务逻辑。它们与中介者进行通信,并将变化通知中介者。
中介者模式的主要优点包括:
- 降低了对象之间的耦合度:通过引入中介者,类之间的依赖关系从"多对多"变成了"一对多",从而减少了对象之间的直接依赖关系。
- 集中控制:所有的通信和协调逻辑集中在中介者对象中,便于管理和维护。
- 易于扩展:如果需要增加新的同事类或更改协作方式,只需要修改中介者对象,而不需要修改每个同事类之间的关系。
然而,中介者模式也存在一些潜在缺点:
- 中介者对象可能变得过于复杂:如果系统中的对象很多,中介者对象可能需要处理大量的通信和协调逻辑,从而变得过于复杂和难以维护。
- 违反了单一职责原则:由于中介者需要管理对象间的复杂通信,它可能承担了过多的职责,导致不易于维护。
示例
以下是一个简单的中介者模式示例,用于模拟一个智能家庭系统中不同电器之间的交互:
// 抽象中介者接口
interface Mediator {
void register(String colleagueName, Colleague colleague);
void notify(Colleague colleague, String event);
}
// 具体中介者类
class ConcreteMediator implements Mediator {
private Map<String, Colleague> colleagues = new HashMap<>();
@Override
public void register(String colleagueName, Colleague colleague) {
colleagues.put(colleagueName, colleague);
}
@Override
public void notify(Colleague colleague, String event) {
// 根据事件类型和同事对象进行具体的通知逻辑
if (colleague instanceof Alarm && event.equals("RING")) {
Colleague coffeeMachine = colleagues.get("COFFEE_MACHINE");
if (coffeeMachine != null) {
coffeeMachine.receive("PREPARE_COFFEE");
}
Colleague curtains = colleagues.get("CURTAINS");
if (curtains != null) {
curtains.receive("CLOSE");
}
}
// 可以添加更多的事件处理逻辑
}
}
// 抽象同事类
abstract class Colleague {
protected Mediator mediator;
protected String name;
public Colleague(Mediator mediator, String name) {
this.mediator = mediator;
this.name = name;
mediator.register(name, this);
}
public abstract void send(String event);
public abstract void receive(String event);
}
// 具体同事类:闹钟
class Alarm extends Colleague {
public Alarm(Mediator mediator) {
super(mediator, "ALARM");
}
@Override
public void send(String event) {
mediator.notify(this, event);
}
@Override
public void receive(String event) {
// 闹钟通常不处理接收到的事件,这里仅作为示例
System.out.println("Alarm received: " + event);
}
}
// 具体同事类:咖啡机
class CoffeeMachine extends Colleague {
public CoffeeMachine(Mediator mediator) {
super(mediator, "COFFEE_MACHINE");
}
@Override
public void send(String event) {
// 咖啡机通常不发送事件,这里仅作为示例
mediator.notify(this, event);
}
@Override
public void receive(String event) {
if (event.equals("PREPARE_COFFEE")) {
System.out.println("Coffee machine is preparing coffee...");
}
}
}
// 具体同事类:窗帘
class Curtains extends Colleague {
public Curtains(Mediator mediator) {
super(mediator, "CURTAINS");
}
@Override
public void send(String event) {
// 窗帘通常不发送事件,这里仅作为示例
mediator.notify(this, event);
}
@Override
public void receive(String event) {
if (event.equals("CLOSE")) {
System.out.println("Curtains are closing...");
}
}
}
// 客户端代码
public class MediatorPatternDemo {
public static void main(String[] args) {
Mediator mediator = new ConcreteMediator();
Alarm alarm = new Alarm(mediator);
CoffeeMachine coffeeMachine = new CoffeeMachine(mediator);
Curtains curtains = new Curtains(mediator);
// 模拟闹钟响起事件
alarm.send("RING");
}
}
在这个例子中,定义了一个抽象的中介者接口Mediator
,以及一个具体的中介者实现类ConcreteMediator
。同时,还定义了抽象同事类Colleague
和三个具体的同事类Alarm
(闹钟)、CoffeeMachine
(咖啡机)和Curtains
(窗帘)。客户端代码中,创建了中介者对象和各个同事对象,并通过中介者注册了这些同事对象。然后,模拟了闹钟响起的事件,中介者接收到该事件后,根据事件类型和同事对象进行了具体的通知逻辑。
通过这个示例,可以看出中介者模式如何简化了多个对象之间的交互,并降低了它们之间的耦合度。
备忘录模式
描述
备忘录模式(Memento Pattern),又称快照模式,是一种行为型设计模式。它允许在不暴露对象实现细节的情况下捕获并保存其内部状态,以后可以将其恢复到先前的状态。该模式的核心是设计备忘录类及用于管理备忘录的管理者类,主要角色包括:
- 原发器(Originator):负责创建一个备忘录,用于记录当前状态,并可以使用备忘录恢复其内部状态。原发器定义了创建备忘录和恢复备忘录数据的方法。
- 备忘录(Memento):用于存储原发器对象的内部状态。它提供了一种将原发器恢复到先前状态的机制。备忘录通常只能被保存或恢复,而不能被修改,以确保状态的一致性和安全性。
- 负责人(Caretaker):负责存储备忘录,但不会操作或检查备忘录的内容。其主要目的是确保备忘录的安全存储,以便在需要时将其提供给原发器。负责人可以管理多个备忘录对象,实现撤销/恢复多步操作等功能。
备忘录模式的主要优点包括:
- 封装性良好:通过将状态保存在备忘录对象中,可以隐藏原发器对象的实现细节,使得原发器对象的状态变化对客户端透明,符合封装原则。
- 状态回滚能力:可以在不破坏封装性的情况下保存对象的内部状态,并能够在需要时将对象恢复到先前的状态,使得状态的回滚变得容易实现。
- 简化了原发器的职责:将状态保存和恢复的责任从原发器中分离出来,使得原发器的代码更加清晰简洁,只需要关注自身的业务逻辑。
然而,备忘录模式也存在一些潜在缺点:
- 资源消耗较大:如果原发器的状态需要频繁保存,并且备忘录对象占用大量内存空间,可能会导致资源消耗较大。
- 备忘录对象管理复杂性:如果需要管理多个备忘录对象,例如实现撤销/恢复多步操作,可能需要引入额外的复杂性来管理这些备忘录对象。
- 可能影响性能:备忘录模式的实现可能涉及对象的序列化和反序列化操作,这些操作可能会对系统性能产生一定影响,特别是在状态较大或频繁保存和恢复的情况下。
- 潜在的安全性问题:如果备忘录对象不受保护地暴露给外部,可能会引发潜在的安全性问题。
示例
以下是一个简单的备忘录模式示例,用于模拟文本编辑器的撤销功能:
// 备忘录类 - 保存文本编辑器的状态
class TextMemento {
private String text;
public TextMemento(String text) {
this.text = text;
}
public String getText() {
return text;
}
}
// 原发器类 - 文本编辑器
class TextEditor {
private String text;
public void setText(String text) {
this.text = text;
}
public String getText() {
return text;
}
public TextMemento save() {
return new TextMemento(text);
}
public void restore(TextMemento memento) {
this.text = memento.getText();
}
}
// 负责人类 - 管理备忘录
class Caretaker {
private TextMemento memento;
public void saveMemento(TextMemento memento) {
this.memento = memento;
}
public TextMemento retrieveMemento() {
return memento;
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
Caretaker caretaker = new Caretaker();
editor.setText("This is the initial text.");
caretaker.saveMemento(editor.save());
editor.setText("This is the modified text.");
// 恢复到之前的状态
editor.restore(caretaker.retrieveMemento());
System.out.println(editor.getText()); // 输出: This is the initial text.
}
}
在这个例子中,TextMemento
类用于存储文本编辑器的状态(即文本内容)。TextEditor
类作为原发器,提供了保存和恢复状态的方法。Caretaker
类作为负责人,负责管理备忘录对象。客户端代码中,创建了一个文本编辑器对象和一个负责人对象,并通过负责人保存了编辑器的初始状态。然后,修改了编辑器的文本内容,并通过负责人恢复了之前的状态。最后,输出了恢复后的文本内容,验证了备忘录模式的有效性。
状态模式
描述
状态模式(State Pattern)是设计模式的一种,属于对象行为型模式。它允许对象在内部状态改变时改变它的行为,对象看起来似乎修改了它的类。状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况,把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。
在状态模式中,有以下几个关键角色:
- 环境(Context)角色:也称为上下文,用于维护对象当前的状态,并在对象状态发生变化时触发对象行为的变化。它持有一个指向当前状态的引用,并且这个引用是可变的,以便随时切换到新的状态。
- 抽象状态(State)角色:定义了一个接口,用于封装对象在不同状态下的行为。它通常包含一个或多个方法,这些方法在不同的具体状态类中会有不同的实现。
- 具体状态(Concrete State)角色:实现了抽象状态接口,并定义了在不同状态下对象应该执行的行为。每个具体状态类都对应于对象的一个特定状态,并包含了在该状态下对象应该执行的具体行为。
状态模式的主要优点包括:
- 封装性良好:通过将对象在不同状态下的行为封装到不同的状态类中,使得代码更加清晰和易于维护。
- 灵活性高:当需要添加新的状态时,只需添加一个新的具体状态类,而不需要修改现有的代码。
- 可扩展性强:状态模式可以很容易地扩展新的状态和行为,而不需要修改现有的类。
- 简化了状态管理:通过状态模式,可以将对象的状态管理逻辑从业务逻辑中分离出来,使得代码更加简洁和易于理解。
然而,状态模式也存在一些潜在缺点:
- 类数量增多:对于每一个状态,都需要一个具体状态类,这可能会导致系统中类的数量增加。
- 状态转换逻辑复杂:如果状态转换逻辑非常复杂,可能会导致代码难以理解和维护。
- 状态管理开销大:在状态切换时,可能需要执行一些额外的操作,如保存和恢复状态等,这可能会增加系统的开销。
示例
以下是一个简单的状态模式示例,用于模拟银行账户在不同状态下的行为:
// 抽象状态类
abstract class AccountState {
protected Account account;
public AccountState(Account account) {
this.account = account;
}
public abstract void deposit(double amount); // 存款
public abstract void withdraw(double amount); // 取款
public abstract void computeInterest(); // 计算利息
public abstract void stateCheck(); // 检查和切换状态
}
// 正常状态类
class NormalState extends AccountState {
public NormalState(Account account) {
super(account);
}
@Override
public void deposit(double amount) {
account.setBalance(account.getBalance() + amount);
System.out.println("存款成功,当前余额:" + account.getBalance());
}
@Override
public void withdraw(double amount) {
if (account.getBalance() >= amount) {
account.setBalance(account.getBalance() - amount);
System.out.println("取款成功,当前余额:" + account.getBalance());
stateCheck();
} else {
System.out.println("余额不足,取款失败!");
}
}
@Override
public void computeInterest() {
// 正常状态下不计算利息
System.out.println("当前为正常状态,不计算利息。");
}
@Override
public void stateCheck() {
if (account.getBalance() < 0 && account.getBalance() > -2000) {
account.setState(new OverdraftState(account));
} else if (account.getBalance() <= -2000) {
account.setState(new RestrictedState(account));
}
}
}
// 透支状态类
class OverdraftState extends AccountState {
public OverdraftState(Account account) {
super(account);
}
@Override
public void deposit(double amount) {
account.setBalance(account.getBalance() + amount);
System.out.println("存款成功,当前余额:" + account.getBalance());
stateCheck();
}
@Override
public void withdraw(double amount) {
account.setBalance(account.getBalance() - amount);
System.out.println("取款成功(透支),当前余额:" + account.getBalance());
stateCheck();
}
@Override
public void computeInterest() {
// 透支状态下计算利息
System.out.println("透支状态下计算利息...");
}
@Override
public void stateCheck() {
if (account.getBalance() >= 0) {
account.setState(new NormalState(account));
} else if (account.getBalance() <= -2000) {
account.setState(new RestrictedState(account));
}
}
}
// 受限状态类
class RestrictedState extends AccountState {
public RestrictedState(Account account) {
super(account);
}
@Override
public void deposit(double amount) {
account.setBalance(account.getBalance() + amount);
System.out.println("存款成功,当前余额:" + account.getBalance());
stateCheck();
}
@Override
public void withdraw(double amount) {
System.out.println("账户受限,不能取款!");
}
@Override
public void computeInterest() {
// 受限状态下计算利息
System.out.println("受限状态下计算利息...");
}
@Override
public void stateCheck() {
if (account.getBalance() >= 0) {
account.setState(new NormalState(account));
} else if (account.getBalance() > -2000) {
account.setState(new OverdraftState(account));
}
}
}
// 账户类(环境类)
class Account {
private double balance;
private AccountState state;
public Account(double initialBalance) {
this.balance = initialBalance;
this.state = new NormalState(this);
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public void setState(AccountState state) {
this.state = state;
}
public void deposit(double amount) {
state.deposit(amount);
}
public void withdraw(double amount) {
state.withdraw(amount);
}
public void computeInterest() {
state.computeInterest();
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
Account account = new Account(0.0);
account.deposit(1000);
account.withdraw(2000);
account.deposit(3000);
account.withdraw(4000);
account.withdraw(1000);
account.computeInterest();
}
}
在这个例子中,AccountState
是一个抽象状态类,定义了账户在不同状态下的行为。NormalState
、OverdraftState
和RestrictedState
是具体状态类,分别实现了账户在正常状态、透支状态和受限状态下的行为。Account
类作为环境类,维护了当前的状态,并在状态发生变化时触发相应的行为。客户端代码中,创建了一个账户对象,并进行了存款、取款和计算利息等操作,观察了账户状态的变化和对应的行为。
访问者模式
描述
访问者模式(Visitor Pattern)是一种对象行为型模式,它允许将一组算法与对象结构分离。其主要思想是将要执行的操作封装到不同的访问者对象中,访问者通过访问对象结构(如树或集合),对结构中的各类对象进行特定操作。这样,在不修改对象结构的情况下,能够扩展新功能,实现开闭原则。
访问者模式的核心是将数据结构和对数据的操作分离开来,使其在不改变数据结构的前提下动态添加作用于这些元素上的操作。它将数据结构的定义和数据操作的定义分离开来,符合"单一职责"原则。通过定义不同的访问者实现对数据的不同操作,因此在需要给数据添加新的操作时,只需为其定义一个新的访问者即可。
访问者模式适用于以下场景:
- 操作稳定而功能变化频繁的对象结构:对象结构(例如元素的属性和方法)稳定,但对其不同种类的操作需求变化较快时,可以使用访问者模式,以便扩展操作,而不必修改对象本身。
- 需要对不同类型元素进行不同操作的复杂对象结构:如果有一个复杂对象结构,并且不同类型元素需要执行不同的操作,访问者模式能提供优雅的实现。
- 多种对象操作的分离:若操作类型较多且相互独立,访问者模式能将这些操作封装成独立的访问者类,提高可读性和维护性。
访问者模式的主要角色包括:
- 抽象访问者(Visitor):定义了一个访问元素的接口,为每类元素都定义了一个访问操作visit(),该操作中的参数类型对应被访问元素的数据类型。
- 具体访问者(ConcreteVisitor):抽象访问者的实现类,实现了不同访问者访问到元素后具体的操作行为。
- 抽象元素(Element):元素的抽象表示,定义了访问该元素的入口的accept()方法,不同的访问者类型代表不同的访问者。
- 具体元素(Concrete Element):实现抽象元素定义的accept()操作,并根据访问者的不同类型实现不同的业务逻辑。
- 对象结构(ObjectStructure):是元素的集合,允许访问者依次访问集合中的每个元素。
此外,访问者模式中的核心是双分派(Double Dispatch),这是一种调度机制,确保在访问者模式中,根据元素和访问者的实际类型选择合适的操作方法。
示例
以下是一个简单的访问者模式示例,模拟一个元素结构包括Book
和Fruit
两类商品,而访问者有PriceVisitor
和DiscountVisitor
,分别计算商品的原价和折扣价。
-
定义访问者接口 :
// 访问者接口,定义访问不同元素的操作方法 interface Visitor { // 访问Book元素的方法 void visit(Book book); // 访问Fruit元素的方法 void visit(Fruit fruit); }
-
定义具体访问者 :
// 具体访问者1:计算价格 class PriceVisitor implements Visitor { @Override public void visit(Book book) { System.out.println("Book Price: $" + book.getPrice()); } @Override public void visit(Fruit fruit) { System.out.println("Fruit Price: $" + fruit.getPricePerKg() * fruit.getWeight() + " for " + fruit.getWeight() + " kg"); } } // 具体访问者2:计算折扣价格 class DiscountVisitor implements Visitor { @Override public void visit(Book book) { System.out.println("Book Discounted Price: $" + (book.getPrice() * 0.9)); // 10% discount } @Override public void visit(Fruit fruit) { System.out.println("Fruit Discounted Price: $" + (fruit.getPricePerKg() * fruit.getWeight() * 0.85)); // 15% discount } }
-
定义元素接口 :
// 元素接口,定义了accept方法 interface ItemElement { // accept方法接收一个访问者对象,这里实现了第一层分派 void accept(Visitor visitor); }
-
定义具体元素 :
// 具体元素1:Book class Book implements ItemElement { private double price; public Book(double price) { this.price = price; } public double getPrice() { return price; } @Override public void accept(Visitor visitor) { // 第二层分派:根据访问者的具体类型,调用访问者对应的visit(Book)方法 visitor.visit(this); } } // 具体元素2:Fruit class Fruit implements ItemElement { private double pricePerKg; private double weight; public Fruit(double pricePerKg, double weight) { this.pricePerKg = pricePerKg; this.weight = weight; } public double getPricePerKg() { return pricePerKg; } public double getWeight() { return weight; } @Override public void accept(Visitor visitor) { // 第二层分派:根据访问者的具体类型,调用访问者对应的visit(Fruit)方法 visitor.visit(this); } }
-
使用访问者模式 :
public class VisitorPatternDemo { public static void main(String[] args) { // 创建具体元素对象 ItemElement book = new Book(20.0); ItemElement fruit = new Fruit(5.0, 2.0); // 创建具体访问者对象 Visitor priceVisitor = new PriceVisitor(); Visitor discountVisitor = new DiscountVisitor(); // 使用访问者模式访问元素 book.accept(priceVisitor); // 输出原价 book.accept(discountVisitor); // 输出折扣价 fruit.accept(priceVisitor); // 输出原价 fruit.accept(discountVisitor); // 输出折扣价 } }
在这个示例中,我们定义了一个访问者接口和两个具体访问者类,分别用于计算商品的原价和折扣价。同时,我们定义了一个元素接口和两个具体元素类,分别表示书籍和水果。通过使用访问者模式,我们可以在不修改元素类的情况下,动态地为元素添加新的操作。
解释器模式详
描述
解释器模式(Interpreter Pattern)是一种行为型设计模式,它主要用于描述如何构建一个解释器以解释特定的语言或表达式。该模式定义了一个文法表示和解释器的类结构,用于解释符合该文法规则的语句。解释器模式的核心思想是将一个复杂的表达式或句子通过一系列简单的类或对象组合起来进行解释,这些类或对象分别对应文法中的不同符号和规则。
解释器模式通常包含以下几个角色:
- 抽象表达式(Abstract Expression):定义一个接口,用于解释一个特定的文法规则。这个接口一般声明了一个解释方法(如interpret()),用于解释该表达式,并返回一个解释结果。所有的具体表达式都应该实现这个接口。
- 终结符表达式(Terminal Expression):实现了抽象表达式接口,对应于文法中的终结符。终结符是最基本的文法单位,它们不能再进一步分解。在解释器模式中,终结符表达式通常包含对终结符的具体解释逻辑。
- 非终结符表达式(Nonterminal Expression):同样实现了抽象表达式接口,对应于文法中的非终结符。非终结符是由其他文法单位(终结符或非终结符)组成的表达式。非终结符表达式通常包含对其他表达式的引用,以及一个解释方法,用于解释其包含的表达式。
- 上下文(Context)(可选):这是一个可选的角色,用于存储解释过程中需要的信息。在某些复杂的解释场景中,解释器可能需要依赖一些上下文信息来正确地解释表达式。环境对象可以被传递给解释器,以提供这些信息。
- 客户端(Client):负责构建抽象语法树(AST)。客户端将输入的表达式转换为一系列具体的表达式对象(终结符表达式和非终结符表达式),并将它们组合成一个语法树。然后,客户端调用语法树的根节点的解释方法,开始解释过程。
解释器模式的工作原理是通过构建一个抽象语法树(AST)来表示输入的表达式,然后通过递归或迭代的方式遍历这棵树,对每个节点(即表达式)进行解释和执行。在遍历过程中,如果遇到终结符节点,则直接返回其对应的值;如果遇到非终结符节点,则根据其包含的表达式和逻辑进行解释和执行。
解释器模式的优点包括:
- 易于改变和扩展:由于语法是由很多类表示的,因此容易通过修改或添加类来改变和扩展语言。
- 复用性:解释器模式中的解释器可以被不同的客户端使用,以执行不同的任务。
然而,解释器模式也存在一些缺点:
- 系统复杂度增加:如果语法规则数目太多,会导致系统复杂度增加,因为每个规则都需要一个对应的类来实现。
- 性能问题:由于解释器是通过递归或迭代的方式工作的,对于复杂的表达式,可能会导致性能问题。
示例
以下是一个基于解释器模式的简单计算器示例,它支持加法和减法运算:
// 抽象表达式接口
interface Expression {
int interpret();
}
// 加法表达式类
class AddExpression implements Expression {
private Expression left;
private Expression right;
public AddExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() + right.interpret();
}
}
// 减法表达式类
class SubtractExpression implements Expression {
private Expression left;
private Expression right;
public SubtractExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() - right.interpret();
}
}
// 数字表达式类
class NumberExpression implements Expression {
private int number;
public NumberExpression(int number) {
this.number = number;
}
@Override
public int interpret() {
return number;
}
}
// 客户端代码,构建表达式树并计算结果
public class Calculator {
public static void main(String[] args) {
Expression expression = new AddExpression(
new NumberExpression(10),
new SubtractExpression(
new NumberExpression(20),
new NumberExpression(5)
)
);
System.out.println("Result: " + expression.interpret()); // 输出 Result: 15
}
}
在这个示例中,定义了一个Expression
接口,它包含一个解释方法interpret()
。然后,实现了三个具体的表达式类:AddExpression
(用于表示加法表达式)、SubtractExpression
(用于表示减法表达式)和NumberExpression
(用于表示数字)。客户端代码构建了一个表达式树,并调用根节点的interpret()
方法来计算结果。
这个示例展示了如何使用解释器模式来解析和计算简单的数学表达式。通过扩展这个示例,可以支持更多的运算符和更复杂的表达式。
委派模式
描述
委派模式(Delegate Pattern),又称委托模式,是一种行为型设计模式,它允许对象组合实现与继承相同的代码重用。然而,它并不属于GoF(四人帮)提出的23种经典设计模式之一。委派模式的基本作用是负责任务的调用和分配,可以看作是一种特殊的静态代理模式,但代理模式更注重过程,而委派模式更注重结果。
委派模式通常包含以下几个角色:
- 抽象任务角色(Task):定义一个抽象接口,该接口有若干实现类。这些实现类代表具体的任务。
- 委派者角色(Delegate):负责在各个具体任务角色实例之间作出决策,判断并调用具体实现的方法。委派者角色通常也实现了抽象任务接口,以便能够与其他任务角色进行交互。
- 具体任务角色(Concrete Task):真正执行任务的角色,它们实现了抽象任务接口,并提供了具体的任务实现。
委派模式的工作流程如下:
- 委派者角色接收一个任务请求。
- 委派者角色根据某种策略(如随机、轮询、优先级等)选择一个具体任务角色来执行任务。
- 委派者角色调用所选具体任务角色的方法,以执行任务。
- 具体任务角色执行完任务后,将结果返回给委派者角色(如果需要)。
- 委派者角色可以根据需要将结果返回给请求者。
委派模式的优点包括:
- 代码重用:通过委派模式,可以将任务分配给不同的具体任务角色,从而实现代码的重用。
- 松耦合:委派者和具体任务角色之间的松耦合关系使得系统更易于扩展和维护。
- 灵活性:委派模式允许动态地选择任务执行者,从而增加了系统的灵活性。
然而,委派模式也存在一些缺点:
- 复杂性:在任务比较复杂的情况下,可能需要进行多重委派,这会增加系统的复杂性。
- 性能问题:如果委派逻辑过于复杂,可能会影响系统的性能。
示例
以下是一个简单的委派模式示例,用于模拟项目经理给员工分配任务:
// 抽象任务角色
interface Employee {
String doTask(String task);
}
// 具体任务角色A
class JavaEmployee implements Employee {
private String name = "Java后端";
@Override
public String doTask(String task) {
return "我" + name + ",开始努力工作,完成【" + task + "】任务";
}
}
// 具体任务角色B
class JsEmployee implements Employee {
private String name = "Js前端";
@Override
public String doTask(String task) {
return "我" + name + ",开始努力工作,完成【" + task + "】任务";
}
}
// 委派者角色
class Leader {
// 假设有一个员工映射表,用于存储员工和他们的任务
// 实际应用中,这个映射表可能是从数据库或配置文件中加载的
private Map<String, Employee> employeeMap = new ConcurrentHashMap<>();
public Leader() {
// 初始化员工映射表
employeeMap.put("JavaEmployee", new JavaEmployee());
employeeMap.put("JsEmployee", new JsEmployee());
}
public void doSomeThing(String employeeName, String taskName) {
System.out.println("委派执行开始");
Employee employee = employeeMap.get(employeeName);
if (employee == null) {
System.out.println("部门不存在该委派人员");
return;
}
String result = employee.doTask(taskName);
System.out.println(result);
System.out.println("委派执行结束");
}
}
// 客户端代码
public class DelegatePatternDemo {
public static void main(String[] args) {
Leader leader = new Leader();
leader.doSomeThing("JavaEmployee", "赵云打野");
System.out.println("-----------");
leader.doSomeThing("JsEmployee", "瑶妹辅助");
}
}
在这个示例中,我们定义了一个Employee
接口,以及两个具体任务角色JavaEmployee
和JsEmployee
。Leader
类作为委派者角色,负责将任务分配给具体的员工。客户端代码通过调用Leader
类的doSomeThing
方法,将任务分配给指定的员工,并打印出执行结果。
这个示例展示了如何使用委派模式来分配和执行任务。通过扩展这个示例,可以支持更多的员工和任务类型,从而实现更复杂的任务分配和管理。