设计模式是软件开发中的宝贵资源,它们提供了经过时间检验的解决方案,特别是在提升软件灵活性方面发挥着关键作用。灵活的软件能够更容易地适应需求变化,降低维护成本,并延长产品生命周期。本文将深入剖析几种最常用的提升软件灵活性的设计模式,通过详细的解释和代码示例,帮助你掌握它们的核心思想和应用场景。

1. 策略模式(Strategy Pattern)

核心思想

策略模式将算法族封装在独立的类中,使它们可以互相替换。这样,算法的变化不会影响使用算法的客户端。

应用场景

  • 当系统需要在运行时动态选择不同算法时
  • 当有多种算法实现同一功能,需要根据上下文选择不同实现时
  • 当需要避免使用大量条件语句来选择不同行为时

结构组成

  1. 策略接口(Strategy):定义所有策略的公共接口
  2. 具体策略(ConcreteStrategy):实现策略接口的具体算法
  3. 上下文(Context):持有策略对象的引用并使用它执行操作

代码示例

让我们以一个文本格式化系统为例,不同的文本可能需要不同的格式化策略:

// 策略接口
interface TextFormatter {
    String format(String text);
}

// 具体策略1:大写转换器
class UpperCaseFormatter implements TextFormatter {
    @Override
    public String format(String text) {
        return text.toUpperCase();
    }
}

// 具体策略2:小写转换器
class LowerCaseFormatter implements TextFormatter {
    @Override
    public String format(String text) {
        return text.toLowerCase();
    }
}

// 具体策略3:首字母大写转换器
class CapitalizeFormatter implements TextFormatter {
    @Override
    public String format(String text) {
        if (text == null || text.isEmpty()) {
            return text;
        }
        
        String[] words = text.split("\\s");
        StringBuilder result = new StringBuilder();
        
        for (String word : words) {
            if (!word.isEmpty()) {
                result.append(Character.toUpperCase(word.charAt(0)))
                      .append(word.substring(1).toLowerCase())
                      .append(" ");
            }
        }
        
        return result.toString().trim();
    }
}

// 上下文类
class TextEditor {
    private TextFormatter formatter;
    
    public void setFormatter(TextFormatter formatter) {
        this.formatter = formatter;
    }
    
    public String formatText(String text) {
        if (formatter == null) {
            return text; // 默认不做任何格式化
        }
        return formatter.format(text);
    }
}

使用示例

public class StrategyDemo {
    public static void main(String[] args) {
        TextEditor editor = new TextEditor();
        String text = "This is a SAMPLE text for FORMATTING";
        
        // 使用大写策略
        editor.setFormatter(new UpperCaseFormatter());
        System.out.println("上大写格式: " + editor.formatText(text));
        
        // 使用小写策略
        editor.setFormatter(new LowerCaseFormatter());
        System.out.println("小写格式: " + editor.formatText(text));
        
        // 使用首字母大写策略
        editor.setFormatter(new CapitalizeFormatter());
        System.out.println("首字母大写格式: " + editor.formatText(text));
    }
}

输出结果:

上大写格式: THIS IS A SAMPLE TEXT FOR FORMATTING
小写格式: this is a sample text for formatting
首字母大写格式: This Is A Sample Text For Formatting

灵活性提升

策略模式极大地提高了系统的灵活性:

  1. 开闭原则:添加新的格式化策略只需创建新的策略类,无需修改已有代码
  2. 消除条件语句:避免了大量的 if-else 或 switch 语句
  3. 运行时切换:允许在运行时动态切换算法
  4. 算法封装:隐藏了算法实现细节

2. 观察者模式(Observer Pattern)

核心思想

观察者模式定义了对象间的一种一对多的依赖关系,当一个对象(主题)的状态发生变化时,所有依赖于它的对象(观察者)都会得到通知并自动更新。

应用场景

  • 当一个对象的改变需要通知其他对象,而你又不希望这些对象与主题紧密耦合时
  • 当一个抽象有两个方面,其中一个方面依赖于另一方面时
  • 当需要在系统中创建触发链时

结构组成

  1. 主题(Subject):维护观察者列表,提供添加和删除观察者的方法
  2. 具体主题(ConcreteSubject):实现主题接口,并在状态变化时通知观察者
  3. 观察者(Observer):定义更新接口
  4. 具体观察者(ConcreteObserver):实现观察者接口,具体响应主题的通知

