1、设计模式
1.1、定义
设计模式是指在软件开发过程中,针对反复出现的问题所总结归纳出的通用解决方案。它就像是建筑师手中的蓝图,为开发者提供了一套经过验证的设计思路和方法,帮助他们更高效、更灵活地构建软件系统。
1.2、作用
- 提高软件可维护性:设计模式使得代码结构更加清晰,模块之间的职责划分明确。当软件系统需要进行修改或扩展时,开发者可以更容易地定位和修改相关代码,降低了维护的难度和成本。
- 增强软件可扩展性:良好的设计模式能够使软件系统更容易适应需求的变化。通过遵循设计模式的原则,开发者可以在不影响现有代码的基础上,方便地添加新的功能模块。
- 实现软件可复用性:设计模式提供了通用的解决方案,这些方案可以在不同的项目中重复使用。开发者可以直接借鉴已有的设计模式,避免了重复开发,提高了开发效率。
- 降低软件复杂度:在处理复杂的软件系统时,设计模式可以将问题分解为多个简单的子问题,并为每个子问题提供相应的解决方案。这样可以降低系统的整体复杂度,使开发过程更加可控。
1.3、分类
一般将 Java 设计模式分为三大类,共 23 种,以下是简要介绍:
- 创建型模式(Creational Patterns):主要用于对象的创建过程,将对象的创建和使用分离,提高了系统的灵活性和可维护性。包括单例模式、工厂方法模式、抽象工厂模式、建造者模式和原型模式。
- 结构型模式(Structural Patterns):关注如何将类或对象组合成更大的结构,以满足不同的需求。这些模式可以帮助开发者优化系统的结构,提高系统的可扩展性和可维护性。包括代理模式、适配器模式、桥接模式、装饰器模式、外观模式、享元模式和组合模式。
- 行为型模式(Behavioral Patterns):主要用于处理对象之间的交互和职责分配,描述了对象之间如何协作完成特定的任务。这些模式可以帮助开发者实现对象之间的解耦,提高系统的灵活性和可维护性。包括策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式和解释器模式。
2、创建型模式
2.1、单例模式
**特点:**通过单例模式的方法创建的类在当前进程中只有一个实例
**优点:**单例模式可以保证内存里只有一个实例,减少了内存的开销;可以避免对资源的多重占用。
**缺点:**单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码
**场景:**创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等;计数器应用等
2.1.1、饿汉模式(可以用到实际生产中)
特点:在定义类时直接new一个对象
public class Demo01 {
public static void main(String[] args) {
SingletonHungry s1 = SingletonHungry.getInstance();
SingletonHungry s2 = SingletonHungry.getInstance();
System.out.println(s1==s2);//true
}
}
class SingletonHungry{
//1.私有的静态的最终的对象--直接new出来
private static final SingletonHungry singl = new SingletonHungry();
//2.私有无参构造方法,外部不可创建
private SingletonHungry(){
}
//3.公共的静态实例方法
public static SingletonHungry getInstance(){
return singl;
}
}
优点:线程安全,执行效率高,类加载时初始化
缺点:可能浪费内存;有些情况实例的初始化是需要传某些参数的,这种场景就不适用了
2.1.2、懒汉式
特点:在使用类时初始化对象
2.1.2.1、第一种(线程不安全):
public class Demo02 {
public static void main(String[] args) {
SingletonLazy s1 = SingletonLazy.getInstance();
SingletonLazy s2 = SingletonLazy.getInstance();
System.out.println(s1 == s2);//true
}
}
class SingletonLazy {
//1.私有的静态变量对象
private static SingletonLazy singletonLazy;
//2.私有构造方法
private SingletonLazy() {
}
//3.公共的静态实例方法
public static SingletonLazy getInstance() {
if (singletonLazy == null) {
singletonLazy = new SingletonLazy();
}
return singletonLazy;
}
}
优点:执行效率高,类用时初始化,不会浪费内存
缺点:线程不安全,没有锁机制,不建议用到实际生产环境
2.1.2.2、第二种(线程安全):
public class Demo02 {
public static void main(String[] args) {
SingletonLazy s1 = SingletonLazy.getInstance();
SingletonLazy s2 = SingletonLazy.getInstance();
System.out.println(s1 == s2);
}
}
class SingletonLazy {
//1.私有的静态变量对象
private static SingletonLazy singletonLazy;
//2.私有构造方法
private SingletonLazy() {
}
//3.公共的静态实例方法 方法加锁
public static synchronized SingletonLazy getInstance() {
if (singletonLazy == null) {
singletonLazy = new SingletonLazy();
}
return singletonLazy;
}
}
优点:拥有方法锁,线程很安全,类用时初始化,不会浪费内存
缺点:执行效率慢,不建议用到实际生产环境
2.1.2.3、DCL双重检查锁(推荐用)
错误用法:
public class Demo03 {
public static void main(String[] args) {
SingletonDoubleInspection s1 = SingletonDoubleInspection.getInstance();
SingletonDoubleInspection s2 = SingletonDoubleInspection.getInstance();
System.out.println(s1 = s2);
}
}
class SingletonDoubleInspection {
//提供一个私有的静态变量对象
private static SingletonDoubleInspection instance;
//私有无参构造方法
private SingletonDoubleInspection() {
}
//向外部提供一个公开获取方法
public static SingletonDoubleInspection getInstance() {
if (instance == null) {
synchronized (SingletonDoubleInspection.class) {
if (instance == null) {
instance = new SingletonDoubleInspection();
}
}
}
return instance;
}
}
**错误点:**jvm将new对象的过程分为三步
-
给 instance 分配内存
-
调用 SingletonDoubleInspection 的构造函数来初始化成员变量
-
将instance对象指向分配的内存空间(执行完这步 instance == null就不成立了)
正常顺序执行这三条是没有问题的,但是在 JVM 中存在指令重排序的优化。如果没有依赖关系的指令之间的执行顺序是不一定的,也就是说上面的第2步和第3步的顺序是不能保证的,最终的执行顺序可能是 123 也可能是 132。如果是按照132执行的,那么在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了,但是此时对象还没有调用构造函数初始化,接下来线程二会直接返回 instance,然后使用,然后顺理成章地报错,因为得到的实例对象还没有初始化。
因为volatile关键字通过在指令前后加内存屏障的方式禁止指令重排序,所以我们只需要将 instance 变量声明成 volatile 就可以完成一个相对完美的双重检索机制的懒汉式单例模式的设计
正确用法:。
public class Demo03 {
public static void main(String[] args) {
SingletonDoubleInspection s1 = SingletonDoubleInspection.getInstance();
SingletonDoubleInspection s2 = SingletonDoubleInspection.getInstance();
System.out.println(s1 = s2);
}
}
class SingletonDoubleInspection {
//提供一个私有的静态变量对象
private volatile static SingletonDoubleInspection instance;
//私有无参构造方法
private SingletonDoubleInspection() {
}
//向外部提供一个公开获取方法
public static SingletonDoubleInspection getInstance() {
if (instance == null) {
synchronized (SingletonDoubleInspection.class) {
if (instance == null) {
instance = new SingletonDoubleInspection();
}
}
}
return instance;
}
}
优点:线程安全且在多线程下能保持高性能
2.1.2.4、静态内部类实现(推荐用)
public class Demo04 {
public static void main(String[] args) {
SingletonInside s1 = SingletonInside.getInstance();
SingletonInside s2 = SingletonInside.getInstance();
System.out.println(s1 == s2);
}
}
//静态内部类实现
class SingletonInside {
//私有构造方法,防止外部new
private SingletonInside() {
}
//提供一个私有的静态内部类
private static class SingletonHolder {
private final static SingletonInside INSTANCE = new SingletonInside();
}
//向外部提供一个公开获取方法
public static SingletonInside getInstance() {
return SingletonHolder.INSTANCE;
}
}
优点:静态内部类会在使用时加载,所以是懒汉式的,线程安全且在多线程下能保持高性能
2.1.3、枚举方法(推荐用)
public class Demo05 {
public static void main(String[] args) {
SingletonEnum s1 = SingletonEnum.INSTANCE;
SingletonEnum s2 = SingletonEnum.INSTANCE;
System.out.println(s1 == s2);
}
}
//枚举
enum SingletonEnum {
INSTANCE;
}
优点:线程很安全,没有锁机制,执行效率高
2.1.4、存在的问题
破坏单例模式:
使上面定义的单例类(Singleton)可以创建多个对象,枚举方式除外。有两种方式,分别是序列化和反射。
2.1.4.1、序列化、反序列化破坏单例模式
public class TestSequence {
public static void main(String[] args) throws Exception {
//往文件中写对象
writeObject2File();
//从文件中读取对象
SingletonSe s1 = readObjectFromFile();
SingletonSe s2 = readObjectFromFile();
//判断两个反序列化后的对象是否是同一个对象
System.out.println(s1 == s2);
}
private static SingletonSe readObjectFromFile() throws Exception {
//创建对象输入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\a.txt"));
//第一个读取Singleton对象
SingletonSe instance = (SingletonSe) ois.readObject();
return instance;
}
public static void writeObject2File() throws Exception {
//获取Singleton类的对象
SingletonSe instance = SingletonSe.getInstance();
//创建对象输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\a.txt"));
//将instance对象写出到文件中
oos.writeObject(instance);
}
}
class SingletonSe implements Serializable {
//私有构造方法
private SingletonSe() {}
private static class SingletonHolder {
private static final SingletonSe INSTANCE = new SingletonSe();
}
//对外提供静态方法获取该对象
public static SingletonSe getInstance() {
return SingletonHolder.INSTANCE;
}
}
上面代码运行结果是false,表明序列化和反序列化已经破坏了单例设计模式
解决方法:
在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。
class SingletonSe implements Serializable {
//私有构造方法
private SingletonSe() {}
private static class SingletonHolder {
private static final SingletonSe INSTANCE = new SingletonSe();
}
//对外提供静态方法获取该对象
public static SingletonSe getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* 下面是为了解决序列化反序列化破解单例模式
*/
private Object readResolve() {
return SingletonHolder.INSTANCE;
}
}
2.1.4.2、反射破坏单例模式
public class TestReflect {
public static void main(String[] args) throws Exception {
//获取Singleton类的字节码对象
Class clazz = Singleton.class;
//获取Singleton类的私有无参构造方法对象
Constructor constructor = clazz.getDeclaredConstructor();
//取消访问检查
constructor.setAccessible(true);
//创建Singleton类的对象s1
Singleton s1 = (Singleton) constructor.newInstance();
//创建Singleton类的对象s2
Singleton s2 = (Singleton) constructor.newInstance();
//判断通过反射创建的两个Singleton对象是否是同一个对象
System.out.println(s1 == s2);//结果为false
}
}
class Singleton {
//私有构造方法
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
//对外提供静态方法获取该对象
public static Singleton getInstance() {
return Singleton.SingletonHolder.INSTANCE;
}
}
解决方法:
通过在构造方法中做一些限制,一旦出现多次重复创建,则直接抛出异常
class Singleton {
//私有构造方法
private Singleton() {
/*
反射破解单例模式需要添加的代码
*/
if(SingletonHolder.INSTANCE != null) {
throw new RuntimeException("不允许通过反射机制创建多个实例");
}
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
//对外提供静态方法获取该对象
public static Singleton getInstance() {
return Singleton.SingletonHolder.INSTANCE;
}
}
2.1.5、单例模式的应用
例如:java.lang.Runtime
2.2、工厂方法模式
2.2.1、介绍
工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它定义了一个创建对象的接口,但让子类决定实例化哪个类。工厂方法模式将对象的创建延迟到子类中进行,使得一个类的实例化延迟到其子类。这样做的好处是,在不修改现有代码的情况下,可以通过创建新的子类来扩展对象的创建逻辑,符合开闭原则。
在工厂方法模式中,主要包含以下几个角色:
- 抽象产品(Product):定义了产品的接口,所有具体产品都要实现这个接口。
- 具体产品(ConcreteProduct):实现了抽象产品接口的具体类。
- 抽象工厂(Creator):声明了工厂方法,该方法返回一个抽象产品类型的对象。
- 具体工厂(ConcreteCreator):实现了抽象工厂中的工厂方法,负责创建具体的产品对象。
2.2.2、优缺点
优点
- 符合开闭原则:当需要新增产品时,只需要创建新的具体产品类和对应的具体工厂类,无需修改现有的抽象工厂和抽象产品类,提高了系统的可扩展性。
- 解耦对象的创建和使用:将对象的创建逻辑封装在具体工厂类中,客户端只需要调用工厂方法获取产品,而不需要关心产品的具体创建过程,降低了代码的耦合度。
- 代码复用性高:工厂方法可以在多个地方被复用,减少了代码的重复编写。
缺点
- 类的数量增加:每增加一个产品,就需要增加一个具体产品类和一个对应的具体工厂类,导致类的数量增多,增加了系统的复杂度。
- 实现复杂度增加:对于简单的对象创建场景,使用工厂方法模式会使代码变得复杂,增加了开发成本。
2.2.3、使用场景
- 对象创建过程复杂:当对象的创建过程涉及到复杂的逻辑,如初始化、配置等,将这些逻辑封装在工厂方法中可以使代码更加清晰和易于维护。
- 需要根据不同条件创建不同类型的对象:例如,根据用户的选择或系统的配置,动态地创建不同类型的对象。
- 系统需要支持多种产品类型,并且这些产品类型可能会不断扩展:使用工厂方法模式可以方便地添加新的产品类型,而不影响现有的代码。
2.2.4、代码案例
// 抽象产品接口
interface Shape {
void draw();
}
// 具体产品类:圆形
class Circle implements Shape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
// 具体产品类:矩形
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
// 抽象工厂接口
interface ShapeFactory {
Shape createShape();
}
// 具体工厂类:圆形工厂
class CircleFactory implements ShapeFactory {
@Override
public Shape createShape() {
return new Circle();
}
}
// 具体工厂类:矩形工厂
class RectangleFactory implements ShapeFactory {
@Override
public Shape createShape() {
return new Rectangle();
}
}
//客户端代码
public class Client {
public static void main(String[] args) {
// 创建圆形工厂
ShapeFactory circleFactory = new CircleFactory();
Shape circle = circleFactory.createShape();
circle.draw();
// 创建矩形工厂
ShapeFactory rectangleFactory = new RectangleFactory();
Shape rectangle = rectangleFactory.createShape();
rectangle.draw();
}
}
2.2.5、Java 中的实际应用
- Java 集合框架:在 Java 集合框架中,
Collection接口的iterator()方法就是一个工厂方法。Collection是抽象工厂,Iterator是抽象产品,不同的集合类(如ArrayList、LinkedList等)是具体工厂,它们实现了iterator()方法来创建不同的迭代器对象(具体产品)。 java.util.Calendar类:Calendar类的getInstance()方法也是一个工厂方法。它根据不同的时区和语言环境等条件,创建不同类型的Calendar对象(如GregorianCalendar等)。
2.2.6、对比简单工厂模式
2.2.6.1、结构差异
简单工厂模式
简单工厂模式只有一个工厂类,该工厂类负责所有产品的创建。它通常包含一个根据不同参数来创建不同产品对象的方法。主要包含以下角色:
- 工厂类:核心部分,负责创建所有具体产品的实例。
- 抽象产品类:定义了具体产品的公共接口。
- 具体产品类:实现了抽象产品类的接口。
工厂方法模式
工厂方法模式将工厂类进行抽象,定义了一个抽象工厂接口或抽象类,其中包含一个抽象的工厂方法。具体的产品创建由具体的工厂子类来完成。主要角色有:
- 抽象工厂类:声明了工厂方法,返回一个抽象产品类型的对象。
- 具体工厂类:实现了抽象工厂类中的工厂方法,负责创建具体的产品对象。
- 抽象产品类:定义了产品的接口。
- 具体产品类:实现了抽象产品类的接口。
2.2.6.2、扩展性差异
简单工厂模式
简单工厂模式不符合开闭原则(对扩展开放,对修改关闭)。当需要新增产品时,必须修改工厂类的代码,在创建产品的方法中添加新的条件分支来创建新的产品对象。这可能会引入新的错误,并且当产品种类较多时,工厂类的代码会变得复杂,难以维护。
工厂方法模式
工厂方法模式符合开闭原则。当需要新增产品时,只需要创建新的具体产品类和对应的具体工厂类,无需修改现有的抽象工厂和抽象产品类。这样可以方便地扩展系统的功能,降低了代码的耦合度。
2.2.6.3、代码复杂度差异
简单工厂模式
简单工厂模式的代码结构相对简单,只有一个工厂类,易于理解和实现。对于产品种类较少且不经常变化的场景,简单工厂模式是一个不错的选择。
工厂方法模式
工厂方法模式的代码结构相对复杂,需要定义抽象工厂类和多个具体工厂类。但它的灵活性更高,适用于产品种类较多且需要频繁扩展的场景。
2.2.6.4、简单工厂模式代码示例
// 抽象产品类
interface Shape {
void draw();
}
// 具体产品类:圆形
class Circle implements Shape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
// 具体产品类:矩形
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
// 工厂类
class ShapeFactory {
public static Shape createShape(String shapeType) {
if ("circle".equalsIgnoreCase(shapeType)) {
return new Circle();
} else if ("rectangle".equalsIgnoreCase(shapeType)) {
return new Rectangle();
}
return null;
}
}
// 客户端代码
public class SimpleFactoryClient {
public static void main(String[] args) {
Shape circle = ShapeFactory.createShape("circle");
circle.draw();
Shape rectangle = ShapeFactory.createShape("rectangle");
rectangle.draw();
}
}
2.3、抽象工厂模式
2.3.1、介绍
抽象工厂模式(Abstract Factory Pattern)是一种创建型设计模式,它提供了一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。抽象工厂模式与工厂方法模式的最大区别在于,抽象工厂模式可以创建多个产品族中的产品对象,而工厂方法模式只能创建一个产品等级中的产品对象。
在抽象工厂模式中,主要包含以下角色:
- 抽象工厂(Abstract Factory):声明了一组用于创建产品对象的方法。
- 具体工厂(Concrete Factory):实现了抽象工厂接口,负责创建具体产品对象。
- 抽象产品(Abstract Product):为一类产品对象声明接口。
- 具体产品(Concrete Product):实现抽象产品接口的具体产品。
2.3.2、优缺点
优点:
- 分离了具体的类:客户端使用抽象工厂创建需要的对象,隔离了客户端与具体类的实现。
- 易于交换产品系列:一个具体工厂类在一个应用中只需要初始化一次。
- 有利于产品的一致性:当一个系列中的产品对象被设计成一起工作时,一个应用只会使用同一个系列中的对象。
缺点:
- 扩展新的产品等级结构麻烦:如果需要增加新的产品等级结构,需要修改抽象工厂和所有的具体工厂类。
- 产品族扩展困难:增加新的产品族需要修改所有的工厂角色。
2.3.3、使用场景
- 系统需要独立于产品的创建、组合和表示时。
- 系统要由多个产品系列中的一个来配置时。
- 要强调一系列相关的产品对象的设计以便进行联合使用时。
- 提供一个产品类库,想隐藏产品的具体实现时。
2.3.4、代码示例
以电子产品工厂为例,可以生产手机和电脑两种产品:
// 抽象产品:手机
interface Phone {
void call();
}
// 抽象产品:电脑
interface Computer {
void process();
}
// 具体产品:小米手机
class XiaomiPhone implements Phone {
@Override
public void call() {
System.out.println("使用小米手机打电话");
}
}
// 具体产品:小米电脑
class XiaomiComputer implements Computer {
@Override
public void process() {
System.out.println("使用小米电脑处理数据");
}
}
// 具体产品:华为手机
class HuaweiPhone implements Phone {
@Override
public void call() {
System.out.println("使用华为手机打电话");
}
}
// 具体产品:华为电脑
class HuaweiComputer implements Computer {
@Override
public void process() {
System.out.println("使用华为电脑处理数据");
}
}
// 抽象工厂
interface ElectronicFactory {
Phone createPhone();
Computer createComputer();
}
// 具体工厂:小米工厂
class XiaomiFactory implements ElectronicFactory {
@Override
public Phone createPhone() {
return new XiaomiPhone();
}
@Override
public Computer createComputer() {
return new XiaomiComputer();
}
}
// 具体工厂:华为工厂
class HuaweiFactory implements ElectronicFactory {
@Override
public Phone createPhone() {
return new HuaweiPhone();
}
@Override
public Computer createComputer() {
return new HuaweiComputer();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
// 使用小米工厂
ElectronicFactory xiaomiFactory = new XiaomiFactory();
Phone xiaomiPhone = xiaomiFactory.createPhone();
Computer xiaomiComputer = xiaomiFactory.createComputer();
xiaomiPhone.call();
xiaomiComputer.process();
// 使用华为工厂
ElectronicFactory huaweiFactory = new HuaweiFactory();
Phone huaweiPhone = huaweiFactory.createPhone();
Computer huaweiComputer = huaweiFactory.createComputer();
huaweiPhone.call();
huaweiComputer.process();
}
}
2.3.5、Java中的实际应用
-
javax.xml.parsers包:
DocumentBuilderFactory用于创建DOM解析器SAXParserFactory用于创建SAX解析器
-
java.sql包:
Connection接口提供了创建Statement和PreparedStatement的方法
-
AWT (Abstract Window Toolkit):
- 不同的操作系统有不同的视觉组件实现
2.4、建造者模式
2.4.1、介绍
建造者模式(Builder Pattern)是一种创建型设计模式,它允许您分步骤创建复杂对象。该模式特别适用于需要创建的对象具有多个组成部分的情况,它可以把复杂对象的创建过程抽象出来,使这个创建过程可以构造出不同表示的对象。
建造者模式主要包含以下角色:
- 产品(Product):要创建的复杂对象。
- 抽象建造者(Builder):规定创建产品各个部件的接口。
- 具体建造者(Concrete Builder):实现抽象建造者接口。
- 指挥者(Director):调用建造者来创建产品对象。
2.4.2、优缺点
优点:
- 可以精细地控制产品的创建过程
- 将复杂对象的创建过程分解成多个简单步骤
- 同样的创建过程可以创建不同的产品
- 隔离了复杂对象的创建和使用
缺点:
- 产品必须有共同点,范围有限制
- 如果内部变化复杂,会有很多的建造类
2.4.3、使用场景
- 需要生成的对象具有复杂的内部结构
- 需要生成的对象内部属性本身相互依赖
- 对象的创建过程独立于创建该对象的类
- 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品
2.4.4、代码示例
以创建一个计算机为例:
// 产品类
class Computer {
private String cpu;
private String ram;
private String storage;
private String gpu;
public void setCpu(String cpu) {
this.cpu = cpu;
}
public void setRam(String ram) {
this.ram = ram;
}
public void setStorage(String storage) {
this.storage = storage;
}
public void setGpu(String gpu) {
this.gpu = gpu;
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", ram='" + ram + '\'' +
", storage='" + storage + '\'' +
", gpu='" + gpu + '\'' +
'}';
}
}
// 抽象建造者
interface ComputerBuilder {
void buildCPU();
void buildRAM();
void buildStorage();
void buildGPU();
Computer getResult();
}
// 具体建造者:游戏电脑
class GamingComputerBuilder implements ComputerBuilder {
private Computer computer = new Computer();
@Override
public void buildCPU() {
computer.setCpu("Intel i9 处理器");
}
@Override
public void buildRAM() {
computer.setRam("32GB 高速内存");
}
@Override
public void buildStorage() {
computer.setStorage("2TB SSD");
}
@Override
public void buildGPU() {
computer.setGpu("RTX 3080显卡");
}
@Override
public Computer getResult() {
return computer;
}
}
// 具体建造者:办公电脑
class OfficeComputerBuilder implements ComputerBuilder {
private Computer computer = new Computer();
@Override
public void buildCPU() {
computer.setCpu("Intel i5 处理器");
}
@Override
public void buildRAM() {
computer.setRam("8GB 内存");
}
@Override
public void buildStorage() {
computer.setStorage("512GB SSD");
}
@Override
public void buildGPU() {
computer.setGpu("集成显卡");
}
@Override
public Computer getResult() {
return computer;
}
}
// 指挥者
class Director {
public Computer construct(ComputerBuilder builder) {
builder.buildCPU();
builder.buildRAM();
builder.buildStorage();
builder.buildGPU();
return builder.getResult();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Director director = new Director();
ComputerBuilder gamingBuilder = new GamingComputerBuilder();
Computer gamingComputer = director.construct(gamingBuilder);
System.out.println("游戏电脑配置:" + gamingComputer);
ComputerBuilder officeBuilder = new OfficeComputerBuilder();
Computer officeComputer = director.construct(officeBuilder);
System.out.println("办公电脑配置:" + officeComputer);
}
}
2.4.5、Java中的实际应用
-
StringBuilder和StringBuffer: 这两个类都使用了建造者模式,它们提供了append()和insert()等方法来构建字符串对象。
-
Lombok的@Builder注解: Lombok库提供的@Builder注解可以自动生成建造者模式相关的代码。
-
DocumentBuilder类: javax.xml.parsers.DocumentBuilder使用建造者模式来解析XML文档。
2.5、原型模式
2.5.1、介绍
原型模式(Prototype Pattern)是一种创建型设计模式,它通过复制(克隆)一个已有对象来创建新的对象,而不是通过new关键字直接创建。这种模式特别适用于创建对象的成本比较大,而且同一类型的对象之间差异较小的情况。
在原型模式中,我们创建一个原型对象,然后通过克隆这个原型对象来创建新对象。这样做的好处是,我们可以避免重复创建对象时的复杂初始化过程。
2.5.2、优缺点
优点:
- 性能优良,内存中直接拷贝,比直接new一个对象性能好
- 可以避免构造函数的约束,直接在内存中拷贝
- 简化对象的创建过程,无需知道对象创建的细节
缺点:
- 对象必须提供克隆方法,对类的功能增加了约束
- 克隆方法位于类的内部,当对已有类进行改造的时候,需要修改代码
- 在实现深克隆时可能需要比较复杂的代码
2.5.3、使用场景
- 资源优化场景:类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等
- 性能和安全要求的场景:通过new产生一个对象需要非常繁琐的数据准备或访问权限
- 一个对象多个修改者的场景:一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时
2.5.4、代码示例
// 原型接口
interface Prototype extends Cloneable {
Prototype clone();
String getType();
}
// 具体原型类
class ConcretePrototype implements Prototype {
private String type;
public ConcretePrototype(String type) {
this.type = type;
}
@Override
public Prototype clone() {
try {
return (Prototype) super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
@Override
public String getType() {
return type;
}
}
// 深克隆示例
class DeepPrototype implements Cloneable {
private String name;
private ArrayList<String> hobbies;
public DeepPrototype(String name) {
this.name = name;
this.hobbies = new ArrayList<>();
}
public void addHobby(String hobby) {
hobbies.add(hobby);
}
@Override
public DeepPrototype clone() {
try {
DeepPrototype clone = (DeepPrototype) super.clone();
// 深克隆
clone.hobbies = new ArrayList<>(this.hobbies);
return clone;
} catch (CloneNotSupportedException e) {
return null;
}
}
@Override
public String toString() {
return "DeepPrototype{name='" + name + "', hobbies=" + hobbies + '}';
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
// 浅克隆示例
ConcretePrototype original = new ConcretePrototype("Type A");
ConcretePrototype clone = (ConcretePrototype) original.clone();
System.out.println("Original type: " + original.getType());
System.out.println("Clone type: " + clone.getType());
// 深克隆示例
DeepPrototype originalDeep = new DeepPrototype("John");
originalDeep.addHobby("Reading");
originalDeep.addHobby("Swimming");
DeepPrototype cloneDeep = originalDeep.clone();
System.out.println("Original: " + originalDeep);
System.out.println("Clone: " + cloneDeep);
}
}
2.5.5、Java中的实际应用
-
Object类的clone()方法: Java中所有类的父类Object都提供了clone()方法,这就是原型模式的典型应用。
-
ArrayList的clone()方法: ArrayList实现了Cloneable接口,提供了浅克隆功能。
-
Spring框架中的原型bean: Spring框架中,当bean的scope设置为prototype时,每次获取bean都会创建一个新的对象。
2.5.6、浅克隆vs深克隆
-
浅克隆(Shallow Clone):
- 只复制对象本身,包括基本数据类型
- 引用类型属性仍指向原对象的引用
- 实现简单,但可能会导致对象状态的混乱
-
深克隆(Deep Clone):
- 复制对象本身及其引用的所有对象
- 创建一个完全独立的对象副本
- 实现较复杂,但对象状态完全独立
3、结构型模式
3.1、代理模式
3.1.1、介绍
代理模式(Proxy Pattern)是一种结构型设计模式,它允许通过创建一个代理对象来控制对其他对象的访问。代理对象起到中介的作用,可以在访问对象时添加额外的逻辑,如权限控制、延迟加载、日志记录等。
代理模式主要包含以下角色:
- 抽象主题(Subject):定义代理类和真实主题的共同接口
- 真实主题(Real Subject):定义代理所代表的真实对象
- 代理(Proxy):包含对真实主题的引用,可以在对真实主题的操作前后添加额外操作
3.1.2、优缺点
优点:
- 职责清晰:真实主题就是实现实际的业务逻辑,不用关心其他非本职责的事务
- 高扩展性:代理类可以在不修改目标对象的前提下扩展目标对象的功能
- 智能化:动态代理可以自动生成代理类,简化开发
缺点:
- 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂
3.1.3、使用场景
- 远程代理:为一个位于不同的地址空间的对象提供一个本地的代理对象
- 虚拟代理:根据需要创建开销很大的对象,通过代理模式来延迟对象的创建
- 安全代理:用来控制真实对象访问时的权限
- 智能指引:调用目标对象时,代理添加额外的操作(如计算函数执行时间等)
3.1.4、代码示例
// 抽象主题
interface Subject {
void request();
}
// 真实主题
class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject handles request");
}
}
// 静态代理
class StaticProxy implements Subject {
private RealSubject realSubject;
public StaticProxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
System.out.println("Proxy: Before request");
realSubject.request();
System.out.println("Proxy: After request");
}
}
// 动态代理
class DynamicProxy implements InvocationHandler {
private Object target;
public DynamicProxy(Object target) {
this.target = target;
}
public static Object createProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new DynamicProxy(target)
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Dynamic Proxy: Before request");
Object result = method.invoke(target, args);
System.out.println("Dynamic Proxy: After request");
return result;
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
// 静态代理示例
RealSubject realSubject = new RealSubject();
StaticProxy staticProxy = new StaticProxy(realSubject);
staticProxy.request();
System.out.println("\n-------------------\n");
// 动态代理示例
Subject dynamicProxy = (Subject) DynamicProxy.createProxy(realSubject);
dynamicProxy.request();
}
}
3.1.5、Java中的实际应用
-
Spring AOP: Spring框架中的AOP功能就是通过动态代理实现的,包括JDK动态代理和CGLIB代理。
-
Java RMI: 远程方法调用(RMI)使用代理模式来处理远程对象。
-
Hibernate延迟加载: Hibernate中的延迟加载机制就是通过代理模式实现的。
3.1.6、代理模式的分类
-
静态代理:
- 代理类在编译时就已经确定
- 需要为每个服务都创建代理类
- 工作量大,不易维护
-
动态代理:
- JDK动态代理:基于接口的代理,通过反射机制生成代理类
- CGLIB动态代理:基于类的代理,通过生成子类来实现
- 运行时动态生成代理类,更加灵活
3.2、适配器模式
3.2.1、介绍
适配器模式(Adapter Pattern)是一种结构型设计模式,它允许将一个类的接口转换成客户端所期望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的类可以一起工作。
适配器模式主要包含以下角色:
- 目标接口(Target):客户端所期望的接口
- 适配者(Adaptee):需要被适配的类
- 适配器(Adapter):将适配者转换成目标接口的类
3.2.2、优缺点
优点:
- 可以让任何两个没有关联的类一起运行
- 提高了类的复用性
- 增加了类的透明度
- 灵活性好,可以适配多个不同的类
缺点:
- 过多使用适配器会让系统变得凌乱,不易整体把握
- 由于JAVA至多继承一个类,所以至多只能适配一个适配者类
3.2.3、使用场景
- 系统需要使用现有的类,而这些类的接口不符合系统的需要
- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的类一起工作
- 需要统一多个类的接口设计时
- 旧系统升级和改造时
3.2.4、代码示例
// 目标接口
interface Target {
void request();
}
// 适配者
class Adaptee {
public void specificRequest() {
System.out.println("适配者的特殊请求");
}
}
// 类适配器(通过继承)
class ClassAdapter extends Adaptee implements Target {
@Override
public void request() {
specificRequest();
}
}
// 对象适配器(通过组合)
class ObjectAdapter implements Target {
private Adaptee adaptee;
public ObjectAdapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
// 使用类适配器
Target classAdapter = new ClassAdapter();
classAdapter.request();
// 使用对象适配器
Adaptee adaptee = new Adaptee();
Target objectAdapter = new ObjectAdapter(adaptee);
objectAdapter.request();
}
}
3.2.5、Java中的实际应用
-
InputStreamReader和OutputStreamWriter: 这两个类就是适配器模式的应用,它们将字节流转换成字符流。
-
Arrays.asList(): 将数组转换为List集合的方法。
-
javax.xml.bind.annotation.adapters.XmlAdapter: 用于XML数据绑定的适配器。
3.2.6、适配器模式的分类
-
类适配器:
- 通过继承实现适配
- 单继承的限制使得使用场景受限
- 可以重写适配者的方法
-
对象适配器:
- 通过组合实现适配
- 更加灵活,推荐使用
- 可以适配多个适配者
3.3、桥接模式
3.3.1、介绍
桥接模式(Bridge Pattern)是一种结构型设计模式,它将抽象部分与实现部分分离,使它们都可以独立地变化。这种模式通过组合的方式建立两个类之间的联系,而不是继承。
桥接模式主要包含以下角色:
- 抽象类(Abstraction):定义抽象类的接口
- 扩展抽象类(Refined Abstraction):扩展抽象类
- 实现者接口(Implementor):定义实现者的接口
- 具体实现者(Concrete Implementor):实现实现者接口
3.3.2、优缺点
优点:
- 分离抽象接口及其实现部分
- 提高了系统的可扩充性
- 实现细节对客户透明,可以对用户隐藏实现细节
缺点:
- 增加了系统的理解与设计难度
- 需要正确地识别出系统中两个独立变化的维度
3.3.3、使用场景
- 不希望在抽象和实现部分之间有一个固定的绑定关系
- 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充
- 一个类存在多个独立变化的维度,且这些维度都需要进行扩展
3.3.4、代码示例
// 实现者接口
interface DrawAPI {
void drawCircle(int radius, int x, int y);
}
// 具体实现者
class RedCircle implements DrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: red, radius: " + radius +
", x: " + x + ", y: " + y + "]");
}
}
class BlueCircle implements DrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: blue, radius: " + radius +
", x: " + x + ", y: " + y + "]");
}
}
// 抽象类
abstract class Shape {
protected DrawAPI drawAPI;
protected Shape(DrawAPI drawAPI) {
this.drawAPI = drawAPI;
}
public abstract void draw();
}
// 扩展抽象类
class Circle extends Shape {
private int x, y, radius;
public Circle(int x, int y, int radius, DrawAPI drawAPI) {
super(drawAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
public void draw() {
drawAPI.drawCircle(radius, x, y);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Shape redCircle = new Circle(100, 100, 10, new RedCircle());
Shape blueCircle = new Circle(200, 200, 20, new BlueCircle());
redCircle.draw();
blueCircle.draw();
}
}
3.3.5、Java中的实际应用
-
JDBC:
- Driver接口就是一个桥接模式的应用
- 不同数据库厂商实现各自的Driver
-
AWT中的Peer架构:
- 在不同的操作系统上实现不同的界面风格
-
SLF4J日志框架:
- 提供统一的日志接口,底层可以对接不同的日志实现
3.3.6、桥接模式vs适配器模式
-
目的不同:
- 桥接模式:将抽象与实现分离,使它们可以独立变化
- 适配器模式:使不兼容的接口可以一起工作
-
使用时机:
- 桥接模式:在系统设计之初就使用
- 适配器模式:系统设计完成后使用
3.4、装饰器模式
3.4.1、介绍
装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许向一个现有的对象添加新的功能,同时又不改变其结构。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
装饰器模式主要包含以下角色:
- 抽象组件(Component):定义一个对象接口,可以给这些对象动态添加职责
- 具体组件(ConcreteComponent):定义一个具体的对象,也可以给这个对象添加一些职责
- 抽象装饰类(Decorator):维持一个指向Component对象的引用,并定义一个与Component接口一致的接口
- 具体装饰类(ConcreteDecorator):具体的装饰对象,给Component添加职责
3.4.2、优缺点
优点:
- 装饰类和被装饰类可以独立发展,不会相互耦合
- 装饰模式是继承的一个替代模式,可以动态扩展一个实现类的功能
- 通过使用不同的装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合
缺点:
- 会产生很多小对象,增加系统的复杂度
- 装饰模式比继承更加灵活,但也更加容易出错,排错也更困难
3.4.3、使用场景
- 需要扩展一个类的功能,或给一个类添加附加职责
- 需要动态地给一个对象添加功能,这些功能可以再动态地撤销
- 需要增加由一些基本功能的排列组合而产生的非常大量的功能
3.4.4、代码示例
// 抽象组件
interface Coffee {
double getCost();
String getDescription();
}
// 具体组件
class SimpleCoffee implements Coffee {
@Override
public double getCost() {
return 1;
}
@Override
public String getDescription() {
return "Simple coffee";
}
}
// 抽象装饰类
abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
@Override
public double getCost() {
return decoratedCoffee.getCost();
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
}
// 具体装饰类
class Milk extends CoffeeDecorator {
public Milk(Coffee coffee) {
super(coffee);
}
@Override
public double getCost() {
return super.getCost() + 0.5;
}
@Override
public String getDescription() {
return super.getDescription() + ", milk";
}
}
class Sugar extends CoffeeDecorator {
public Sugar(Coffee coffee) {
super(coffee);
}
@Override
public double getCost() {
return super.getCost() + 0.2;
}
@Override
public String getDescription() {
return super.getDescription() + ", sugar";
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Coffee coffee = new SimpleCoffee();
System.out.println("Cost: " + coffee.getCost() + "; Description: " + coffee.getDescription());
coffee = new Milk(coffee);
System.out.println("Cost: " + coffee.getCost() + "; Description: " + coffee.getDescription());
coffee = new Sugar(coffee);
System.out.println("Cost: " + coffee.getCost() + "; Description: " + coffee.getDescription());
}
}
3.4.5、Java中的实际应用
-
Java I/O流体系:
- InputStream的各种装饰类(BufferedInputStream, DataInputStream等)
- OutputStream的各种装饰类(BufferedOutputStream, DataOutputStream等)
-
Servlet API:
- HttpServletRequestWrapper
- HttpServletResponseWrapper
-
Spring框架:
- TransactionAwareCacheDecorator
- BeanDefinitionDecorator
3.4.6、装饰器模式vs代理模式
-
目的不同:
- 装饰器模式:为对象添加新功能
- 代理模式:控制对对象的访问
-
关系不同:
- 装饰器模式:装饰类和被装饰类可以独立发展
- 代理模式:代理类和实际类之间通常会有关联关系
-
使用方式不同:
- 装饰器模式:通常会形成一条装饰链
- 代理模式:通常只有一个代理类对应一个实际类
3.5、外观模式
3.5.1、介绍
外观模式(Facade Pattern)是一种结构型设计模式,它提供了一个统一的接口,用来访问子系统中的一群接口。外观模式定义了一个高层接口,让子系统更容易使用。
外观模式主要包含以下角色:
- 外观(Facade):知道哪些子系统类负责处理请求,将客户的请求代理给适当的子系统对象
- 子系统类(Subsystem classes):实现子系统的功能,处理外观对象指派的任务
3.5.2、优缺点
优点:
- 对客户屏蔽子系统组件,减少了客户处理的对象数目
- 实现了子系统与客户之间的松耦合关系
- 更好的划分访问层次,提高了安全性
缺点:
- 不符合开闭原则,修改很麻烦
- 如果设计不当,增加新的子系统可能需要修改外观类的源代码
3.5.3、使用场景
- 要为一个复杂子系统提供一个简单接口时
- 客户程序与多个子系统之间存在很大的依赖性
- 需要构建一个层次结构的子系统时
3.5.4、代码示例
// 子系统类
class CPU {
public void freeze() {
System.out.println("CPU freezing...");
}
public void jump(long position) {
System.out.println("CPU jumping to position " + position);
}
public void execute() {
System.out.println("CPU executing commands...");
}
}
class Memory {
public void load(long position, byte[] data) {
System.out.println("Memory loading from position " + position);
}
}
class HardDrive {
public byte[] read(long lba, int size) {
System.out.println("HardDrive reading sector " + lba);
return new byte[size];
}
}
// 外观类
class ComputerFacade {
private CPU cpu;
private Memory memory;
private HardDrive hardDrive;
public ComputerFacade() {
this.cpu = new CPU();
this.memory = new Memory();
this.hardDrive = new HardDrive();
}
public void start() {
cpu.freeze();
memory.load(0, hardDrive.read(0, 1024));
cpu.jump(0);
cpu.execute();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
ComputerFacade computer = new ComputerFacade();
computer.start();
}
}
3.5.5、Java中的实际应用
-
JDBC:
- Connection接口就是一个外观接口,它简化了对数据库的访问
-
Spring框架:
- Spring的JdbcTemplate是一个外观类
- Spring的ApplicationContext也是一个外观接口
-
SLF4J:
- 为不同的日志框架提供了一个统一的外观接口
3.5.6、外观模式vs中介者模式
-
目的不同:
- 外观模式:为子系统提供一个简单的接口
- 中介者模式:用一个中介对象来封装一系列的对象交互
-
交互方式:
- 外观模式:单向交互,外观类知道子系统
- 中介者模式:双向交互,中介者和同事类互相知道
-
使用场景:
- 外观模式:简化复杂系统的访问
- 中介者模式:降低系统中对象之间的耦合度
3.6、享元模式
3.6.1、介绍
享元模式(Flyweight Pattern)是一种结构型设计模式,它通过共享技术有效地支持大量细粒度对象的复用。享元模式通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。
享元模式主要包含以下角色:
- 抽象享元(Flyweight):定义了对象的内部状态和外部状态的接口或实现
- 具体享元(ConcreteFlyweight):实现抽象享元角色定义的接口
- 非共享具体享元(UnsharedConcreteFlyweight):不能被共享的子类
- 享元工厂(FlyweightFactory):创建并管理享元对象,确保合理地共享享元对象
3.6.2、优缺点
优点:
- 大大减少对象的创建,降低系统的内存占用
- 提高系统的性能
- 享元模式的外部状态相对独立,使得对象可以在不同的环境中被共享
缺点:
- 使得系统变得复杂,需要分离出内部状态和外部状态
- 享元模式将享元对象的状态外部化,而读取外部状态使得运行时间变长
3.6.3、使用场景
- 系统中存在大量相似的对象
- 需要缓冲池的场景
- 对象的大部分状态可以外部化,而且可以将这些外部状态传入对象中
- 系统中存在大量的对象,这些对象耗费大量的内存,而且对象的状态大部分可以外部化
3.6.4、代码示例
// 抽象享元
interface Shape {
void draw();
}
// 具体享元
class Circle implements Shape {
private String color;
private int x;
private int y;
private int radius;
public Circle(String color) {
this.color = color;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setRadius(int radius) {
this.radius = radius;
}
@Override
public void draw() {
System.out.println("Circle: Draw() [Color : " + color + ", x : " + x + ", y : " + y + ", radius : " + radius);
}
}
// 享元工厂
class ShapeFactory {
private static final HashMap<String, Shape> circleMap = new HashMap<>();
public static Shape getCircle(String color) {
Circle circle = (Circle)circleMap.get(color);
if(circle == null) {
circle = new Circle(color);
circleMap.put(color, circle);
System.out.println("Creating circle of color : " + color);
}
return circle;
}
}
// 客户端代码
public class Client {
private static final String[] colors = {"Red", "Green", "Blue", "Yellow"};
public static void main(String[] args) {
for(int i=0; i < 20; ++i) {
Circle circle = (Circle)ShapeFactory.getCircle(getRandomColor());
circle.setX(getRandomX());
circle.setY(getRandomY());
circle.setRadius(100);
circle.draw();
}
}
private static String getRandomColor() {
return colors[(int)(Math.random()*colors.length)];
}
private static int getRandomX() {
return (int)(Math.random()*100);
}
private static int getRandomY() {
return (int)(Math.random()*100);
}
}
3.6.5、Java中的实际应用
-
String常量池:
- JVM为了提高性能和减少内存开销,在实例化字符串时进行缓存
- 创建字符串时优先从常量池中获取
-
Integer缓存:
- Java中Integer类使用了享元模式
- 默认缓存-128到127之间的数值
-
数据库连接池:
- 数据库连接池的设计也运用了享元模式的思想
- 重复利用现有的连接,避免重复创建
3.6.6、享元模式vs单例模式
-
目的不同:
- 享元模式:复用对象,减少内存占用
- 单例模式:确保类只有一个实例
-
实例数量:
- 享元模式:可以有多个实例,但是会被共享使用
- 单例模式:只有一个实例
-
状态管理:
- 享元模式:将对象的状态分为内部状态和外部状态
- 单例模式:对象的状态都由自己维护
3.7、组合模式
3.7.1、介绍
组合模式(Composite Pattern)是一种结构型设计模式,它允许你将对象组合成树形结构来表现"整体/部分"层次结构,使得用户对单个对象和组合对象的使用具有一致性。
组合模式主要包含以下角色:
- 抽象组件(Component):为组合中的对象声明接口
- 叶子(Leaf):在组合中表示叶子节点对象,叶子节点没有子节点
- 组合(Composite):定义有子部件的那些部件的行为
3.7.2、优缺点
优点:
- 定义了包含基本对象和组合对象的类层次结构
- 简化客户端代码,客户端可以一致地使用组合结构和单个对象
- 容易增加新类型的组件
缺点:
- 使设计变得更加抽象
- 很难对组件类型进行限制
3.7.3、使用场景
- 表示对象的部分-整体层次结构
- 希望用户忽略组合对象与单个对象的不同,用户统一地使用组合结构中的所有对象
- 需要动态地添加或删除容器对象中的子部件
3.7.4、代码示例
// 抽象组件
abstract class Employee {
protected String name;
protected String dept;
protected int salary;
protected List<Employee> subordinates;
public Employee(String name, String dept, int salary) {
this.name = name;
this.dept = dept;
this.salary = salary;
subordinates = new ArrayList<>();
}
public abstract void add(Employee e);
public abstract void remove(Employee e);
public abstract void display(int depth);
}
// 叶子
class Developer extends Employee {
public Developer(String name, String dept, int salary) {
super(name, dept, salary);
}
@Override
public void add(Employee e) {
// 叶子节点不支持该操作
}
@Override
public void remove(Employee e) {
// 叶子节点不支持该操作
}
@Override
public void display(int depth) {
StringBuilder prefix = new StringBuilder();
for(int i = 0; i < depth; i++) {
prefix.append("-");
}
System.out.println(prefix + "Developer [Name: " + name + ", Dept: " + dept + ", Salary: " + salary + "]");
}
}
// 组合
class Manager extends Employee {
public Manager(String name, String dept, int salary) {
super(name, dept, salary);
}
@Override
public void add(Employee e) {
subordinates.add(e);
}
@Override
public void remove(Employee e) {
subordinates.remove(e);
}
@Override
public void display(int depth) {
StringBuilder prefix = new StringBuilder();
for(int i = 0; i < depth; i++) {
prefix.append("-");
}
System.out.println(prefix + "Manager [Name: " + name + ", Dept: " + dept + ", Salary: " + salary + "]");
for(Employee e : subordinates) {
e.display(depth + 2);
}
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Employee CEO = new Manager("John", "CEO", 30000);
Employee headSales = new Manager("Robert", "Sales", 20000);
Employee headDev = new Manager("Michel", "Dev", 20000);
Employee clerk1 = new Developer("Laura", "Sales", 10000);
Employee clerk2 = new Developer("Bob", "Sales", 10000);
Employee programmer1 = new Developer("Jack", "Dev", 15000);
Employee programmer2 = new Developer("Tom", "Dev", 15000);
CEO.add(headSales);
CEO.add(headDev);
headSales.add(clerk1);
headSales.add(clerk2);
headDev.add(programmer1);
headDev.add(programmer2);
CEO.display(1);
}
}
3.7.5、Java中的实际应用
-
Swing/AWT:
- Java GUI库中的容器和组件就是组合模式的典型应用
- Container可以包含其他Component
-
文件系统:
- 目录和文件的结构就是组合模式
- 目录可以包含子目录和文件
-
XML文档对象模型:
- DOM树结构使用了组合模式
- 节点可以包含其他节点
3.7.6、组合模式vs装饰器模式
-
目的不同:
- 组合模式:构建树形结构
- 装饰器模式:动态添加职责
-
关系不同:
- 组合模式:部分-整体关系
- 装饰器模式:包装关系
-
使用场景:
- 组合模式:需要表示整体与部分的层次结构
- 装饰器模式:需要动态地给对象添加功能
4、行为型模式
4.1、策略模式
4.1.1、介绍
策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,将每个算法都封装起来,并且使它们之间可以互相替换。策略模式让算法可以独立于使用它的客户端而变化。
策略模式主要包含以下角色:
- 抽象策略(Strategy):定义所有支持的算法的公共接口
- 具体策略(ConcreteStrategy):实现了抽象策略定义的接口,提供具体的算法实现
- 上下文(Context):维护一个对策略对象的引用,负责调用具体策略
4.1.2、优缺点
优点:
- 算法可以自由切换,避免使用多重条件判断
- 扩展性良好,增加新的策略只需要添加一个类
- 策略类之间可以自由替换,策略的变化不会影响到客户端
缺点:
- 策略类会增多,每个策略都需要一个类
- 所有策略类都需要对外暴露,客户端必须知道所有的策略类
4.1.3、使用场景
- 系统需要动态地在几种算法中选择一种
- 有一个类定义了多种行为,通过多个条件语句来选择具体行为
- 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节
4.1.4、代码示例
// 抽象策略
interface PaymentStrategy {
void pay(int amount);
}
// 具体策略
class CreditCardPayment implements PaymentStrategy {
private String cardNumber;
public CreditCardPayment(String cardNumber) {
this.cardNumber = cardNumber;
}
@Override
public void pay(int amount) {
System.out.println("Paid " + amount + " using Credit Card: " + cardNumber);
}
}
class PayPalPayment implements PaymentStrategy {
private String email;
public PayPalPayment(String email) {
this.email = email;
}
@Override
public void pay(int amount) {
System.out.println("Paid " + amount + " using PayPal account: " + email);
}
}
// 上下文
class ShoppingCart {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
public void checkout(int amount) {
paymentStrategy.pay(amount);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
// 使用信用卡支付
cart.setPaymentStrategy(new CreditCardPayment("1234-5678-9012-3456"));
cart.checkout(100);
// 使用PayPal支付
cart.setPaymentStrategy(new PayPalPayment("example@email.com"));
cart.checkout(200);
}
}
4.1.5、Java中的实际应用
-
Comparator接口:
- Collections.sort()方法可以接收不同的Comparator实现
- 每个Comparator实现都是一个不同的排序策略
-
javax.servlet.http.HttpServlet:
- service()方法会根据请求类型选择不同的处理策略
- doGet(), doPost()等方法就是不同的处理策略
-
Spring框架:
- Resource接口的不同实现(ClassPathResource, FileSystemResource等)
- 不同的事务管理策略
4.1.6、策略模式vs状态模式
-
意图不同:
- 策略模式:定义一系列算法,使它们可以互相替换
- 状态模式:让一个对象在其内部状态改变时改变它的行为
-
状态转换:
- 策略模式:客户端显式指定使用哪个策略
- 状态模式:状态转换由状态类自身决定
-
使用场景:
- 策略模式:算法可以自由切换
- 状态模式:对象行为随状态改变而改变
4.2、模板方法模式
4.2.1、介绍
模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义了一个算法的骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤。
模板方法模式主要包含以下角色:
- 抽象类(Abstract Class):定义抽象的原语操作(钩子操作),实现一个模板方法作为算法的骨架
- 具体类(Concrete Class):实现原语操作以完成算法中与特定子类相关的步骤
4.2.2、优缺点
优点:
- 封装不变部分,扩展可变部分,提高代码复用性
- 行为由父类控制,子类实现,符合开闭原则
- 提取公共代码,便于维护
缺点:
- 每一个不同的实现都需要一个子类,导致类的个数增加
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果
4.2.3、使用场景
- 有一个算法的整体骨架,但其某些步骤在不同的子类中有不同的实现
- 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复
- 需要控制子类扩展的场景
4.2.4、代码示例
// 抽象类
abstract class DataMiner {
// 模板方法
public final void mine() {
openFile();
extractData();
parseData();
analyzeData();
sendReport();
closeFile();
}
// 具体方法
private void openFile() {
System.out.println("Opening file...");
}
// 抽象方法
abstract void extractData();
abstract void parseData();
// 钩子方法
protected void analyzeData() {
System.out.println("Analyzing data...");
}
private void sendReport() {
System.out.println("Sending report...");
}
private void closeFile() {
System.out.println("Closing file...");
}
}
// 具体类
class PDFDataMiner extends DataMiner {
@Override
void extractData() {
System.out.println("Extracting data from PDF file...");
}
@Override
void parseData() {
System.out.println("Parsing PDF data...");
}
@Override
protected void analyzeData() {
System.out.println("Analyzing PDF data...");
}
}
class DocDataMiner extends DataMiner {
@Override
void extractData() {
System.out.println("Extracting data from DOC file...");
}
@Override
void parseData() {
System.out.println("Parsing DOC data...");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
DataMiner pdfMiner = new PDFDataMiner();
pdfMiner.mine();
System.out.println("\n-------------------\n");
DataMiner docMiner = new DocDataMiner();
docMiner.mine();
}
}
4.2.5、Java中的实际应用
-
Java集合框架:
- AbstractList, AbstractSet等抽象类提供了集合操作的通用实现
- 具体的子类只需要实现特定的方法
-
Servlet:
- HttpServlet类提供了service()方法作为模板方法
- doGet(), doPost()等方法由子类实现
-
Spring框架:
- AbstractController类
- JdbcTemplate类
4.2.6、模板方法模式vs策略模式
-
实现方式:
- 模板方法模式:通过继承来实现
- 策略模式:通过组合来实现
-
行为改变:
- 模板方法模式:在编译时静态决定
- 策略模式:在运行时动态决定
-
适用场景:
- 模板方法模式:算法骨架固定,部分步骤可变
- 策略模式:整个算法可以互换
4.2.7、钩子方法
钩子方法(Hook Method)是模板方法模式的一个重要概念:
-
定义:
- 在抽象类中定义的方法
- 提供了默认实现
- 子类可以选择性地覆盖
-
作用:
- 提供了一种机制让子类参与和控制模板方法的某些步骤
- 增加了灵活性,不强制子类实现
-
使用场景:
- 算法的某些步骤是可选的
- 需要让子类决定是否执行某些步骤
4.3、观察者模式
4.3.1、介绍
观察者模式(Observer Pattern)是一种行为型设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
观察者模式主要包含以下角色:
- 主题(Subject):被观察的对象,包含观察者列表和通知观察者的方法
- 观察者(Observer):定义了一个更新接口,使得在主题状态发生改变时能够得到通知
- 具体主题(ConcreteSubject):将有关状态存入具体观察者对象
- 具体观察者(ConcreteObserver):实现抽象观察者角色所要求的更新接口
4.3.2、优缺点
优点:
- 观察者和被观察者之间是抽象耦合的
- 建立了一套触发机制,支持广播通信
- 符合开闭原则,易于扩展
缺点:
- 如果一个被观察者对象有很多的直接和间接的观察者,通知所有观察者会花费很多时间
- 如果观察者和观察目标之间有循环依赖,可能导致系统崩溃
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的
4.3.3、使用场景
- 一个对象的改变需要同时改变其他对象,而且它不知道具体有多少对象有待改变
- 一个对象必须通知其他对象,而它又不能假定其他对象是谁
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象...
4.3.4、代码示例
// 观察者接口
interface Observer {
void update(String message);
}
// 主题接口
interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
// 具体主题
class NewsAgency implements Subject {
private List<Observer> observers = new ArrayList<>();
private String news;
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for(Observer observer : observers) {
observer.update(news);
}
}
public void setNews(String news) {
this.news = news;
notifyObservers();
}
}
// 具体观察者
class NewsChannel implements Observer {
private String name;
public NewsChannel(String name) {
this.name = name;
}
@Override
public void update(String news) {
System.out.println(name + " received news: " + news);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
NewsAgency newsAgency = new NewsAgency();
NewsChannel channel1 = new NewsChannel("CNN");
NewsChannel channel2 = new NewsChannel("BBC");
NewsChannel channel3 = new NewsChannel("FOX");
newsAgency.registerObserver(channel1);
newsAgency.registerObserver(channel2);
newsAgency.registerObserver(channel3);
newsAgency.setNews("Breaking News: Important Event!");
newsAgency.removeObserver(channel2);
newsAgency.setNews("Another Breaking News!");
}
}
4.3.5、Java中的实际应用
-
java.util包:
- Observable类和Observer接口
- EventListener接口及其实现
-
Swing框架:
- 事件处理机制就是观察者模式的应用
- 按钮点击事件的监听
-
Spring框架:
- ApplicationContext发布事件
- ApplicationListener监听事件
4.3.6、观察者模式vs发布-订阅模式
-
耦合度:
- 观察者模式:观察者和被观察者之间是松耦合的
- 发布-订阅模式:发布者和订阅者完全解耦,通过中间件通信
-
通信方式:
- 观察者模式:直接通信
- 发布-订阅模式:通过消息代理/事件通道通信
-
使用场景:
- 观察者模式:适用于程序内部的对象交互
- 发布-订阅模式:适用于跨系统的消息通信
4.3.7、推模型vs拉模型
-
推模型:
- 主题主动将所有数据推送给观察者
- 观察者被动接收数据
- 可能推送一些观察者不需要的数据
-
拉模型:
- 主题仅通知观察者数据已更新
- 观察者主动获取所需的数据
- 更灵活但可能需要多次交互
4.4、迭代器模式
4.4.1、介绍
迭代器模式(Iterator Pattern)是一种行为型设计模式,它提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。迭代器模式把在元素之间游走的责任交给迭代器,而不是聚合对象。
迭代器模式主要包含以下角色:
- 迭代器(Iterator):定义访问和遍历元素的接口
- 具体迭代器(ConcreteIterator):实现迭代器接口,完成集合元素的遍历
- 聚合(Aggregate):定义创建相应迭代器对象的接口
- 具体聚合(ConcreteAggregate):实现创建相应迭代器的接口
4.4.2、优缺点
优点:
- 支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式
- 简化了聚合类,将遍历责任交给迭代器
- 在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码
缺点:
- 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加
- 抽象迭代器的设计难度较大,需要充分考虑系统将来的扩展
4.4.3、使用场景
- 访问一个聚合对象的内容而无须暴露它的内部表示
- 需要为聚合对象提供多种遍历方式
- 为遍历不同的聚合结构提供一个统一的接口
4.4.4、代码示例
// 迭代器接口
interface Iterator<T> {
boolean hasNext();
T next();
}
// 聚合接口
interface Container {
Iterator getIterator();
}
// 具体聚合类
class NameRepository implements Container {
private String[] names = {"Robert", "John", "Julie", "Lora"};
@Override
public Iterator getIterator() {
return new NameIterator();
}
// 具体迭代器类
private class NameIterator implements Iterator<String> {
private int index;
@Override
public boolean hasNext() {
return index < names.length;
}
@Override
public String next() {
if(this.hasNext()) {
return names[index++];
}
return null;
}
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
NameRepository namesRepository = new NameRepository();
for(Iterator iter = namesRepository.getIterator(); iter.hasNext();) {
String name = (String)iter.next();
System.out.println("Name: " + name);
}
}
}
4.4.5、Java中的实际应用
-
Java集合框架:
- Collection接口的iterator()方法
- List的ListIterator接口
-
JDBC:
- ResultSet提供了遍历查询结果的迭代器
-
Spring框架:
- CompositeIterator类
- BeanFactoryIterator接口
4.4.6、迭代器模式的变体
-
内部迭代器:
- 由迭代器自己控制遍历下一个元素的步骤
- 客户端无法控制遍历过程
-
外部迭代器:
- 由客户端控制遍历下一个元素的步骤
- 更加灵活但使用相对复杂
-
快照迭代器:
- 迭代器创建时对集合进行快照
- 遍历时不受集合修改的影响
4.4.7、迭代器模式vs访问者模式
-
目的不同:
- 迭代器模式:提供统一的遍历接口
- 访问者模式:定义作用于对象结构中各元素的操作
-
使用场景:
- 迭代器模式:遍历集合元素
- 访问者模式:对集合元素执行操作
-
扩展性:
- 迭代器模式:易于增加新的遍历方式
- 访问者模式:易于增加新的操作
4.5、责任链模式
4.5.1、介绍
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。
责任链模式主要包含以下角色:
- 抽象处理者(Handler):定义一个处理请求的接口,包含对下一个处理者的引用
- 具体处理者(ConcreteHandler):处理它所负责的请求,可以访问它的后继者
- 客户端(Client):向链上的具体处理者对象提交请求
4.5.2、优缺点
优点:
- 降低耦合度,发送者和接收者都没有对方的明确引用
- 简化了对象,对象不需要知道链的结构
- 增强了给对象指派职责的灵活性
缺点:
- 不能保证请求一定被处理
- 系统性能将受到一定影响,而且在进行代码调试时不太方便
- 可能造成循环调用
4.5.3、使用场景
- 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定
- 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求
- 可动态指定一组对象处理请求
4.5.4、代码示例
// 抽象处理者
abstract class PurchaseHandler {
protected PurchaseHandler successor;
protected double limit;
public void setSuccessor(PurchaseHandler successor) {
this.successor = successor;
}
public abstract void handleRequest(double amount);
}
// 具体处理者
class DepartmentManager extends PurchaseHandler {
public DepartmentManager() {
this.limit = 5000.0;
}
@Override
public void handleRequest(double amount) {
if(amount <= limit) {
System.out.println("部门经理审批通过,金额: " + amount);
} else if(successor != null) {
successor.handleRequest(amount);
}
}
}
class VicePresident extends PurchaseHandler {
public VicePresident() {
this.limit = 10000.0;
}
@Override
public void handleRequest(double amount) {
if(amount <= limit) {
System.out.println("副总裁审批通过,金额: " + amount);
} else if(successor != null) {
successor.handleRequest(amount);
}
}
}
class President extends PurchaseHandler {
public President() {
this.limit = 50000.0;
}
@Override
public void handleRequest(double amount) {
if(amount <= limit) {
System.out.println("总裁审批通过,金额: " + amount);
} else {
System.out.println("金额太大,需要开董事会: " + amount);
}
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
PurchaseHandler manager = new DepartmentManager();
PurchaseHandler vp = new VicePresident();
PurchaseHandler president = new President();
// 设置责任链
manager.setSuccessor(vp);
vp.setSuccessor(president);
// 测试不同金额的采购请求
manager.handleRequest(3000.0);
manager.handleRequest(8000.0);
manager.handleRequest(35000.0);
manager.handleRequest(80000.0);
}
}
4.5.5、Java中的实际应用
-
Java Servlet过滤器:
- Filter接口和FilterChain
- 请求和响应会经过一系列过滤器的处理
-
Spring Security:
- 认证和授权过程中的过滤器链
- 每个过滤器负责特定的安全处理
-
日志框架:
- Log4j中的日志级别处理
- 异常处理机制
4.5.6、责任链模式vs命令模式
-
处理方式:
- 责任链模式:请求沿着链传递,直到被处理
- 命令模式:将请求封装成对象,由调用者统一处理
-
灵活性:
- 责任链模式:处理者可以动态改变
- 命令模式:命令对象相对固定
-
使用场景:
- 责任链模式:处理流程需要动态变化
- 命令模式:需要将请求排队或记录请求日志
4.6、命令模式
4.6.1、介绍
命令模式(Command Pattern)是一种行为型设计模式,它将一个请求封装成一个对象,从而让你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
命令模式主要包含以下角色:
- 命令(Command):声明执行操作的接口
- 具体命令(ConcreteCommand):将一个接收者对象绑定于一个动作
- 调用者(Invoker):要求该命令执行这个请求
- 接收者(Receiver):知道如何实施与执行一个请求相关的操作
4.6.2、优缺点
优点:
- 降低系统的耦合度,将调用者和接收者解耦
- 容易扩展新的命令
- 可以实现请求的排队和记录日志
- 可以实现对请求的撤销和重做
缺点:
- 可能会导致某些系统有过多的具体命令类
- 增加了系统的复杂度
4.6.3、使用场景
- 系统需要将请求调用者和请求接收者解耦
- 系统需要在不同的时间指定请求、将请求排队和执行请求
- 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作
- 系统需要将一组操作组合在一起,即支持宏命令
4.6.4、代码示例
// 命令接口
interface Command {
void execute();
void undo();
}
// 接收者
class Light {
private boolean isOn = false;
public void turnOn() {
isOn = true;
System.out.println("Light is on");
}
public void turnOff() {
isOn = false;
System.out.println("Light is off");
}
}
// 具体命令
class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOn();
}
@Override
public void undo() {
light.turnOff();
}
}
class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOff();
}
@Override
public void undo() {
light.turnOn();
}
}
// 调用者
class RemoteControl {
private Command command;
private Stack<Command> history = new Stack<>();
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
history.push(command);
}
public void undo() {
if(!history.isEmpty()) {
Command command = history.pop();
command.undo();
}
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Light light = new Light();
Command lightOn = new LightOnCommand(light);
Command lightOff = new LightOffCommand(light);
RemoteControl remote = new RemoteControl();
remote.setCommand(lightOn);
remote.pressButton(); // 开灯
remote.setCommand(lightOff);
remote.pressButton(); // 关灯
remote.undo(); // 撤销(开灯)
remote.undo(); // 撤销(关灯)
}
}
4.6.5、Java中的实际应用
-
Runnable接口:
- Java线程中的Runnable就是命令模式的典型应用
- Thread类就是调用者,而Runnable的实现类就是具体命令
-
javax.swing包:
- Action接口及其实现类
- 按钮和菜单项的事件处理
-
Junit框架:
- Test注解
- 测试方法的执行
4.6.6、命令模式的变体
-
宏命令:
- 可以将多个命令组合成一个命令
- 一次执行多个命令
-
智能命令:
- 命令对象直接实现操作
- 不引用接收者
-
可撤销命令:
- 命令维护状态以支持撤销
- 实现undo/redo功能
4.6.7、命令模式vs策略模式
-
意图不同:
- 命令模式:将请求封装成对象
- 策略模式:定义一系列算法,使它们可以互换
-
封装内容:
- 命令模式:封装调用操作的对象
- 策略模式:封装算法
-
使用场景:
- 命令模式:需要支持撤销、排队等操作
- 策略模式:需要在运行时选择算法
4.7、备忘录模式
4.7.1、介绍
备忘录模式(Memento Pattern)是一种行为型设计模式,它在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。
备忘录模式主要包含以下角色:
- 发起人(Originator):记录当前时刻的内部状态,负责创建和恢复备忘录数据
- 备忘录(Memento):负责存储发起人对象的内部状态
- 管理者(Caretaker):对备忘录进行管理、保存和提供备忘录
4.7.2、优缺点
优点:
- 提供了一种可以恢复状态的机制
- 实现了信息的封装,使得用户不需要关心状态的保存细节
- 提供了一种可以存档的机制
缺点:
- 消耗资源,如果类的成员变量过多,势必会占用较大的资源
- 如果发起人对象的状态变化很频繁,可能会导致大量的备忘录对象
4.7.3、使用场景
- 需要保存和恢复数据的场景,如撤销操作
- 需要提供一个可回滚的操作
- 需要保存某个对象在某一个时刻的状态或部分状态
4.7.4、代码示例
// 备忘录类
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 add(Memento state) {
mementoList.add(state);
}
public Memento get(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.add(originator.saveStateToMemento());
originator.setState("State #3");
caretaker.add(originator.saveStateToMemento());
originator.setState("State #4");
System.out.println("Current State: " + originator.getState());
originator.getStateFromMemento(caretaker.get(0));
System.out.println("First saved State: " + originator.getState());
originator.getStateFromMemento(caretaker.get(1));
System.out.println("Second saved State: " + originator.getState());
}
}
4.7.5、Java中的实际应用
-
Java的序列化机制:
- 将对象的状态保存到磁盘
- 需要时可以恢复对象状态
-
事务管理:
- 数据库事务的回滚操作
- Spring框架的事务管理
-
编辑器:
- IDE的撤销/重做功能
- 文本编辑器的历史记录
4.7.6、备忘录模式的实现方式
-
标准实现:
- 使用专门的备忘录类存储状态
- 提供完整的封装性
-
简化实现:
- 发起人自己存储状态
- 牺牲一定的封装性换取实现的简单性
-
多重检查点:
- 管理多个备忘录
- 支持多次撤销/恢复
4.7.7、备忘录模式vs命令模式
-
目的不同:
- 备忘录模式:保存对象的状态
- 命令模式:封装调用命令
-
实现方式:
- 备忘录模式:直接存储对象状态
- 命令模式:存储命令及其参数
-
使用场景:
- 备忘录模式:需要保存/恢复对象状态
- 命令模式:需要支持命令的撤销/重做
4.8、状态模式
4.8.1、介绍
状态模式(State Pattern)是一种行为型设计模式,它允许一个对象在其内部状态改变时改变它的行为。状态模式将状态封装成独立的类,并将动作委托到代表当前状态的对象。
状态模式主要包含以下角色:
- 环境(Context):定义客户感兴趣的接口,维护一个当前状态的实例
- 抽象状态(State):定义一个接口以封装与Context的一个特定状态相关的行为
- 具体状态(ConcreteState):实现抽象状态定义的接口
4.8.2、优缺点
优点:
- 封装了转换规则,对客户端隐藏了状态转换细节
- 将所有与某个状态有关的行为放到一个类中
- 可以让多个环境对象共享一个状态对象
缺点:
- 状态模式的使用必然会增加系统类和对象的个数
- 状态模式的结构与实现都较为复杂
- 对"开闭原则"的支持并不太好
4.8.3、使用场景
- 对象的行为取决于它的状态,并且必须在运行时根据状态改变它的行为
- 代码中包含大量与对象状态有关的条件语句
- 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态
4.8.4、代码示例
// 抽象状态
interface State {
void handle(Context context);
}
// 具体状态
class ConcreteStateA implements State {
@Override
public void handle(Context context) {
System.out.println("当前状态是 A.");
context.setState(new ConcreteStateB());
}
}
class ConcreteStateB implements State {
@Override
public void handle(Context context) {
System.out.println("当前状态是 B.");
context.setState(new ConcreteStateA());
}
}
// 环境类
class Context {
private State state;
public Context() {
// 初始状态
state = new ConcreteStateA();
}
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();
// 第一次请求
context.request();
// 第二次请求
context.request();
// 第三次请求
context.request();
}
}
4.8.5、Java中的实际应用
-
线程状态:
- Thread类中的状态转换
- 不同状态下的行为差异
-
TCP连接:
- Socket的连接状态
- 不同状态下的操作限制
-
游戏开发:
- 游戏角色的不同状态
- 状态机的实现
4.8.6、状态模式vs策略模式
-
意图不同:
- 状态模式:根据状态改变行为
- 策略模式:算法的封装与切换
-
状态转换:
- 状态模式:状态之间可以相互转换
- 策略模式:策略之间独立,不存在转换
-
使用场景:
- 状态模式:对象行为随状态改变
- 策略模式:算法可以互相替换
4.8.7、状态模式的实现方式
-
状态驱动方式:
- 由状态类控制状态转换
- 状态类知道下一个状态是什么
-
表驱动方式:
- 使用状态转换表
- 更容易维护和修改状态转换规则
-
共享状态对象:
- 多个Context共享状态实例
- 节省内存,但需要注意线程安全
4.9、访问者模式
4.9.1、介绍
访问者模式(Visitor Pattern)是一种行为型设计模式,它定义了一种操作,可以在不改变各元素类的前提下定义作用于这些元素的新操作。访问者模式主要用于数据结构与数据操作分离。
访问者模式主要包含以下角色:
- 抽象访问者(Visitor):声明了一组访问操作,每个操作对应一种类型的元素
- 具体访问者(ConcreteVisitor):实现了每个由抽象访问者声明的操作
- 抽象元素(Element):定义一个accept方法,接受访问者对象
- 具体元素(ConcreteElement):实现accept方法
4.9.2、优缺点
优点:
- 符合单一职责原则,让数据结构与数据操作分离
- 优秀的扩展性,增加新的操作很方便
- 使得数据结构和操作解耦,符合开闭原则
缺点:
- 增加新的元素类很困难
- 具体元素变更比较麻烦
- 违反了依赖倒置原则,依赖了具体类而不是抽象类
4.9.3、使用场景
- 对象结构中包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作
- 需要对一个对象结构中的对象进行很多不同且不相关的操作
- 数据结构相对稳定,但经常需要在此数据结构上定义新的操作
4.9.4、代码示例
// 抽象元素
interface ComputerPart {
void accept(ComputerPartVisitor visitor);
}
// 具体元素
class Keyboard implements ComputerPart {
@Override
public void accept(ComputerPartVisitor visitor) {
visitor.visit(this);
}
}
class Monitor implements ComputerPart {
@Override
public void accept(ComputerPartVisitor visitor) {
visitor.visit(this);
}
}
class Mouse implements ComputerPart {
@Override
public void accept(ComputerPartVisitor visitor) {
visitor.visit(this);
}
}
class Computer implements ComputerPart {
private ComputerPart[] parts;
public Computer() {
parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()};
}
@Override
public void accept(ComputerPartVisitor visitor) {
for(ComputerPart part : parts) {
part.accept(visitor);
}
visitor.visit(this);
}
}
// 抽象访问者
interface ComputerPartVisitor {
void visit(Computer computer);
void visit(Mouse mouse);
void visit(Keyboard keyboard);
void visit(Monitor monitor);
}
// 具体访问者
class ComputerPartDisplayVisitor implements ComputerPartVisitor {
@Override
public void visit(Computer computer) {
System.out.println("Displaying Computer.");
}
@Override
public void visit(Mouse mouse) {
System.out.println("Displaying Mouse.");
}
@Override
public void visit(Keyboard keyboard) {
System.out.println("Displaying Keyboard.");
}
@Override
public void visit(Monitor monitor) {
System.out.println("Displaying Monitor.");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
ComputerPart computer = new Computer();
computer.accept(new ComputerPartDisplayVisitor());
}
}
4.9.5、Java中的实际应用
-
javax.lang.model.element.Element接口:
- 定义了accept方法
- 用于Java编译器的注解处理
-
ASM框架:
- 用于字节码操作和分析
- ClassVisitor和MethodVisitor等访问者
-
Spring框架:
- BeanDefinitionVisitor
- PropertyValues的访问机制
4.9.6、访问者模式的变体
-
双分派:
- 根据请求接收者的类型和请求参数的类型来决定具体的操作
- 实现更灵活的访问机制
-
层次访问者:
- 为不同层次的元素定义不同的访问者
- 处理复杂的对象结构
-
简化访问者:
- 只针对特定的元素类型定义访问操作
- 减少不必要的接口方法
4.9.7、访问者模式vs观察者模式
-
目的不同:
- 访问者模式:定义对象结构的新操作
- 观察者模式:处理对象间的一对多依赖关系
-
实现方式:
- 访问者模式:通过双分派实现
- 观察者模式:通过通知机制实现
-
使用场景:
- 访问者模式:数据结构稳定但操作多变
- 观察者模式:对象状态改变需要通知其他对象
4.10、中介者模式
4.10.1、介绍
中介者模式(Mediator Pattern)是一种行为型设计模式,它用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
中介者模式主要包含以下角色:
- 中介者(Mediator):定义了各同事对象通信的接口
- 具体中介者(ConcreteMediator):实现中介者接口,协调各同事对象的交互
- 同事类(Colleague):每个同事对象都知道中介者对象,但不知道其他同事对象
4.10.2、优缺点
优点:
- 减少了对象之间的耦合,使得对象易于独立地改变和复用
- 将对象间的一对多关联转变为一对一的关联
- 将控制逻辑集中,简化了系统维护
缺点:
- 中介者可能会变得过于复杂
- 把对象间的交互复杂性转移到了中介者身上
4.10.3、使用场景
- 一组对象以定义良好但是复杂的方式进行通信
- 一个对象引用其他很多对象并且直接与这些对象通信
- 想定制一个分布在多个类中的行为,而又不想生成太多的子类
4.10.4、代码示例
// 抽象中介者
interface ChatMediator {
void sendMessage(String msg, User user);
void addUser(User user);
}
// 抽象同事类
abstract class User {
protected ChatMediator mediator;
protected String name;
public User(ChatMediator mediator, String name) {
this.mediator = mediator;
this.name = name;
}
public abstract void send(String msg);
public abstract void receive(String msg);
}
// 具体中介者
class ChatRoom implements ChatMediator {
private List<User> users;
public ChatRoom() {
this.users = new ArrayList<>();
}
@Override
public void addUser(User user) {
this.users.add(user);
}
@Override
public void sendMessage(String msg, User user) {
for(User u : users) {
// 消息不用发给发送者自己
if(u != user) {
u.receive(msg);
}
}
}
}
// 具体同事类
class ChatUser extends User {
public ChatUser(ChatMediator mediator, String name) {
super(mediator, name);
}
@Override
public void send(String msg) {
System.out.println(this.name + " 发送消息: " + msg);
mediator.sendMessage(msg, this);
}
@Override
public void receive(String msg) {
System.out.println(this.name + " 收到消息: " + msg);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
ChatMediator mediator = new ChatRoom();
User user1 = new ChatUser(mediator, "张三");
User user2 = new ChatUser(mediator, "李四");
User user3 = new ChatUser(mediator, "王五");
mediator.addUser(user1);
mediator.addUser(user2);
mediator.addUser(user3);
user1.send("大家好!");
}
}
4.10.5、Java中的实际应用
-
Java Swing:
- JButton等组件之间的交互
- 通过ActionListener处理事件
-
MVC框架:
- Controller作为View和Model的中介者
- 处理它们之间的交互
-
Java并发:
- java.util.concurrent.Executor
- 线程池作为线程和任务的中介者
4.10.6、中介者模式vs观察者模式
-
目的不同:
- 中介者模式:封装对象之间的交互
- 观察者模式:定义对象间的一对多依赖
-
通信方式:
- 中介者模式:通过中介者进行双向通信
- 观察者模式:单向通知机制
-
耦合度:
- 中介者模式:同事对象之间完全解耦
- 观察者模式:观察者和主题之间松耦合
4.10.7、中介者模式的变体
-
同步中介者:
- 同步处理对象间的交互
- 适用于简单的交互场景
-
异步中介者:
- 异步处理对象间的交互
- 适用于复杂的交互场景
-
事件中介者:
- 基于事件的交互处理
- 更灵活的消息传递机制
4.11、解释器模式
4.11.1、介绍
解释器模式(Interpreter Pattern)是一种行为型设计模式,它定义了一个语言的文法,并且建立一个解释器来解释该语言中的句子。这种模式被用在SQL解析、符号处理引擎等场景中。
解释器模式主要包含以下角色:
- 抽象表达式(AbstractExpression):声明一个抽象的解释操作
- 终结符表达式(TerminalExpression):实现与文法中的终结符相关的解释操作
- 非终结符表达式(NonterminalExpression):为文法中的非终结符实现解释操作
- 环境(Context):包含解释器之外的一些全局信息
4.11.2、优缺点
优点:
- 易于改变和扩展文法
- 每个文法规则都可以表示为一个类,方便维护
- 实现文法较为容易
缺点:
- 对于复杂的文法难以维护
- 执行效率较低
- 可能产生大量的类
4.11.3、使用场景
- 需要解释一个简单的语言时
- 一个语言需要解释执行,并且可以将该语言中的句子表示为抽象语法树
- 重复发生的问题可以用一种简单的语言来进行表达
4.11.4、代码示例
// 抽象表达式
interface Expression {
int interpret(Context context);
}
// 环境类
class Context {
private Map<String, Integer> variables;
public Context() {
variables = new HashMap<>();
}
public void setVariable(String name, int value) {
variables.put(name, value);
}
public int getVariable(String name) {
return variables.get(name);
}
}
// 终结符表达式
class NumberExpression implements Expression {
private int number;
public NumberExpression(int number) {
this.number = number;
}
@Override
public int interpret(Context context) {
return number;
}
}
class VariableExpression implements Expression {
private String name;
public VariableExpression(String name) {
this.name = name;
}
@Override
public int interpret(Context context) {
return context.getVariable(name);
}
}
// 非终结符表达式
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(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;
}
@Override
public int interpret(Context context) {
return left.interpret(context) - right.interpret(context);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Context context = new Context();
context.setVariable("x", 10);
context.setVariable("y", 5);
// 解释 x + (y - 2)
Expression expression = new AddExpression(
new VariableExpression("x"),
new SubtractExpression(
new VariableExpression("y"),
new NumberExpression(2)
)
);
int result = expression.interpret(context);
System.out.println("x + (y - 2) = " + result);
}
}
4.11.5、Java中的实际应用
-
java.util.Pattern:
- 正则表达式解释器
- 用于解析正则表达式
-
SQL解析器:
- 数据库查询语言的解释
- JPQL(Java Persistence Query Language)
-
Spring表达式语言(SpEL):
- 支持在运行时查询和操作对象图
- 用于配置中的表达式解析
4.11.6、解释器模式的变体
-
组合模式结合:
- 使用组合模式表示语法树
- 更容易处理复杂表达式
-
享元模式结合:
- 共享终结符表达式
- 减少内存使用
-
访问者模式结合:
- 分离语法树的结构和操作
- 便于增加新的操作
4.11.7、解释器模式vs其他模式
-
vs命令模式:
- 解释器模式:解释语言的语法
- 命令模式:封装调用命令
-
vs组合模式:
- 解释器模式:关注语言的解释
- 组合模式:关注对象的组织结构
-
vs策略模式:
- 解释器模式:解释特定的语言
- 策略模式:封装可替换的算法
5、总结
设计模式和设计原则不是必须死板遵循的规范,而是一种指导思想和最佳实践:
5.1、关于设计原则
- 七大设计原则是面向对象设计的基本指导方针
- 目的是帮助我们写出更好的代码,而不是限制我们
- 在实际开发中要根据具体情况灵活运用,不能生搬硬套
- 有时候过分遵循某个原则反而会使代码变得复杂
5.2、关于设计模式
- 设计模式是解决特定问题的经验总结
- 不是越多越好,要在恰当的场景使用恰当的模式
- 过度使用设计模式会导致代码过于复杂,反而不利于维护
- 要在"代码简单性"和"设计灵活性"之间找到平衡
5.3、使用建议
- 先写出能工作的代码,然后在需要的时候才重构使用设计模式
- 不要为了使用设计模式而使用设计模式
- 在以下情况考虑使用设计模式:
- 代码确实存在设计问题
- 后期很可能需要扩展或修改
- 当前场景符合某个设计模式的使用场景
5.4、学习建议
- 理解设计模式背后的思想比记住模式本身更重要
- 多看优秀的开源项目是如何运用设计模式的
- 在实践中逐步体会和运用,不要急于求成
总之,设计模式和原则是工具而不是教条,关键是要用在正确的地方。正如《设计模式》一书中所说:"模式并不保证任何东西,它们只是提供一个解决方案。使用模式还是要靠你的判断。"