MENU

PHP《设计模式》- 策略模式

November 9, 2020 • php,技术杂谈,设计模式阅读设置

目的:

策略模式定义了算法族,分别封装起来,让他们之间可以相互替换。该模式让算法独立于使用它的客户而独立变化。

看到上面文字的同学可能有一点懵,设计模式的概述总是这么深奥和苦涩。

没事,我们来换个说法方便大家理解一下。

老规矩,还是以举例子来说,就拿老干妈吧。(迷之举例老干妈o(╥﹏╥)o)

场景

举个例子,我们现在是老干妈的一个生产厂家,根据老干妈公司不同的需求生产不同的老干妈产品。

老干妈最基础的材料我们知道是什么对吧,相信大家都吃过,有豆豉(黄豆)辣椒,可以根据老干妈公司的要求生产不同的口味

例如香笋味芽菜味,先就简单举例这两个味道吧。

那么我们现在有可以确定的基础材料豆豉(黄豆)辣椒对吧,不同口味只需要加入不同的材料:香笋芽菜就可以生产出不一样的商品。

那么我们先按照以上思路来用代码实现,这么简单的需求大家一定会想到使用工厂模式解决,只需要将固定材料作为基础父类,然后不同口味作为子类。

很简单吧!

工厂实现

老干妈基础材料父类:

<?php
/**
 * 老干妈基类
 */
abstract class oldMother
{

    // 黄豆
    public $soy;

    // 辣椒
    public $chili;

    // 口味
    public $taste;

    public function __construct()
    {
        $this->soy = '黄豆';
        $this->chili = '辣椒';
    }

    /**
     * 制造老干妈
     * @return string
     */
    public function manufacture()
    {
        return '制造老干妈:' . $this->soy . '+' . $this->chili . '!口味:' . $this->taste;
    }

    /**
     * 加入口味
     * 各自口味子类实现
     */
    public abstract function setTaste();
}
  

竹笋味老干妈:

/**
 * 竹笋味老干妈
 */
class bambooShootsOldMother extends oldMother
{
    /**
     * 加入香笋
     */
    public function setTaste()
    {
        $this->taste = '香笋';
    }
}

芽菜味老干妈:

/**
 * 芽菜味老干妈
 */
class sproutsOldMother extends oldMother
{

    /**
     * 加入芽菜
     */
    public function setTaste()
    {
        $this->taste = '芽菜';
    }

}

老干妈生产工厂:

/**
 * 老干妈生产工厂
 */
class oldMotherFactory
{

    /**
     * 生产老干妈,默认为芽菜口味
     * @param string $type
     */
    public function getOldMother(string $type = 'sprouts')
    {
        switch ($type) {
            case 'sprouts':
                return new sproutsOldMother();
            case 'bambooShoots':
                return new bambooShootsOldMother();
        }

    }
}

测试生产:

// 测试
// 获得一个香笋味老干妈类
$bambooShoots = (new oldMotherFactory())->getOldMother('bambooShoots');
// 设置口味
$bambooShoots->setTaste();
// 制造
var_dump($bambooShoots->manufacture());

// 获得一个芽菜味老干妈类
$sprouts = (new oldMotherFactory())->getOldMother('sprouts');
// 设置口味
$sprouts->setTaste();
// 制造
var_dump($sprouts->manufacture());

测试结果:

string(49) "制造老干妈:黄豆+辣椒!口味:香笋"
string(49) "制造老干妈:黄豆+辣椒!口味:芽菜"

执行结果似乎十分令人满意是吧,就这样,我们的老干妈生产工厂稳定的生产全国老干妈几个月,赚的盆满钵满。

可是......

由于大众对竹笋味的老干妈口味不太接受,偏爱芽菜味的老干妈,导致竹笋味老干妈和芽菜味老干妈销量差距过于悬殊,老干妈公司为此头疼不已!

但是就在昨天,腾讯公司找上老干妈公司,需要给公司旗下的QQ飞车待达成合作,给竹笋味的老干妈的瓶子里印上CDK,这样吃竹笋味的老干妈就可以兑换QQ飞车的道具,这样以来竹笋味的老干妈销量增加了,到时候再跟腾讯一起分润,共赢!这场合作使老干妈公司非常开心(滑稽.jpg)

于是乎....

老干妈公司找到我们的生产公司,要求我们在生产老干妈的时候给瓶子里加上CDK。

生产主管杰克马觉得这个需求非常简单,我们只需要在老干妈的基类里面加上CDK方法不就行了?

