常见设计模式详解——新手入门指南

Ubanillx 发布于 23 天前 71 次阅读


一、单例模式(Singleton Pattern)

{"type":"load_by_key","id":"","key":"banner_image_0","width":0,"height":0,"image_type":"search","pages_id":"7363440935614210","genre":"博客文章","artifact_key":7363225609921282}

1.1 模式定义与作用

单例模式确保一个类仅有一个实例,并提供一个全局访问点来访问这个唯一实例。这在一些需要控制资源访问,避免重复创建资源的场景中非常有用。例如,在一个应用程序中,可能需要一个全局的数据库连接对象,如果创建多个数据库连接,不仅会消耗更多资源,还可能导致数据不一致等问题。

1.2 实现方式

以 Java 语言为例,实现单例模式通常有以下几种方式:

  • 饿汉式:在类加载时就创建单例对象。
 public class Singleton {
     // 私有静态成员变量,在类加载时就实例化
     private static Singleton instance = new Singleton();
 ​
     // 私有构造函数,防止外部实例化
     private Singleton() {}
 ​
     // 提供公共的静态方法,返回唯一实例
     public static Singleton getInstance() {
         return instance;
     }
 }

这种方式的优点是实现简单,并且线程安全,因为在类加载阶段就完成了实例化,而类加载是由 JVM 保证线程安全的。缺点是如果这个单例对象一直没有被使用,会造成资源浪费。

  • 懒汉式(线程不安全):在第一次调用getInstance方法时才创建实例。
 public class Singleton {
     // 私有静态成员变量
     private static Singleton instance;
 ​
     // 私有构造函数,防止外部实例化
     private Singleton() {}
 ​
     // 提供公共的静态方法,返回唯一实例
     public static Singleton getInstance() {
         if (instance == null) {
             instance = new Singleton();
         }
         return instance;
     }
 }

这种方式实现了延迟加载,只有在真正需要使用单例对象时才创建,避免了资源浪费。但在多线程环境下,当多个线程同时判断instance为null时,可能会创建多个实例,因此线程不安全。

  • 懒汉式(线程安全):通过在getInstance方法上加锁来保证线程安全。
 public class Singleton {
     // 私有静态成员变量
     private static Singleton instance;
 ​
     // 私有构造函数,防止外部实例化
     private Singleton() {}
 ​
     // 提供公共的静态方法,返回唯一实例,方法上加锁保证线程安全
     public static synchronized Singleton getInstance() {
         if (instance == null) {
             instance = new Singleton();
         }
         return instance;
     }
 }

这种方式解决了线程安全问题,但每次调用getInstance方法都需要进行同步操作,会影响性能。

  • 双重检查锁(DCL):既实现延迟加载,又保证线程安全。
 public class Singleton {
     // 私有静态成员变量,使用volatile关键字保证可见性和禁止指令重排
     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;
     }
 }

这种方式首先检查instance是否为null,如果为null,再进行同步操作,在同步块内再次检查instance是否为null,只有在真正需要创建实例时才会进行创建。volatile关键字保证了在多线程环境下,一个线程对instance的修改能及时被其他线程看到,并且禁止了指令重排,防止在创建实例时出现问题。

  • 枚举方式:这是 Effective Java 作者 Josh Bloch 提倡的方式。
 public enum Singleton {
     INSTANCE;
     // 可以在枚举中定义其他方法和属性
     public void doSomething() {
         System.out.println("执行一些操作");
     }
 }

使用枚举实现单例模式,不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,确保了单例的唯一性。

1.3 应用场景举例

在 Java 的日志记录中,java.util.logging.Logger类就采用了类似单例模式的设计。在一个应用程序中,通常只需要一个全局的日志记录器来记录各种信息,避免多个日志记录器产生混乱。通过单例模式,无论在应用程序的哪个部分获取日志记录器,得到的都是同一个实例,方便统一管理和配置日志记录的行为。

二、工厂模式(Factory Pattern)

2.1 模式定义与作用

工厂模式定义了一个创建对象的接口,让子类决定实例化哪一个类。它将对象的创建和使用分离,提高了代码的可维护性和可扩展性。当我们需要创建多个不同类型但具有相同接口的对象时,使用工厂模式可以避免在客户端代码中出现大量的if - else或switch - case语句来判断创建哪种对象,使代码更加简洁和易于维护。

2.2 简单工厂模式(Simple Factory Pattern)

简单工厂模式虽然不属于 GoF(Gang of Four,即设计模式的经典书籍《设计模式 - 可复用的面向对象软件元素》的四位作者)定义的 23 种设计模式之一,但它是工厂模式的基础。

假设我们有一个图形绘制系统,需要绘制不同类型的图形,如圆形、矩形等。首先定义一个图形接口Shape:

 public interface Shape {
     void draw();
 }

然后实现圆形和矩形类:

 public class Circle implements Shape {
     @Override
     public void draw() {
         System.out.println("绘制圆形");
     }
 }
 public class Rectangle implements Shape {
     @Override
     public void draw() {
         System.out.println("绘制矩形");
     }
 }

接着创建一个简单工厂类ShapeFactory:

 public class ShapeFactory {
     public Shape createShape(String shapeType) {
         if ("circle".equalsIgnoreCase(shapeType)) {
             return new Circle();
         } else if ("rectangle".equalsIgnoreCase(shapeType)) {
             return new Rectangle();
         }
         return null;
     }
 }

在客户端代码中使用简单工厂类来创建图形对象:

 public class Client {
     public static void main(String[] args) {
         ShapeFactory factory = new ShapeFactory();
         Shape circle = factory.createShape("circle");
         circle.draw();
         Shape rectangle = factory.createShape("rectangle");
         rectangle.draw();
     }
 }

简单工厂模式的优点是实现简单,将对象的创建逻辑封装在工厂类中,客户端只需要关心使用什么对象,而不需要关心对象的创建过程。缺点是当需要增加新的图形类型时,需要修改工厂类的createShape方法,违背了开闭原则(对扩展开放,对修改关闭)。

