php常用设计模式之策略模式

207 阅读13分钟

引言:多种支付方式的业务场景

在日常业务开发中,用户支付场景通常涉及多种支付方式,比如支付宝、微信、钱包等。为了满足用户的需求,系统需要支持这些不同的支付方式。通常情况下,用户发起支付时,我们会根据支付类型执行相应的支付逻辑。

假设我们不使用设计模式,直接写出多种支付逻辑的代码,代码可能如下所示:

<?php
class PaymentService {
    public function pay($paymentType, $amount) {
        if ($paymentType === 'Alipay') {
            // 支付宝支付逻辑
            echo "Using Alipay to pay $amount";
        } elseif ($paymentType === 'WeChat') {
            // 微信支付逻辑
            echo "Using WeChat to pay $amount";
        } elseif ($paymentType === 'Wallet') {
            // 钱包支付逻辑
            echo "Using Wallet to pay $amount";
        } else {
            throw new Exception("Unknown payment type");
        }
    }
}

// 使用示例
$paymentService = new PaymentService();
$paymentService->pay('Alipay', 100);

在这种实现方式下,我们将所有的支付逻辑写在了 pay() 方法内,根据支付类型进行条件判断,然后执行不同的支付逻辑。

这样写的问题

  1. 代码难以维护
    随着支付方式的增加或变更,pay() 方法内的条件判断也会越来越多,代码变得臃肿、难以维护,每次新增支付方式都需要修改方法内部的逻辑,容易出错。
  2. 不符合开闭原则
    这种写法违反了开闭原则(对扩展开放,对修改封闭)。每当需要添加新的支付方式,就必须修改已有代码,这样的设计增加了代码的修改频率和出错风险。
  3. 不利于扩展和复用
    各种支付方式的逻辑都耦合在一起,代码难以复用。例如,如果需要将支付逻辑单独封装成模块,便于在其他地方调用,代码将需要进行较多的拆分和重构。

为了优化这种业务场景的代码,我们可以采用设计模式来进行重构。在众多设计模式中,策略模式非常适合这种情况,可以帮助我们将不同的支付方式封装成独立的策略类,使代码结构更加清晰、灵活,易于扩展。

策略模式

策略模式(Strategy Pattern)是一种行为型设计模式,旨在定义一系列算法或行为,并将它们封装到独立的策略类中,以便在运行时动态选择和切换。通过策略模式,客户端代码可以在不同策略之间自由切换,而不需要直接依赖具体的实现类,从而实现代码的灵活性和扩展性。

原理

策略模式的核心思想是将不同的算法、行为或业务逻辑封装在独立的策略类中,通过策略接口将它们统一起来,并通过上下文类在运行时决定选择哪个策略。这样实现了算法的灵活切换,同时减少了代码的冗余和重复。

策略模式一般包括以下几个部分:

  1. 策略接口:定义了所有策略类必须实现的通用方法,使得具体策略可以通过相同的接口被调用。
  2. 具体策略类:每个具体策略类实现策略接口,并包含具体的算法或行为。
  3. 上下文类:在客户端和策略类之间起到连接作用,通过组合的方式接收策略对象并调用策略中的方法。

这种设计可以使得不同策略类之间互不影响,客户端只需要依赖接口,而不依赖具体的实现类。

适用场景

策略模式适用于以下场景:

  1. 多种算法或行为可替换
    当系统中有多个可以替换的算法或行为(如不同的排序算法、不同的计算逻辑等),并且客户端需要根据不同条件选择不同算法时,策略模式能够很好地组织和管理这些算法。
  2. 需要动态切换算法或行为
    当系统需要根据某些条件在运行时动态切换算法或行为时,策略模式允许在不修改客户端代码的前提下,灵活选择或替换不同的算法。
  3. 消除多重条件判断
    在代码中,如果某个方法包含大量的条件判断(如 if-elseswitch-case),可以将不同的条件逻辑封装到不同的策略类中,以简化代码逻辑,减少条件判断。

实际业务场景