代码示例

以一个股票监控系统为例,多个投资者需要实时监控特定股票的价格变动:

import java.util.ArrayList;
import java.util.List;

// 观察者接口
interface StockObserver {
    void update(String stockSymbol, double price);
}

// 主题接口
interface StockSubject {
    void registerObserver(StockObserver observer);
    void removeObserver(StockObserver observer);
    void notifyObservers();
}

// 具体主题:股票
class Stock implements StockSubject {
    private String symbol;
    private double price;
    private List<StockObserver> observers;
    
    public Stock(String symbol, double price) {
        this.symbol = symbol;
        this.price = price;
        this.observers = new ArrayList<>();
    }
    
    @Override
    public void registerObserver(StockObserver observer) {
        if (!observers.contains(observer)) {
            observers.add(observer);
        }
    }
    
    @Override
    public void removeObserver(StockObserver observer) {
        observers.remove(observer);
    }
    
    @Override
    public void notifyObservers() {
        for (StockObserver observer : observers) {
            observer.update(symbol, price);
        }
    }
    
    public void setPrice(double newPrice) {
        // 只有价格变化时才通知
        if (this.price != newPrice) {
            this.price = newPrice;
            notifyObservers();
        }
    }
    
    public double getPrice() {
        return price;
    }
    
    public String getSymbol() {
        return symbol;
    }
}

// 具体观察者:投资者
class Investor implements StockObserver {
    private String name;
    private List<String> interestedStocks = new ArrayList<>();
    
    public Investor(String name) {
        this.name = name;
    }
    
    public void addInterest(String stockSymbol) {
        if (!interestedStocks.contains(stockSymbol)) {
            interestedStocks.add(stockSymbol);
        }
    }
    
    @Override
    public void update(String stockSymbol, double price) {
        if (interestedStocks.contains(stockSymbol)) {
            System.out.println(name + " 收到通知: " + stockSymbol + 
                " 股票价格更新为 $" + price);
        }
    }
}

使用示例

public class ObserverDemo {
    public static void main(String[] args) {
        // 创建股票
        Stock appleStock = new Stock("AAPL", 150.25);
        Stock googleStock = new Stock("GOOGL", 2700.50);
        
        // 创建投资者
        Investor investor1 = new Investor("张三");
        investor1.addInterest("AAPL");
        
        Investor investor2 = new Investor("李四");
        investor2.addInterest("AAPL");
        investor2.addInterest("GOOGL");
        
        Investor investor3 = new Investor("王五");
        investor3.addInterest("GOOGL");
        
        // 注册观察者
        appleStock.registerObserver(investor1);
        appleStock.registerObserver(investor2);
        googleStock.registerObserver(investor2);
        googleStock.registerObserver(investor3);
        
        // 更新股票价格
        System.out.println("--- 苹果股票价格变动 ---");
        appleStock.setPrice(152.30);
        
        System.out.println("\n--- 谷歌股票价格变动 ---");
        googleStock.setPrice(2695.75);
        
        // 投资者取消订阅
        System.out.println("\n--- 张三取消订阅苹果股票 ---");
        appleStock.removeObserver(investor1);
        
        System.out.println("\n--- 苹果股票再次价格变动 ---");
        appleStock.setPrice(153.50);
    }
}

输出结果:

--- 苹果股票价格变动 ---
张三 收到通知: AAPL 股票价格更新为 $152.3
李四 收到通知: AAPL 股票价格更新为 $152.3

--- 谷歌股票价格变动 ---
李四 收到通知: GOOGL 股票价格更新为 $2695.75
王五 收到通知: GOOGL 股票价格更新为 $2695.75

--- 张三取消订阅苹果股票 ---

--- 苹果股票再次价格变动 ---
李四 收到通知: AAPL 股票价格更新为 $153.5

灵活性提升

观察者模式对软件灵活性的贡献包括:

  1. 松耦合:主题与观察者之间是松散耦合的,它们可以独立变化
  2. 广播通信:实现一对多的依赖关系,一个变化可以通知多个对象
  3. 开闭原则:新增观察者无需修改主题代码
  4. 动态订阅机制:运行时可动态地添加和删除订阅关系