2.3 工厂方法模式(Factory Method Pattern)

为了解决简单工厂模式违背开闭原则的问题,引入了工厂方法模式。在工厂方法模式中,将创建对象的方法抽象成抽象方法,由具体的子类来实现。

首先,将简单工厂模式中的ShapeFactory类改为抽象类,并将createShape方法改为抽象方法:

 public abstract class ShapeFactory {
     public abstract Shape createShape();
 }

然后创建具体的工厂子类,如CircleFactory和RectangleFactory:

 public class CircleFactory extends ShapeFactory {
     @Override
     public Shape createShape() {
         return new Circle();
     }
 }
 public class RectangleFactory extends 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();
     }
 }

工厂方法模式的优点是符合开闭原则,当需要增加新的图形类型时,只需要创建一个新的具体工厂类并实现createShape方法,而不需要修改现有的工厂类代码。缺点是如果图形类型较多,会导致工厂子类数量过多,增加代码的复杂性。

2.4 抽象工厂模式(Abstract Factory Pattern)

抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。它适用于创建对象的家族,这些对象之间有一定的关联或依赖关系。

假设我们有一个游戏角色创建系统,角色分为战士和法师,每个角色有不同的武器和装备。首先定义武器和装备的接口:

 public interface Weapon {
     void use();
 }
 public interface Armor {
     void wear();
 }

然后实现战士和法师的武器与装备类:

 public class Sword implements Weapon {
     @Override
     public void use() {
         System.out.println("使用剑进行攻击");
     }
 }
 public class Shield implements Armor {
     @Override
     public void wear() {
         System.out.println("穿上盾牌进行防御");
     }
 }
 public class Staff implements Weapon {
     @Override
     public void use() {
         System.out.println("使用法杖释放魔法");
     }
 }
 public class Robe implements Armor {
     @Override
     public void wear() {
         System.out.println("穿上法袍增强魔法");
     }
 }

接着定义一个抽象工厂接口CharacterFactory,用于创建武器和装备:

 public interface CharacterFactory {
     Weapon createWeapon();
     Armor createArmor();
 }

再创建具体的工厂子类,如WarriorFactory和MageFactory:

 public class WarriorFactory implements CharacterFactory {
     @Override
     public Weapon createWeapon() {
         return new Sword();
     }
     @Override
     public Armor createArmor() {
         return new Shield();
     }
 }
 public class MageFactory implements CharacterFactory {
     @Override
     public Weapon createWeapon() {
         return new Staff();
     }
     @Override
     public Armor createArmor() {
         return new Robe();
     }
 }

在客户端代码中,通过具体的工厂子类来创建战士或法师所需的武器和装备:

 public class Client {
     public static void main(String[] args) {
         CharacterFactory warriorFactory = new WarriorFactory();
         Weapon sword = warriorFactory.createWeapon();
         Armor shield = warriorFactory.createArmor();
         sword.use();
         shield.wear();
         CharacterFactory mageFactory = new MageFactory();
         Weapon staff = mageFactory.createWeapon();
         Armor robe = mageFactory.createArmor();
         staff.use();
         robe.wear();
     }
 }

抽象工厂模式的优点是将对象的创建和使用进一步解耦,客户端只需要关心使用什么类型的对象家族,而不需要关心具体对象的创建过程。缺点是当对象家族的种类或对象的创建逻辑发生变化时,修改和扩展的难度较大,因为需要同时修改抽象工厂接口和具体工厂子类。

2.5 应用场景举例