策略模式在实际业务中应用广泛,尤其在以下场景中效果显著:

  1. 支付方式选择
    在支付系统中,用户可以选择不同的支付方式(如支付宝、微信、信用卡等)。每种支付方式的实现逻辑不同,策略模式可以将每种支付方式的逻辑独立封装,客户端可以根据用户的选择在运行时动态应用不同的支付策略。

  2. 促销活动和优惠策略
    在电商促销活动中,不同的活动可能有不同的优惠计算方式(如满减、打折、满赠等)。通过策略模式,可以将不同的促销方式封装成独立的策略类,动态选择合适的优惠策略,以便灵活适应业务需求的变化。

  3. 物流配送策略
    不同的物流公司和配送方式有不同的计费标准和配送时效。策略模式可以封装不同的物流配送逻辑,在运行时根据用户的选择或系统的配置,动态切换配送策略。

  4. 内容推荐系统
    在推荐系统中,可以使用不同的推荐算法(如热门推荐、个性化推荐、基于时间的推荐等)。策略模式可以封装不同的推荐算法,使得系统可以灵活切换推荐策略,向用户展示最合适的内容。

  5. 数据排序与筛选
    在数据处理系统中,不同的业务场景可能需要不同的排序或筛选规则。策略模式可以封装这些规则,使得代码更清晰,并且能够在运行时根据条件自由切换不同的排序或筛选方式。

  6. 动态业务规则
    某些业务系统中会有一系列动态变化的规则(如保险定价规则、税率计算等),策略模式可以帮助将这些规则逻辑封装为策略,使系统能够根据不同条件灵活地应用相应规则,确保业务逻辑的灵活性。


工厂模式和策略模式的业务范围是否相同?

我之前在设计这部分业务的时候 有过一个疑问: 正常我通常会选择使用策略模式来优化这种场景代码结构,使得不同的算法或行为可以灵活替换。但是在这些场景中,是否可以用工厂模式来替代策略模式,达到同样的效果?工厂模式和策略模式的业务范围是否重叠,或者说它们的使用范围相同吗?

为了解这种问题,我们可以先来看看工厂模式与策略模式的的区别

工厂模式与策略模式的区别

  1. 关注点

    • 工厂模式主要关注对象的创建。它通过工厂类来选择和创建具体的对象实例,从而将对象的创建过程与使用过程解耦。
    • 策略模式主要关注行为的替换。策略模式将算法或行为封装到独立的策略类中,通过接口管理,以便在运行时灵活选择或替换。
  2. 使用场景

    • 工厂模式适用于需要根据条件创建不同对象的情况。例如,创建不同支付类型的对象(如支付宝、微信支付等),可以用工厂模式来管理这些对象的创建。
    • 策略模式适用于需要动态替换算法或行为的情况。例如,支付中的折扣策略或物流中的计费规则等场景,可以用策略模式在运行时灵活选择具体的策略。
  3. 扩展性

    • 工厂模式的扩展性体现在能通过新增工厂类来创建新的对象实例。
    • 策略模式的扩展性体现在通过新增策略类来添加新的行为,使系统能够在不修改客户端代码的前提下,灵活应用新的策略。
  4. 设计目标

    • 工厂模式的设计目标是对象的创建解耦,通过工厂来隐藏对象创建的细节。
    • 策略模式的设计目标是行为的灵活替换,通过不同的策略类来动态切换具体的算法或行为。

如何选择:工厂模式和策略模式的结合使用

实际上,在许多业务场景中,我们可以将工厂模式和策略模式结合使用。例如,在支付场景中,我们可以:

  • 使用工厂模式来创建不同的支付服务实例,如支付宝支付、微信支付等。
  • 使用策略模式来封装支付过程中的折扣策略或计费逻辑,使得不同支付方式在执行支付时可以应用不同的算法或规则。

在支付场景中使用策略模式的设计

在支付场景中,我们可以使用策略模式来设计不同支付方式的实现。每种支付方式都实现相同的支付接口,并封装各自的支付逻辑。这样,用户选择不同的支付方式时,系统会调用相应的策略对象来执行支付操作。

1. 定义支付策略接口

首先,定义一个 PaymentStrategy 接口,规定所有支付方式的基本方法 pay

<?php
interface PaymentStrategy {
    public function pay($amount);
}

2. 创建具体的支付策略类

为每种支付方式创建具体策略类,分别实现 PaymentStrategy 接口,定义不同的支付逻辑:

<?php
class AlipayStrategy implements PaymentStrategy {
    public function pay($amount) {
        echo "Using Alipay to pay $amount\n";
    }
}

class WeChatPayStrategy implements PaymentStrategy {
    public function pay($amount) {
        echo "Using WeChat to pay $amount\n";
    }
}

class WalletPayStrategy implements PaymentStrategy {
    public function pay($amount) {
        echo "Using Wallet to pay $amount\n";
    }
}

3. 创建支付上下文类

上下文类 PaymentContext 持有一个支付策略对象,通过策略对象来执行支付操作:

<?php
class PaymentContext {
    private $paymentStrategy;

    // 注入具体的支付策略
    public function __construct(PaymentStrategy $paymentStrategy) {
        $this->paymentStrategy = $paymentStrategy;
    }

    // 执行支付操作
    public function executePayment($amount) {
        $this->paymentStrategy->pay($amount);
    }
}