3. 装饰器模式(Decorator Pattern)

核心思想

装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。它通过将对象包装在装饰器类中来实现这一点,从而动态地给对象添加职责。

应用场景

  • 当需要透明且动态地扩展对象功能时
  • 当继承不适用或过于繁重时
  • 当需要在不影响其他对象的情况下,给特定对象添加职责时

结构组成

  1. 组件(Component):定义一个对象接口,可以给这些对象动态添加职责
  2. 具体组件(ConcreteComponent):定义一个对象,可以给这个对象添加一些职责
  3. 装饰器(Decorator):维持一个指向组件对象的引用,并定义一个与组件接口一致的接口
  4. 具体装饰器(ConcreteDecorator):实际添加功能的装饰器

代码示例

以一个咖啡订购系统为例,客户可以在基础咖啡上添加各种配料:

// 组件接口
interface Coffee {
    String getDescription();
    double getCost();
}

// 具体组件:基础咖啡
class SimpleCoffee implements Coffee {
    @Override
    public String getDescription() {
        return "简单咖啡";
    }
    
    @Override
    public double getCost() {
        return 10.0; // 基础价格
    }
}

// 装饰器基类
abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;
    
    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }
    
    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription();
    }
    
    @Override
    public double getCost() {
        return decoratedCoffee.getCost();
    }
}

// 具体装饰器:牛奶
class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }
    
    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", 加牛奶";
    }
    
    @Override
    public double getCost() {
        return decoratedCoffee.getCost() + 2.0; // 牛奶价格
    }
}

// 具体装饰器:糖
class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }
    
    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", 加糖";
    }
    
    @Override
    public double getCost() {
        return decoratedCoffee.getCost() + 1.0; // 糖价格
    }
}

// 具体装饰器:巧克力
class ChocolateDecorator extends CoffeeDecorator {
    public ChocolateDecorator(Coffee coffee) {
        super(coffee);
    }
    
    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", 加巧克力";
    }
    
    @Override
    public double getCost() {
        return decoratedCoffee.getCost() + 3.0; // 巧克力价格
    }
}

使用示例

public class DecoratorDemo {
    public static void main(String[] args) {
        // 创建一个简单咖啡
        Coffee coffee = new SimpleCoffee();
        System.out.println("订单1: " + coffee.getDescription() + 
                          " | 价格: ¥" + coffee.getCost());
        
        // 用牛奶装饰
        Coffee milkCoffee = new MilkDecorator(coffee);
        System.out.println("订单2: " + milkCoffee.getDescription() + 
                          " | 价格: ¥" + milkCoffee.getCost());
        
        // 用糖和牛奶装饰
        Coffee sweetMilkCoffee = new SugarDecorator(milkCoffee);
        System.out.println("订单3: " + sweetMilkCoffee.getDescription() + 
                          " | 价格: ¥" + sweetMilkCoffee.getCost());
        
        // 创建一个更复杂的咖啡:双牛奶、加糖、加巧克力
        Coffee specialCoffee = new ChocolateDecorator(
                               new SugarDecorator(
                               new MilkDecorator(
                               new MilkDecorator(
                               new SimpleCoffee()))));
        
        System.out.println("订单4: " + specialCoffee.getDescription() + 
                          " | 价格: ¥" + specialCoffee.getCost());
    }
}

输出结果:

订单1: 简单咖啡 | 价格: ¥10.0
订单2: 简单咖啡, 加牛奶 | 价格: ¥12.0
订单3: 简单咖啡, 加牛奶, 加糖 | 价格: ¥13.0
订单4: 简单咖啡, 加牛奶, 加牛奶, 加糖, 加巧克力 | 价格: ¥18.0

灵活性提升

装饰器模式显著提升了软件灵活性:

  1. 动态扩展:在运行时动态地给对象添加功能
  2. 组合优于继承:通过组合实现功能扩展,避免了继承带来的静态特性
  3. 无限组合:可以创建无限多的功能组合,而不需要为每种组合定义新类
  4. 单一职责:每个装饰器类专注于单一功能的添加,符合单一职责原则

4. 适配器模式(Adapter Pattern)

核心思想

适配器模式使得原本由于接口不兼容而不能一起工作的类可以一起工作。它将一个类的接口转换成客户希望的另外一个接口。