在 Java 的 JDBC(Java Database Connectivity)中,DriverManager类就使用了工厂模式。DriverManager负责管理数据库驱动程序,应用程序通过DriverManager获取数据库连接对象(Connection)。不同的数据库厂商(如 MySQL、Oracle 等)提供各自的数据库驱动实现类,这些实现类实现了 JDBC 定义的接口。DriverManager根据配置的数据库类型(如jdbc:mysql://localhost:3306/mydb或jdbc:oracle:thin:@localhost:1521:orcl)来创建相应的数据库连接对象,将数据库连接对象的创建过程封装起来,应用程序只需要关注如何使用数据库连接进行数据操作,而不需要关心具体的连接创建细节。

三、适配器模式(Adapter Pattern)

3.1 模式定义与作用

适配器模式将一个类的接口转换成客户希望的另外一个接口。它使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。适配器模式就像一个转换器,在不改变原有类的基础上,将其接口适配成符合新需求的接口。

3.2 实现方式

适配器模式有两种实现方式:类适配器和对象适配器。

3.2.1 类适配器

假设我们有一个旧的音频播放器类OldAudioPlayer,它只能播放 MP3 格式的音频文件:

 public class OldAudioPlayer {
     public void playMP3(String fileName) {
         System.out.println("播放MP3文件:" + fileName);
     }
 }

现在有一个新的需求,需要播放 WAV 格式的音频文件,而OldAudioPlayer类不支持。我们可以创建一个适配器类AudioPlayerAdapter,继承自OldAudioPlayer,并实现新的播放 WAV 文件的接口NewAudioPlayer:

 public interface NewAudioPlayer {
     void playWAV(String fileName);
 }
 public class AudioPlayerAdapter extends OldAudioPlayer implements NewAudioPlayer {
     @Override
     public void playWAV(String fileName) {
         // 这里可以添加将WAV文件转换为MP3格式的逻辑(假设可以转换)
         // 或者调用其他能播放WAV文件的方法
         System.out.println("通过适配器播放WAV文件:" + fileName);
     }
 }

在客户端代码中,使用适配器类来播放 WAV 文件:

 public class Client {
     public static void main(String[] args) {
         NewAudioPlayer audioPlayer = new AudioPlayerAdapter();
         audioPlayer.playWAV("song.wav");
     }
 }

类适配器的优点是实现简单,通过继承旧类并实现新接口,直接复用了旧类的功能。缺点是由于 Java 不支持多重继承,如果旧类已经有父类,就无法使用类适配器。

3.2.2 对象适配器

对象适配器不通过继承,而是通过组合的方式来实现。同样以音频播放器为例,我们对适配器类进行修改:

 public class AudioPlayerAdapter implements NewAudioPlayer {
     private OldAudioPlayer oldAudioPlayer;
 ​
     public AudioPlayerAdapter(OldAudioPlayer oldAudioPlayer) {
         this.oldAudioPlayer = oldAudioPlayer;
     }
 ​
     @Override
     public void playWAV(String fileName) {
         // 这里可以添加将WAV文件转换为MP3格式的逻辑(假设可以转换)
         // 或者调用其他能播放WAV文件的方法
         System.out.println("通过适配器播放WAV文件:" + fileName);
     }
 }

在客户端代码中,创建适配器对象时传入旧音频播放器对象:

 public class Client {
     public static void main(String[] args) {
         OldAudioPlayer oldPlayer = new OldAudioPlayer();
         NewAudioPlayer audioPlayer = new AudioPlayerAdapter(oldPlayer);
         audioPlayer.playWAV("song.wav");
     }
 }

对象适配器的优点是灵活性更高,它可以适配不同类型的对象,并且避免了类适配器的多重继承问题。缺点是实现相对复杂一些,需要通过组合的方式来关联旧对象。

3.3 应用场景举例

在 Android 开发中,当我们使用第三方库时,可能会遇到第三方库的接口与我们项目的接口不兼容的情况。例如,某个第三方图片加载库提供的加载图片的接口参数和返回值类型与我们项目中已有的图片加载接口不同。这时可以使用适配器模式,创建一个适配器类,将第三方库的接口适配成符合我们项目需求的接口,这样就可以在不修改第三方库代码和项目中大量现有代码的情况下,顺利使用第三方库的功能。

四、观察者模式(Observer Pattern)

4.1 模式定义与作用

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象的状态发生变化时,会通知所有的观察者对象,使它们能够自动更新自己。这种模式在很多场景中都有应用,比如消息通知系统、事件驱动系统等。

4.2 实现方式

以 Java 语言为例,实现观察者模式通常需要以下几个部分:

  • 主题接口(Subject):定义了添加、删除观察者以及通知观察者的方法。
 import java.util.ArrayList;
 import java.util.List;
 ​
 public interface Subject {
     void registerObserver(Observer observer);
     void removeObserver(Observer observer);
     void notifyObservers();
 }
  • 具体主题类(ConcreteSubject):实现主题接口,维护一个观察者列表,并在状态发生变化时通知观察者。
 import java.util.ArrayList;
 import java.util.List;
 ​
 public class ConcreteSubject implements Subject {
     private List<Observer> observers = new ArrayList<>();
     private int state;
 ​
     public int getState() {
         return state;
     }
 ​
     public void setState(int state) {
         this.state = state;
         notifyObservers();
     }
 ​
     @Override
     public void 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(this);
         }
     }
 }
  • 观察者接口(Observer):定义了更新方法,当主题状态变化时,观察者通过该方法进行更新。
 public interface Observer {
     void update(Subject subject);
 }
  • 具体观察者类(ConcreteObserver):实现观察者接口,在update方法中实现具体的更新逻辑。
 public class ConcreteObserver implements Observer {
     private String name;
 ​
     public ConcreteObserver(String name) {
         this.name = name;
     }
 ​
     @Override
     public void update(Subject subject) {
         int state = subject.getState();
         System.out.println(name + " 接收到更新,状态为:" + state);
     }
 }

在客户端代码中使用观察者模式:

 public class Client {
     public static void main(String[] args) {
         ConcreteSubject subject = new ConcreteSubject();
         ConcreteObserver observer1 = new ConcreteObserver("观察者1");
         ConcreteObserver observer2 = new ConcreteObserver("观察者2");
 ​
         subject.registerObserver(observer1);
         subject.registerObserver(observer2);
 ​
         subject.setState(10);
     }
 }

运行结果会输出:

 观察者1 接收到更新,状态为:10
 观察者2 接收到更新,状态为:10

4.3 应用场景举例

在社交媒体平台中,用户关注某个博主(主题)后,当博主发布新内容(主题状态变化),所有关注该博主的用户(观察者)都会收到通知,比如微博上用户关注明星账号,明星发布新动态时粉丝能及时收到推送,这就是观察者模式的典型应用。

五、装饰器模式(Decorator Pattern)

5.1 模式定义与作用

装饰器模式动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式比生成子类更为灵活。它可以在不修改原有类代码的情况下,为对象添加新的功能,并且可以通过组合多个装饰器来实现复杂的功能组合。

5.2 实现方式

假设我们有一个饮品店,提供基础的咖啡饮品,现在需要为咖啡添加不同的配料(如牛奶、糖、奶油等)来增加其风味,使用装饰器模式来实现。

首先定义饮品接口Beverage:

 public interface Beverage {
     String getDescription();
     double cost();
 }

实现基础咖啡类Espresso:

 public class Espresso implements Beverage {
     @Override
     public String getDescription() {
         return "浓缩咖啡";
     }
 ​
     @Override
     public double cost() {
         return 2.5;
     }
 }

定义装饰器抽象类CondimentDecorator,它也实现Beverage接口,并且持有一个Beverage对象的引用:

 public abstract class CondimentDecorator implements Beverage {
     protected Beverage beverage;
 ​
     public CondimentDecorator(Beverage beverage) {
         this.beverage = beverage;
     }
 ​
     @Override
     public abstract String getDescription();
     @Override
     public abstract double cost();
 }