杰克马说干就干,马上修改了基类:

<?php
/**
 * 老干妈基类
 */
abstract class oldMother
{

    // 黄豆
    public $soy;

    // 辣椒
    public $chili;

    // 口味
    public $taste;
    
    // 给瓶子加上了cdk
    public $cdk;

    public function __construct()
    {
        $this->soy = '黄豆';
        $this->chili = '辣椒';
        $this->cdk = 'QQ飞车';
    }

    /**
     * 制造老干妈
     * @return string
     */
    public function manufacture()
    {
        return '制造老干妈:' . $this->soy . '+' . $this->chili . '!口味:' . $this->taste . ', CDK:' . $this->cdk;
    }

    /**
     * 加入口味
     * 各自口味子类实现
     */
    public abstract function setTaste();
}

花了一首歌的时间,杰克马就改好了生产方法,开始生产带CDK的老干妈了,老干妈对于生产厂的效率似乎也感到十分满意,决定每瓶老干妈多给生产厂3毛钱提成。

杰克马为此开心不已。

但是好久不长....

由于杰克马直接在基类中加入了CDK属性,导致每个口味的老干妈都能获得CDK,既然都能获得CDK,用户都去买芽菜味的老干妈了,因为竹笋味的老干妈没有那么好吃,导致销量急剧下滑,也偏离了最初跟腾讯合作提高竹笋味销量的目的!

这下杰克马慌乱不已,这下不仅提成没着落,出这么大的事故给老干妈生产的机会都不知道还有没有。o(╥﹏╥)o

这个时候,手下的小王提出了一个解决方案:

我们应该把基类中的cdk属性去掉,放置在每个子类当中,如果子类需要给瓶子贴上CDK的话自己在类里面处理

杰克马顿时觉得小王说的十分有道理,不枉自己辛苦栽培小王,关键时刻还是没有掉链子。

于是乎,杰克马正准备补救开始着手修改的时候...

慢着!这个方案只能治标,不能治根

突然,另一个手下小方站了出来。

杰克马似乎跟小方十分不对付,平时不请他洗脚不上道也就算了,关键时刻还来耽误事?

杰克马不满的说道:are you ok? 闭嘴,小方,我正在跟小王商量补救大计,这里哪有你说话的份?

小王不紧不慢的说道:

马哥,你先听我把话说完!
老王的方案虽然目前能解燃眉之急,但是此法只能治标,不能治本。
我们试想一下,把cdk放到每个子类当中由子类自己去解决是否贴牌,看似十分简单,其实里面后患无穷。
就算我们解决了腾讯CDK问题,还有以下的问题等着我们:
1.万一哪天老干妈新出了很多口味,有牛肉,五香仁,韭菜味等,都需要贴CDK,只有芽菜不需要,那是不是我们除了芽菜其他子类都要去写一遍CDK的方法?
2.万一哪天老干妈跟阿里爸爸合作了,要在瓶子里面贴掏包的二维码呢?那我们是不是需要每个子类再去写个二维码方法?
3.这么多子类都有同一个方法代码,而只有贴的内容不同,那我们的代码是不是冗余严重了呢?
3.每增加一个口味都需要增加一个子类
我们必须要想一个设计上,代码上都可继续扩展的方法,而不是图一时之快只解决眼前问题!

杰克马听完顿时惊觉:雾草,牛批,想不到平日里如此普通的你竟有如此高见!那依你看?

小王继续说道:

首先我们可以确定老干妈的原材料黄豆和辣椒是不变的,这个叫“共同语言”。(如果原材料黄豆和辣椒变了那老干妈就可以直接倒闭了)
其次我们目前能确定的“不共同语言”有:合作的厂商、口味这两个。
那我们就把“不共同语言”部分抽象出来,组成老干妈的行为,然后动态的去生产,这样就不会因为老干妈的合作厂商和口味的不同去耦合冗余代码!

下面请看小王的实现!

策略模式

定义算法族(本文也称行为)

口味算法族:

/**
 * 口味行为接口
 * Interface tasteBehavior
 */
interface tasteBehavior
{
    public function taste();
}

/**
 * 香笋口味
 * Class bambooShootsBehavior
 */
class bambooShootsBehavior implements tasteBehavior
{
    /**
     * 具体行为
     * @return string
     */
    public function taste()
    {
        return '香笋';
    }
}

/**
 * 芽菜口味
 * Class sproutsBehavior
 */