应用场景

  • 当需要使用现有的类,但其接口不符合需求时
  • 当需要创建一个可复用的类,该类可以与不相关或不可预见的类协作
  • 当需要使用多个现有子类,但不实际每一个都去进行子类化以匹配它们的接口时

结构组成

  1. 目标接口(Target):客户期望的接口
  2. 适配者(Adaptee):需要被适配的类或接口
  3. 适配器(Adapter):将适配者接口转换为目标接口的类

代码示例

假设我们有一个外部的支付处理系统,它与我们系统的接口不兼容,我们需要创建一个适配器:

// 目标接口:我们的系统使用的支付接口
interface PaymentProcessor {
    boolean processPayment(double amount);
    PaymentStatus getStatus(String paymentId);
}

// 支付状态枚举
enum PaymentStatus {
    PENDING, COMPLETED, FAILED, REFUNDED
}

// 适配者:外部支付系统的接口
class ExternalPaymentSystem {
    // 这个系统使用不同的方法名和参数类型
    public int makePayment(float amount, String currency) {
        System.out.println("外部系统处理支付: " + amount + " " + currency);
        // 模拟支付处理
        return (amount > 0) ? 1 : 0; // 1表示成功,0表示失败
    }
    
    public String checkPaymentStatus(int paymentReference) {
        System.out.println("检查外部系统支付状态,参考号: " + paymentReference);
        // 模拟检查支付状态
        return "SUCCESS"; // 可能的值: "SUCCESS", "PENDING", "FAILED"
    }
}

// 适配器:将ExternalPaymentSystem适配到PaymentProcessor接口
class PaymentSystemAdapter implements PaymentProcessor {
    private ExternalPaymentSystem externalSystem;
    private Map<String, Integer> paymentReferences; // 存储支付ID到外部参考号的映射
    
    public PaymentSystemAdapter() {
        this.externalSystem = new ExternalPaymentSystem();
        this.paymentReferences = new HashMap<>();
    }
    
    @Override
    public boolean processPayment(double amount) {
        // 适配接口:将double转为float,添加默认货币,并转换返回类型
        int result = externalSystem.makePayment((float)amount, "CNY");
        
        // 生成一个支付ID并存储外部系统的参考号
        if (result == 1) {
            String paymentId = UUID.randomUUID().toString();
            paymentReferences.put(paymentId, result);
            return true;
        }
        return false;
    }
    
    @Override
    public PaymentStatus getStatus(String paymentId) {
        // 适配接口:使用存储的参考号查询状态,并转换状态格式
        if (!paymentReferences.containsKey(paymentId)) {
            return PaymentStatus.FAILED;
        }
        
        int reference = paymentReferences.get(paymentId);
        String externalStatus = externalSystem.checkPaymentStatus(reference);
        
        // 转换状态格式
        switch (externalStatus) {
            case "SUCCESS":
                return PaymentStatus.COMPLETED;
            case "PENDING":
                return PaymentStatus.PENDING;
            default:
                return PaymentStatus.FAILED;
        }
    }
}

使用示例

public class AdapterDemo {
    public static void main(String[] args) {
        // 创建支付处理器(使用适配器)
        PaymentProcessor paymentProcessor = new PaymentSystemAdapter();
        
        // 使用我们的接口处理支付
        double paymentAmount = 100.50;
        System.out.println("发起支付: ¥" + paymentAmount);
        
        boolean paymentResult = paymentProcessor.processPayment(paymentAmount);
        System.out.println("支付结果: " + (paymentResult ? "成功" : "失败"));
        
        // 假设我们有一个支付ID(在实际应用中,这应该是从processPayment返回的)
        String paymentId = "test-payment-123";
        
        // 检查支付状态
        PaymentStatus status = paymentProcessor.getStatus(paymentId);
        System.out.println("支付状态: " + status);
    }
}

灵活性提升

适配器模式通过以下方式提升软件灵活性:

  1. 接口转换:允许不兼容的接口一起工作
  2. 复用现有代码:避免了重写已有功能的代码
  3. 解耦系统:将客户端与实际实现分离
  4. 平滑过渡:可用于在系统迁移或API版本更新时保持兼容性

5. 工厂模式(Factory Pattern)

