策略模式

| 选择喜欢的代码风格  

使用策略模式,你可以把一族不同的算法(业务)封装到不同的类中,使 client 类可以在不知道具体实现的情况下选择实例化其中一个算法。策略模式有几种不同的变体,最简单的是下面这种:

第一段代码展示了一族输出算法,分别具体实现了 OutputInterfaceload 方法,返回序列化结果,json 和数组:

<?php
interface OutputInterface
{
    public function load();
}

class SerializedArrayOutput implements OutputInterface
{
    public function load()
    {
        return serialize($arrayOfData);
    }
}

class JsonStringOutput implements OutputInterface
{
    public function load()
    {
        return json_encode($arrayOfData);
    }
}

class ArrayOutput implements OutputInterface
{
    public function load()
    {
        return $arrayOfData;
    }
}

通过像上面这样把不同类型的输出算法封装起来,其他的开发者可以很容易地在不影响 client 代码的情况下添加新的输出类型。

每个具体的输出类实现了 OutputInterface —— 这有两个目的:

  1. 第一是它提供了一个所有输出类都必须遵守的契约;
  2. 第二,你将会在本文后面的部分看到,通过实现公共的接口,你可以利用类型约束保证 client 中使用的输出类必须是实现了 OutputInterface 的类。

注意:PHP 类型约束不能用于标量类型如 int 或 string。Traits 也不允许

接下来的一小段代码展示了一个 client 类如何使用其中一个输出算法,并可以在运行时根据需要选用不同的算法。

<?php
class SomeClient
{
    private $output;

    public function setOutput(OutputInterface $outputType)
    {
        $this->output = $outputType;
    }

    public function loadOutput()
    {
        return $this->output->load();
    }
}

上面的 client类有一个必须在运行时设置的私有属性,并且是“OutputInterface”类型的。 一旦这个属性被设置为具体的实例(三个输出类中之一的实例),并且 loadOutput 方法被调用,那么它的 load 方法就会被调用,返回回序列化结果或 json 或数组。

<?php
$client = new SomeClient();

// 希望是个数组?
$client->setOutput(new ArrayOutput());
$data = $client->loadOutput();

// 希望是个 JSON?
$client->setOutput(new JsonStringOutput());
$data = $client->loadOutput();

PHP 策略模式扩展阅读:


PHP 策略模式具体实例


<?php
header('Content-Type:text/html;charset=utf-8');
/**
 * 策略模式演示代码
 *
 * 为了更好地突出“策略”,我们这里以出行为例演示,日
 * 常出行大概分为以下几种工具:自驾车,公交车,地铁,火车,飞机,轮船
 *
 * 下面一起看代码,领会何为策略模式
 */

/**
 * Interface Travel 抽象策略角色
 * 约定具体方法
 */
interface Travel
{
    public function go();
}

/**
 * Class selfDriving 具体策略角色
 * 自驾车
 */
class bySelfDriving implements Travel
{
    public function go()
    {
        echo '我自己开着车出去玩<br>';
    }
}

/**
 * Class byBus具体策略角色
 * 乘公交
 */
class byBus implements Travel {
    public function go()
    {
        echo '我乘公交出去玩<br>';
    }
}

/**
 * Class byMetro 具体策略角色
 * 乘地铁
 */
class byMetro implements Travel
{
    public function go()
    {
        echo '我乘地铁出去玩<br>';
    }
}

/**
 * Class byTrain 具体策略角色
 * 乘火车
 */
class byTrain implements Travel
{
    public function go()
    {
        echo '我乘火车出去玩<br>';
    }
}

/**
 * Class byAirplane 具体策略角色
 * 乘飞机
 */
class byAirplane implements Travel
{
    public function go()
    {
        echo '我乘飞机出去玩<br>';
    }
}

/**
 * Class bySteamship 具体策略角色
 * 乘轮船
 */
class bySteamship implements Travel
{
    public function go()
    {
        echo '我乘轮船出去玩<br>';
    }
}
/**
 * Class Mine 环境角色
 */
class Mine{
    private $_strategy;
    private $_isChange = false;

    /**
     * 构造方法
     * 此处使用到了依赖注入和类型约束的概念,详情请参考
     * 1.聊一聊PHP的依赖注入(DI) 和 控制反转(IoC)
     * @link https://segmentfault.com/a/1190000007209266
     * 2.浅谈PHP的类型约束
     * @link https://segmentfault.com/a/1190000007226476
     *
     * @param Travel $travel
     */
    public function __construct(Travel $travel)
    {
        $this->_strategy = $travel;
    }

    /**
     * 改变出行方式
     *
     * @param Travel $travel
     */
    public function change(Travel $travel)
    {
        $this->_strategy = $travel;
        $this->_isChange = true;
    }

    public function goTravel()
    {
        if ($this->_isChange) {
            echo '现在改变主意,';
            $this->_strategy->go();
        } else {
            $this->_strategy->go();
        }

    }
}
/**
 * 客户端使用
 */
$strategy = new Mine(new byBus());// 乘公交
$strategy->goTravel();

$strategy->change(new byMetro());// 乘地铁
$strategy->goTravel();

$strategy->change(new bySelfDriving());// 自驾车
$strategy->goTravel();

// 其他根据具体应用选择实现

运行结果


我乘公交出去玩
现在改变主意,我乘地铁出去玩
现在改变主意,我自己开着车出去玩

策略模式总结


  1. 抽象策略角色: 策略类,通常由一个接口或者抽象类实现。
  2. 具体策略角色:包装了相关的算法和行为。
  3. 环境角色:持有一个策略类的引用,最终给客户端调用。

策略模式应用场景


  1. 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。
  2. 需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。
  3. 对客户隐藏具体策略(算法)的实现细节,彼此完全独立。

策略类的坑:


客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。



发表评论