然后实现具体的装饰器类,如添加牛奶的Milk装饰器:

 public class Milk extends CondimentDecorator {
     public Milk(Beverage beverage) {
         super(beverage);
     }
 ​
     @Override
     public String getDescription() {
         return beverage.getDescription() + ", 加牛奶";
     }
 ​
     @Override
     public double cost() {
         return beverage.cost() + 0.5;
     }
 }

添加糖的Sugar装饰器:

 public class Sugar extends CondimentDecorator {
     public Sugar(Beverage beverage) {
         super(beverage);
     }
 ​
     @Override
     public String getDescription() {
         return beverage.getDescription() + ", 加糖";
     }
 ​
     @Override
     public double cost() {
         return beverage.cost() + 0.2;
     }
 }

在客户端代码中使用装饰器模式:

 public class Client {
     public static void main(String[] args) {
         Beverage espresso = new Espresso();
         System.out.println(espresso.getDescription() + " 花费: $" + espresso.cost());
 ​
         Beverage espressoWithMilk = new Milk(espresso);
         System.out.println(espressoWithMilk.getDescription() + " 花费: $" + espressoWithMilk.cost());
 ​
         Beverage espressoWithMilkAndSugar = new Sugar(espressoWithMilk);
         System.out.println(espressoWithMilkAndSugar.getDescription() + " 花费: $" + espressoWithMilkAndSugar.cost());
     }
 }

运行结果会输出:

 浓缩咖啡 花费: $2.5
 浓缩咖啡, 加牛奶 花费: $3.0
 浓缩咖啡, 加牛奶, 加糖 花费: $3.2

5.3 应用场景举例

{"type":"load_by_key","id":"","key":"banner_image_1","width":0,"height":0,"image_type":"search","pages_id":"7363440935614210","genre":"博客文章","artifact_key":7363236642707970}

在 Java 的 IO 流中广泛使用了装饰器模式。例如,FileInputStream是基础的文件输入流,BufferedInputStream可以为其添加缓冲功能,DataInputStream可以为其添加读取基本数据类型的功能。通过组合不同的装饰器流,我们可以灵活地实现各种复杂的文件读取操作,而无需修改基础 IO 流类的代码。

六、代理模式(Proxy Pattern)

6.1 模式定义与作用

代理模式为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用,并且可以在代理过程中进行一些额外的操作,如权限验证、缓存处理等。

6.2 实现方式

假设我们有一个图片加载功能,为了减少内存消耗和网络请求,我们使用代理模式来实现图片的延迟加载。

首先定义图片接口Image:

 public interface Image {
     void display();
 }

实现真实图片类RealImage:

 public class RealImage implements Image {
     private String fileName;
 ​
     public RealImage(String fileName) {
         this.fileName = fileName;
         loadImageFromDisk(fileName);
     }
 ​
     private void loadImageFromDisk(String fileName) {
         System.out.println("从磁盘加载图片: " + fileName);
     }
 ​
     @Override
     public void display() {
         System.out.println("显示图片: " + fileName);
     }
 }

定义代理图片类ProxyImage:

 public class ProxyImage implements Image {
     private String fileName;
     private RealImage realImage;
 ​
     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 proxyImage = new ProxyImage("image.jpg");
         // 这里不会真正加载图片
         proxyImage.display(); 
         // 再次调用才会加载图片
         proxyImage.display(); 
     }
 }

运行结果会输出:

 从磁盘加载图片: image.jpg
 显示图片: image.jpg
 显示图片: image.jpg

6.3 应用场景举例

在网络访问中,我们经常会使用代理服务器。当客户端请求访问某个网站时,请求先发送到代理服务器,代理服务器可以进行权限验证、缓存网页内容等操作,然后再将请求转发到目标网站,或者直接将缓存的内容返回给客户端。这种方式既可以提高访问效率,又能增强安全性,这就是代理模式在实际网络场景中的应用。

验证权限是是否否客户端代理服务器权限是否通过是否有缓存返回缓存内容目标网站拒绝访问

七、策略模式(Strategy Pattern)

7.1 模式定义与作用

策略模式定义了一系列算法,将每个算法都封装起来,并且使它们可以相互替换。该模式让算法的变化不会影响到使用算法的客户端,实现了行为的动态切换,提升了代码的可维护性和扩展性,适用于有多种算法或行为,且需要在运行时动态选择的场景 。

7.2 实现方式

以一个电商系统的促销活动为例,不同商品可能有不同的折扣策略,如固定金额减免、百分比折扣等。首先定义折扣策略接口DiscountStrategy:

 public interface DiscountStrategy {
     double calculateDiscount(double originalPrice);
 }

然后实现具体的折扣策略类。固定金额减免策略FixedAmountDiscount:

 public class FixedAmountDiscount implements DiscountStrategy {
     private double discountAmount;
 ​
     public FixedAmountDiscount(double discountAmount) {
         this.discountAmount = discountAmount;
     }
 ​
     @Override
     public double calculateDiscount(double originalPrice) {
         return Math.max(0, originalPrice - discountAmount);
     }
 }

百分比折扣策略PercentageDiscount:

 public class PercentageDiscount implements DiscountStrategy {
     private double discountPercentage;
 ​
     public PercentageDiscount(double discountPercentage) {
         this.discountPercentage = discountPercentage;
     }
 ​
     @Override
     public double calculateDiscount(double originalPrice) {
         return originalPrice * (1 - discountPercentage);
     }
 }

接着创建商品类Product,它持有一个DiscountStrategy对象:

 public class Product {
     private String name;
     private double price;
     private DiscountStrategy discountStrategy;
 ​
     public Product(String name, double price, DiscountStrategy discountStrategy) {
         this.name = name;
         this.price = price;
         this.discountStrategy = discountStrategy;
     }
 ​
     public double getDiscountedPrice() {
         return discountStrategy.calculateDiscount(price);
     }
 }