核心思想

工厂模式提供了一种创建对象的接口,但允许子类决定实例化的对象类型。它将对象的创建与使用分离,使得系统更容易扩展。

应用场景

  • 当一个类无法预知它需要创建的对象的类时
  • 当一个类希望由其子类来指定它所创建的对象时
  • 当类将创建对象的职责委托给多个帮助子类中的某一个时

结构组成

  1. 产品(Product):定义工厂所创建对象的接口
  2. 具体产品(ConcreteProduct):实现产品接口的类
  3. 工厂(Factory):声明创建产品的方法
  4. 具体工厂(ConcreteFactory):实现创建产品的方法

代码示例

以一个导出系统为例,可以导出不同格式的文件:

// 产品接口
interface ExportFile {
    void export(String data);
}

// 具体产品:PDF导出
class PdfExport implements ExportFile {
    @Override
    public void export(String data) {
        System.out.println("导出数据为PDF格式: " + data);
        // PDF导出逻辑
    }
}

// 具体产品:Excel导出
class ExcelExport implements ExportFile {
    @Override
    public void export(String data) {
        System.out.println("导出数据为Excel格式: " + data);
        // Excel导出逻辑
    }
}

// 具体产品:JSON导出
class JsonExport implements ExportFile {
    @Override
    public void export(String data) {
        System.out.println("导出数据为JSON格式: " + data);
        // JSON导出逻辑
    }
}

// 工厂接口
interface ExportFactory {
    ExportFile createExporter();
}

// 具体工厂:PDF工厂
class PdfExportFactory implements ExportFactory {
    @Override
    public ExportFile createExporter() {
        return new PdfExport();
    }
}

// 具体工厂:Excel工厂
class ExcelExportFactory implements ExportFactory {
    @Override
    public ExportFile createExporter() {
        return new ExcelExport();
    }
}

// 具体工厂:JSON工厂
class JsonExportFactory implements ExportFactory {
    @Override
    public ExportFile createExporter() {
        return new JsonExport();
    }
}

使用示例

public class FactoryDemo {
    public static void main(String[] args) {
        String data = "用户报表数据";
        
        // 创建PDF导出工厂并导出
        ExportFactory pdfFactory = new PdfExportFactory();
        ExportFile pdfExporter = pdfFactory.createExporter();
        pdfExporter.export(data);
        
        // 创建Excel导出工厂并导出
        ExportFactory excelFactory = new ExcelExportFactory();
        ExportFile excelExporter = excelFactory.createExporter();
        excelExporter.export(data);
        
        // 创建JSON导出工厂并导出
        ExportFactory jsonFactory = new JsonExportFactory();
        ExportFile jsonExporter = jsonFactory.createExporter();
        jsonExporter.export(data);
    }
}

输出结果:

导出数据为PDF格式: 用户报表数据
导出数据为Excel格式: 用户报表数据
导出数据为JSON格式: 用户报表数据

灵活性提升

工厂模式通过以下方式提升软件灵活性:

  1. 创建与使用分离:将对象的创建与使用代码分离
  2. 扩展性:可以轻松添加新的产品类型
  3. 封装变化:封装了实例化的过程
  4. 依赖抽象:客户端依赖于抽象接口,而非具体实现

6. 命令模式(Command Pattern)

核心思想

命令模式将请求封装成对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。

应用场景

  • 当需要将操作的请求者与执行者解耦时
  • 当需要将操作参数化时
  • 当需要支持撤销操作时
  • 当需要支持事务或操作日志时

结构组成

  1. 命令(Command):声明执行操作的接口
  2. 具体命令(ConcreteCommand):实现命令接口,调用接收者的相应操作
  3. 调用者(Invoker):要求命令执行请求
  4. 接收者(Receiver):知道如何实施与执行一个请求相关的操作

代码示例

以一个简单的文本编辑器为例,支持撤销操作:

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

// 接收者:文本编辑器
class TextEditor {
    private StringBuilder content;
    
    public TextEditor() {
        this.content = new StringBuilder();
    }
    
    public void append(String text) {
        content.append(text);
        System.out.println("追加文本: \"" + text + "\"");
        System.out.println("当前内容: " + content.toString());
    }
    
