降低圈复杂度的设计模式
Design pattern to reduce cyclomatic complexity
我必须通过相关客户的 API.
向客户发送发票
每个客户都可以选择联系方式(电子邮件、传真、短信...)。
我的问题是每次添加新的 "contact type" 都会增加脚本的圈复杂度。
这是我的代码:
<?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 语句都会增加。
你知道哪种设计模式可以解决这个问题吗?
使用 send
和 shouldSend
方法创建类似于 SendInvoiceInterface
的界面。然后创建3个实现:
EmailInvoiceSender
FaxInvoiceSender
SMSInvoiceSender
接下来将这些作为数组全部注入到您的 InvoiceSender
中。然后,您可以循环遍历所有发件人,如果他们 shouldSend
,则 'ask' 他们可以代替 switch-case。如果计算结果为真,您可以对它们调用 send
。
这样逻辑就保留在每个发件人内部,而不是不断增长的开关盒。
谢谢大家的指点。
你关于使用包含所有服务的数组的建议让我想到了另一个解决方案:
<?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 class.
的私有方法
此外,我不必为每种发送模式编写额外的 classes。
我必须通过相关客户的 API.
向客户发送发票 每个客户都可以选择联系方式(电子邮件、传真、短信...)。
我的问题是每次添加新的 "contact type" 都会增加脚本的圈复杂度。
这是我的代码:
<?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 语句都会增加。
你知道哪种设计模式可以解决这个问题吗?
使用 send
和 shouldSend
方法创建类似于 SendInvoiceInterface
的界面。然后创建3个实现:
EmailInvoiceSender
FaxInvoiceSender
SMSInvoiceSender
接下来将这些作为数组全部注入到您的 InvoiceSender
中。然后,您可以循环遍历所有发件人,如果他们 shouldSend
,则 'ask' 他们可以代替 switch-case。如果计算结果为真,您可以对它们调用 send
。
这样逻辑就保留在每个发件人内部,而不是不断增长的开关盒。
谢谢大家的指点。
你关于使用包含所有服务的数组的建议让我想到了另一个解决方案:
<?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 class.
的私有方法
此外,我不必为每种发送模式编写额外的 classes。