class sproutsBehavior implements tasteBehavior
{
    /**
     * 具体行为
     * @return string
     */
    public function taste()
    {
        return '芽菜';
    }
}

合作公司算法族:

/**
 * 厂商行为接口
 * Interface vendorBehavior
 */
interface vendorBehavior
{
    public function vendor();
}

/**
 * 疼迅公司
 */
class tencentBehavior implements vendorBehavior
{

    public function vendor()
    {
        return '腾讯:QQ飞车CDK';
    }
}

/**
 * 阿里爸爸
 */
class alibabaBehavior implements vendorBehavior
{

    public function vendor()
    {
        return '阿里爸爸:掏包二维码';
    }
}

老干妈基类【环境角色】

/**
 * 老干妈基类
 */
class oldMother
{

    // 黄豆
    public $soy;

    // 辣椒
    public $chili;

    /**
     * 口味行为
     * @var tasteBehavior
     */
    public $tasteBehavior;

    /**
     * 合作厂商行为
     * @var vendorBehavior
     */
    public $vendorBehavior;

    public function __construct()
    {
        $this->soy = '黄豆';
        $this->chili = '辣椒';
    }

    /**
     * 制造老干妈
     * @return string
     */
    public function manufacture()
    {
        return '制造老干妈:' . $this->soy . '+' . $this->chili . '!';
    }

    /**
     * 设置口味行为
     * @param tasteBehavior $tasteBehavior
     */
    public function setTasteBehavior(tasteBehavior $tasteBehavior)
    {
        $this->tasteBehavior = $tasteBehavior;
    }

    /**
     * 执行口味行为
     * @return string
     */
    public function doTasteBehavior()
    {
        return '口味:' . $this->tasteBehavior->taste();
    }

    /**
     * 设置合作企业行为
     * @param vendorBehavior $tasteBehavior
     */
    public function setVendorBehavior(vendorBehavior $tasteBehavior)
    {
        $this->tasteBehavior = $tasteBehavior;
    }

    /**
     * 执行合作企业行为
     * @return string
     */
    public function doVendorBehavior()
    {
        return '合作企业:' . $this->vendorBehavior->vendor();
    }

}

动态生产测试:

// 生产一个芽菜口味的老干妈,并且跟腾讯合作QQ飞车合作CDK
$tencentOldMother = new OldMother();
// 为腾讯老干妈加上芽菜口味和QQ飞车CDK
$tencentOldMother->setTasteBehavior(new sproutsBehavior());
$tencentOldMother->setVendorBehavior(new tencentBehavior());
var_dump($tencentOldMother->manufacture()); // 开始生产
var_dump($tencentOldMother->doTasteBehavior()); // 加入口味
var_dump($tencentOldMother->doVendorBehavior()); // 加入合作企业

var_dump('========================');

// 生产一个香笋味老干妈,并且跟阿里爸爸合作掏包二维码
$alibabaOldMother = new OldMother();
// 为阿里爸爸加上香笋口味和掏包网二维码
$alibabaOldMother->setTasteBehavior(new bambooShootsBehavior());
$alibabaOldMother->setVendorBehavior(new alibabaBehavior());
var_dump($alibabaOldMother->manufacture()); // 开始生产
var_dump($alibabaOldMother->doTasteBehavior()); // 加入口味
var_dump($alibabaOldMother->doVendorBehavior()); // 加入合作企业

测试结果:

string(32) "制造老干妈:黄豆+辣椒!"
string(13) "口味:芽菜"
string(31) "合作企业:腾讯:QQ飞车CDK"
string(24) "========================"
string(32) "制造老干妈:黄豆+辣椒!"
string(13) "口味:香笋"
string(41) "合作企业:阿里爸爸:掏包二维码"

就这样,生产厂可以给阿里爸爸或疼迅组合任何口味的老干妈,甚至还可以给阿里爸爸贴QQ飞车CDK,或者给疼迅贴掏包网二维码。

这个就是算法族的动态组合!

通过小王的改造,厂里革新了技术,超额完成了指标还获得了表扬!

杰克马也终于获得了老干妈公司的提成。。。。

Archives QR Code
QR Code for this page
Tipping QR Code
Leave a Comment

2 Comments
  1. 卖女孩的小火柴 卖女孩的小火柴

    感觉看故事汇的感觉

  2. 你好,看到独角数卡的订单处理模块有您的署名,冒昧想请教下下单的邮件自动发货,是怎么配置的?感激不尽