    public void delete(int length) {
        int currentLength = content.length();
        if (length > currentLength) {
            length = currentLength;
        }
        
        String deletedText = content.substring(currentLength - length);
        content.delete(currentLength - length, currentLength);
        
        System.out.println("删除文本: \"" + deletedText + "\"");
        System.out.println("当前内容: " + content.toString());
    }
    
    public String getContent() {
        return content.toString();
    }
}

// 具体命令:追加文本
class AppendCommand implements Command {
    private TextEditor editor;
    private String text;
    
    public AppendCommand(TextEditor editor, String text) {
        this.editor = editor;
        this.text = text;
    }
    
    @Override
    public void execute() {
        editor.append(text);
    }
    
    @Override
    public void undo() {
        editor.delete(text.length());
    }
}

// 具体命令:删除文本
class DeleteCommand implements Command {
    private TextEditor editor;
    private int length;
    private String deletedText;
    
    public DeleteCommand(TextEditor editor, int length) {
        this.editor = editor;
        this.length = length;
        this.deletedText = "";
    }
    @Override
    public void execute() {
        String content = editor.getContent();
        int actualLength = Math.min(length, content.length());
        deletedText = content.substring(content.length() - actualLength);
        editor.delete(actualLength);
    }
    
    @Override
    public void undo() {
        editor.append(deletedText);
    }
}

// 调用者:命令管理器
class CommandManager {
    private Stack<Command> history = new Stack<>();
    
    public void executeCommand(Command command) {
        command.execute();
        history.push(command);
    }
    
    public void undo() {
        if (!history.isEmpty()) {
            Command command = history.pop();
            command.undo();
            System.out.println("执行撤销操作");
        } else {
            System.out.println("没有操作可以撤销");
        }
    }
}

使用示例

public class CommandDemo {
    public static void main(String[] args) {
        TextEditor editor = new TextEditor();
        CommandManager manager = new CommandManager();
        
        // 执行追加命令
        Command appendCommand1 = new AppendCommand(editor, "Hello, ");
        manager.executeCommand(appendCommand1);
        
        Command appendCommand2 = new AppendCommand(editor, "Design Pattern!");
        manager.executeCommand(appendCommand2);
        
        // 执行删除命令
        Command deleteCommand = new DeleteCommand(editor, 8);
        manager.executeCommand(deleteCommand);
        
        // 撤销上一个操作
        manager.undo();
        
        // 再次撤销
        manager.undo();
    }
}

输出结果:

追加文本: "Hello, "
当前内容: Hello, 
追加文本: "Design Pattern!"
当前内容: Hello, Design Pattern!
删除文本: "Pattern!"
当前内容: Hello, Design 
执行撤销操作
追加文本: "Pattern!"
当前内容: Hello, Design Pattern!
执行撤销操作
删除文本: "Design Pattern!"
当前内容: Hello, 

灵活性提升

命令模式通过以下方式提升软件灵活性:

  1. 解耦:将请求者与执行者解耦
  2. 可扩展性:容易添加新的命令
  3. 支持撤销和重做:通过命令对象的状态和操作反向实现
  4. 命令组合:支持宏命令(由多个命令组成的命令)
  5. 异步执行:命令可以排队并异步执行

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

核心思想

模板方法模式定义了一个算法的骨架,将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的特定步骤。

应用场景

  • 当多个算法具有相似的结构,只在某些特定步骤上有所不同时
  • 当需要控制子类扩展,只允许在特定点进行扩展时
  • 当需要提取多个类中的公共行为到一个公共父类中时

结构组成

  1. 抽象类(AbstractClass):定义抽象的基本操作,具体的模板方法
  2. 具体类(ConcreteClass):实现抽象操作,完成算法中与特定子类相关的步骤

代码示例

以数据处理为例,不同的处理器共享相同的处理流程,但在具体步骤上有差异:

// 抽象类:定义处理模板
abstract class DataProcessor {
    // 模板方法,定义算法骨架
    public final void processData(String data) {
        // 1. 打开资源
        openResources();
        
        // 2. 解析数据
        String parsedData = parseData(data);
        
        // 3. 处理数据(具体实现由子类决定)
        String processedData = doProcessing(parsedData);
        
        // 4. 分析结果
        analyzeResult(processedData);
        
        // 5. 关闭资源
        closeResources();
    }
    
