【PHP 设计模式大头菜】策略模式 Strategy Pattern

Banner

策略模式 Strategy Pattern

策略模式,可以让物件在运作时更改其行为或算法,你可以透过切换策略物件来改变计有的功能,你需要实作一个介面来代表这个策略物件,然後在主要类别当中去引入这个策略物件,在需要变更时来切换策略物件,来达成不同状况下所需要的功能,就像是大头菜的铃钱有两种模式,一种是原本的铃钱,另一种则是过期後归零,这个铃钱运算的模式就可以抽离出来作为策略物件。

UML

UML

实作

首先我们要定义策略的介面,这个介面我们会希望策略物件必须要实作铃钱运算(calculatePrice)的方法。

Strategy.php

/**
 * Interface Strategy.
 */
interface Strategy
{
    /**
     * @return int
     */
    public function calculatePrice(int $price, int $count): int;
}

再来要实践大头菜的策略模式,首先是正常状况下的大头菜,会直接拿铃钱价格、总数相成後即是铃钱总价,并且将其回传。

TurnipsStrategy.php

/**
 * Class TurnipsStrategy.
 */
class TurnipsStrategy implements Strategy
{
    /**
     * @return int
     */
    public function calculatePrice(int $price, int $count): int
    {
        return $price * $count;
    }
}

至於坏掉的部分,只要大头菜坏掉就是卖不出去,所以不用进行任何运算,直接回传 0 铃钱即可。

SpoliedStrategy.php

/**
 * Class SpoliedStrategy.
 */
class SpoliedStrategy implements Strategy
{
    /**
     * @return int
     */
    public function calculatePrice(int $price, int $count): int
    {
        return 0;
    }
}

最後实作大头菜物件,我们需要顺便把策略物件丢进去,如果在建立大头菜物件时没有指定策略物件,那麽预设就给予正常的策略物件,并且提供一个可以临时切换策略物件的方法,以及计算铃钱总价的方法,这个计算的方法是透过呼叫策略物件的方法来实践。

Turnips.php

/**
 * Class Turnips.
 */
class Turnips
{
    /**
     * @var int
     */
    protected int $price;

    /**
     * @var int
     */
    protected int $count;

    /**
     * @var Strategy
     */
    protected Strategy $strategy;

    /**
     * Turnips constructor.
     * 
     * @param int $price
     * @param int $count
     * @param Strategy $strategy
     */
    public function __construct(int $price, int $count, Strategy $strategy = null)
    {
        $this->price = $price;
        $this->count = $count;

        // 如果在建立大头菜物件时没有指定策略物件,那麽预设就给予正常的策略物件。
        $this->strategy = empty($strategy) ? new TurnipsStrategy() : $strategy;
    }

    /**
     * @param Strategy $strategy
     */
    public function setStrategy(Strategy $strategy)
    {
        $this->strategy = $strategy;
    }

    /**
     * @return int
     */
    public function calculatePrice(): int
    {
        return $this->strategy->calculatePrice($this->price, $this->count);
    }
}

测试

最後我们要测试策略大头菜是否如预期的可以运行,我们接下来有一项测试分别是建立大头菜物件,并且给予预设正常的策略物件,正常情况下可以计算出铃钱,这时候把策略物件替换为坏掉的模式,再重复呼叫方法时,则是获得 0 铃钱。

StrategyPatternTest.php

/**
 * Class StrategyPatternTest.
 */
class StrategyPatternTest extends TestCase
{
    /**
     * @test
     */
    public function test_strategy()
    {
        $turnips = new Turnips(100, 40, new TurnipsStrategy);
        $this->assertEquals(4000, $turnips->calculatePrice());

        $turnips->setStrategy(new SpoliedStrategy());
        $this->assertEquals(0, $turnips->calculatePrice());
    }
}

最後测试的执行结果会获得如下:

PHPUnit Pretty Result Printer 0.28.0 by Codedungeon and contributors.
==> Configuration: ~/php-design-pattern/vendor/codedungeon/phpunit-result-printer/src/phpunit-printer.yml

PHPUnit 9.2.6 by Sebastian Bergmann and contributors.


 ==> ...fResponsibilitiesTest   ✔  ✔  ✔  
 ==> CommandPatternTest         ✔  
 ==> IteratorPatternTest        ✔  ✔  ✔  ✔  
 ==> MediatorPatternTest        ✔  ✔  ✔  
 ==> MementoPatternTest         ✔  
 ==> NullObjectPatternTest      ✔  ✔  ✔  ✔  
 ==> ObserverPatternTest        ✔  
 ==> SpecificationPatternTest   ✔  ✔  ✔  ✔  
 ==> StatePatternTest           ✔  
 ==> StrategyPatternTest        ✔  
 ==> AbstractFactoryTest        ✔  ✔  ✔  ✔  
 ==> BuilderPatternTest         ✔  ✔  ✔  ✔  
 ==> FactoryMethodTest          ✔  ✔  ✔  ✔  
 ==> PoolPatternTest            ✔  ✔  
 ==> PrototypePatternTest       ✔  ✔  
 ==> SimpleFactoryTest          ✔  ✔  ✔  ✔  
 ==> SingletonPatternTest       ✔  
 ==> StaticFactoryTest          ✔  ✔  ✔  ✔  ✔  
 ==> AdapterPatternTest         ✔  ✔  
 ==> BridgePatternTest          ✔  ✔  ✔  
 ==> CompositePatternTest       ✔  ✔  ✔  
 ==> DataMapperTest             ✔  ✔  
 ==> DecoratorPatternTest       ✔  ✔  
 ==> DependencyInjectionTest    ✔  ✔  ✔  
 ==> FacadePatternTest          ✔  
 ==> FluentInterfaceTest        ✔  
 ==> FlyweightPatternTest       ✔  
 ==> ProxyPatternTest           ✔  ✔  
 ==> RegistryPatternTest        ✔  ✔  ✔  ✔  ✔  

Time: 00:00.084, Memory: 8.00 MB

OK (74 tests, 147 assertions)

完整程序码

设计模式不难,找回快乐而已,以大头菜为例。

参考文献


<<:  [Android 开发经验三十天]D29一小画家小问题跟改善方法

>>:  Python API Lab 1.0 –增加更多API需求

[Python 爬虫这样学,一定是大拇指拉!] DAY28 - 实战演练:集大成 - 自动更新每日个股日成交资讯

自动更新每日个股日成交资讯 结合前几篇所学,我们来做一个可以自动更新日成交资讯的程序吧! Reque...

使用 DOM Parser 取值

这篇会讲解怎麽样用 DOM 的 parser 把 RSS 资讯拿出来,首先我们可以先 new 一个 ...

[day25]Vue实作-历史交易查询画面

在昨天的铁人贴文中制作了交易建立的画面,之前也有提到,透过批次,会於日档批次中,定期抓取历史缴费纪录...

Day 08:八爪章鱼之 tmux 快捷键

今天的 Home 目录没有修改,https://github.com/simba-fs/2021-...

大脑如何精准学习 (3) 错误回馈

「错误」的定义 重复上章节对「好奇心」的心理假设: 大脑只有在感知到预测和实际认知有缺口时,才会启动...