PHP|策略+工厂模式优化if-else

1,092 阅读2分钟

做一件事,无论大小,倘无恒心,是很不好的。而看一切太难,固然能使人无成,但若看得太容易,也能使事情无结果。

前言

那天回顾曾经写的代码,发现当初为了快速完成任务,写了很多if-else的判断,密密麻麻一大串下来,看着着实不大美观而且臃肿,于是便动手使用了策略工厂模式稍微优化一下。这里我就不多赘述工厂模式和策略模式的概念,全网随便一搜很多人都说的很清楚,我只稍微写点简单伪代码,作为一下优化的心得。

新建一个策略接口类InsuranceInterface.php

这个接口类是定义了标准使用方法,之后对接新的保险都继承于该接口类实现

<?php

declare(strict_types=1);

namespace App\Service\ProjectInterface;

interface InsuranceInterface
{
    // 计算价格
    public function cal(): float;

    // 获取险种名字
    public function name(): string;

    // 获取详情
    public function info(): array;
}

继承InsuranceInterface.php完成两个险种的对接

比如我需要对接一个中国人寿(ChinaLife)和对接一个平安保险(PingAn),实现都是随便写写的,具体实现根据业务实际情况完成

新建一个ChinaLifeInsurance.php
<?php

declare(strict_types=1);

namespace App\Service\ProjectInterface;

class ChinaLifeInsurance implements InsuranceInterface
{
    public function cal(): float
    {
        // TODO: Implement cal() method.
        return 2;
    }

    public function info(): array
    {
        // TODO: Implement info() method.
        return [
            'money' => 10.5,
            'year' => 10,
        ];
    }

    public function name(): string
    {
        // TODO: Implement name() method.
        return 'ChinaLifeInsurance';
    }
}
新建一个PingAnInsurance.php
<?php

declare(strict_types=1);

namespace App\Service\ProjectInterface;

class PingAnInsurance implements InsuranceInterface
{
    public function cal(): float
    {
        // TODO: Implement cal() method.
        return 1;
    }

    public function info(): array
    {
        // TODO: Implement info() method.
        return [
            'money' => 10,
            'year' => 10,
        ];
    }

    public function name(): string
    {
        // TODO: Implement name() method.
        return 'PingAnInsurance';
    }
}

新建一个工厂类InsuranceStrategyFactory.php

这里我是使用了Hyperf框架来写的,因此程序是基于cli模式,内存常驻,所以可以使用单例模式,适当减少内存资源和系统资源的消耗

<?php

declare(strict_types=1);

namespace App\Service\ProjectInterface;

class InsuranceStrategyFactory
{
    public static array $instance = [];

    /**
     * @param $strategyName
     * @return InsuranceInterface
     */
    public static function getInstance($strategyName): InsuranceInterface
    {
        /**
         * 根据传来的策略名进行拼接,如我们上面的平安保险的实现接口名为PingAnInsurance
         * 故$strategyName = PingAn,其他命名空间以及后缀固定拼上即可
         */
        $className = __NAMESPACE__ . '\\' . $strategyName . 'Insurance';
        // 判断是否已存在该类,存在直接返回,不存在new一个
        if (! isset(self::$instance[$strategyName])) {
            // 判断类是否存在,不存在抛出异常
            if (class_exists($className)) {
                $class = new $className();
                self::$instance[$strategyName] = $class;
            } else {
                throw new \RuntimeException('不存在该保险');
            }
        }

        return self::$instance[$strategyName];
    }
}

如果你是用传统的php-fpm框架如laravel,thinkPHP这类的,可以不用单例模式,因为PHP是解释性脚本语言,这种运行机制会使每个PHP程序解释执行后,相关资源都被回收,因此单例模式几乎没什么意义。所以可以稍微改造一下,代码如下

<?php

declare(strict_types=1);

namespace App\Service\ProjectInterface;

class StrategyFactory
{
    /**
     * @param $strategyName
     * @return InsuranceInterface
     */
    public static function getInstance($strategyName): InsuranceInterface
    {
        $className = __NAMESPACE__ . '\\' . $strategyName . 'Insurance';

        if (class_exists($className)) {
            return new $className();
        }
        throw new \RuntimeException('不存在该保险');
    }
}

测试一下代码

新建一个TestController.php

<?php

declare(strict_types=1);

namespace App\Controller;

use App\Service\ProjectInterface\StrategyFactory;
use Hyperf\HttpServer\Annotation\AutoController;

#[AutoController(prefix: 'test')]
class TestController extends AbstractController
{
    public function insurance(): \Psr\Http\Message\ResponseInterface
    {
        try {
            // 由工厂类那里可以得知我们insurance_name的值,实际情况需要传什么在接口文档写清楚即可
            $insuranceName = $this->request->input('insurance_name');
            $insuranceFactory = StrategyFactory::getInstance($insuranceName);
            return $this->response->json([
                'code' => 1,
                'msg' => 'success',
                'data' => $insuranceFactory->name(),
            ]);
        } catch (\Throwable $throwable) {
            return $this->response->json([
                'code' => 0,
                'msg' => $throwable->getMessage(),
            ]);
        }
    }
}

请求一下接口并附上参数,如我请求http://127.0.0.1:9501/test/insurance?insurance_name=PingAn,结果如下:

image.png 同理请求http://127.0.0.1:9501/test/insurance?insurance_name=PingAn

image.png 假如传递了一个约定外的值http://127.0.0.1:9501/test/insurance?insurance_name=Foo,结果如下:

image.png 至此,通过策略工厂模式,优化了当时大量对接的险种类型使用的if-else或者switch的代码,使整体代码可阅读性提高,代码更加简洁。当然,如果代码极少发生改变,几乎没有新的类需要继承实现,策略模式只会使程序过于复杂,具体操作需要根据业务需求来定,一切以业务为准。