    // 公共方法,所有子类共享
    protected void openResources() {
        System.out.println("打开资源...");
    }
    
    protected void closeResources() {
        System.out.println("关闭资源...");
    }
    
    // 可能在子类中重写的方法
    protected String parseData(String data) {
        System.out.println("解析数据...");
        return data.trim();
    }
    
    protected void analyzeResult(String processedData) {
        System.out.println("分析处理结果: " + processedData);
    }
    
    // 抽象方法,必须由子类实现
    protected abstract String doProcessing(String data);
}

// 具体类:CSV数据处理器
class CsvDataProcessor extends DataProcessor {
    @Override
    protected String doProcessing(String data) {
        System.out.println("执行CSV数据处理...");
        // 模拟CSV处理
        return data.replace(",", "|");
    }
    
    @Override
    protected String parseData(String data) {
        String parsed = super.parseData(data);
        System.out.println("执行CSV特定解析...");
        return parsed;
    }
}

// 具体类:XML数据处理器
class XmlDataProcessor extends DataProcessor {
    @Override
    protected String doProcessing(String data) {
        System.out.println("执行XML数据处理...");
        // 模拟XML处理
        return "<processed>" + data + "</processed>";
    }
}

// 具体类:JSON数据处理器
class JsonDataProcessor extends DataProcessor {
    @Override
    protected String doProcessing(String data) {
        System.out.println("执行JSON数据处理...");
        // 模拟JSON处理
        return "{\"processed\": \"" + data + "\"}";
    }
    
    @Override
    protected void analyzeResult(String processedData) {
        System.out.println("执行JSON特定分析...");
        super.analyzeResult(processedData);
    }
}

使用示例

public class TemplateMethodDemo {
    public static void main(String[] args) {
        String data = "原始数据,需要处理";
        
        System.out.println("===== CSV处理 =====");
        DataProcessor csvProcessor = new CsvDataProcessor();
        csvProcessor.processData(data);
        
        System.out.println("\n===== XML处理 =====");
        DataProcessor xmlProcessor = new XmlDataProcessor();
        xmlProcessor.processData(data);
        
        System.out.println("\n===== JSON处理 =====");
        DataProcessor jsonProcessor = new JsonDataProcessor();
        jsonProcessor.processData(data);
    }
}

输出结果:

===== CSV处理 =====
打开资源...
解析数据...
执行CSV特定解析...
执行CSV数据处理...
分析处理结果: 原始数据|需要处理
关闭资源...

===== XML处理 =====
打开资源...
解析数据...
执行XML数据处理...
分析处理结果: <processed>原始数据,需要处理</processed>
关闭资源...

===== JSON处理 =====
打开资源...
解析数据...
执行JSON数据处理...
执行JSON特定分析...
分析处理结果: {"processed": "原始数据,需要处理"}
关闭资源...

灵活性提升

模板方法模式通过以下方式提升软件灵活性:

  1. 代码复用:共享算法结构,避免代码重复
  2. 控制扩展点:只允许子类重写特定步骤
  3. 反向控制:父类调用子类方法,实现”好莱坞原则”(不要调用我们,我们会调用你)
  4. 集中算法框架:核心算法结构在一个地方定义和维护

8. 代理模式(Proxy Pattern)

核心思想

代理模式为其他对象提供一个替身或占位符以控制对这个对象的访问。代理对象充当了客户与目标对象之间的中介。

应用场景

  • 当需要控制对对象的访问时(如权限控制)
  • 当需要延迟加载对象以优化性能时
  • 当需要在访问对象前后添加额外处理时(如日志记录)
  • 当远程访问对象时

结构组成

  1. 主题(Subject):代理和实际对象的共同接口
  2. 实际主题(RealSubject):代理所代表的真实对象
  3. 代理(Proxy):保存一个对实际主题的引用,实现与主题相同的接口

代码示例

以图片加载为例,使用代理实现延迟加载和访问控制:

// 主题接口
interface Image {
    void display();
}

// 实际主题
class RealImage implements Image {
    private String filename;
    
    public RealImage(String filename) {
        this.filename = filename;
        loadFromDisk();
    }
    
