【问题标题】:Design pattern to reduce cyclomatic complexity降低圈复杂度的设计模式
【发布时间】:2020-12-26 22:47:05
【问题描述】:

我必须通过相关客户的 API 将发票发送给客户。
每个客户都可以选择联系方式(电子邮件、传真、短信...)。
我的问题是每次添加新的“联系人类型”时都会增加脚本的圈复杂度。
这是我的代码:
<?php

interface ExternalApi
{
    public function sendByEmail();
    public function sendByFax();
    public function sendBySMS();
    // more sending modes ...
}

class Api1 implements ExternalApi
{   
    public function sendByEmail()
    {
        echo __CLASS__." sending email<br>\n";
    }
    
    public function sendByFax()
    {
        echo __CLASS__." sending fax<br>\n";
    }
    
    public function sendBySMS()
    {
        echo __CLASS__." sending SMS<br>\n";
    }
}

class Customer
{
    public const EMAIL = 'email';
    public const FAX = 'fax';
    public const SMS = 'sms';
    public const EMAIL_AND_FAX = 'email_and_fax';
    public const PHONE = 'phone';
    public const PLANE = 'plane';
    public const BOAT = 'boat';
    public const SATELITE = 'satelite';
    public const CAB = 'cab';
    // more contact types...
    
    public ?string $contactType;
}

class Invoice
{
    public Customer $customer;
    
    public function __construct(Customer $customer)
    {
        $this->customer = $customer;
    }
}

class InvoiceSender
{
    private ExternalApi $api;

    public function __construct(ExternalApi $api)
    {
        $this->api = $api;
    }
    
    public function send(Invoice $invoice)
    {
        switch($invoice->customer->contactType) {
            case Customer::EMAIL :
                $this->api->sendByEmail();
                break;
            case Customer::FAX :
                $this->api->sendByFax();
                break;
            case Customer::SMS :
                $this->api->sendBySMS();
                break;
            case Customer::EMAIL_AND_FAX:
                $this->api->sendByEmail();
                $this->api->sendByFax();
                break;
            // more cases ...
        }
    }
}

$customer = new Customer();
$customer->contactType = Customer::EMAIL_AND_FAX;
$invoice = new Invoice($customer);
$api = new Api1();
$invoiceSender = new InvoiceSender($api);
$invoiceSender->send($invoice);

如您所见,每次添加新的“联系人类型”时,InvoiceSender::send 的 switch 语句都会增加。
你知道哪种设计模式可以解决这个问题吗?

【问题讨论】:

  • 将这些作为单独的类提取到关联数组中,然后通过$invoice-&gt;customer-&gt;contactType 从那里获取,并执行run() 方法或它们之间的共同点。经典的strategy pattern,恰好有助于解决这里的圈复杂度。

标签: php design-patterns cyclomatic-complexity


【解决方案1】:

使用sendshouldSend 方法创建类似SendInvoiceInterface 的接口。然后创建 3 个实现:

  • EmailInvoiceSender
  • FaxInvoiceSender
  • SMSInvoiceSender

接下来将这些都作为数组注入您的InvoiceSender。然后,您可以遍历所有发件人并“询问”他们是否shouldSend,而不是switch-case。如果评估结果为 true,您可以对它们调用 send

这样,逻辑就保留在每个发送者自身内部,而不是不断增长的 switch-case。

【讨论】:

    【解决方案2】:

    谢谢大家的建议。
    您关于使用包含所有服务的数组的建议让我想到了另一个解决方案:

    <?php
    
    interface ExternalApi
    {
        public function getSendingModes();
    }
    
    class Api1 implements ExternalApi
    {   
        public function getSendingModes()
        {
            return [
                Customer::EMAIL => fn() => $this->sendByEmail(),
                Customer::FAX => fn() => $this->sendByFax(),
                Customer::SMS => fn() => $this->sendBySMS(),
                Customer::EMAIL_AND_FAX => function() {
                    $this->sendByEmail();
                    $this->sendByFax();
                },
            ];
        }
        
        private function sendByEmail()
        {
            echo __CLASS__." sending email<br>\n";
        }
        
        private function sendByFax()
        {
            echo __CLASS__." sending fax<br>\n";
        }
        
        private function sendBySMS()
        {
            echo __CLASS__." sending SMS<br>\n";
        }
    }
    
    class Customer
    {
        public const EMAIL = 'email';
        public const FAX = 'fax';
        public const SMS = 'sms';
        public const EMAIL_AND_FAX = 'email_and_fax';
        public const PHONE = 'phone';
        public const PLANE = 'plane';
        public const BOAT = 'boat';
        public const SATELITE = 'satelite';
        public const CAB = 'cab';
        // more contact types...
        
        public ?string $contactType;
    }
    
    class Invoice
    {
        public Customer $customer;
        
        public function __construct(Customer $customer)
        {
            $this->customer = $customer;
        }
    }
    
    class InvoiceSender
    {
        private ExternalApi $api;
        private array $sendingModes;
    
        public function __construct(ExternalApi $api)
        {
            $this->api = $api;
            $this->sendingModes = $api->getSendingModes();
        }
        
        public function send(Invoice $invoice)
        {
            $sendingMode = $this->sendingModes[$invoice->customer->contactType] ?? null;
            if (null === $sendingMode) {
                throw new RuntimeException("API ".get_class($this->api)." doesn't have sending mode ".$invoice->customer->contactType);
            }
            $sendingMode();
        }
    }
    
    $customer = new Customer();
    $customer->contactType = Customer::EMAIL_AND_FAX;
    //$customer->contactType = Customer::EMAIL;
    //$customer->contactType = Customer::BOAT;
    $invoice = new Invoice($customer);
    $api = new Api1();
    try {
        $invoiceSender = new InvoiceSender($api);
        $invoiceSender->send($invoice);
    } catch (Exception $ex) {
        echo $ex->getMessage()."\n";
    }
    

    我更改了 ExternalApi 接口以检索一系列发送模式。
    这样做,每个 API 都知道自己的发送模式,因此每个 API 不需要实现它们不处理的发送模式。
    数组的每个值都是一个匿名函数,会触发 API 类的私有方法。
    我也不必为每种发送模式编写额外的类。

    【讨论】:

      猜你喜欢
      • 2020-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-13
      • 1970-01-01
      相关资源
      最近更新 更多