策略模式,英文全称是 Strategy Design Pattern。在 GoF 的《设计模式》一书中,它是这样定义的:

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

翻译成中文就是:定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。

策略模式的结构与实现

结构

代码实现

Strategy.php

<?php
namespace DesignPatterns\Strategy\example1;
/**
 * Strategy类,定义所有支持算法的公共接口  可以是抽象类或接口
 * Class Strategy
 * @package DesignPatterns\Strategy\example1
 */
abstract class Strategy
{
    //算法方法
    abstract public function algorithmInterface();
}

ConcreteStrategyA.php

<?php
namespace DesignPatterns\Strategy\example1;
/**
 * ConcreteStrategy,封装了具体的算法或行为,继承于Strategy
 * Class ConcreteStrategyA
 * @package DesignPatterns\Strategy\example1
 */
class ConcreteStrategyA extends Strategy
{
    //算法A实现方法
    public function algorithmInterface()
    {
        echo "算法A实现";
    }
}

ConcreteStrategyB.php

<?php
namespace DesignPatterns\Strategy\example1;

class ConcreteStrategyB extends Strategy
{

    public function algorithmInterface()
    {
        echo "算法B实现";
    }
}

ConcreteStrategyC.php

<?php
namespace DesignPatterns\Strategy\example1;

class ConcreteStrategyC extends Strategy
{

    public function algorithmInterface()
    {
        echo "算法C实现";
    }
}

Context.php

<?php
namespace DesignPatterns\Strategy\example1;
/**
 * Context,用一个ConcreteStrategy来配置,维护一个对 Strategy 对象的引用。
 * Class Context
 * @package DesignPatterns\Strategy\example1
 */

class Context
{
    private $strategy;
    //初始化时传入具体的策略对象
    public function __construct(Strategy $strategy)
    {
        $this->strategy = $strategy;
    }
    //根据具体的策略对象调用其算法的方法
    public function ContextInterface()
    {
        $this->strategy->algorithmInterface();
    }

}

运行 Client.php

<?php
namespace DesignPatterns\Strategy\example1;
require dirname(__DIR__).'/../vendor/autoload.php';
class Client
{
    public function run()
    {
        //由于示例不用的策略最终在调用ContextInterface()所获的的结果也不尽相同
        $contextA = new Context(new ConcreteStrategyA());
        $contextA->ContextInterface();
        echo '<br>';

        $contextB = new Context(new ConcreteStrategyB());
        $contextB->ContextInterface();
        echo '<br>';

        $contextC = new Context(new ConcreteStrategyC());
        $contextC->ContextInterface();

    }

}
$worker = new Client();
$worker->run();

运行结果:

策略模式的实现比较简单,我们需要定义策略抽象类或策略接口,然后让所有的具体策略实现相同的接口。然后我们在context里实例化策略类,根据需要调用不同的方法。
我们可以发现策略模式和我们之前讲过的简单工厂模式 其实十分类似。不同的是,工厂相关的模式属于创建型模式,顾名思义,这种模式是用来创建对象的,返回的是new出来的对象。要调用对象的什么方法是由客户端来决定的。而策略模式属性行为型模式,通过执行上下文,将要调用的函数方法封装了起来,客户端只需要调用执行上下文的方法就可以了。

示例

假如你需要前往机场。 你可以选择乘坐公共汽车、 预约出租车或骑自行车。 这些就是你的出行策略。 你可以根据预算或时间等因素来选择其中一种策略。
Strategy.php

<?php
namespace DesignPatterns\Strategy\example2;
/**
 * 出行方式策略类
 * Class Strategy
 * @package DesignPatterns\Strategy\example2
 */
abstract class Strategy
{
    abstract function goAirport();
}

BikeStrategy.php

<?php
namespace DesignPatterns\Strategy\example2;
/**
 * 自行车出行子类
 * Class BikeStrategy
 * @package DesignPatterns\Strategy\example2
 */
class BikeStrategy extends Strategy
{

    public function goAirport()
    {
        echo '骑自行车去机场,需要2个小时,花费0元';
    }
}

TransitStrategy.php

<?php
namespace DesignPatterns\Strategy\example2;
/**
 * 公交出行子类
 * Class TransitStrategy
 * @package DesignPatterns\Strategy\example2
 */
class TransitStrategy extends Strategy
{

    public function goAirport()
    {
        echo  '坐公交去机场,需要1小时,花费2元';
    }
}

TaxiStrategy.php

<?php
namespace DesignPatterns\Strategy\example2;

class TaxiStrategy extends Strategy
{
    public function goAirport()
    {
        echo '坐出租车去机场,需要0.5小时,花费30元';
    }

}

Context.php

<?php
namespace DesignPatterns\Strategy\example2;

class Context
{
    private $strategy;
    public function __construct(Strategy $strategy)
    {
        $this->strategy = $strategy;
    }

    public function ContextInterface()
    {
        $this->strategy->goAirport();
    }
}

运行Client.php

<?php
namespace DesignPatterns\Strategy\example2;
require dirname(__DIR__).'/../vendor/autoload.php';
class Client
{
    public function run($type){
        switch ($type){
            case 'bike':
                $TravelStrategy = new Context(new BikeStrategy());
                break;
            case 'transit':
                $TravelStrategy = new Context(new TransitStrategy());
                break;
            case 'taxi':
                $TravelStrategy = new Context(new TaxiStrategy());
                break;
            default:
                throw new \Exception("不支持的方式");
                break;
        }
        $TravelStrategy->ContextInterface();
    }
}
$worker = new Client();
$worker->run('bike');

运行结果:

在本例中,我们需要在客户端根据不同的类型中 实例化 Context 以及实例化对应的具体策略。客户端与Context以及具体策略耦合在一起。如何减少客户端的耦合呢?其实我们可是策略模式与简单工厂相结合来解决这个问题
策略模式与简单工厂相结合

class ContextFactory
{
    private $strategy;
    //
    public function __construct($type)
    {
        switch ($type){
            case 'bike':
                $this->strategy = new BikeStrategy();
                break;
            case 'transit':
                $this->strategy = new TransitStrategy();
                break;
            case 'taxi':
                $this->strategy = new TaxiStrategy();
                break;
            default:
                throw new \Exception("不支持的方式");
                break;
        }
    }
    //根据具体的策略对象调用其算法的方法
    public function ContextInterface()
    {
        $this->strategy->algorithmInterface();
    }

}

更改前,客户端策略模式与简单工厂结合后,具体的出行算法与客户端分离,客户端只需要认识Context,耦合性相对更低。

总结

策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。

策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中公共功能。

当不同的行为堆砌在一个类中时,就很难避免使用条件语句来选择合适的行为。将这些行为封装在一个个Strategy类中,可以在使用这些行为的类中消除条件语句。
策略模式就是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。

在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的Context对象。这本身并没有解除客户端需要选择判断的压力,而策略模式与简单工厂模式结合后,选择具体实现的职责也可以由Context来承担,这就最大化地减轻了客户端的职责。

github示例:https://github.com/yangpanyao/design-patterns/tree/master/Strategy

Last modification:July 3rd, 2020 at 10:42 pm