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