深入剖析Java 23种设计模式:从入门到精通 - 实践
深入剖析Java 23种设计模式:从入门到精通
设计模式概述

设计模式(Design Pattern)是一套被反复使用、多数人知晓、经过分类编目的、代码设计经验的总结。它的核心目的是为了实现代码的可重用性,让代码更易于被他人理解,同时保证代码的可靠性,使代码编写真正工程化,堪称软件工程的基石脉络。
设计模式的概念最早起源于建筑领域,后来被引入到软件工程中。1995 年,Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名为《Design Patterns - Elements of Reusable Object-Oriented Software》(中文译名:《设计模式 - 可复用的面向对象软件元素》)的书,该书首次提到了软件开发中设计模式的概念,并总结了 23 种经典的设计模式,这四人也因此被称为 “四人帮(Gang of Four,GoF)” ,这 23 种设计模式也被称为 GoF 设计模式。
在 Java 开发中,设计模式起着举足轻重的作用:
提高代码的可维护性:当项目规模不断扩大,代码量急剧增加时,遵循设计模式编写的代码结构清晰,模块职责明确,维护人员能够快速理解代码的意图和逻辑,从而降低维护成本。例如,在一个大型电商项目中,如果采用了合适的设计模式,如 MVC(Model - View - Controller)模式,将业务逻辑、数据展示和用户交互分离,当需要修改业务逻辑时,只需关注 Model 层,而不会影响到 View 层和 Controller 层,大大提高了代码的可维护性。
增强代码的可扩展性:随着业务的发展,软件系统需要不断添加新功能。设计模式能够使代码具有良好的扩展性,在不修改原有核心代码的基础上轻松添加新功能。以策略模式为例,在一个支付系统中,可能有多种支付方式,如微信支付、支付宝支付、银行卡支付等。通过策略模式,可以将每种支付方式封装成一个独立的策略类,当需要添加新的支付方式时,只需新增一个策略类,而无需修改支付系统的核心代码。
实现代码的可复用性:设计模式提供了通用的解决方案,能够避免重复开发,提高开发效率。例如,工厂模式可以将对象的创建过程封装起来,在多个地方需要创建相同类型的对象时,只需调用工厂类的创建方法即可,而无需重复编写创建对象的代码。
这 23 种设计模式可以分为三大类:
创建型模式(Creational Patterns):创建型模式主要用于对象的创建过程,将对象的创建和使用分离,降低对象创建和使用之间的耦合度,使得代码在创建对象时更加灵活和可维护。属于创建型模式的有工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式 。
结构型模式(Structural Patterns):结构型模式关注如何将类或对象组合成更大的结构,通过组合和继承的方式,使得类或对象之间能够协同工作,提高系统的灵活性和可维护性。包括适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式(Behavioral Patterns):行为型模式主要用于处理对象之间的交互和职责分配,描述类或对象之间怎样相互协作共同完成某些任务,以及如何分配职责,使得系统的行为更加清晰和可维护。包含策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
创建型模式
创建型模式主要用于对象的创建过程,将对象的创建和使用分离,降低对象创建和使用之间的耦合度,使得代码在创建对象时更加灵活和可维护。
单例模式(Singleton)
单例模式确保一个类只有一个实例,并提供一个全局访问点。在 Java 中,实现单例模式通常需要将构造函数私有化,防止外部实例化,同时提供一个静态方法来获取唯一的实例。
实现方式:
- 饿汉式:在类加载时就创建实例,线程安全,但可能造成资源浪费。
public class EagerSingleton {
// 类加载时即创建实例
private static final EagerSingleton instance = new EagerSingleton();
// 私有构造方法,防止外部实例化
private EagerSingleton() {}
// 提供获取实例的全局方法
public static EagerSingleton getInstance() {
return instance;
}
}
- 懒汉式:在第一次使用时才创建实例,实现延迟加载,但在多线程环境下需要额外处理线程安全问题。
public class LazySingleton {
// 私有静态变量,存储单例实例
private static LazySingleton instance;
// 私有构造方法,防止外部实例化
private LazySingleton() {}
// 提供获取实例的全局方法
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
上述懒汉式实现中,使用 synchronized 关键字修饰 getInstance 方法,保证在多线程环境下只有一个线程能进入该方法,从而避免了多个实例的创建。然而,这种方式会导致每次调用 getInstance 方法时都需要获取锁,性能开销较大。为了优化性能,可以采用双重检查锁定(Double-Checked Locking)的方式。
public class DoubleCheckedLockingSingleton {
// 使用 volatile 关键字,确保多线程环境下的可见性
private static volatile DoubleCheckedLockingSingleton instance;
private DoubleCheckedLockingSingleton() {}
public static DoubleCheckedLockingSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckedLockingSingleton.class) {
if (instance == null) {
instance = new DoubleCheckedLockingSingleton();
}
}
}
return instance;
}
}
在双重检查锁定的实现中,首先进行一次 if (instance == null) 的检查,避免每次都进入同步块,只有在 instance 为 null 时才进入同步块。在同步块中,再次进行 if (instance == null) 的检查,这是因为可能有多个线程同时通过了第一次检查,进入同步块后,如果不进行第二次检查,还是会创建多个实例。使用 volatile 关键字修饰 instance 变量,是为了防止指令重排序,保证在多线程环境下 instance 的创建过程按预期执行,避免其他线程拿到未初始化完全的实例。
应用场景:
配置管理类:在许多应用程序中,配置文件读取和解析通常是全局性的,且只需要读取一次。为了避免多次读取同一个配置文件并保证其全局一致性,可以使用单例模式管理配置文件的加载。
日志管理类:日志系统通常需要将信息记录到文件或其他存储介质中。使用单例模式可以确保日志管理器在应用程序中只有一个实例,避免重复初始化和多次访问同一文件。
数据库连接池:数据库连接池是后台系统中常见的资源管理器。由于每个连接都消耗大量系统资源,连接池通常被设计为单例,确保连接的合理分配与复用 。
工厂模式(Factory)
工厂模式是一种创建对象的设计模式,它将对象的创建和使用分离,通过一个工厂类来负责创建对象,使得代码更加灵活和可维护。工厂模式主要有三种类型:简单工厂模式、工厂方法模式和抽象工厂模式。
简单工厂模式
简单工厂模式(Simple Factory Pattern)不属于 GoF23 种经典设计模式之一,但在实际开发中非常常见。它定义了一个工厂类,用于创建产品对象,根据传入的参数决定创建并返回哪一种产品类的实例。简单工厂模式的核心思想是将对象的创建过程封装在一个工厂类中,客户端不需要知道具体的创建细节,只需要告诉工厂类需要什么类型的对象即可。
假设我们有一个生产手机的场景,不同品牌的手机有不同的功能,但都实现了 Phone 接口。
// 产品接口
public interface Phone {
void call();
}
// 具体产品A:小米手机
public class XiaomiPhone implements Phone {
@Override
public void call() {
System.out.println("使用小米手机打电话");
}
}
// 具体产品B:华为手机
public class HuaweiPhone implements Phone {
@Override
public void call() {
System.out.println("使用华为手机打电话");
}
}
// 工厂类
public class PhoneFactory {
public static Phone createPhone(String brand) {
if ("Xiaomi".equals(brand)) {
return new XiaomiPhone();
} else if ("Huawei".equals(brand)) {
return new HuaweiPhone();
} else {
throw new IllegalArgumentException("未知的手机品牌");
}
}
}
客户端使用:
public class Client {
public static void main(String\[] args) {
Phone xiaomi = PhoneFactory.createPhone("Xiaomi");
xiaomi.call();
Phone huawei = PhoneFactory.createPhone("Huawei");
huawei.call();
}
}
在上述代码中,PhoneFactory 类是工厂类,它根据传入的 brand 参数来创建不同品牌的手机实例。客户端只需要调用 PhoneFactory.createPhone 方法并传入品牌名称,就可以获取到对应的手机对象,而不需要关心手机对象的具体创建过程。
优点:
实现简单:简单工厂模式的实现相对简单,只需要一个工厂类来负责对象的创建,易于理解和维护。
解耦创建和使用:将对象的创建和使用分离,客户端不需要知道对象的创建细节,只需要关心如何使用对象,降低了代码的耦合度。
便于管理和维护:对象的创建逻辑集中在工厂类中,当需要修改创建逻辑时,只需要在工厂类中进行修改,而不会影响到客户端代码。
缺点:
不符合开闭原则:当需要增加新的产品类型时,需要修改工厂类的代码,在
createPhone方法中添加新的判断逻辑,这不符合开闭原则(对扩展开放,对修改关闭)。工厂类职责过重:所有产品的创建逻辑都集中在一个工厂类中,导致工厂类的职责过重,不符合单一职责原则。当产品类型较多时,工厂类的代码会变得复杂,难以维护。
简单工厂模式适用于产品类型较少且不经常变化的场景,以及客户端不关心对象创建过程,只需要获取产品实例的情况。它可以作为其他更复杂工厂模式的基础。
工厂方法模式
工厂方法模式(Factory Method Pattern)是对简单工厂模式的进一步抽象和扩展,它定义了一个创建对象的接口,但由子类决定实例化哪一个类,将类的实例化操作延迟到子类。工厂方法模式是 “开闭原则” 的典型体现,当需要增加新的产品类型时,只需要增加一个具体的工厂类和产品类,而不需要修改工厂接口和其他已有的代码。
还是以上述手机生产的场景为例,使用工厂方法模式来实现:
// 产品接口
public interface Phone {
void call();
}
// 具体产品A:小米手机
public class XiaomiPhone implements Phone {
@Override
public void call() {
System.out.println("使用小米手机打电话");
}
}
// 具体产品B:华为手机
public class HuaweiPhone implements Phone {
@Override
public void call() {
System.out.println("使用华为手机打电话");
}
}
// 抽象工厂接口
public interface PhoneFactory {
Phone createPhone();
}
// 具体工厂A:小米手机工厂
public class XiaomiPhoneFactory implements PhoneFactory {
@Override
public Phone createPhone() {
return new XiaomiPhone();
}
}
// 具体工厂B:华为手机工厂
public class HuaweiPhoneFactory implements PhoneFactory {
@Override
public Phone createPhone() {
return new HuaweiPhone();
}
}
客户端使用:
public class Client {
public static void main(String\[] args) {
PhoneFactory xiaomiFactory = new XiaomiPhoneFactory();
Phone xiaomi = xiaomiFactory.createPhone();
xiaomi.call();
PhoneFactory huaweiFactory = new HuaweiPhoneFactory();
Phone huawei = huaweiFactory.createPhone();
huawei.call();
}
}
在这个实现中,PhoneFactory 是抽象工厂接口,定义了创建手机的抽象方法 createPhone。XiaomiPhoneFactory 和 HuaweiPhoneFactory 是具体的工厂类,分别实现了 createPhone 方法,用于创建小米手机和华为手机的实例。客户端通过创建具体的工厂类对象,调用其 createPhone 方法来获取相应的手机对象。
优点:
符合开闭原则:当需要增加新的产品类型时,只需要增加一个具体的工厂类和产品类,实现工厂接口和产品接口,而不需要修改已有的代码,满足了开闭原则,使得系统具有良好的扩展性。
提高可维护性:每个具体工厂类只负责创建一种产品,符合单一职责原则,使得代码结构更加清晰,易于维护和理解。当某个产品的创建逻辑发生变化时,只需要修改对应的具体工厂类,不会影响其他部分的代码。
增强灵活性:客户端可以根据需要选择不同的具体工厂类来创建对象,提高了系统的灵活性和可定制性。
缺点:
工厂类数量过多:如果产品类型较多,会导致具体工厂类的数量也相应增多,增加了系统的复杂度和维护成本。例如,当有 10 种不同品牌的手机时,就需要创建 10 个具体的工厂类,这会使得代码量增加,管理起来也更加困难。
增加系统复杂度:相比于简单工厂模式,工厂方法模式引入了抽象工厂接口和多个具体工厂类,增加了系统的抽象性和复杂度,对于初学者来说理解和实现起来可能有一定难度。
工厂方法模式适用于需要解耦对象创建与使用,无法预知对象确切类型及其依赖关系,以及需要为不同环境提供不同产品实现的场景。在实际开发中,当产品类型较多且需要频繁扩展时,工厂方法模式是一个较好的选择。
抽象工厂模式(Abstract Factory)
抽象工厂模式(Abstract Factory Pattern)提供了一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。在抽象工厂模式中,具体工厂负责创建一族产品,这些产品构成了一个产品族,每种产品都位于某个产品等级结构中。
假设我们有一个电子产品制造公司,它生产两种主要产品:智能手机和智能手表。这些产品都有不同的系列,比如高端系列和经济型系列。每个系列的产品都有其独特的设计和功能,使用抽象工厂模式来实现这个场景:
// 抽象产品1:智能手机接口
public interface SmartPhone {
void display();
}
// 抽象产品2:智能手表接口
public interface SmartWatch {
void display();
}
// 具体产品1:经济型智能手机
public class BudgetSmartPhone implements SmartPhone {
@Override
public void display() {
System.out.println("Displaying Budget SmartPhone");
}
}
// 具体产品2:经济型智能手表
public class BudgetSmartWatch implements SmartWatch {
@Override
public void display() {
System.out.println("Displaying Budget SmartWatch");
}
}
// 具体产品3:高端智能手机
public class HighEndSmartPhone implements SmartPhone {
@Override
public void display() {
System.out.println("Displaying High-End SmartPhone");
}
}
// 具体产品4:高端智能手表
public class HighEndSmartWatch implements SmartWatch {
@Override
public void display() {
System.out.println("Displaying High-End SmartWatch");
}
}
// 抽象工厂接口
public interface ElectronicsFactory {
SmartPhone createSmartPhone();
SmartWatch createSmartWatch();
}
// 具体工厂1:经济型系列产品工厂
public class BudgetFactory implements ElectronicsFactory {
@Override
public SmartPhone createSmartPhone() {
return new BudgetSmartPhone();
}
@Override
public SmartWatch createSmartWatch() {
return new BudgetSmartWatch();
}
}
// 具体工厂2:高端系列产品工厂
public class HighEndFactory implements ElectronicsFactory {
@Override
public SmartPhone createSmartPhone() {
return new HighEndSmartPhone();
}
@Override
public SmartWatch createSmartWatch() {
return new HighEndSmartWatch();
}
}
客户端使用:
public class Client {
public static void main(String\[] args) {
// 创建经济型系列产品工厂对象
ElectronicsFactory budgetFactory = new BudgetFactory();
SmartPhone budgetPhone = budgetFactory.createSmartPhone();
SmartWatch budgetWatch = budgetFactory.createSmartWatch();
budgetPhone.display();
budgetWatch.display();
// 创建高端系列产品工厂对象
ElectronicsFactory highEndFactory = new HighEndFactory();
SmartPhone highEndPhone = highEndFactory.createSmartPhone();
SmartWatch highEndWatch = highEndFactory.createSmartWatch();
highEndPhone.display();
highEndWatch.display();
}
}
在这个例子中,ElectronicsFactory 是抽象工厂接口,定义了创建智能手机和智能手表的方法。BudgetFactory 和 HighEndFactory 是具体的工厂类,分别实现了抽象工厂接口,用于创建经济型系列和高端系列的产品。SmartPhone 和 SmartWatch 是抽象产品接口,BudgetSmartPhone、BudgetSmartWatch、HighEndSmartPhone 和 HighEndSmartWatch 是具体的产品类,分别实现了对应的抽象产品接口。客户端通过创建不同的具体工厂对象,来获取不同系列的产品。
优点:
隔离具体类的生成:客户端只需要依赖抽象工厂接口和抽象产品接口,而不需要知道具体产品类的实现细节,降低了客户端与具体产品类之间的耦合度,使得更换具体工厂或产品实现更加容易。
保证产品族的一致性:当一个产品族中的多个对象被设计成一起工作时,抽象工厂模式能够保证客户端始终只使用同一个产品族中的对象,确保了产品之间的兼容性和一致性。
符合开闭原则:增加新的产品族比较方便,只需要增加一个具体的工厂类和相应的产品类,实现抽象工厂接口和抽象产品接口,而不需要修改已有的代码。
缺点:
增加新产品等级结构困难:如果要增加新的产品等级结构,比如增加一个智能音箱产品,就需要修改抽象工厂接口、所有的具体工厂类以及客户端代码,这违背了开闭原则,增加了系统的维护成本和复杂度。
实现复杂:抽象工厂模式涉及到多个抽象和具体的类,实现起来相对复杂,对于初学者来说理解和掌握起来有一定难度。
抽象工厂模式适用于一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,以及需要创建一系列相关或相互依赖对象的场景。当产品族比较稳定,且不需要频繁增加新的产品等级结构时,抽象工厂模式能够发挥其优势,提高系统的灵活性和可维护性。
建造者模式(Builder)
建造者模式(Builder Pattern)将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式通常包含以下几个角色:
Builder(抽象建造者):定义创建产品各个部分的抽象方法。
ConcreteBuilder(具体建造者):实现抽象建造者定义的方法,构建和装配产品的各个部件。
Product(产品):表示被构建的复杂对象,包含多个部件。
Director(指挥者):负责安排复杂对象的构建步骤,调用建造者的方法来创建产品。
以建造房子为例,房子有不同的部分,如地基、墙壁、屋顶等,使用建造者模式实现:
// 产品:房子
public class House {
private String foundation;
private String walls;
private String roof;
public void setFoundation(String foundation) {
this.foundation = foundation;
}
public void setWalls(String walls) {
this.walls = walls;
}
public void setRoof(String roof) {
this.roof = roof;
}
public void show() {
System.out.println("房子信息:");
System.out.println("地基:" + foundation);
System.out.println("墙壁:" + walls);
System.out.println("屋顶:" + roof);
}
}
// 抽象建造者
public abstract class HouseBuilder {
protected House house = new House();
public abstract void buildFoundation();
public abstract void buildWalls();
public abstract void buildRoof();
public House getHouse() {
return house;
}
}
// 具体建造者:普通房子建造者
public class CommonHouseBuilder extends HouseBuilder {
@Override
public void buildFoundation() {
house.set
\## 结构型模式
结构型模式关注如何将类或对象组合成更大的结构,通过组合和继承的方式,使得类或对象之间能够协同工作,提高系统的灵活性和可维护性。
\### 适配器模式(Adapter)
适配器模式将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。适配器模式有类适配器和对象适配器两种实现方式。
假设我们有一个旧的音频播放器,它只能播放MP3格式的音频文件,现在我们需要让它能够播放WAV格式的音频文件,就可以使用适配器模式。
\*\*类适配器\*\*:通过继承适配者类和实现目标接口来实现适配。
\`\`\`java
// 目标接口,定义新的播放方法
interface MediaPlayer {
void play(String audioType, String fileName);
}
// 适配者类,旧的音频播放器,只有播放MP3的方法
class Mp3Player {
public void playMp3(String fileName) {
System.out.println("Playing MP3 file. Name: " + fileName);
}
}
// 类适配器,继承Mp3Player并实现MediaPlayer接口
class WavToMp3Adapter extends Mp3Player implements MediaPlayer {
@Override
public void play(String audioType, String fileName) {
// 这里简单模拟将WAV转换为MP3的过程
if ("wav".equals(audioType)) {
System.out.println("Converting WAV to MP3...");
playMp3(fileName);
}
}
}
// 客户端
class Client {
public static void main(String\[] args) {
MediaPlayer player = new WavToMp3Adapter();
player.play("wav", "oldSong.wav");
}
}
在上述代码中,MediaPlayer 是目标接口,Mp3Player 是适配者类,WavToMp3Adapter 是类适配器,它继承了 Mp3Player 并实现了 MediaPlayer 接口,在 play 方法中,将对 WAV 格式文件的播放请求转换为对 MP3 格式文件的播放请求。
对象适配器:通过持有适配者对象并实现目标接口来实现适配。
// 目标接口,定义新的播放方法
interface MediaPlayer {
void play(String audioType, String fileName);
}
// 适配者类,旧的音频播放器,只有播放MP3的方法
class Mp3Player {
public void playMp3(String fileName) {
System.out.println("Playing MP3 file. Name: " + fileName);
}
}
// 对象适配器,持有Mp3Player对象并实现MediaPlayer接口
class WavToMp3ObjectAdapter implements MediaPlayer {
private Mp3Player mp3Player;
public WavToMp3ObjectAdapter(Mp3Player mp3Player) {
this.mp3Player = mp3Player;
}
@Override
public void play(String audioType, String fileName) {
// 这里简单模拟将WAV转换为MP3的过程
if ("wav".equals(audioType)) {
System.out.println("Converting WAV to MP3...");
mp3Player.playMp3(fileName);
}
}
}
// 客户端
class Client {
public static void main(String\[] args) {
Mp3Player mp3Player = new Mp3Player();
MediaPlayer player = new WavToMp3ObjectAdapter(mp3Player);
player.play("wav", "oldSong.wav");
}
}
在对象适配器的实现中,WavToMp3ObjectAdapter 持有一个 Mp3Player 对象,通过组合的方式将对 WAV 格式文件的播放请求委托给 Mp3Player 对象的 playMp3 方法。
优点:
提高兼容性:使不兼容的接口能够协同工作,让现有类可以复用,无需对现有类进行大量修改,就能满足新的需求。
增强灵活性:在不修改原有代码的基础上,通过适配器可以灵活地适配不同的接口,适应不同的场景和需求。
解耦目标和适配者:将目标接口和适配者解耦,使得它们可以独立变化,互不影响,提高了系统的可维护性和可扩展性。
缺点:
增加系统复杂度:引入了新的适配器类,增加了系统的类的数量和复杂性,使得系统结构变得更加复杂,尤其是在有多个适配器和适配者的情况下,可能会导致代码难以理解和维护。
存在性能损耗:在进行适配的过程中,可能会涉及到一些转换操作,这可能会带来一定的性能损耗,影响系统的运行效率。
适配器模式适用于希望使用某个类,但其接口与其他代码不兼容的场景;需要复用一些类,但这些类的接口不符合当前需求的情况;以及在不同的接口协议、数据格式之间进行转换的场景 。在实际开发中,如在整合旧系统与新系统、使用第三方库时,如果接口不匹配,就可以考虑使用适配器模式来解决。
装饰器模式(Decorator)
装饰器模式动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式相比生成子类更为灵活。它通过组合而非继承的方式来扩展对象的功能,避免了继承带来的类爆炸问题。
以咖啡加糖和牛奶的场景为例,使用装饰器模式实现:
// 抽象组件:咖啡
interface Coffee {
double getCost();
String getDescription();
}
// 具体组件:普通咖啡
class SimpleCoffee implements Coffee {
@Override
public double getCost() {
return 2.0;
}
@Override
public String getDescription() {
return "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 MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double getCost() {
return super.getCost() + 0.5;
}
@Override
public String getDescription() {
return super.getDescription() + " + Milk";
}
}
// 具体装饰器:糖装饰器
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double getCost() {
return super.getCost() + 0.2;
}
@Override
public String getDescription() {
return super.getDescription() + " + Sugar";
}
}
// 客户端
class Client {
public static void main(String\[] args) {
Coffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
System.out.println("Total Cost: \$" + coffee.getCost());
System.out.println("Description: " + coffee.getDescription());
}
}
在上述代码中,Coffee 是抽象组件接口,SimpleCoffee 是具体组件,实现了 Coffee 接口。CoffeeDecorator 是抽象装饰器,它持有一个 Coffee 对象,并实现了 Coffee 接口。MilkDecorator 和 SugarDecorator 是具体装饰器,分别继承自 CoffeeDecorator,并在 getCost 和 getDescription 方法中添加了各自的装饰逻辑,实现了对咖啡功能的扩展。客户端通过层层装饰,为普通咖啡添加了牛奶和糖的功能。
优点:
扩展性好:通过组合不同的装饰器,可以在运行时动态地为对象添加或撤销功能,而不需要修改对象的原有代码,符合开闭原则,相比继承具有更好的扩展性。
灵活性高:可以根据需要灵活地选择不同的装饰器进行组合,实现不同的功能组合,满足不同的业务需求,提高了系统的灵活性和可定制性。
避免子类爆炸:如果使用继承来实现功能扩展,每增加一个功能就需要创建一个新的子类,随着功能的增多,子类的数量会急剧增加,导致代码难以维护。而装饰器模式通过组合的方式,避免了这种子类爆炸的问题。
缺点:
产生大量小对象:由于装饰器模式会创建多个装饰器对象来实现功能扩展,可能会导致系统中产生大量的小对象,增加了内存的开销和对象管理的复杂度。
调试困难:多层装饰会使得代码的执行路径变得复杂,当出现问题时,调试和排查错误会比较困难,需要花费更多的时间和精力来分析问题。
装饰顺序影响结果:不同的装饰器组合顺序可能会导致不同的结果,这就需要在使用装饰器模式时,特别注意装饰器的组合顺序,增加了使用的难度和复杂性。
装饰器模式适用于需要动态地给对象添加功能,并且这些功能可以动态撤销的场景;无法通过继承来扩展功能,或者继承会导致子类过多的情况;以及需要组合多个可选功能的场景。在 Java 的 IO 流体系中,就广泛使用了装饰器模式,例如 BufferedInputStream、DataInputStream 等都是对 InputStream 的装饰,通过不同的装饰器组合,可以实现不同的输入流功能 。
代理模式(Proxy)
代理模式为其他对象提供一种代理以控制对这个对象的访问。代理对象可以在客户端和目标对象之间起到中介的作用,在访问目标对象时,可以进行一些额外的操作,如权限控制、日志记录、缓存等。代理模式分为静态代理和动态代理。
静态代理
静态代理在编译时就已经确定了代理类的实现,代理类和目标类实现相同的接口,代理类持有目标类的引用,通过代理类来调用目标类的方法。
假设我们有一个用户服务接口,使用静态代理来实现对用户服务方法的日志记录:
// 接口:用户服务
interface UserService {
void addUser(String username, String password);
}
// 目标类:用户服务实现类
class UserServiceImpl implements UserService {
@Override
public void addUser(String username, String password) {
System.out.println("Adding user: " + username + " with password: " + password);
}
}
// 代理类:用户服务代理类
class UserServiceProxy implements UserService {
private UserService userService;
public UserServiceProxy(UserService userService) {
this.userService = userService;
}
@Override
public void addUser(String username, String password) {
System.out.println("Before adding user, logging...");
userService.addUser(username, password);
System.out.println("After adding user, logging...");
}
}
// 客户端
class Client {
public static void main(String\[] args) {
UserService userService = new UserServiceImpl();
UserService proxy = new UserServiceProxy(userService);
proxy.addUser("John", "123456");
}
}
在上述代码中,UserService 是接口,UserServiceImpl 是目标类,实现了 UserService 接口。UserServiceProxy 是代理类,也实现了 UserService 接口,并持有一个 UserService 对象的引用。在 addUser 方法中,代理类在调用目标类的 addUser 方法前后添加了日志记录的操作。
优点:
结构简单:静态代理的实现相对简单,容易理解和掌握,对于小型项目或者代理类相对固定的场景非常适用。
功能扩展方便:可以在代理类中方便地对目标类的方法进行功能扩展,如添加日志记录、权限控制、事务管理等,而不需要修改目标类的代码。
缺点:
代码冗余:每一个目标类都需要创建一个对应的代理类,并且代理类中的很多代码都是相似的,只是调用的目标方法不同,这会导致大量的代码冗余,增加了代码的维护成本。
扩展性差:当需要代理的接口或目标类发生变化时,需要同时修改代理类和目标类的代码,不符合开闭原则,扩展性较差。
动态代理
动态代理是在运行时动态地创建代理对象,而不需要手动编写代理类。Java 中的动态代理主要通过 java.lang.reflect.Proxy 和 InvocationHandler 来实现。
还是以上述用户服务接口为例,使用动态代理实现日志记录:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 接口:用户服务
interface UserService {
void addUser(String username, String password);
}
// 目标类:用户服务实现类
class UserServiceImpl implements UserService {
@Override
public void addUser(String username, String password) {
System.out.println("Adding user: " + username + " with password: " + password);
}
}
// 动态代理处理器
class UserServiceInvocationHandler implements InvocationHandler {
private Object target;
public UserServiceInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object\[] args) throws Throwable {
System.out.println("Before method invocation, logging...");
Object result = method.invoke(target, args);
System.out.println("After method invocation, logging...");
return result;
}
}
// 客户端
class Client {
public static void main(String\[] args) {
UserService userService = new UserServiceImpl();
UserServiceInvocationHandler handler = new UserServiceInvocationHandler(userService);
UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
handler);
proxy.addUser("John", "123456");
}
}
在动态代理的实现中,UserServiceInvocationHandler 实现了 InvocationHandler 接口,重写了 invoke 方法,在 invoke 方法中实现了日志记录的功能,并通过反射调用目标对象的方法。Proxy.newProxyInstance 方法用于动态地创建代理对象,它接收三个参数:目标对象的类加载器、目标对象实现的接口数组以及 InvocationHandler 对象。
优点:
灵活性高:动态代理可以在运行时根据需要动态地创建代理对象,代理多个不同类型的对象,只要这些对象实现了相同的接口,具有很高的灵活性,特别适合在 AOP(面向切面编程)中使用,如 Spring 框架中的动态代理。
减少代码冗余:通过动态代理,只需要一个代理处理器就可以为多个不同的目标对象创建代理,避免了静态代理中每个目标对象都需要创建一个代理类的代码冗余问题,提高了代码的复用性和可维护性。
缺点:
实现复杂:动态代理的实现依赖于 Java 的反射机制,相比静态代理,其实现原理和代码结构更加复杂,对于初学者来说理解和掌握起来有一定难度。
性能损耗:由于动态代理在运行时通过反射来调用目标对象的方法,相比直接调用目标对象的方法,会有一定的性能损耗,在对性能要求较高的场景下需要谨慎使用。
代理模式适用于需要对目标对象的访问进行控制,如权限控制、日志记录、缓存等场景;需要隐藏目标对象的具体实现细节,提供一个统一的访问接口的情况;以及需要在目标对象的方法执行前后进行一些额外操作的场景。在实际开发中,代理模式被广泛应用于各种框架和系统中,如 RPC(远程过程调用)框架中的服务代理、数据库连接池中的代理对象等 。
外观模式(Facade)
外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。它将复杂的子系统封装起来,提供给客户端一个简单的接口,降低了客户端与子系统之间的耦合度。
以家庭影院系统为例,假设家庭影院系统包含投影仪、音响、屏幕等设备,使用外观模式实现一键启动和关闭家庭影院的功能:
// 子系统类:投影仪
class Projector {
public void on() {
System.out.println("Projector is on");
}
public void off() {
System.out.println("Projector is off");
}
public void wideScreenMode() {
System.out.println("Projector setting wide screen mode");
}
}
// 子系统类:音响
class Amplifier {
public void on() {
System.out.println("Amplifier is on");
}
public void off() {
System.out.println("Amplifier is off");
}
public void setDvd(DvdPlayer dvd) {
System.out.println("Amplifier setting DVD player");
}
public void setSurroundSound() {
System.out.println("Amplifier setting surround sound");
}
}
// 子系统类:DVD播放器
class DvdPlayer {
private String description;
public DvdPlayer(String description) {
this.description = description;
}
public void on() {
System.out.println(description
\## 行为型模式
行为型模式主要用于处理对象之间的交互和职责分配,描述类或对象之间怎样相互协作共同完成某些任务,以及如何分配职责,使得系统的行为更加清晰和可维护。
\### 策略模式(Strategy)
策略模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户端。策略模式体现了两个非常基本的面向对象设计的原则:封装变化的概念;编程中使用接口,而不是对接口的实现。
假设我们正在开发一个电商系统,其中涉及多种支付方式,如微信支付、支付宝支付、银行卡支付等。使用策略模式来实现这个支付系统,代码如下:
\`\`\`java
// 抽象策略接口
interface PaymentStrategy {
void pay(double amount);
}
// 具体策略:微信支付
class WeChatPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("微信支付:" + amount + "元");
}
}
// 具体策略:支付宝支付
class AlipayPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("支付宝支付:" + amount + "元");
}
}
// 具体策略:银行卡支付
class BankCardPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("银行卡支付:" + amount + "元");
}
}
// 上下文类
class PaymentContext {
private PaymentStrategy strategy;
public PaymentContext(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void executePayment(double amount) {
strategy.pay(amount);
}
}
// 客户端调用
class Client {
public static void main(String\[] args) {
PaymentContext context = new PaymentContext(new WeChatPayment());
context.executePayment(100.0);
context = new PaymentContext(new AlipayPayment());
context.executePayment(200.0);
context = new PaymentContext(new BankCardPayment());
context.executePayment(300.0);
}
}
在上述代码中,PaymentStrategy 是抽象策略接口,定义了支付的抽象方法 pay。WeChatPayment、AlipayPayment 和 BankCardPayment 是具体策略类,分别实现了微信支付、支付宝支付和银行卡支付的逻辑。PaymentContext 是上下文类,持有一个 PaymentStrategy 对象的引用,并通过 executePayment 方法调用具体策略的 pay 方法来执行支付操作。客户端通过创建不同的具体策略对象,并将其传递给 PaymentContext 来实现不同支付方式的切换。
优点:
消除条件判断:策略模式用多态替代了
if-else或switch-case条件判断,使代码更加简洁和易于维护。在支付系统中,如果不使用策略模式,可能需要在一个方法中通过大量的条件判断来处理不同的支付方式,而使用策略模式后,每种支付方式都封装在独立的策略类中,代码结构更加清晰。遵循开闭原则:新增策略时,只需实现抽象策略接口,无需修改原有代码,符合开闭原则,提高了系统的可扩展性。当电商系统需要添加新的支付方式,如 Apple Pay 时,只需要新增一个实现
PaymentStrategy接口的ApplePayPayment类,而不需要修改其他已有的代码。提高代码复用性:每个策略都是独立的类,便于复用和测试,提高了代码的复用性。例如,微信支付策略类可以在其他需要微信支付功能的项目中复用。
动态切换算法:可以在运行时根据需要动态切换不同的策略,增加了系统的灵活性和可定制性。在电商系统中,用户可以在支付时选择不同的支付方式,通过动态切换策略对象来实现不同支付方式的选择。
缺点:
策略类数量增多:如果系统中存在大量的策略,会导致策略类的数量增多,增加了代码的管理难度和维护成本。例如,当电商系统支持多种不同的优惠策略、积分策略等,策略类的数量会相应增加,可能会使代码结构变得复杂。
客户端需要了解策略:客户端需要了解不同的策略,并根据具体情况选择合适的策略,这增加了客户端的使用难度和复杂性。在支付系统中,客户端需要知道微信支付、支付宝支付等不同策略的存在,并根据用户的选择来创建相应的策略对象,这对客户端的要求较高。
策略模式适用于一个系统需要动态地在几种算法中选择一种的场景;算法需要自由切换的情况;避免使用多重条件判断的场景;以及将算法封装在独立的类中,便于复用和维护的场景。在实际开发中,策略模式常用于实现各种不同的业务规则、算法选择等,如游戏中的角色行为策略、搜索引擎中的排序策略等 。
模板方法模式(Template Method)
模板方法模式定义了一个算法的骨架,将一些步骤延迟到子类中实现,允许子类在不改变算法结构的情况下重新定义算法的某些步骤。
以制作饮料为例,制作咖啡和茶的步骤有一些相似之处,如都需要烧水、冲泡、倒入杯子等,但冲泡和添加调料的具体方式不同,使用模板方法模式实现:
// 抽象类
abstract class BeverageTemplate {
// 模板方法(final防止子类覆盖)
public final void prepareBeverage() {
boilWater();
brew();
pourInCup();
if (needCondiments()) {
addCondiments();
}
}
// 通用步骤实现
private void boilWater() {
System.out.println("煮沸水");
}
private void pourInCup() {
System.out.println("倒入杯子");
}
// 抽象方法(必须实现)
protected abstract void brew();
protected abstract void addCondiments();
// 钩子方法(可选覆盖)
protected boolean needCondiments() {
return true;
}
}
// 具体子类:咖啡
class Coffee extends BeverageTemplate {
@Override
protected void brew() {
System.out.println("冲泡咖啡粉");
}
@Override
protected void addCondiments() {
System.out.println("加入糖和牛奶");
}
@Override
protected boolean needCondiments() {
return false; // 示例:咖啡不要调料
}
}
// 具体子类:茶
class Tea extends BeverageTemplate {
@Override
protected void brew() {
System.out.println("浸泡茶叶");
}
@Override
protected void addCondiments() {
System.out.println("加入柠檬");
}
}
// 客户端
class Client {
public static void main(String\[] args) {
BeverageTemplate coffee = new Coffee();
coffee.prepareBeverage();
System.out.println("\n===============\n");
BeverageTemplate tea = new Tea();
tea.prepareBeverage();
}
}
在上述代码中,BeverageTemplate 是抽象类,定义了制作饮料的模板方法 prepareBeverage,该方法包含了制作饮料的通用步骤,如烧水、倒入杯子等,同时定义了抽象方法 brew 和 addCondiments,具体的冲泡和添加调料的逻辑由子类实现。Coffee 和 Tea 是具体子类,分别实现了 brew 和 addCondiments 方法,提供了制作咖啡和茶的具体步骤。needCondiments 是钩子方法,子类可以根据需要选择是否覆盖,用于控制是否添加调料。客户端通过创建具体子类的对象,并调用模板方法来制作饮料。
优点:
提高代码复用性:将算法中通用的部分封装在抽象类的模板方法中,子类只需实现特定的步骤,避免了代码的重复编写,提高了代码的复用性。在制作饮料的例子中,烧水和倒入杯子的步骤是通用的,不需要在每个子类中重复实现。
符合开闭原则:通过继承抽象类,子类可以在不修改抽象类代码的基础上,扩展和修改算法的某些步骤,符合开闭原则,提高了系统的可维护性和可扩展性。当需要新增一种饮料时,只需要创建一个新的子类,实现抽象类中的抽象方法即可。
控制算法执行顺序:抽象类控制了算法的整体结构和执行顺序,子类只能按照模板方法定义的顺序执行步骤,保证了算法的一致性和正确性。在制作饮料的过程中,必须先烧水,再冲泡,最后倒入杯子,模板方法确保了这些步骤的正确执行顺序。
缺点:
子类过多:每增加一个具体的实现,就需要新建一个子类,可能会导致子类数量过多,增加系统的复杂度和维护成本。如果有多种不同口味的咖啡和茶,就需要创建多个子类,管理起来会比较困难。
父类修改影响子类:如果抽象类中的模板方法或其他通用方法发生修改,可能会影响到所有的子类,需要对子类进行相应的调整,增加了维护的难度。例如,如果抽象类中
prepareBeverage方法的步骤顺序发生改变,所有子类都需要检查是否受到影响。
模板方法模式适用于有多个相似算法流程但部分步骤不同的场景;需要固定算法执行顺序的场景;需要扩展算法特定步骤的场景;框架设计中,父类控制流程,子类实现具体逻辑;以及需要消除重复代码,多个子类有相同结构的情况。在实际开发中,模板方法模式常用于实现一些通用的框架和算法,如 Java Servlet 的 service 方法、Spring 框架的 JdbcTemplate、Hibernate 的 HibernateTemplate 等 。
观察者模式(Observer)
观察者模式实现了对象间的一对多依赖,当一个对象的状态发生改变时,所有依赖于它的对象都会自动收到通知并更新。
以新闻发布系统为例,当有新的新闻发布时,需要通知所有订阅该新闻的用户,使用观察者模式实现:
import java.util.ArrayList;
import java.util.List;
// 被观察者接口
interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
// 观察者接口
interface Observer {
void update(String news);
}
// 具体被观察者:新闻发布中心
class NewsPublisher implements Subject {
private List\ observers = new ArrayList<>();
private String news;
public void setNews(String news) {
this.news = news;
notifyObservers();
}
@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);
}
}
}
// 具体观察者:用户
class User implements Observer {
private String name;
public User(String name) {
this.name = name;
}
@Override
public void update(String news) {
System.out.println(name + " 收到新闻:" + news);
}
}
// 客户端
class Client {
public static void main(String\[] args) {
NewsPublisher publisher = new NewsPublisher();
User user1 = new User("张三");
User user2 = new User("李四");
publisher.registerObserver(user1);
publisher.registerObserver(user2);
publisher.setNews("Java设计模式详解");
publisher.removeObserver(user2);
publisher.setNews("Spring框架入门教程");
}
}
在上述代码中,Subject 是被观察者接口,定义了注册观察者、移除观察者和通知观察者的方法。Observer 是观察者接口,定义了接收通知时的更新方法 update。NewsPublisher 是具体被观察者,维护了一个观察者列表,并在新闻更新时通知所有观察者。User 是具体观察者,实现了 update 方法,用于接收并处理新闻通知。客户端通过创建 NewsPublisher 和 User 对象,将用户注册到新闻发布中心,当新闻发布中心有新的新闻时,会通知所有注册的用户。
优点:
松耦合:被观察者和观察者之间通过接口交互,降低了耦合度,被观察者不需要知道具体的观察者是谁,只需要维护一个观察者列表,观察者也不需要直接依赖被观察者的具体实现,提高了系统的灵活性和可维护性。在新闻发布系统中,新闻发布中心不需要关心具体的用户是谁,只需要在有新新闻时通知所有观察者即可。
动态扩展:可以在运行时动态地添加或移除观察者,增强了系统的灵活性。例如,用户可以随时订阅或取消订阅新闻,而不会影响新闻发布系统的其他部分。
广泛适用:适用于多种场景,如事件处理、发布 - 订阅系统、状态变化联动等。在 GUI 编程中,按钮的点击事件、窗口的关闭事件等都可以使用观察者模式来实现事件监听;在消息队列中,生产者和消费者之间的关系也可以看作是观察者模式的应用。
缺点:
性能问题:当观察者数量过多时,通知所有观察者的过程可能会变慢,影响系统的性能。例如,在一个拥有大量用户的新闻发布系统中,每次发布新闻时通知所有用户可能会消耗大量的时间和资源。
通知顺序无保障:无法保证观察者被通知的顺序,这可能会导致一些问题,特别是当观察者的处理逻辑依赖于通知顺序时。在某些情况下,可能需要按照特定的顺序通知观察者,但观察者模式本身无法保证这一点。
内存泄漏风险:如果观察者未正确移除,可能会导致内存泄漏。当一个观察者不再需要接收通知时,如果没有从被观察者的列表中移除,被观察者仍然会持有对该观察者的引用,可能会导致内存无法释放。
观察者模式在 Java 标准库中也有广泛应用,如 java.util.Observable 和 java.util.Observer 提供了内置的观察者模式支持(虽然现已标记为过时,但仍可参考);在 Swing 或 JavaFX 中,按钮的 ActionListener 就是观察者模式的典型应用。观察者模式适合组件间紧密耦合的场景,在实际开发中,常用于实现事件驱动的系统、消息传递系统等 。
迭代器模式(Iterator)
迭代器模式提供了一种方法来顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。它将元素的遍历和集合的管理分离,使得代码更加清晰和可维护。
假设我们有一个自定义的书架类,需要提供遍历书架上书籍的功能,使用迭代器模式实现:
// 迭代器接口
interface Iterator\ {
boolean hasNext();
T next();
void remove();
}
// 聚合接口
interface IterableCollection\ {
Iterator\ createIterator();
}
// 具体集合:书架
class BookShelf implements IterableCollection\ {
private List\ books = new ArrayList<>();
public void addBook(Book book) {
books.add(book);
}
public Book getBook(int index) {
return books.get(index);
}
public int size() {
return books.size();
}
@Override
public Iterator\ createIterator() {
return new BookShelfIterator(this);
}
}
// 具体迭代器
class BookShelfIterator implements Iterator\ {
private BookShelf bookShelf;
private int position;
public BookShelfIterator(BookShelf bookShelf) {
this.bookShelf = bookShelf;
this.position = 0;
}
@Override
public boolean hasNext() {
return position < bookShelf.size();
}
@Override
public Book next() {
Book book = bookShelf.getBook(position);
position++;
return book;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
// 书籍类
class Book {
private String title;
public Book(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
}
// 客户端调用
class Client {
public static void main(String\[] args) {
BookShelf shelf = new BookShelf();
shelf.addBook(new Book("设计模式"));
shelf.addBook(new Book("Java核心技术"));
shelf.addBook(new Book("算法导论"));
Iterator\ it = shelf.createIterator();
while (it.hasNext()) {
System.out.println(it.next().getTitle());
}
}
}
在上述代码中,Iterator 是迭代器接口,定义了遍历元素的方法 hasNext、next 和 remove。IterableCollection 是聚合接口,定义了创建迭代器的方法 createIterator。BookShelf 是具体集合类,实现了 IterableCollection 接口,维护了一个书籍列表,并提供了创建迭代器的方法。BookShelfIterator 是具体迭代器类,实现了 Iterator 接口,用于遍历书架上的书籍。Book 是书籍类,包含书籍的标题。客户端通过创建 BookShelf 对象,添加书籍,然后获取迭代器,使用迭代器遍历书架上的书籍。
优点:
- 解耦集合与遍历:迭代器模式将集合对象的遍历行为与集合本身分离,客户端只需要通过迭代器来遍历集合,而不需要了解集合的内部结构,降低
总结与展望
23 种设计模式是 Java 开发中的宝贵财富,涵盖了创建型、结构型和行为型三个主要领域,每种模式都有其独特的核心要点和适用的应用场景。
创建型模式解决对象创建的问题,如单例模式确保类仅有一个实例,工厂模式将对象创建和使用分离,建造者模式用于构建复杂对象,抽象工厂模式创建相关对象的家族,原型模式通过复制现有对象创建新对象。这些模式在不同场景下,帮助我们更灵活、高效地创建对象,减少代码的重复和耦合。
结构型模式关注类和对象的组合与结构,适配器模式解决接口不兼容问题,装饰器模式动态添加对象功能,代理模式控制对对象的访问,外观模式提供统一接口简化子系统访问,桥接模式分离抽象与实现,组合模式处理树形结构对象,享元模式共享细粒度对象以减少内存消耗。它们使我们能够优化系统的结构,提高代码的复用性和可维护性。
行为型模式处理对象之间的交互和职责分配,策略模式定义算法族并可动态切换,模板方法模式定义算法骨架由子类实现具体步骤,观察者模式实现对象间的一对多依赖通知,迭代器模式提供遍历聚合对象的方法,责任链模式处理请求的链式传递,命令模式将请求封装成对象,备忘录模式保存和恢复对象状态,状态模式根据对象状态改变行为,访问者模式为对象结构中的元素添加新行为,中介者模式封装对象间的交互,解释器模式解析特定语言。这些模式使得系统的行为更加清晰、灵活,提高了系统的可扩展性和可维护性。
随着 Java 技术的不断发展,设计模式也在持续演进和发展。未来,设计模式在 Java 开发中可能会呈现以下趋势:
与新兴技术融合:随着云计算、大数据、人工智能等新兴技术的发展,设计模式将与这些技术深度融合,以满足新的业务需求和技术挑战。例如,在微服务架构中,设计模式可以用于解决服务之间的通信、协调和容错等问题;在大数据处理中,设计模式可以优化数据处理流程,提高数据处理效率。
更加注重性能和效率:随着软件系统的规模和复杂性不断增加,对系统性能和效率的要求也越来越高。未来的设计模式将更加注重性能和效率的优化,通过合理的设计和实现,减少系统的资源消耗,提高系统的响应速度和吞吐量。
向智能化方向发展:人工智能技术的发展将为设计模式带来新的机遇和挑战。未来的设计模式可能会借助人工智能技术,实现自动化的设计和优化,提高设计的效率和质量。例如,通过机器学习算法自动识别代码中的设计模式,或者根据业务需求自动生成符合设计模式的代码。
作为 Java 开发者,深入理解和熟练掌握这 23 种设计模式是提升编程能力和软件设计水平的关键。通过不断学习和实践,我们能够在实际项目中灵活运用设计模式,编写出更加健壮、可维护和可扩展的 Java 代码,为构建高质量的软件系统奠定坚实的基础。

浙公网安备 33010602011771号