4. 使用策略模式

在客户端代码中,根据用户的支付方式选择对应的策略对象,并注入到上下文类中以执行支付操作:

<?php
// 用户选择支付宝支付
$paymentMethod = new AlipayStrategy();
$paymentContext = new PaymentContext($paymentMethod);
$paymentContext->executePayment(100); // 输出: Using Alipay to pay 100

// 用户选择微信支付
$paymentMethod = new WeChatPayStrategy();
$paymentContext = new PaymentContext($paymentMethod);
$paymentContext->executePayment(200); // 输出: Using WeChat to pay 200

这样设计的好处

  1. 代码的扩展性强
    新增支付方式时,只需新增一个具体策略类即可,符合开闭原则,无需修改原有代码。
  2. 消除多重条件判断
    将不同支付方式的逻辑从客户端代码中分离,避免了 if-elseswitch-case 的条件判断,使代码结构更加清晰。
  3. 提高灵活性
    支持在运行时动态选择和切换支付方式,客户端代码可以根据业务需求自由切换支付策略。

工厂模式与策略模式结合使用

在实际业务中,可以将工厂模式与策略模式结合使用。工厂模式负责创建具体的策略实例,策略模式负责定义行为。这样我们可以通过工厂类根据不同条件创建具体的策略对象,并传递到上下文中执行。

1. 创建支付策略工厂

定义一个 PaymentStrategyFactory 工厂类,根据支付类型来创建对应的策略对象:

<?php
class PaymentStrategyFactory {
    public static function create($type) {
        switch ($type) {
            case 'Alipay':
                return new AlipayStrategy();
            case 'WeChat':
                return new WeChatPayStrategy();
            case 'Wallet':
                return new WalletPayStrategy();
            default:
                throw new Exception("未知支付类型");
        }
    }
}

2. 使用工厂和策略模式结合的支付流程

在客户端代码中,根据用户选择的支付类型,通过工厂创建相应的支付策略实例,然后注入到上下文类中执行支付操作:

<?php
// 通过工厂创建策略
$paymentMethod = PaymentStrategyFactory::create('Alipay');
$paymentContext = new PaymentContext($paymentMethod);
$paymentContext->executePayment(100); // 输出: Using Alipay to pay 100

// 使用微信支付
$paymentMethod = PaymentStrategyFactory::create('WeChat');
$paymentContext = new PaymentContext($paymentMethod);
$paymentContext->executePayment(200); // 输出: Using WeChat to pay 200

这样结合使用的好处

  1. 进一步解耦对象创建与行为执行
    工厂模式负责策略对象的创建,策略模式负责行为的定义和执行,两者分工明确,客户端代码只需关心如何调用,进一步降低了耦合度。
  2. 提升代码的复用性
    工厂模式和策略模式相互独立且相互配合,使得策略类可以复用于不同的上下文,工厂类可以复用于不同的对象创建,代码复用性提高。
  3. 增强系统的灵活性和扩展性
    工厂模式提供了扩展新的策略实例的途径,策略模式提供了替换行为的方式。两者结合使系统既可以灵活地增加新支付方式,也可以根据需求自由调整支付逻辑,实现了高扩展性和灵活性。

总结

策略模式通过将不同的算法或行为封装在独立的策略类中,使系统能够根据需要动态选择和切换策略。这种设计带来了以下好处:

  1. 增强扩展性:增加新的策略时,只需创建新的策略类,不需要修改现有代码,符合开闭原则。
  2. 减少条件判断:将不同算法或行为从客户端代码中分离,避免了大量的 if-elseswitch-case 语句,使代码更简洁和易读。
  3. 提高灵活性:策略模式让系统能够在运行时灵活选择和替换行为,满足多变的业务需求。

工厂模式与策略模式结合使用时,工厂模式负责策略实例的创建,策略模式负责行为的定义和执行,两者相辅相成,进一步提升系统的灵活性和维护性:

  1. 进一步解耦对象创建与行为执行:工厂模式集中管理策略对象的创建,策略模式负责定义和切换行为,使系统结构更清晰,职责分工更明确。
  2. 提高代码复用性:工厂类和策略类相互独立且能灵活组合,代码能够复用于不同场景,提升代码的复用性。
  3. 增强系统扩展性:工厂模式使系统能够轻松接入新的策略类,而策略模式则确保行为的替换简洁流畅。这种组合在复杂业务场景下可以带来高度的可扩展性和灵活性。

通过这种设计,工厂模式和策略模式的结合为系统提供了强大的扩展能力,使代码在应对复杂需求和多变业务时更加简洁、高效、易维护。