    private void loadFromDisk() {
        System.out.println("从磁盘加载图片: " + filename);
        // 模拟耗时操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    @Override
    public void display() {
        System.out.println("显示图片: " + filename);
    }
}

// 代理
class ImageProxy implements Image {
    private String filename;
    private RealImage realImage;
    private boolean isAdmin;
    
    public ImageProxy(String filename, boolean isAdmin) {
        this.filename = filename;
        this.isAdmin = isAdmin;
    }
    
    @Override
    public void display() {
        // 访问控制
        if (filename.contains("restricted") && !isAdmin) {
            System.out.println("访问拒绝:需要管理员权限查看此图片");
            return;
        }
        
        // 延迟加载
        if (realImage == null) {
            realImage = new RealImage(filename);
        }
        
        // 委托给真实对象
        realImage.display();
    }
}

使用示例

public class ProxyDemo {
    public static void main(String[] args) {
        // 创建普通用户的代理
        Image normalUserImage1 = new ImageProxy("photo.jpg", false);
        Image normalUserImage2 = new ImageProxy("restricted_photo.jpg", false);
        
        // 创建管理员用户的代理
        Image adminImage = new ImageProxy("restricted_photo.jpg", true);
        
        // 普通用户访问普通图片
        System.out.println("普通用户访问普通图片:");
        normalUserImage1.display();
        
        System.out.println("\n普通用户访问受限图片:");
        normalUserImage2.display();
        
        System.out.println("\n管理员访问受限图片:");
        adminImage.display();
        
        // 再次访问已加载的图片
        System.out.println("\n再次访问已加载的图片:");
        normalUserImage1.display();
    }
}

输出结果:

普通用户访问普通图片:
从磁盘加载图片: photo.jpg
显示图片: photo.jpg

普通用户访问受限图片:
访问拒绝:需要管理员权限查看此图片

管理员访问受限图片:
从磁盘加载图片: restricted_photo.jpg
显示图片: restricted_photo.jpg

再次访问已加载的图片:
显示图片: photo.jpg

灵活性提升

代理模式通过以下方式提升软件灵活性:

  1. 透明访问:客户端无需了解代理的存在
  2. 控制访问:可以添加访问控制逻辑
  3. 延迟加载:节省资源,只在需要时加载
  4. 添加功能:可以在不修改原始类的情况下添加新功能

总结与设计模式选择指南

以上介绍的设计模式都是提升软件灵活性的强大工具,但要选择合适的模式,需要根据具体问题进行分析:

设计模式选择矩阵

需求/问题 推荐模式 主要优势
算法需要动态切换 策略模式 运行时更换算法,避免条件语句
对象状态改变需通知其他对象 观察者模式 松耦合通知机制,支持广播
需要动态添加功能 装饰器模式 运行时组合功能,无需修改原始类
接口不兼容需适配 适配器模式 使不兼容接口协同工作
对象创建复杂或多变 工厂模式 封装创建逻辑,支持扩展
需要支持撤销操作 命令模式 请求参数化,支持撤销/重做
算法结构相似但步骤不同 模板方法模式 复用算法骨架,子类定义步骤
需要控制对象访问 代理模式 访问控制,延迟加载

设计模式综合运用

在实际项目中,通常需要组合使用多种设计模式。例如:

  1. MVC架构结合了观察者模式(视图观察模型)和策略模式(不同的控制器)
  2. 构建系统可能结合使用工厂模式、建造者模式和命令模式
  3. GUI框架通常结合使用装饰器模式、组合模式和命令模式

设计模式使用原则

  1. 保持简单:不要为了使用设计模式而使用设计模式
  2. 关注变化点:设计模式应用于系统中最可能变化的部分
  3. 平衡灵活性和复杂性:过度设计会增加系统复杂性
  4. 渐进式应用:通过重构逐步引入设计模式,而不是一开始就过度设计

结论

设计模式是提升软件灵活性的强大工具,它们通过封装变化、分离关注点、提供松耦合等方式,使软件能够更好地适应需求变化。深入理解这些模式及其应用场景,将帮助你设计出更灵活、更易维护的软件系统。

记住,设计模式不是目的,而是手段。最终目标是构建能够满足需求、易于维护并能够适应未来变化的高质量软件。通过合理选择和应用这些设计模式,你将能够大幅提升软件的灵活性和可维护性。

Categorized in: