初识设计模式
一、设计原则
1. SOLID
SOLID 是最常提到的最经典的五个设计原则,分别是单一职责原则、开放封闭原则、里氏替换原则、接口隔离原则、依赖反转原则。
-
单一职责原则的英文是 Single Responsibility Principle,缩写为 SRP。它的英文描述是:
A class or module should have a single responsibility.
如果我们把它翻译成中文,那就是:一个类或者模块只负责完成一个职责(或者功能)。
代码示例:
<?php class User { public int $id; public string $openId; public string $unionId; public string $username; public string $avatar; public string $country; public string $province; public string $city; public int $gender; public function getAddress() { return $this->country . ',' . $this->province . ',' . $this->city; } } # User 类里面的 getAddress() 方法是否满足单一职责原则? # 其实是满足的,因为在这里面 country、province、city 都是通过微信授权获取的,指用户的基本信息。 # 如果例子里面 getAddress() 代表的是获取用户的收货地址,那么就不符合单一职责原则了,因为微信信息中的地区并不就代表收货地址,所以我们最好再新建一个 UserAddress 类。
-
开放封闭原则的英文是 Open Closed Principle,缩写为 OCP。它的英文描述是:
Software entities (modules, classes, functions, etc.) should be open for extension, but closed for modification.
我们把它翻译成中文就是:软件实体(模块、类、方法等)应该“对扩展开放、对修改关闭”。
代码示例:
class PayService { public function pay(array $params) { } } class PayController { public function pay() { $payService = new PayService(); $payService->pay([ 'order_id' => 1, 'order_amount' => 100, 'pay_amount' => 100 ]); } } # 上面是一个支付的例子,目前只有一种支付方式,现在要支持第二种支付方式。 # 我们做出如下改造: class PayService { public function pay(array $params, $tpye = 0) { if ($tpye) { // do pay2 } else { // do pay1 } } } class PayController { public function pay() { $payService = new PayService(); $payService->pay([ 'order_id' => 2, 'order_amount' => 100, 'pay_amount' => 100 ], 1); } } # 如此一来,好像完美解决了两种支付的需求,而且能肯定不会影响到老的逻辑。 # 但是万一又要有第三种支付方式呢?继续这样改造下去代码将混乱不堪。而且这种改造很明显违背了开闭原则。 # 所以,正确而优雅的支付方式应该是这样的: interface Pay { public function pay(array $params); } class Pay1 implements Pay { public function pay(array $params) { } } class Pay2 implements Pay { public function pay(array $params) { } } class PayService { public function pay(Pay $pay, $params) { $pay->pay($params); } } class PayController { public function pay() { $payService = new PayService(); // do pay1 $payService->pay(new Pay1, [ 'order_id' => 3, 'order_amount' => 100, 'pay_amount' => 100 ]); // do pay2 $payService->pay(new Pay2, [ 'order_id' => 4, 'order_amount' => 100, 'pay_amount' => 100 ]); } } # 这样一来,即使再加上几种支付方式,只需要创建 PayN 并实现 pay() 方法就行了。满足了对扩展开放、修改关闭。
-
里式替换原则的英文是:Liskov Substitution Principle,缩写为 LSP。这个原则最早是在 1986 年由 Barbara Liskov 提出,他是这么描述这条原则的:
If S is a subtype of T, then objects of type T may be replaced with objects of type S, without breaking the program.
在 1996 年,Robert Martin 在他的 SOLID 原则中,重新描述了这个原则,英文原话是这样的:
Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it.
我们综合两者的描述,将这条原则用中文描述出来,是这样的:子类对象(object of subtype/derived class)能够替换程序(program)中父类对象(object of base/parent class)出现的任何地方,并且保证原来程序的逻辑行为(behavior)不变及正确性不被破坏。
代码示例:
class P { public function sortOrderByAmount(array $params) { // 根据订单金额排序 foreach ($params as $param) { $amount[] = $param['order_amount']; } array_multisort($amount, SORT_DESC, $params); return $params; } } class C extends P { public function sortOrderByAmount(array $params) { // 根据支付金额排序 foreach ($params as $param) { $amount[] = $param['pay_amount']; } array_multisort($amount, SORT_DESC, $params); return $params; } } # 此处很明显就是违背里氏替换原则的,因为子类对象不能在任何地方替换父类对象,这种情况应该在子类对象中新增一个 sortOrderByPayAmount() 方法,而不是去重写父类方法。
-
接口隔离原则的英文是: Interface Segregation Principle,缩写为 ISP。Robert Martin 在 SOLID 原则中是这样定义它的:
Clients should not be forced to depend upon interfaces that they do not use.
直译成中文的话就是:客户端不应该被强迫依赖它不需要的接口。其中的“客户端”,可以理解为接口的调用者或者使用者。
代码示例:
interface Pay { public function pay(); public function callback(); } class WechatPay implements Pay { public function pay() { } public function callback() { } } class WalletPay implements Pay { public function pay() { } public function callback() { } } # 这里有两种支付方式,微信支付和钱包支付。其中微信支付是需要回调的,而钱包支付不需要,所以这么写违背了接口隔离原则。应该作如下改动: interface ExternalPay { public function pay(); public function callback(); } interface InteriorPay { public function pay(); } class WechatPay implements ExternalPay { public function pay() { } public function callback() { } } class WalletPay implements InteriorPay { public function pay() { } }
-
依赖反转原则的英文是:Dependency Inversion Principle,缩写为 DIP。中文翻译有时候也叫依赖倒置原则。它的英文描述是:
High-level modules shouldn’t depend on low-level modules. Both modules should depend on abstractions. In addition, abstractions shouldn’t depend on details. Details depend on abstractions.
我们将它翻译成中文,大概意思就是:高层模块(high-level modules)不要依赖低层模块(low-level)。高层模块和低层模块应该通过抽象(abstractions)来互相依赖。除此之外,抽象(abstractions)不要依赖具体实现细节(details),具体实现细节(details)依赖抽象(abstractions)。
代码示例:
interface Pay { public function pay(array $params); } class PayService { public function pay(Pay $pay, $params) { $pay->pay($params); } } # 拿上面展示过的例子,PayService 类不应该依赖于具体的 Pay1 或者 Pay2,依赖于低层的抽象即可。
2. KISS
如此浪漫的一个单词,在这里的意思却是“简短”。
Keep It Short and Simple.
我觉得“简短”是一个抽象的词,它的意思并不是代表越短越好,比如说你的代码不空行,或者一行写很多东西。而是上升到一种哲学的思想高度,简单就好。为了满足 KISS 原则,我们在日常开发中需要注意如下几点:
- 不要用复杂的技术解决简单的问题。
- 不要重复造轮子。
- 不要过度优化。
3. YAGNI
You Ain’t Gonna Need It.
活在当下,不要为不大可能发生之事而杞人忧天。当用在软件开发中的时候,它的意思是:不要去设计当前用不到的功能;不要去编写当前用不到的代码。实际上,这条原则的核心思想就是:不要做过度设计。
4. DRY
Don’t Repeat Yourself.
我觉得这句话对程序员来说,奉为金科玉律也不为过。写代码不要老是复制粘贴,不要重复执行相同的逻辑代码,不要十年如一日的 CRUD。
class EmailController
{
public function sendEmail()
{
$params = $_POST;
if (validateParams($params)) {
$emailService = new EmailService();
$emailService->send($params['email'], $params['content']);
}
}
}
class EmailService
{
public function send($email, $content)
{
if (validateEmail($email) && $content) {
// do send
}
}
}
function validateParams($params)
{
if (!empty($params['email'])) {
validateEmail($params['email']);
}
}
function validateEmail($email)
{
// do validate email
}
# 我们通常会在 controller 层验证参数,但也有人喜欢在 service 层再做一次验证,这是没有必要的。如果仅仅是在内存中操作还好,但万一涉及到磁盘 IO,那就得不偿失了。
5. LOD
迪米特法则的英文翻译是:Law of Demeter,缩写是 LOD。它的英文描述是:
Each unit should have only limited knowledge about other units: only units “closely” related to the current unit. Or: Each unit should only talk to its friends; Don’t talk to strangers.
翻译成中文就是:每个模块(unit)只应该了解那些与它关系密切的模块(units: only units "closely" related to the current unit)的有限知识(knowledge)。或者说,每个模块只和自己的朋友“说话”(talk),不和陌生人“说话”(talk)。也称最少知识原则。
简而言之就是:不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。
class Sms
{
public function sendSmsByYunPian(YunPian $yunPian)
{
$yunPianService = new YunPianService();
$yunPianService->send($yunPian);
}
public function sendSmsByAli(Ali $ali)
{
$aliService = new AliService();
$aliService->send($ali);
}
}
class YunPianService
{
public function send(YunPian $yunPian)
{
}
}
class YunPian
{
public $phoneNumber;
public $msg;
}
class AliService
{
public function send(Ali $ali)
{
}
}
class Ali
{
public $phoneNumber;
public $msg;
}
# 这里我们以发送短信的业务为例,要同时支持云片网和阿里云发送短信。上述代码 Sms 类依赖于 YunPian、YunPianService、Ali、AliService 类,违背了迪米特法则。
# 我们作出如下改造:
interface SendSms
{
public function send($phoneNumber, $msg);
}
class YunPianSms implements SendSms
{
public function send($phoneNumber, $msg)
{
}
}
class AliSms implements SendSms
{
public function send($phoneNumber, $msg)
{
}
}
class Sms
{
public function send(SendSms $sendSms, $phoneNumber, $msg)
{
$sendSms->send($phoneNumber, $msg);
}
}
# 这里我取消了先前的 YunPian 类和 Ali 类,因为其实我们不需要它们,我们需要的只是手机号($phoneNumber)和短信内容($msg)。
# 除此之外,我们把具体的类(YunPianService、AliService)抽象出来(SendSms),如此,Sms 类只依赖于抽象的 SendSms 类,符合迪米特法则。
二、常用的设计模式
1. 创建型模式
单例模式
一个类只允许创建一个对象(或者实例)。
代码示例:
interface Single
{
public static function getInstance();
}
class JWT implements Single
{
private $key;
private static $instance;
public static function getInstance()
{
if (is_null(self::$instance)) {
self::$instance = new self;
}
return self::$instance;
}
public function __construct()
{
$this->key = getenv('JWT_KEY');
}
public function encode(int $uid)
{
}
public function decode(string $token)
{
}
}
# 这里我们以 JWT 为例,单例模式保证了整个项目中只有唯一的一个实例。
工厂模式
-
简单工厂
有这样一个业务场景,根据订单选择最合适的优惠券(显示最大优惠金额)。我们可以使用简单工厂模式:
class CouponFactory { public static function createCoupon($type) { $coupon = null; if ($type == 1) { // 满减券 $coupon = new MJCoupon(); } elseif ($type == 2) { // 兑换券 $coupon = new DHCoupon(); } elseif ($type == 3) { // 折扣券 $coupon = new ZKCoupon(); } return $coupon; } } class Coupon { public function getMaxReduceAmount(Order $order) { $maxReduce = 0; $couponTypes = [1, 2, 3]; foreach ($couponTypes as $type) { $coupon = CouponFactory::createCoupon($type); $reduceAmount = $coupon->getMaxReduceAmountByOrder($order); $maxReduce = $reduceAmount > $maxReduce ? $reduceAmount : $maxReduce; } return $maxReduce; } } class MJCoupon { public function getMaxReduceAmountByOrder(Order $order) { } } // ...
-
工厂方法
上面的简单工厂模式有一个小小的问题,就是 if else 语句太多,如果要新增一种优惠券支持的话就需要修改代码,不符合开闭原则,为此我们可以作出如下改造:
class CouponFactory { public static function createCoupon(BaseCouponFactory $couponFactory) { return $couponFactory->createCoupon(); } } class Coupon { public function getMaxReduceAmount(Order $order) { $maxReduce = 0; $couponTypes = [1, 2, 3]; foreach ($couponTypes as $type) { if ($type == 1) { $baseCoupon = new MJCoupon(); } elseif ($type == 2) { $baseCoupon = new DHCoupon(); } elseif ($type == 3) { $baseCoupon = new ZKCoupon(); } $coupon = CouponFactory::createCoupon($baseCoupon); $reduceAmount = $coupon->getMaxReduceAmountByOrder($order); $maxReduce = $reduceAmount > $maxReduce ? $reduceAmount : $maxReduce; } return $maxReduce; } } interface BaseCouponFactory { public function createCoupon(); } class MJCouponFactory implements BaseCouponFactory { public function createCoupon() { return new MJCoupon(); } } class MJCoupon { public function getMaxReduceAmountByOrder(Order $order) { } } // ... # 如此,当我们还需要新增优惠券的时候,只需要创建一个新的实现 BaseCouponFactory 接口的类就可以了,而不需要修改 CouponFactory 类,满足开闭原则。与简单工厂相比,工厂方法需要新增 BaseCouponFactory 类和 MJCouponFactory 类,无疑是变复杂了,而且 if else 没有消失,只是移到了 Coupon 类里。如果不是 CouponFactory 类需要创建很多复杂的类,还是用简单工厂更为方便。
-
抽象工厂
在简单工厂和工厂方法中,类只有一种分类方式,示例中就是 createCoupon() 方法。假如需求变化,现在还要支持第三方联名卡优惠方案,比如“京东E卡”、“山姆会员卡”之类的东西,那我们是不是还需要再创建一个 CardFactory 类?其实是没必要的,过多的工厂类会使代码变得复杂。第三方联名卡优惠也是优惠的一种,所以我们可以抽象成一个抽象工厂。抽象工厂就是可以让一个工厂负责创建多个不同类型的对象。
interface Discount { public static function createCoupon(BaseCouponFactory $couponFactory); public static function createCard(BaseCardFactory $cardFactory); } class DiscountsFactory implements Discount { public static function createCoupon(BaseCouponFactory $couponFactory) { return $couponFactory->createCoupon(); } public static function createCard(BaseCardFactory $cardFactory) { return $cardFactory->createCard(); } }
建造者模式
如果我们要创建一个用户,最简单的实现方式是这样的:
class User
{
public $username;
public $password;
public $createTime;
public function save()
{
}
}
$user = new User();
$user->username = '74percent';
$user->password = md5(111111);
$user->createTime = time();
$user->save();
以上代码在 PHP 中是非常常见的,不过有一个很明显的问题,就是所有的属性都是公有的,谁都可以访问,谁都可以修改,看起来好像不是那么安全。那我们可以用建造者模式改造一下。
interface IBuilder
{
// start build
public static function builder();
// end build
public static function build();
}
class BBuilder implements IBuilder
{
public static $model;
public static $arr = [];
public function __call($name, $arguments)
{
self::$arr[$name] = $arguments[0];
return $this;
}
public static function builder()
{
$childClassName = get_called_class();
self::$model = new $childClassName;
return new self;
}
public static function build()
{
foreach (self::$arr as $k => $v) {
$method = 'set' . ucfirst($k);
call_user_func([self::$model, $method], $v);
}
self::$arr = [];
return self::$model;
}
}
class User extends BBuilder
{
private $username;
private $password;
private $createTime;
public function setUsername($username)
{
$this->username = $username;
}
public function setPassword($password)
{
$this->password = $password;
}
public function setCreateTime($createTime)
{
$this->createTime = $createTime;
}
public function save()
{
}
}
$user = User::builder()
->username('74percent')
->password(md5(111111))
->createTime(time())
->build();
$user->save();
2. 结构型模式
代理模式
在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。
class ReceiveController
{
public function handle()
{
}
}
class ReceiveControllerProxy extends ReceiveController
{
public function before()
{
}
public function after()
{
}
public function handle()
{
$this->before();
parent::handle();
$this->after();
}
}
// before
$receive = new ReceiveController();
$receive->handle();
// now
$receive = new ReceiveControllerProxy();
$receive->handle();
# 我觉得代理模式非常容易理解,不再过多阐述。Spring AOP 底层的实现原理就是基于动态代理。
桥接模式
桥接模式,也叫作桥梁模式,英文是 Bridge Design Pattern。在 GoF 的《设计模式》一书中,桥接模式是这么定义的:“Decouple an abstraction from its implementation so that the two can vary independently。”翻译成中文就是:“将抽象和实现解耦,让它们可以独立变化。”
一句话就是,桥接就是面向接口编程的集大成者。面向接口编程只是说在系统的某一个功能上将接口和实现解藕,而桥接是详细的分析系统功能,将各个独立的纬度都抽象出来,使用时按需组合。
以一个报警的通知为例,报警有三个等级,SEVERE(严重)、NORMAL(普通)、TRIVIAL(无关紧要)。通知有两种方式,邮件、短信。
interface MsgSender
{
public function send($msg);
}
class EmailMsgSender implements MsgSender
{
private $people = [
'110@163.com',
'120@163.com'
];
public function send($msg)
{
foreach ($this->people as $p) {
// send
}
}
}
class TelMsgSender implements MsgSender
{
private $people = [
'110',
'120'
];
public function send($msg)
{
foreach ($this->people as $p) {
// send
}
}
}
class Notify
{
const LEVEL_LOW = 'TRIVIAL';
const LEVEL_MIDDLE = 'NORMAL';
const LEVEL_HIGH = 'SEVERE';
public function sendMsg($level, $msg)
{
if ($level === 'SEVERE') {
$sender = new TelMsgSender();
} elseif ($level === 'NORMAL') {
$sender = new EmailMsgSender();
} else {
$sender = new EmailMsgSender();
}
$this->send($sender, $msg);
}
private function send(MsgSender $sender, $msg)
{
return $sender->send($msg);
}
}
乍看起来,桥接模式和策略模式有点像,但其实是有所区别的。桥接模式是结构型模式,策略模式是行为型模式,可以说桥接模式中包含策略模式。桥接模式是多个纬度独立变化的组合,比如报警等级和通知方式,两两组合一共有6种实现。而策略模式没有组合,侧重于单个变化维度的解耦。
装饰者模式
装饰者模式和代理模式的代码实现基本上是一样的,不过在定义上还是有所区别的。装饰器模式关注于在一个对象上动态的添加方法,这个对象对用户来说是可以感知的。而代理模式对用户隐藏了源对象的具体信息,用户只需要感知到代理类就行。代理和真实对象之间的关系通常在编译时就已经确定了,而装饰者能够在运行时递归地被构造。
代码示例:
interface Receive
{
public function handle();
}
class ReceiveController implements Receive
{
public function handle()
{
}
}
class ReceiveControllerDecorator implements Receive
{
private $receive;
public function __construct(Receive $receive)
{
$this->receive = $receive;
}
public function before()
{
}
public function after()
{
}
public function handle()
{
$this->before();
$this->receive->handle();
$this->after();
}
}
适配器模式
适配器模式有两种实现方式:类适配器和对象适配器。其中,类适配器使用继承关系来实现,对象适配器使用组合关系来实现。
# 类适配器:基于继承
interface ITarget
{
public function f1();
public function f2();
public function fc();
}
class Adaptee
{
public function fa()
{
}
public function fb()
{
}
public function fc()
{
}
}
class Adaptor extends Adaptee implements ITarget
{
public function f1()
{
parent::fa();
}
public function f2()
{
}
}
# 对象适配器:基于组合
interface ITarget
{
public function f1();
public function f2();
public function fc();
}
class Adaptee
{
public function fa()
{
}
public function fb()
{
}
public function fc()
{
}
}
class Adaptor implements ITarget
{
private $adaptee;
public function __construct(Adaptee $adaptee)
{
$this->adaptee = $adaptee;
}
public function f1()
{
$this->adaptee->fa();
}
public function f2()
{
}
public function fc()
{
}
}
针对这两种实现方式,在实际的开发中,到底该如何选择使用哪一种呢?判断的标准主要有两个,一个是 Adaptee 接口的个数,另一个是 Adaptee 和 ITarget 的契合程度。
- 如果 Adaptee 接口并不多,那两种实现方式都可以。
- 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都相同,那我们推荐使用类适配器,因为 Adaptor 复用父类 Adaptee 的接口,比起对象适配器的实现方式,Adaptor 的代码量要少一些。
- 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都不相同,那我们推荐使用对象适配器,因为组合结构相对于继承更加灵活。
3. 行为型模式
观察者模式
观察者模式其实就是发布订阅模式,是一个非常常用的设计模式。它是这样定义的:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。
我们以用户下单为例,下单这个动作涉及到很多操作,比如减库存、给主播算销售额(未支付的订单还不能算有效订单,已支付的订单其实也不能算有效订单,因为可能会涉及到退款,只有已支付无退款并且关闭了的订单才能算有效订单)、写日志等。下单这个行为就很适合用观察者模式。
class BaseSubject implements SplSubject
{
private $observers = [];
public function attach(SplObserver $observer)
{
return array_push($this->observers, $observer);
}
public function detach(SplObserver $observer)
{
$key = array_search($observer, $this->observers, true);
if ($key !== false) {
unset($this->observers[$key]);
return true;
}
return false;
}
public function notify()
{
if (!empty($this->observers)) {
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
return true;
}
}
class OrderSubject extends BaseSubject
{
private $order;
public function __construct(Order $order)
{
$this->order = $order;
}
public function placeOrder()
{
// ...
$this->notify();
}
}
class GoodsObserver implements SplObserver
{
public function update(SplSubject $subject)
{
}
}
class AnchorObserver implements SplObserver
{
public function update(SplSubject $subject)
{
}
}
class LogObserver implements SplObserver
{
public function update(SplSubject $subject)
{
}
}
class OrderController
{
public function placeOrder()
{
$order = new Order();
$orderSubject = new OrderSubject($order);
$orderSubject->attach(new GoodsObserver());
$orderSubject->attach(new AnchorObserver());
$orderSubject->attach(new LogObserver());
$orderSubject->placeOrder();
}
}
模板模式
模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
代码示例:
abstract class RequestTemplate
{
abstract protected function doGet($params);
abstract protected function doPost($params);
final public function getResponse($params)
{
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$res = $this->doGet($params);
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
$res = $this->doPost($params);
} else {
throw new Exception('Invalid request method.');
}
return $res;
}
}
class Request extends RequestTemplate
{
protected function doGet($params)
{
throw new Exception('Get request is invalid.');
}
protected function doPost($params)
{
return 'Post request is valid.';
}
}
$request = new Request();
$res = $request->getResponse($_REQUEST);
# 这里我定义了一个 Request 类继承 RequestTemplate 模板类,重写 doGet() 和 doPost() 方法,来处理不同的请求逻辑。Java 中 Servlet 就是这么实现的。
策略模式
策略模式,英文全称是 Strategy Design Pattern。在 GoF 的《设计模式》一书中,它是这样定义的:定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。
策略模式非常常见,使用也相当广泛,其实我在上面讲工厂模式、桥接模式的时候,就已经用到策略模式了。在业务中,使用优惠券、支付、发短信都是可以使用策略模式的。
abstract class PayStrategy
{
abstract public function pay();
}
abstract class ExternalPay extends PayStrategy
{
abstract public function callback();
}
abstract class InteriorPay extends PayStrategy
{
}
class WechatPay extends ExternalPay
{
public function pay()
{
echo 'wechat pay' . "
";
}
public function callback()
{
}
}
class WalletPay extends InteriorPay
{
public function pay()
{
echo 'wallet pay' . "
";
}
}
class PayController
{
public function pay($payType)
{
if ($payType === 1) { // 微信支付
$payStrategy = new WechatPay();
} elseif ($payType === 2) { // 钱包支付
$payStrategy = new WalletPay();
}
// ...
$this->_pay($payStrategy);
}
private function _pay(PayStrategy $payStrategy)
{
return $payStrategy->pay();
}
}
职责链模式
职责链模式的英文翻译是 Chain Of Responsibility Design Pattern。它是这么定义的:将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。
这里举一个游戏直播发弹幕敏感词汇过滤的例子,针对游戏公司,有自己的敏感词汇库,不过不全,还需要引用第三方的敏感词汇过滤接口。
interface SensitiveWordFilter
{
public function filter($content);
}
class SelfWordFilter implements SensitiveWordFilter
{
public function filter($content)
{
$legal = true;
// ...
return $legal;
}
}
class BaiduWordFilter implements SensitiveWordFilter
{
public function filter($content)
{
$legal = true;
// ...
return $legal;
}
}
class SensitiveWordFilterChain
{
private $filters = [];
public function addFilter(SensitiveWordFilter $filter)
{
array_push($this->filters, $filter);
}
public function filter($content)
{
foreach ($this->filters as $filter) {
if (!$filter->filter($content)) {
return false;
}
}
return true;
}
}
$chain = new SensitiveWordFilterChain();
$chain->addFilter(new SelfWordFilter());
$chain->addFilter(new BaiduWordFilter());
var_dump($chain->filter('包子'));
上述职责链模式是用数组实现的,下面我再用链表实现一个。
abstract class SensitiveWordFilter
{
protected $next = null;
public function setNext(?SensitiveWordFilter $filter)
{
$this->next = $filter;
}
public abstract function filter($content);
}
class SelfWordFilter extends SensitiveWordFilter
{
public function filter($content)
{
$legal = true;
// ...
if ($legal && $this->next) {
$legal = $this->next->filter($content);
}
return $legal;
}
}
class BaiduWordFilter extends SensitiveWordFilter
{
public function filter($content)
{
$legal = true;
// ...
if ($legal && $this->next) {
$legal = $this->next->filter($content);
}
return $legal;
}
}
class SensitiveWordFilterChain
{
private $head = null;
private $tail = null;
public function addFilter(SensitiveWordFilter $filter)
{
$filter->setNext(null);
if ($this->head == null) {
$this->head = $filter;
$this->tail = $filter;
return;
}
$this->tail->setNext($filter);
$this->tail = $filter;
}
public function filter($content)
{
if ($this->head != null) {
return $this->head->filter($content);
}
}
}
$chain = new SensitiveWordFilterChain();
$chain->addFilter(new SelfWordFilter());
$chain->addFilter(new BaiduWordFilter());
var_dump($chain->filter('包子'));
职责链模式还有一个变体,就是必须让链上所有的对象都处理一遍请求,不存在中途终止的情况。这个也很好理解,这里不再阐述。
迭代器模式
迭代器模式(Iterator Design Pattern),也叫作游标模式(Cursor Design Pattern)。迭代器模式用来遍历集合对象。这里说的“集合对象”也可以叫“容器”、“聚合对象”,实际上就是包含一组对象的对象,比如数组、链表、树、图、跳表。迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。
代码示例:
# PHP 中有现成的迭代器
$names = ['Dick', 'Erick', 'Alicia', 'Tom', 'Jack'];
$arrIterator = new ArrayIterator($names);
while ($arrIterator->valid()) {
echo $arrIterator->current() . "
";
$arrIterator->next();
}
# 实现原理
interface Iterato
{
public function valid();
public function current();
public function next();
}
class ArrIterator implements Iterato
{
private $index = 0;
private $arr = [];
public function __construct(array $arr)
{
$this->arr = $arr;
}
public function valid()
{
return $this->index < count($this->arr);
}
public function current()
{
return $this->arr[$this->index];
}
public function next()
{
$this->index++;
}
}
$names = ['Dick', 'Erick', 'Alicia', 'Tom', 'Jack'];
$arrIterator = new ArrIterator($names);
while ($arrIterator->valid()) {
echo $arrIterator->current() . "
";
$arrIterator->next();
}
状态模式
状态模式并不是很常用,但是在能够用到的场景里,它可以发挥很大的作用。状态模式一般用来实现状态机,而状态机常用在游戏、工作流引擎等系统开发中。下面我以电商修改订单状态为例:
/**
* 订单状态
*/
class OrderState
{
const OBLIGATION = 0; // 待付款
const CANCELED = 1; // 已取消
const WAITDELIVER = 2; // 待发货
const WAITRECEIPT = 3; // 待收货
const RECEIPED = 4; // 已完成
}
/**
* 售后状态
*/
class AfterSaleState
{
const DEFAULT = 0; // 未提交售后
const SUBMITTED = 1; // 已提交售后
}
/**
* 退款状态
*/
class RefundState
{
const DEFALUT = 0; // 未退款
const REFUNDED = 1; // 已退款
}
interface IOrder
{
public function getOrderState();
public function cancelOrder(OrderStateMachine $orderStateMachine); // 取消订单
public function paidOrder(OrderStateMachine $orderStateMachine); // 支付订单
public function receiptOrder(OrderStateMachine $orderStateMachine); // 确认收货
public function afterOrder(OrderStateMachine $orderStateMachine); // 提交售后
}
/**
* 待付款订单
*/
class ObligationOrder implements IOrder
{
public function getOrderState()
{
return OrderState::OBLIGATION;
}
public function cancelOrder(OrderStateMachine $orderStateMachine)
{
$orderStateMachine->setCurrentState(new CanceledOrder());
}
public function paidOrder(OrderStateMachine $orderStateMachine)
{
$orderStateMachine->setCurrentState(new WaitDeliverOrder());
}
public function receiptOrder(OrderStateMachine $orderStateMachine)
{
// can not do this
}
public function afterOrder(OrderStateMachine $orderStateMachine)
{
// can not do this
}
}
/**
* 已取消订单
*/
class CanceledOrder implements IOrder
{
public function getOrderState()
{
return OrderState::CANCELED;
}
public function cancelOrder(OrderStateMachine $orderStateMachine)
{
// can not do this
}
public function paidOrder(OrderStateMachine $orderStateMachine)
{
// can not do this
}
public function receiptOrder(OrderStateMachine $orderStateMachine)
{
// can not do this
}
public function afterOrder(OrderStateMachine $orderStateMachine)
{
// can not do this
}
}
/**
* 待发货订单
*/
class WaitDeliverOrder implements IOrder
{
public function getOrderState()
{
return OrderState::WAITDELIVER;
}
public function cancelOrder(OrderStateMachine $orderStateMachine)
{
$orderStateMachine->setCurrentState(new CanceledOrder());
$orderStateMachine->setRefundState(RefundState::REFUNDED);
}
public function paidOrder(OrderStateMachine $orderStateMachine)
{
// can not do this
}
public function receiptOrder(OrderStateMachine $orderStateMachine)
{
// can not do this
}
public function afterOrder(OrderStateMachine $orderStateMachine)
{
$orderStateMachine->setAfterState(AfterSaleState::SUBMITTED);
}
}
// 省略 WaitReceiptOrder、ReceipedOrder 类
class OrderStateMachine
{
private IOrder $currentOrderState;
private $currentAfterSaleState;
private $currentRefundState;
public function __construct()
{
$this->currentOrderState = new ObligationOrder();
$this->currentAfterSaleState = AfterSaleState::DEFAULT;
$this->currentRefundState = RefundState::DEFALUT;
}
public function setCurrentState(IOrder $currentState)
{
$this->currentOrderState = $currentState;
}
public function getCurrentState()
{
$this->currentOrderState->getOrderState();
}
public function setAfterState($state)
{
$this->currentAfterSaleState = $state;
}
public function setRefundState($state)
{
$this->currentRefundState = $state;
}
public function cancel()
{
$this->currentOrderState->cancelOrder($this);
}
public function paid()
{
$this->currentOrderState->paidOrder($this);
}
public function receipt()
{
$this->currentOrderState->receiptOrder($this);
}
public function after()
{
$this->currentOrderState->afterOrder($this);
}
}
参考资料:《设计模式之美》