在客户端代码中使用策略模式:

 public class Client {
     public static void main(String[] args) {
         DiscountStrategy fixedDiscount = new FixedAmountDiscount(10);
         Product product1 = new Product("T恤", 50, fixedDiscount);
         System.out.println(product1.getName() + " 的折后价为: " + product1.getDiscountedPrice());
 ​
         DiscountStrategy percentageDiscount = new PercentageDiscount(0.2);
         Product product2 = new Product("牛仔裤", 100, percentageDiscount);
         System.out.println(product2.getName() + " 的折后价为: " + product2.getDiscountedPrice());
     }
 }

运行结果会输出:

 T恤 的折后价为: 40.0
 牛仔裤 的折后价为: 80.0

7.3 应用场景举例

在游戏开发中,角色的攻击行为可以使用策略模式实现。不同角色可能有不同的攻击策略,如近战角色采用近身攻击策略,远程角色采用远程射击策略。通过策略模式,可以方便地在运行时为角色切换攻击策略,而不需要修改角色类的核心代码 。

八、模板方法模式(Template Method Pattern)

8.1 模式定义与作用

模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中实现。它使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤,有助于代码复用和扩展,常用于有固定流程,但部分步骤实现可能不同的场景。

8.2 实现方式

以制作饮品为例,制作饮品有一个大致的流程:准备材料、制作饮品、倒入杯子,但不同饮品的制作方式不同。首先定义抽象类BeverageMaker:

 public abstract class BeverageMaker {
     // 模板方法,定义算法骨架
     public final void makeBeverage() {
         prepareIngredients();
         brew();
         pourIntoCup();
         addCondiments();
     }
 ​
     protected abstract void prepareIngredients();
     protected abstract void brew();
     protected void pourIntoCup() {
         System.out.println("将饮品倒入杯子");
     }
     protected abstract void addCondiments();
 }

然后实现具体的饮品制作类,如咖啡制作类CoffeeMaker:

 public class CoffeeMaker extends BeverageMaker {
     @Override
     protected void prepareIngredients() {
         System.out.println("准备咖啡豆和水");
     }
 ​
     @Override
     protected void brew() {
         System.out.println("煮咖啡");
     }
 ​
     @Override
     protected void addCondiments() {
         System.out.println("添加牛奶和糖");
     }
 }

茶制作类TeaMaker:

 public class TeaMaker extends BeverageMaker {
     @Override
     protected void prepareIngredients() {
         System.out.println("准备茶叶和水");
     }
 ​
     @Override
     protected void brew() {
         System.out.println("泡茶");
     }
 ​
     @Override
     protected void addCondiments() {
         System.out.println("添加柠檬");
     }
 }

在客户端代码中使用模板方法模式:

 public class Client {
     public static void main(String[] args) {
         BeverageMaker coffeeMaker = new CoffeeMaker();
         coffeeMaker.makeBeverage();
 ​
         BeverageMaker teaMaker = new TeaMaker();
         teaMaker.makeBeverage();
     }
 }

运行结果会输出:

 准备咖啡豆和水
 煮咖啡
 将饮品倒入杯子
 添加牛奶和糖
 准备茶叶和水
 泡茶
 将饮品倒入杯子
 添加柠檬

8.3 应用场景举例

在 Spring 框架中,JdbcTemplate类就使用了模板方法模式。JdbcTemplate定义了执行数据库操作的通用流程,如获取数据库连接、执行 SQL 语句、处理结果集等,而具体的 SQL 语句和结果集处理逻辑则由子类或调用者提供,这样大大简化了数据库操作代码,提高了开发效率 。

九、责任链模式(Chain of Responsibility Pattern)

9.1 模式定义与作用

责任链模式使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。适用于处理请求的对象不明确,或者需要动态指定处理请求的对象的场景。

9.2 实现方式

以员工请假审批流程为例,员工请假需要不同级别的领导审批,如项目经理、部门经理、总经理等。首先定义审批者抽象类Approver:

 public abstract class Approver {
     protected Approver successor;
 ​
     public void setSuccessor(Approver successor) {
         this.successor = successor;
     }
 ​
     public abstract void processRequest(int days);
 }

然后实现具体的审批者类。项目经理ProjectManager:

 public class ProjectManager extends Approver {
     @Override
     public void processRequest(int days) {
         if (days <= 3) {
             System.out.println("项目经理批准请假 " + days + " 天");
         } else if (successor != null) {
             successor.processRequest(days);
         }
     }
 }

部门经理DepartmentManager:

 public class DepartmentManager extends Approver {
     @Override
     public void processRequest(int days) {
         if (days <= 7) {
             System.out.println("部门经理批准请假 " + days + " 天");
         } else if (successor != null) {
             successor.processRequest(days);
         }
     }
 }

总经理GeneralManager:

 public class GeneralManager extends Approver {
     @Override
     public void processRequest(int days) {
         System.out.println("总经理批准请假 " + days + " 天");
     }
 }

在客户端代码中构建责任链并处理请求:

 public class Client {
     public static void main(String[] args) {
         Approver projectManager = new ProjectManager();
         Approver departmentManager = new DepartmentManager();
         Approver generalManager = new GeneralManager();
 ​
         projectManager.setSuccessor(departmentManager);
         departmentManager.setSuccessor(generalManager);
 ​
         projectManager.processRequest(2);
         projectManager.processRequest(5);
         projectManager.processRequest(10);
     }
 }

运行结果会输出:

 项目经理批准请假 2 天
 部门经理批准请假 5 天
 总经理批准请假 10 天

9.3 应用场景举例

在 Web 开发中,过滤器(Filter)机制就是责任链模式的应用。当客户端发送请求到服务器时,请求会依次经过多个过滤器,每个过滤器可以对请求进行处理,如权限验证、数据编码转换等,直到所有过滤器处理完毕后,请求才会到达目标资源。

十、迭代器模式(Iterator Pattern)

10.1 模式定义与作用

迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。它将遍历数据集合的逻辑从集合对象中分离出来,使得集合对象和遍历逻辑可以独立变化,提升了代码的可维护性和扩展性,同时也方便了不同类型数据集合的遍历操作。

10.2 实现方式

假设我们要实现一个简单的书籍管理系统,书籍存储在自定义的数据结构中,需要通过迭代器来遍历书籍列表。首先定义迭代器接口Iterator:

 public interface Iterator {
     boolean hasNext();
     Object next();
 }

然后定义聚合对象接口Aggregate,它负责创建迭代器对象:

 public interface Aggregate {
     Iterator createIterator();
 }

接着实现具体的书籍集合类BookShelf,作为聚合对象:

 public class BookShelf implements Aggregate {
     private Book[] books;
     private int last = 0;
 ​
     public BookShelf(int initialSize) {
         this.books = new Book[initialSize];
     }
 ​
     public Book getBookAt(int index) {
         return books[index];
     }
 ​
     public void appendBook(Book book) {
         books[last] = book;
         last++;
     }
 ​
     public int getLength() {
         return last;
     }
 ​
     @Override
     public Iterator createIterator() {
         return new BookShelfIterator(this);
     }
 }

再实现具体的迭代器类BookShelfIterator:

 public class BookShelfIterator implements Iterator {
     private BookShelf bookShelf;
     private int index;
 ​
     public BookShelfIterator(BookShelf bookShelf) {
         this.bookShelf = bookShelf;
         this.index = 0;
     }
 ​
     @Override
     public boolean hasNext() {
         if (index < bookShelf.getLength()) {
             return true;
         } else {
             return false;
         }
     }
 ​
     @Override
     public Object next() {
         Book book = bookShelf.getBookAt(index);
         index++;
         return book;
     }
 }

定义书籍类Book:

 public class Book {
     private String name;
 ​
     public Book(String name) {
         this.name = name;
     }
 ​
     public String getName() {
         return name;
     }
 }

在客户端代码中使用迭代器模式:

 public class Client {
     public static void main(String[] args) {
         BookShelf bookShelf = new BookShelf(3);
         bookShelf.appendBook(new Book("《设计模式》"));
         bookShelf.appendBook(new Book("《Java核心技术》"));
         bookShelf.appendBook(new Book("《Effective Java》"));
 ​
         Iterator it = bookShelf.createIterator();
         while (it.hasNext()) {
             Book book = (Book) it.next();
             System.out.println(book.getName());
         }
     }
 }

运行结果会输出:

 《设计模式》
 《Java核心技术》
 《Effective Java》

10.3 应用场景举例

在 Java 的集合框架中,List、Set、Map等集合类都广泛应用了迭代器模式。例如,当我们需要遍历一个ArrayList时,可以通过调用iterator()方法获取迭代器对象,然后使用迭代器的hasNext()和next()方法来遍历集合中的元素。这种方式使得我们在遍历集合时,不需要了解集合内部的存储结构和实现细节,提高了代码的通用性和可复用性。

十一、命令模式(Command Pattern)

11.1 模式定义与作用

命令模式将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。它将请求发送者和请求执行者解耦,使系统的扩展性和维护性更好,常用于需要实现操作的撤销、重做、排队执行等功能的场景。

11.2 实现方式

以一个简单的遥控器控制家电为例,遥控器上的按钮对应不同的家电操作命令。首先定义命令接口Command:

 public interface Command {
     void execute();
     void undo();
 }

定义接收者类TV,表示具体执行操作的对象:

 public class TV {
     public void on() {
         System.out.println("电视打开");
     }
 ​
     public void off() {
         System.out.println("电视关闭");
     }
 }

实现具体的命令类,如打开电视的命令TVOnCommand:

 public class TVOnCommand implements Command {
     private TV tv;
 ​
     public TVOnCommand(TV tv) {
         this.tv = tv;
     }
 ​
     @Override
     public void execute() {
         tv.on();
     }
 ​
     @Override
     public void undo() {
         tv.off();
     }
 }

关闭电视的命令TVOffCommand:

 public class TVOffCommand implements Command {
     private TV tv;
 ​
     public TVOffCommand(TV tv) {
         this.tv = tv;
     }
 ​
     @Override
     public void execute() {
         tv.off();
     }
 ​
     @Override
     public void undo() {
         tv.on();
     }
 }

定义调用者类RemoteControl,它负责调用命令的执行方法:

 public class RemoteControl {
     private Command command;
 ​
     public void setCommand(Command command) {
         this.command = command;
     }
 ​
     public void pressButton() {
         command.execute();
     }
 ​
     public void pressUndoButton() {
         command.undo();
     }
 }

在客户端代码中使用命令模式:

 public class Client {
     public static void main(String[] args) {
         TV tv = new TV();
         Command tvOnCommand = new TVOnCommand(tv);
         Command tvOffCommand = new TVOffCommand(tv);
 ​
         RemoteControl remoteControl = new RemoteControl();
 ​
         remoteControl.setCommand(tvOnCommand);
         remoteControl.pressButton();
 ​
         remoteControl.setCommand(tvOffCommand);
         remoteControl.pressButton();
 ​
         remoteControl.pressUndoButton();
     }
 }

运行结果会输出:

 电视打开
 电视关闭
 电视打开

11.3 应用场景举例

在文本编辑器中,撤销和重做操作就是命令模式的典型应用。用户的每一次编辑操作(如插入文本、删除文本等)都被封装为一个命令对象,这些命令对象被存储在命令队列中。当用户点击撤销按钮时,系统从命令队列中取出上一个执行的命令,调用其undo()方法来撤销操作;当用户点击重做按钮时,调用命令的execute()方法重新执行操作 。

十二、备忘录模式(Memento Pattern)

12.1 模式定义与作用

备忘录模式在不破坏封装性的前提下,捕获并保存一个对象的内部状态,以便以后恢复对象到先前的状态。它提供了一种状态恢复的机制,使得系统可以在需要的时候回到某个历史状态,常用于需要实现数据备份、恢复、撤销等功能的场景。

12.2 实现方式

以一个游戏角色的状态保存和恢复为例。首先定义备忘录类Memento,用于存储对象的状态:

 public class Memento {
     private int health;
     private int mana;
 ​
     public Memento(int health, int mana) {
         this.health = health;
         this.mana = mana;
     }
 ​
     public int getHealth() {
         return health;
     }
 ​
     public int getMana() {
         return mana;
     }
 }

定义原发器类GameCharacter,它创建和恢复备忘录:

 public class GameCharacter {
     private int health;
     private int mana;
 ​
     public GameCharacter(int health, int mana) {
         this.health = health;
         this.mana = mana;
     }
 ​
     public Memento saveState() {
         return new Memento(health, mana);
     }
 ​
     public void restoreState(Memento memento) {
         health = memento.getHealth();
         mana = memento.getMana();
     }
 ​
     public void takeDamage(int damage) {
         health -= damage;
     }
 ​
     public void useMana(int cost) {
         mana -= cost;
     }
 ​
     public int getHealth() {
         return health;
     }
 ​
     public int getMana() {
         return mana;
     }
 }

定义负责人类CareTaker,它负责管理备忘录:

 public class CareTaker {
     private Memento memento;
 ​
     public Memento getMemento() {
         return memento;
     }
 ​
     public void setMemento(Memento memento) {
         this.memento = memento;
     }
 }

在客户端代码中使用备忘录模式:

 public class Client {
     public static void main(String[] args) {
         GameCharacter character = new GameCharacter(100, 50);
         CareTaker careTaker = new CareTaker();
 ​
         System.out.println("初始状态 - 生命值: " + character.getHealth() + ", 魔法值: " + character.getMana());
 ​
         careTaker.setMemento(character.saveState());
 ​
         character.takeDamage(20);
         character.useMana(10);
 ​
         System.out.println("当前状态 - 生命值: " + character.getHealth() + ", 魔法值: " + character.getMana());
 ​
         character.restoreState(careTaker.getMemento());
 ​
         System.out.println("恢复状态后 - 生命值: " + character.getHealth() + ", 魔法值: " + character.getMana());
     }
 }

运行结果会输出:

 初始状态 - 生命值: 100, 魔法值: 50
 当前状态 - 生命值: 80, 魔法值: 40
 恢复状态后 - 生命值: 100, 魔法值: 50

12.3 应用场景举例

在数据库管理系统中,事务回滚功能就应用了备忘录模式。在事务开始前,系统会记录数据库的当前状态(创建备忘录),当事务执行过程中出现错误时,系统可以通过恢复备忘录将数据库状态回滚到事务开始前的状态,保证数据的一致性和完整性。

十三、状态模式(State Pattern)

{"type":"load_by_key","id":"","key":"banner_image_0","width":0,"height":0,"image_type":"search","pages_id":"7366982913227010","genre":"博客文章","artifact_key":7363336998262530}

13.1 模式定义与作用

状态模式允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。该模式将状态相关的行为封装到独立的状态类中,消除了大量的条件判断语句,使代码结构更清晰,便于维护和扩展,适用于对象的行为依赖于其状态,且状态频繁变化的场景。

13.2 实现方式

以自动售货机为例,售货机有不同的状态,如空闲状态、投币状态、出货状态等,不同状态下对用户操作(投币、退币、购买)的响应不同。首先定义状态接口VendingMachineState:

 public interface VendingMachineState {
     void insertCoin();
     void ejectCoin();
     void dispenseProduct();
 }

然后实现具体的状态类。空闲状态IdleState:

 public class IdleState implements VendingMachineState {
     private VendingMachine vendingMachine;
 ​
     public IdleState(VendingMachine vendingMachine) {
         this.vendingMachine = vendingMachine;
     }
 ​
     @Override
     public void insertCoin() {
         System.out.println("已投币,等待选择商品");
         vendingMachine.setState(vendingMachine.getHasCoinState());
     }
 ​
     @Override
     public void ejectCoin() {
         System.out.println("未投币,无法退币");
     }
 ​
     @Override
     public void dispenseProduct() {
         System.out.println("未投币,无法出货");
     }
 }

已投币状态HasCoinState:

 public class HasCoinState implements VendingMachineState {
     private VendingMachine vendingMachine;
 ​
     public HasCoinState(VendingMachine vendingMachine) {
         this.vendingMachine = vendingMachine;
     }
 ​
     @Override
     public void insertCoin() {
         System.out.println("已投币,无需再次投币");
     }
 ​
     @Override
     public void ejectCoin() {
         System.out.println("退币成功");
         vendingMachine.setState(vendingMachine.getIdleState());
     }
 ​
     @Override
     public void dispenseProduct() {
         System.out.println("出货成功");
         vendingMachine.setState(vendingMachine.getIdleState());
     }
 }

接着定义自动售货机类VendingMachine:

 public class VendingMachine {
     private VendingMachineState idleState;
     private VendingMachineState hasCoinState;
     private VendingMachineState currentState;
 ​
     public VendingMachine() {
         idleState = new IdleState(this);
         hasCoinState = new HasCoinState(this);
         currentState = idleState;
     }
 ​
     public void insertCoin() {
         currentState.insertCoin();
     }
 ​
     public void ejectCoin() {
         currentState.ejectCoin();
     }
 ​
     public void dispenseProduct() {
         currentState.dispenseProduct();
     }
 ​
     public VendingMachineState getIdleState() {
         return idleState;
     }
 ​
     public VendingMachineState getHasCoinState() {
         return hasCoinState;
     }
 ​
     public void setState(VendingMachineState state) {
         this.currentState = state;
     }
 }

在客户端代码中使用状态模式:

 public class Client {
     public static void main(String[] args) {
         VendingMachine vendingMachine = new VendingMachine();
         vendingMachine.insertCoin();
         vendingMachine.dispenseProduct();
         vendingMachine.ejectCoin();
         vendingMachine.ejectCoin();
     }
 }

运行结果会输出:

 已投币,等待选择商品
 出货成功
 未投币,无法退币

13.3 应用场景举例

在游戏开发中,角色的状态(如站立、奔跑、跳跃、攻击等)变化频繁,且不同状态下对用户输入的响应不同。使用状态模式,可以将角色的每种状态封装成独立的类,根据用户操作和游戏逻辑切换角色状态,使代码更易于维护和扩展 。

十四、中介者模式(Mediator Pattern)

14.1 模式定义与作用

中介者模式定义一个中介对象来封装一系列对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。该模式减少了对象之间的直接依赖关系,降低系统的复杂性,提高了可维护性和扩展性,适用于对象之间存在复杂的网状交互关系的场景。

14.2 实现方式

以一个简单的聊天系统为例,多个用户之间进行聊天,用户对象之间不直接交互,而是通过中介者对象来传递消息。首先定义中介者接口ChatMediator:

 public interface ChatMediator {
     void sendMessage(String message, User user);
     void addUser(User user);
 }

然后实现具体的中介者类ConcreteChatMediator:

 import java.util.ArrayList;
 import java.util.List;
 ​
 public class ConcreteChatMediator implements ChatMediator {
     private List<User> users = new ArrayList<>();
 ​
     @Override
     public void sendMessage(String message, User user) {
         for (User u : users) {
             if (u != user) {
                 u.receiveMessage(message);
             }
         }
     }
 ​
     @Override
     public void addUser(User user) {
         users.add(user);
     }
 }

定义用户类User,用户通过中介者发送和接收消息:

 public class User {
     private String name;
     private ChatMediator mediator;
 ​
     public User(String name, ChatMediator mediator) {
         this.name = name;
         this.mediator = mediator;
     }
 ​
     public void sendMessage(String message) {
         mediator.sendMessage(message, this);
     }
 ​
     public void receiveMessage(String message) {
         System.out.println(name + " 收到消息: " + message);
     }
 }

在客户端代码中使用中介者模式:

 public class Client {
     public static void main(String[] args) {
         ChatMediator mediator = new ConcreteChatMediator();
         User user1 = new User("Alice", mediator);
         User user2 = new User("Bob", mediator);
         User user3 = new User("Charlie", mediator);
 ​
         mediator.addUser(user1);
         mediator.addUser(user2);
         mediator.addUser(user3);
 ​
         user1.sendMessage("大家好!");
         user2.sendMessage("你好呀!");
     }
 }

运行结果会输出:

 Bob 收到消息: 大家好!
 Charlie 收到消息: 大家好!
 Alice 收到消息: 你好呀!
 Charlie 收到消息: 你好呀!

14.3 应用场景举例

在图形界面设计中,窗口内的多个组件(如按钮、文本框、列表框等)之间可能存在复杂的交互关系。使用中介者模式,创建一个窗口中介者类,负责协调各个组件之间的交互,使得组件之间的耦合度降低,便于对界面进行修改和扩展 。

十五、访问者模式(Visitor Pattern)

15.1 模式定义与作用

访问者模式表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。该模式将数据结构和作用于结构上的操作解耦,使得操作集合可相对自由地演化,适用于数据结构相对稳定,但经常需要在此数据结构上定义新的操作的场景。

15.2 实现方式

以一个图形绘制系统为例,图形有不同的类型(如圆形、矩形),现在需要为图形添加新的操作(如计算面积、绘制边框等)。首先定义访问者接口Visitor:

 public interface Visitor {
     void visitCircle(Circle circle);
     void visitRectangle(Rectangle rectangle);
 }

定义图形元素接口Element:

 public interface Element {
     void accept(Visitor visitor);
 }

实现具体的图形元素类。圆形Circle:

 public class Circle implements Element {
     private int radius;
 ​
     public Circle(int radius) {
         this.radius = radius;
     }
 ​
     public int getRadius() {
         return radius;
     }
 ​
     @Override
     public void accept(Visitor visitor) {
         visitor.visitCircle(this);
     }
 }

矩形Rectangle:

 public class Rectangle implements Element {
     private int width;
     private int height;
 ​
     public Rectangle(int width, int height) {
         this.width = width;
         this.height = height;
     }
 ​
     public int getWidth() {
         return width;
     }
 ​
     public int getHeight() {
         return height;
     }
 ​
     @Override
     public void accept(Visitor visitor) {
         visitor.visitRectangle(this);
     }
 }

实现具体的访问者类,如计算面积的访问者AreaVisitor:

 public class AreaVisitor implements Visitor {
     @Override
     public void visitCircle(Circle circle) {
         double area = Math.PI * circle.getRadius() * circle.getRadius();
         System.out.println("圆形面积: " + area);
     }
 ​
     @Override
     public void visitRectangle(Rectangle rectangle) {
         int area = rectangle.getWidth() * rectangle.getHeight();
         System.out.println("矩形面积: " + area);
     }
 }

在客户端代码中使用访问者模式:

 public class Client {
     public static void main(String[] args) {
         Circle circle = new Circle(5);
         Rectangle rectangle = new Rectangle(4, 6);
 ​
         Visitor areaVisitor = new AreaVisitor();
         circle.accept(areaVisitor);
         rectangle.accept(areaVisitor);
     }
 }

运行结果会输出:

 圆形面积: 78.53981633974483
 矩形面积: 24

15.3 应用场景举例

在编译器设计中,语法树节点是稳定的数据结构,而对语法树的操作(如语义分析、代码生成等)可能经常变化。使用访问者模式,可以将不同的操作封装成访问者类,在不修改语法树节点类的情况下,方便地添加新的操作 。

此作者没有提供个人介绍
最后更新于 2025-05-31