设计模式总结(模式篇)
设计模式
设计模式是针对软件开发中经常遇到的一些设计问题,总结出来的一套解决方案或者设计思路。大部分设计模式要解决的都是代码的可扩展性问题。设计模式相对于设计原则来说,没有那么抽象,而且大部分都不难理解,代码实现也并不复杂。
它们又可以分为三大类:创建型、结构型、行为型。
创建型设计模式主要解决“对象的创建”问题,结构型设计模式主要解决“类或对象的组合”问题,那行为型设计模式主要解决的就是“类或对象之间的交互”问题
1. 创建型
常用的有:
不常用的有:
2. 结构型
常用的有:
不常用的有:
3. 行为型
常用的有:
不常用的有:
单例模式
单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
要实现一个单例,需要关注的点无外乎下面几个:
构造函数需要是 private 访问权限的,这样才能避免外部通过 new 创建实例;
考虑对象创建时的线程安全问题;
考虑是否支持延迟加载;
考虑 getInstance() 性能是否高(是否加锁)。
参考单例模式
<?php
final class Singleton
{
/**
* @var Singleton
*/
private static $instance;
/**
* 通过懒加载获得实例(在第一次使用的时候创建)
*/
public static function getInstance(): Singleton
{
if (null === static::$instance) {
static::$instance = new static();
}
return static::$instance;
}
/**
* 不允许从外部调用以防止创建多个实例
* 要使用单例,必须通过 Singleton::getInstance() 方法获取实例
*/
private function __construct()
{
}
/**
* 防止实例被克隆(这会创建实例的副本)
*/
private function __clone()
{
}
/**
* 防止反序列化(这将创建它的副本)
*/
private function __wakeup()
{
}
}
尽管单例是一个很常用的设计模式,在实际的开发中,我们也确实经常用到它,但是,有些人认为单例是一种反模式(anti-pattern)
单例存在哪些问题?
1. 单例对 OOP 特性的支持不友好
2. 单例会隐藏类之间的依赖关系
3. 单例对代码的扩展性不友好
4. 单例对代码的可测试性不友好
5. 单例不支持有参数的构造函数
工厂模式
简单工厂(Simple Factory)
在下面这段代码中,我们根据配置文件的后缀(json、xml、yaml、properties),选择不同的解析器(JsonRuleConfigParser、XmlRuleConfigParser……),将存储在文件中的配置解析成内存对象 RuleConfig。
class RuleConfigSource {
public function load($path){
$fileExtension = $this->getFileExtension($path);
$parser = $this->createParser($fileExtension);
if(!$parse){
throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);
}
//文件中读取配置
$content = $parser->parse($path);
return $content;
}
private function getFileExtension($filePath){
//...解析文件名获取扩展名,比如rule.json,返回json
return "json";
}
//创建相应的文件解析器类
private function createParser($extension){
if($extension == 'json'){
return new JsonConfigParser();
}else if($extension == 'xml'){
return new XmlConfigParser();
}else if($extension == 'yaml'){
return new YamlConfigParser();
}else if($extension == 'properties'){
return new PropertiesConfigParser();
}
return null;
}
}
//定义配置解析接口类
interface ConfigParse(){
//定义解析方法
function parse();
}
//json文件配置解析类
class JsonConfigParser extends ConfigParse{
private public parse($path){
//解析json文件配置
return $fileContext;
}
}
//xml文件配置解析类
class XmlConfigParser extends ConfigParse{
private public parse($path){
//解析xml文件配置
return $fileContext;
}
}
....
$ruleConfigSource = new RuleConfigSource();
$content = $ruleConfigSource->load($path);
尽管简单工厂模式的代码实现中,有多处 if 分支判断逻辑,违背开闭原则,但权衡扩展性和可读性,这样的代码实现在大多数情况下(比如,不需要频繁地添加 parser,也没有太多的 parser)是没有问题的。
抽象工厂(Abstract Factory)
在不指定具体类的情况下创建一系列相关或依赖对象。 通常创建的类都实现相同的接口。 抽象工厂的客户并不关心这些对象是如何创建的,它只是知道它们是如何一起运行的。
还是根据以上的例子
//定义配置解析接口类
interface ConfigParse(){
//定义解析方法
function parse();
}
//json文件配置解析类
class JsonConfigParser extends ConfigParse{
private public parse($path){
//解析json文件配置
return $fileContext;
}
}
//xml文件配置解析类
class XmlConfigParser extends ConfigParse{
private public parse($path){
//解析xml文件配置
return $fileContext;
}
}
//工厂类
class ConfigParserFactory{
public function createJsonConfigParser(){
return new JsonConfigParser();
}
public function createXmlConfigParser(){
return new XmlConfigParser();
}
}
$parserFactory = new ConfigParserFactory();
$jsonContent = $parserFactory->createJsonConfigParser()->parse($path);
判断要不要使用工厂模式的最本质的参考标准。
封装变化:创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明。
代码复用:创建代码抽离到独立的工厂类之后可以复用。
隔离复杂性:封装复杂的创建逻辑,调用者无需了解如何创建对象。
控制复杂度:将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁。
建造者模式
参考建造者模式(Builder)
建造者模式是让建造者类来负责对象的创建工作。
假设我们定义一个商品类,商品类包含相关的参数,如商品名称,现价,原价,分类,商品图片
//定义建造者接口
interface BuilderInterface {
//创建类对象
public function createBuilder();
//设置默认参数
public function setParameter();
//获取类对象
public function getBuilder();
}
//定义建造者类
class builder{
public function build(BuilderInterface $builder){
$this->createBuilder();
$this->setParameter();
return $this->getBuilder();
}
}
//定义商品建造者类
class GoodsBuilder implements BuilderInterface{
private $goods;
public function createBuilder(){
$this->goods = new Goods();
}
public function setParameter(){
$this->goods->setPart('name','test');
$this->goods->setPart('price','10.00');
$this->goods->setPart('img','test.jpg');
}
public function getBuilder(){
return $this->goods;
}
}
//定义商品类并继承Setting抽象类
class Goods implements Setting{
}
//定义抽象类,用于设置类属性
abstract class Setting {
private $data = [];
public function setPart($key, $value){
$this->data[$key] = $value;
}
}
$goodsBuilder = new GoodsBuilder();
$goods = (new builder())->build($goodsBuilder);
如果存在下面情况中的任意一种,我们就要考虑使用建造者模式了。
我们把类的必填属性放到构造函数中,强制创建对象的时候就设置。如果必填的属性有很多,把这些必填属性都放到构造函数中设置,那构造函数就又会出现参数列表很长的问题。如果我们把必填属性通过 set() 方法设置,那校验这些必填属性是否已经填写的逻辑就无处安放了。
如果类的属性之间有一定的依赖关系或者约束条件,我们继续使用构造函数配合 set() 方法的设计思路,那这些依赖关系或约束条件的校验逻辑就无处安放了。
如果我们希望创建不可变对象,也就是说,对象在创建好之后,就不能再修改内部的属性值,要实现这个功能,我们就不能在类中暴露 set() 方法。构造函数配合 set() 方法来设置属性值的方式就不适用了。
原型模式
如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式来创建新对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型设计模式(Prototype Design Pattern),简称原型模式。
//定义接口原型类
interface Prototype {
public function copy();
}
//定义商品原型类
class GoodsPrototype implements Prototype{
private $name;
__constrcut($name = null){
$this->name = $name;
}
public function setName($name){
$this->name = $name;
}
public function getName(){
return $this->name;
}
public function copy(){
//深拷贝
//$serialize_obj = serialize($this);
//return unserialize($serialize_obj);
//浅拷贝
return clone $this;
}
}
$goods = new GoodsPrototype('test');
$cloneGoods = $goods->copy();
创建型设计模式总结
- 单例模式用来创建全局唯一的对象。
- 工厂模式用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。
- 建造者模式是用来创建复杂对象,可以通过设置不同的可选参数,“定制化”地创建不同的对象。
- 原型模式针对创建成本比较大的对象,利用对已有对象进行复制的方式进行创建,以达到节省创建时间的目的。
代理模式
代理模式在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。
代理模式最常用的一个应用场景就是,在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类中统一处理,让程序员只需要关注业务方面的开发。
以下我定义了一个商品类,create方法表示创建商品的业务逻辑,这时需要在创建商品的时候写下相关的日志或者扩展其他功能时,在不修改原创建商品业务逻辑的情况下。可以使用代理模式
//定义商品类
class Goods{
public function create(){
//新增创建商品业务逻辑
return $goodsId;
}
}
//定义商品代理类
class GoodsProxy extends Goods(){
public function create(){
parent::create();
//添加日志
Log::info();
}
}
桥接模式
在 GoF 的《设计模式》一书中,桥接模式被定义为:“将抽象和实现解耦,让它们可以独立变化。”在其他资料和书籍中,还有另外一种更加简单的理解方式:“一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。”
第一种 GoF 的理解方式,弄懂定义中“抽象”和“实现”两个概念,是理解它的关键。定义中的“抽象”,指的并非“抽象类”或“接口”,而是被抽象出来的一套“类库”,它只包含骨架代码,真正的业务逻辑需要委派给定义中的“实现”来完成。而定义中的“实现”,也并非“接口的实现类”,而是一套独立的“类库”。“抽象”和“实现”独立开发,通过对象之间的组合关系,组装在一起。
对于第二种理解方式,它非常类似我们之前讲过的“组合优于继承”设计原则,通过组合关系来替代继承关系,避免继承层次的指数级爆炸。
在使用laravel进行开发,在开发过程中会应用到队列,laravel的队列使用起来非常方便,我们需要选择队列的驱动方式,可以选择database,redis,beanstalkd等相关驱动,但是使用的job类还是一样的,这里就应用到了桥接模式
//定义队列接口
interface JobInterface{
public function run();
}
//定义数据库队列类
class DataBaseJob implements JobInterface{
public function run(){
//数据库驱动运行函数
}
}
//定义redis队列类
class RedisJob implements JobInterface{
public function run(){
//redis驱动运行函数
}
}
//定义使用队列服务抽象类
abstract class JobServer{
protected $jobRunning;
public function __constrcut(JobInterface $jobInterface){
$this->jobRunning = $jobInterface;
}
public function setJobRunning(JobInterface $jobInterface){
$this->jobRunning = $jobInterface;
}
public function running();
}
//定义队列类,通过继承抽象类获取具体驱动run()函数
class Job implements JobServer{
public function running(){
$this->jobRunning->run();
}
}
$job = new Job(new DataBaseJob());
$job->running();
装饰器模式
装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。它主要的作用是给原始类添加增强功能。这也是判断是否该用装饰器模式的一个重要的依据。除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这个应用场景,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接口。
假设在Web应用要在返回响应给用户之前执行一系列操作,例如验证用户及记录请求等。也许还需要将请求中的原始输入处理成某种数据结构。最后,还必须执行核心操作。
class RequestHelper{}
//定义抽象基类
abstract class ProcessRequest(){
abstract function process(RequestHelper $req);
}
//定义一个具体的组件
class MainProcess extends ProcessRequest{
function process(RequestHelper $req){
print __class__.":doing something useful with request \n";
}
}
//定义抽象装饰类
abstract class DecorateProcess extends ProcessRequest(){
protected $processRequest;
function __construct(ProcessRequest $pr){
$this->processRequest = $pr;
}
}
//定义具体的日志装饰类
class LogRequest extends DecorateProcess(){
function process(RequestHelper $req){
print __class__.":logging request \n";
$this->processRequest->process();
}
}
//定义具体的验证装饰类
class AuthenticateRequest extends DecorateProcess(){
function process(RequestHelper $req){
print __class__.":authenticating request \n";
$this->processRequest->process();
}
}
$mainProcess = $new MainProcess();
$process = new AuthenticateRequest(new LogRequest($mainProcess));
适配器模式
适配器模式是用来做适配,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。适配器模式有两种实现方式:类适配器和对象适配器。其中,类适配器使用继承关系来实现,对象适配器使用组合关系来实现。对于这个模式,有一个经常被拿来解释它的例子,就是 USB 转接头充当适配器,把两种不兼容的接口,通过转接变得可以一起工作。
以USB转接头为例子
//定义USB接口类
interface USBInterface{
//USB插头
public function plug();
//输入电压
public function voltage();
}
//定义安卓USB类
class Android implements USBInterface{
public function plug(){
return true;
}
public function voltage(){
return '220(V)';
}
}
//定义IOS接口
interface IOSUsbInterface{
public function IosPlug();
public function IosVoltage();
}
//定义IOS类
class IOS implements IOSUsbInterface(){
public function IosPlug(){
return true;
}
public function IosVoltage(){
return '200(V)';
}
}
//定义USB适配器类,适配USB接口类
class USBAdapter implements USBInterface(){
private $iosInterface;
__constrct(IOSUsbInterface $iosUsbInterface){
$this->iosInterface = $iosUsbInterface;
}
public function plug(){
$this->iosInterface->IosPlug();
return true;
}
public function voltage(){
$this->iosInterface->iosVoltage();
return '220(V)';
}
}
$ios = new IOS();
$adapter = new USBAdapter($ios);
$adapter->plug();
那在实际的开发中,什么情况下才会出现接口不兼容呢?
封装有缺陷的接口设计
统一多个类的接口设计
替换依赖的外部系统
兼容老版本接口
适配不同格式的数据
代理、桥接、装饰器、适配器 4 种设计模式的区别
代理、桥接、装饰器、适配器,这 4 种模式是比较常用的结构型设计模式。它们的代码结构非常相似。笼统来说,它们都可以称为 Wrapper 模式,也就是通过 Wrapper 类二次封装原始类。
尽管代码结构相似,但这 4 种设计模式的用意完全不同,也就是说要解决的问题、应用场景不同,这也是它们的主要区别。
代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。
桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。
装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。
适配器模式:适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。
门面模式
门面模式,也叫外观模式,英文全称是 Facade Design Pattern。在 GoF 的《设计模式》一书中,门面模式是这样定义的:
门面模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用。
//定义登录接口
interface AuthInterface(){
public function login();
public function logout();
}
//定义购物车接口
interface CartInterface(){
public function getCartItem();
public function deleteCartItem();
}
//定义登录授权类
class Auth implements AuthInterface(){
public function login(){
//登录相关操作
}
public function logout(){
//登出相关操作
}
}
class Cart implements CartInterface(){
public function getCartItem(){
//获取用户购物车操作
}
public function deleteCartItem(){
//删除用户购物车操作
}
}
class Facade{
private $authInterface;
private $cartInterface;
public function __construct(AuthInterface $authInterface,CartInterface $cartInterface){
$this->authInterface = $authInterface;
$this->cartInterface = $cartInterface;
}
public function getUserCart(){
$this->authInterface->login();
$this->cartInterface->getCartItem();
}
}
$facade = new Facade(new Auth(),new Cart());
$facade->getUserCart();
接口粒度设计得太大,太小都不好。太大会导致接口不可复用,太小会导致接口不易用。在实际的开发中,接口的可复用性和易用性需要“微妙”的权衡。针对这个问题,我的一个基本的处理原则是,尽量保持接口的可复用性,但针对特殊情况,允许提供冗余的门面接口,来提供更易用的接口。
组合模式
在 GoF 的《设计模式》一书中,组合模式是这样定义的:
将一组对象组织(Compose)成树形结构,以表示一种“部分 - 整体”的层次结构。组合让客户端(在很多设计模式书籍中,“客户端”代指代码的使用者。)可以统一单个对象和组合对象的处理逻辑。
假设需要构建整个公司的人员架构图(部门、子部门、员工的隶属关系),并且提供接口计算出部门的薪资成本(隶属于这个部门的所有员工的薪资和)。
//定义基础员工接口
interface HumanInterface {
public function calculSalary();
}
//定义员工类
class Employee implements HumanInterface{
protected $id;
protected $salary;
public function __construct($id,$salary){
//员工id和员工薪资
$this->id = $id;
$this->salary = $salary;
}
//计算员工薪资
public function calculSalary(){
return $salary;
}
}
//定义部门类
class Department implements HumanInterface{
protected $id;
protected $employee_ids = [];
public function __construct($id){
//部门id
$this->id = $id;
}
public function addEmployee(Employee $employee){
$this->employee_ids[] = $employee;
}
//计算部门薪资
public function calculSalary(){
$salary = 0;
foreach($this->employee_ids as $employee){
$salary += $employee->calculSalary();
}
return $salary;
}
}
$department = new Department(1);
$employee1 = new Employee(1,5000);
$employee2 = new Employee(2,7000);
$employee3 = new Employee(3,10000);
$department->addEmployee($employee1);
$department->addEmployee($employee2);
$department->addEmployee($employee3);
$department->calculSalary();
享元模式
所谓“享元”,顾名思义就是被共享的单元。享元模式的意图是复用对象,节省内存,前提是享元对象是不可变对象。
当一个系统中存在大量重复对象的时候,如果这些重复的对象是不可变对象,我们就可以利用享元模式将对象设计成享元,在内存中只保留一份实例,供多处代码引用。这样可以减少内存中对象的数量,起到节省内存的目的。实际上,不仅仅相同对象可以设计成享元,对于相似对象,我们也可以将这些对象中相同的部分(字段)提取出来,设计成享元,让这些大量相似对象引用这些享元。
定义中的“不可变对象”指的是,一旦通过构造函数初始化完成之后,它的状态(对象的成员变量或者属性)就不会再被修改了。所以,不可变对象不能暴露任何 set() 等修改内部状态的方法。之所以要求享元是不可变对象,那是因为它会被多处代码共享使用,避免一处代码对享元进行了修改,影响到其他使用它的代码。
//抽象享元角色
interface Flyweight{
function show();
}
//共享的具体享元角色
class ConcreteFlyweight implements Flyweight{
private $state;
function __construct($state){
$this->state = $state;
}
function show(){
return $this->state;
}
}
//享元工厂模式
class FlyweightFactory{
private $flyweights = array();
function getFlyweight($state){
if(!isset($this->flyweights[$state])){
$this->flyweights[$state]=new ConcreteFlyweight($state);
}
return $this->flyweights[$state];
}
}
$flyweightFactory = new FlyweightFactory();
$flyweightOne = $flyweightFactory->getFlyweight("state A");
享元模式的代码实现非常简单,主要是通过工厂模式,在工厂类中,通过一个数组来缓存已经创建好的享元对象,以达到复用的目的。
观察者模式
观察者模式(Observer Design Pattern)也被称为发布订阅模式(Publish-Subscribe Design Pattern)。在 GoF 的《设计模式》一书中,它的定义是这样的:
在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。
一般情况下,被依赖的对象叫作被观察者(Observable),依赖的对象叫作观察者(Observer)。不过,在实际的项目开发中,这两种对象的称呼是比较灵活的,有各种不同的叫法,比如:Subject-Observer、Publisher-Subscriber、Producer-Consumer、EventEmitter-EventListener、Dispatcher-Listener。不管怎么称呼,只要应用场景符合刚刚给出的定义,都可以看作观察者模式。
对于PHP来说,PHP内置提供了两个接口来供外部应用区实现这个模式。
SplSubject 接口,它代表着被观察的对象,其结构:
interface SplSubject{
publicfunction attach(SplObserver $observer);
publicfunction detach(SplObserver $observer);
publicfunction notify();
}
SplObserver 接口,它代表着充当观察者的对象,其结构:
interface SplObserver{
public function update(SplSubject $subject);
}
//定义被观察者类
class subject implements SplSubject{
private $observers , $value;
public function __construct(){
$this->observers =array();
}
public function attach(SplObserver $observer){
$this->observers[] = $observer;
}
public function detach(SplObserver $observer){
if($idx = array_search($observer, $this->observers,true)) {
unset($this->observers[$idx]);
}
}
public function notify(){
foreach($this->observers as $observer){
$observer->update($this);
}
}
public function setValue($value){
$this->value = $value;
$this->notify();
}
public function getValue(){
return$this->value;
}
}
//定义观察类
class observer implements SplObserver{
public function update(SplSubject $subject){
echo 'The new state ofsubject'.$subject->getValue();
}
}
$subject = new subject();
$observer = new observer();
$subject->attach($observer);
$subject->setValue(5);
模板模式
模板模式,全称是模板方法设计模式,英文是 Template Method Design Pattern。在 GoF 的《设计模式》一书中,它是这么定义的:
模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
这里的“算法”,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式名字的由来。
//定义模版抽象类
abstract class template{
final public function templateMethod(){
$this->method1();
$this->method2();
}
//继承类必须实现该方法
abstract protected function method1();
abstract protected function method2();
}
//定义子类继承抽象模版类
class ConcreteClass extends template(){
//实现模版类的方法
protected function method1(){
//do something
}
protected function method2(){
//do something
}
}
$concreate = new ConcreteClass();
$concreate->templateMethod();
模板模式有两大作用:复用和扩展。其中,复用指的是,所有的子类可以复用父类中提供的模板方法的代码。扩展指的是,框架通过模板模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能。
策略模式
策略模式,英文全称是 Strategy Design Pattern。在 GoF 的《设计模式》一书中,它是这样定义的:
定义一组算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。
//定义策略接口
interface Strategy{
function AlgorithmInterface();
}
//定义A策略算法类
class ConcreteStrategyA implements Strategy{
function AlgorithmInterface(){
echo "算法A";
}
}
//定义B策略算法类
class ConcreteStrategyB implements Strategy{
function AlgorithmInterface(){
echo "算法B";
}
}
//定义C策略算法类
class ConcreteStrategyC implements Strategy{
function AlgorithmInterface(){
echo "算法C";
}
}
//定义执行环境上下文。
class Context{
private $strategy;
function __construct(Strategy $s){
$this->strategy = $s;
}
function ContextInterface(){
$this->strategy->AlgorithmInterface();
}
}
$strategyA = new ConcreteStrategyA();
$context = new Context($strategyA);
$context->ContextInterface();
这个模式和简单工厂非常类似,那么他们的区别呢?
- 工厂相关的模式属于创建型模式,顾名思义,这种模式是用来创建对象的,返回的是new出来的对象。要调用对象的什么方法是由客户端来决定的
- 而策略模式属性行为型模式,通过执行上下文,将要调用的函数方法封装了起来,客户端只需要调用执行上下文的方法就可以了
策略模式主要的作用还是解耦策略的定义、创建和使用,控制代码的复杂度,让每个部分都不至于过于复杂、代码量过多。除此之外,对于复杂代码来说,策略模式还能让其满足开闭原则,添加新策略的时候,最小化、集中化代码改动,减少引入 bug 的风险。
责任链模式
责任链模式的英文翻译是 Chain Of Responsibility Design Pattern。在 GoF 的《设计模式》中,它是这么定义的:
将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。
在责任链模式中,多个处理器(也就是刚刚定义中说的“接收对象”)依次处理同一个请求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作责任链模式。
//定义基础责任链抽象类
abstract class Handler
{
protected $successor;
public function setSuccessor($successor)
{
$this->successor = $successor;
}
abstract public function HandleRequst($request);
}
//三个责任链条的具体实现,主要功能是判断传入的数据类型,如果是数字由第一个类处理,如果是字符串,则第二个类处理。如果是其他类型,第三个类统一处理。
class ConcreteHandler1 extends Handler
{
public function HandleRequst($request)
{
if (is_numeric($request)) {
return '请求参数是数字:' . $request;
} else {
return $this->successor->HandleRequst($request);
}
}
}
class ConcreteHandler2 extends Handler
{
public function HandleRequst($request)
{
if (is_string($request)) {
return '请求参数是字符串:' . $request;
} else {
return $this->successor->HandleRequst($request);
}
}
}
class ConcreteHandler3 extends Handler
{
public function HandleRequst($request)
{
return '我也不知道请求参数是啥了,你猜猜?' . gettype($request);
}
}
$handle1 = new ConcreteHandler1();
$handle2 = new ConcreteHandler2();
$handle3 = new ConcreteHandler3();
$handle1->setSuccessor($handle2);
$handle2->setSuccessor($handle3);
$requests = [22, 'aaa', 55, 'cc', [1, 2, 3], null, new stdClass];
foreach ($requests as $request) {
echo $handle1->HandleRequst($request) . PHP_EOL;
}
- 责任链非常适合的一种场景,就是对请求参数进行逐层过滤,就像我们工作时使用钉钉之类的办公软件。当需要提加班或者休假申请时,那一层层的审批流程就是对这个模式最完美的解释
- 我们可以拦截请求,直接返回,也可以对请求内容进行完善修改交给下一个类来进行处理,但至少有一个类是要返回结果的。
- 请求不一定都会被处理,也有可能完全不处理就返回或者传递给下一个处理类来进行处理
状态模式
状态模式当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。
//状态接口
interface IState
{
function WriteCode(Work $w);
}
//上午工作状态
class AmState implements IState
{
public function WriteCode(Work $w)
{
if($w->hour<=12)
{
echo "当前时间:{$w->hour}点,上午工作;犯困,午休。<br/>";
}
else
{
$w->SetState(new PmState());
$w->WriteCode();
}
}
}
//下午工作状态
class PmState implements IState
{
public function WriteCode(Work $w)
{
if($w->hour<=17)
{
echo "当前时间:{$w->hour}点,下午工作状态还不错,继续努力。<br/>";
}
else
{
$w->SetState(new NightState());
$w->WriteCode();
}
}
}
//晚上工作状态
class NightState implements IState
{
public function WriteCode(Work $w)
{
if($w->IsDone)
{
$w->SetState(new BreakState());
$w->WriteCode();
}
else
{
if($w->hour<=21)
{
echo "当前时间:{$w->hour}点,加班哦,疲累至极。<br/>";
}
else
{
$w->SetState(new SleepState());
$w->WriteCode();
}
}
}
}
//休息状态
class BreakState implements IState
{
public function WriteCode(Work $w)
{
echo "当前时间:{$w->hour}点,下班回家了。<br/>";
}
}
//睡眠状态
class SleepState implements IState
{
public function WriteCode(Work $w)
{
echo "当前时间:{$w->hour}点,不行了,睡着了。<br/>";
}
}
//工作状态
class Work
{
private $current;
public function Work()
{
$this->current = new AmState();
}
public $hour;
public $isDone;
public function SetState(IState $s)
{
$this->current = $s;
}
public function WriteCode()
{
$this->current->WriteCode($this);
}
}
$emergWork = new Work();
$emergWork->hour = 9;
$emergWork->WriteCode();
$emergWork->hour = 10;
$emergWork->WriteCode();
$emergWork->hour = 13;
$emergWork->WriteCode();
$emergWork->hour=14;
$emergWork->WriteCode();
$emergWork->hour = 17;
$emergWork->WriteCode();
$emergWork->IsDone = true;
$emergWork->IsDone = false;
$emergWork->hour = 19;
$emergWork->WriteCode();
$emergWork->hour = 22;
$emergWork->WriteCode();
适用场景:
1.一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
2.一个操作中含有庞大的多分支结构,并且这些分支决定于对象的状态。
迭代器模式
迭代器:类继承PHP的Iterator接口,批量操作。
1. 迭代器模式,在不需要了解内部实现的前提下,遍历一个聚合对象的内部元素。
2. 相比传统的编程模式,迭代器模式可以隐藏遍历元素的所需操作。
接口Iterator
- current() 返回当前元素
- key() 返回当前元素的键
- next() 向前移动到下一个元素
- rewind() 返回到迭代器的第一个元素
class AllUser implements \Iterator
{
protected $index = 0;
protected $data = [];
public function __construct()
{
$link = mysqli_connect('192.168.0.91', 'root', '123', 'xxx');
$rec = mysqli_query($link, 'select id from doc_admin');
$this->data = mysqli_fetch_all($rec, MYSQLI_ASSOC);
}
//1 重置迭代器
public function rewind()
{
$this->index = 0;
}
xxx
//2 验证迭代器是否有数据
public function valid()
{
return $this->index < count($this->data);
}
//3 获取当前内容
public function current()
{
$id = $this->data[$this->index];
return User::find($id);
}
//4 移动key到下一个
public function next()
{
return $this->index++;
}
//5 迭代器位置key
public function key()
{
return $this->index;
}
}
//实现迭代遍历用户表
$users = new AllUser();
//可实时修改
foreach ($users as $user){
$user->add_time = time();
$user->save();
}
迭代器模式封装集合内部的复杂数据结构,开发者不需要了解如何遍历,直接使用容器提供的迭代器即可;
迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一;
迭代器模式让添加新的遍历算法更加容易,更符合开闭原则。除此之外,因为迭代器都实现自相同的接口,在开发中,基于接口而非实现编程,替换迭代器也变得更加容易。
访问者模式
访问者模式的英文翻译是 Visitor Design Pattern。在 GoF 的《设计模式》一书中,它是这么定义的:
允许一个或者多个操作应用到一组对象上,解耦操作和对象本身。
案例参考PHP设计模式——访问者模式
/*男人这本书的内容要比封面吸引人;女人这本书的封面通常比内容更吸引人
男人成功时,背后多半有一个伟大的女人;女人成功时,背后多半有一个失败的男人
男人失败时,闷头喝酒,谁也不用劝;女人失败时,眼泪汪汪,谁也劝不了
男人恋爱时,凡事不懂也要装懂;女人恋爱时,遇事懂也要装作不懂*/
//抽象状态
abstract class State
{
protected $state_name;
//得到男人反应
public abstract function GetManAction(VMan $elementM);
//得到女人反应
public abstract function GetWomanAction(VWoman $elementW);
}
//抽象人
abstract class Person
{
public $type_name;
public abstract function Accept(State $visitor);
}
//成功状态
class Success extends State
{
public function __construct()
{
$this->state_name="成功";
}
public function GetManAction(VMan $elementM)
{
echo "{$elementM->type_name}:{$this->state_name}时,背后多半有一个伟大的女人。<br/>";
}
public function GetWomanAction(VWoman $elementW)
{
echo "{$elementW->type_name} :{$this->state_name}时,背后大多有一个不成功的男人。<br/>";
}
}
//失败状态
class Failure extends State
{
public function __construct()
{
$this->state_name="失败";
}
public function GetManAction(VMan $elementM)
{
echo "{$elementM->type_name}:{$this->state_name}时,闷头喝酒,谁也不用劝。<br/>";
}
public function GetWomanAction(VWoman $elementW)
{
echo "{$elementW->type_name} :{$this->state_name}时,眼泪汪汪,谁也劝不了。<br/>";
}
}
//恋爱状态
class Amativeness extends State
{
public function __construct()
{
$this->state_name="恋爱";
}
public function GetManAction(VMan $elementM)
{
echo "{$elementM->type_name}:{$this->state_name}时,凡事不懂也要装懂。<br/>";
}
public function GetWomanAction(VWoman $elementW)
{
echo "{$elementW->type_name} :{$this->state_name}时,遇事懂也要装作不懂。<br/>";
}
}
//男人
class VMan extends Person
{
function __construct()
{
$this->type_name="男人";
}
public function Accept(State $visitor)
{
$visitor->GetManAction($this);
}
}
//女人
class VWoman extends Person
{
public function __construct()
{
$this->type_name="女人";
}
public function Accept(State $visitor)
{
$visitor->GetWomanAction($this);
}
}
//对象结构
class ObjectStruct
{
private $elements=array();
//增加
public function Add(Person $element)
{
array_push($this->elements,$element);
}
//移除
public function Remove(Person $element)
{
foreach($this->elements as $k=>$v)
{
if($v==$element)
{
unset($this->elements[$k]);
}
}
}
//查看显示
public function Display(State $visitor)
{
foreach ($this->elements as $v)
{
$v->Accept($visitor);
}
}
}
$os = new ObjectStruct();
$os->Add(new VMan());
$os->Add(new VWoman());
//成功时反应
$ss = new Success();
$os->Display($ss);
//失败时反应
$fs = new Failure();
$os->Display($fs);
//恋爱时反应
$ats=new Amativeness();
$os->Display($ats);
备忘录模式
备忘录模式,也叫快照(Snapshot)模式,英文翻译是 Memento Design Pattern。在 GoF 的《设计模式》一书中,备忘录模式是这么定义的:
在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。
编写一个小程序,可以接收命令行的输入。用户输入文本时,程序将其追加存储在内存文本中;用户输入“:list”,程序在命令行中输出内存文本的内容;用户输入“:undo”,程序会撤销上一次输入的文本,也就是从内存文本中将上次输入的文本删除掉。
如下所示:
>hello
>:list
hello
>world
>:list
helloworld
>:undo
>:list
hello
//定义文本输入类
public class InputText{
private $text;
//获取文本信息
public function getText(){
return $this->text;
}
//追加文本信息
public function appendText($string){
$this->text = $this->text.$string
}
//创建快照
public function createSnapshot(){
return new Snapshot($this->text);
}
//重置为快照数据
public function restoreSnapshot(Snapshot $snapshot){
$this->text = $snapshot->getText();
}
}
//定义快照类
public class Snapshot{
private String text;
public function __construct($text) {
this.text = text;
}
//返回快照信息
public function getText() {
return this.text;
}
}
//定义快照管理类
public class SnapshotHolder{
private $snapshots = array();
//返回最新快照
public function popSnapshot(){
return array_pop($this->snapshots);
}
//添加快照
public function pushSnapshot(Snapshot $snapshot){
array_push($this->snapshots,$snapshot);
}
}
$inputText = new InputText();
$snapshotHolder = new SnapshotHolder();
while(读取终端输入){
//$line为终端输入值
if($line == ':list'){
echo $inputText->getText;
}else if($line == ':undo'){
$snapshot = $snapshotHolder->popSnapshot();
$inputText->restoreSnapshot($snapshot);
}else{
$snapshotHolder->pushSnapshot($inputText->createSnapshot());
$inputText->appendText($line);
}
}
备忘录模式也叫快照模式,具体来说,就是在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。这个模式的定义表达了两部分内容:一部分是,存储副本以便后期恢复;另一部分是,要在不违背封装原则的前提下,进行对象的备份和恢复。
备忘录模式的应用场景也比较明确和有限,主要是用来防丢失、撤销、恢复等。它跟平时我们常说的“备份”很相似。两者的主要区别在于,备忘录模式更侧重于代码的设计和实现,备份更侧重架构设计或产品设计。
命令模式
命令模式的英文翻译是 Command Design Pattern。在 GoF 的《设计模式》一书中,它是这么定义的:
命令模式将请求(命令)封装为一个对象,这样可以使用不同的请求参数化其他对象(将不同请求依赖注入到其他对象),并且能够支持请求(命令)的排队执行、记录日志、撤销等(附加控制)功能。
落实到编码实现,命令模式用的最核心的实现手段,是将函数封装成对象。当我们把函数封装成对象之后,对象就可以存储下来,方便控制执行。所以,命令模式的主要作用和应用场景,是用来控制命令的执行,比如,异步、延迟、排队执行命令、撤销重做命令、存储命令、给命令记录日志等等,这才是命令模式能发挥独一无二作用的地方。
//定义执行命令接口
interface ICommand {
function onCommand($name, $args);
}
//定义命令执行调度类
class CommandChain {
private $_commands = array();
public function addCommand($cmd) {
$this->_commands []= $cmd;
}
public function runCommand($name, $args) {
foreach($this->_commands as $cmd) {
if ($cmd->onCommand($name, $args)) return;
}
}
}
//定义User命令类
class UserCommand implements ICommand {
public function onCommand($name, $args) {
if ($name != 'addUser') return false;
echo("UserCommand handling 'addUser'\n");
return true;
}
}
//定义邮件类
class MailCommand implements ICommand {
public function onCommand($name, $args) {
if ($name != 'mail') return false;
echo("MailCommand handling 'mail'\n");
return true;
}
}
$cc = new CommandChain();
$cc->addCommand(new UserCommand());
$cc->addCommand(new MailCommand());
$cc->runCommand('addUser', null);
$cc->runCommand('mail', null);
中介模式
中介模式的英文翻译是 Mediator Design Pattern。在 GoF 中的《设计模式》一书中,它是这样定义的:
中介模式定义了一个单独的(中介)对象,来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互。
//中介者接口:可以是公共的方法,如Change方法,也可以是小范围的交互方法。
//同事类定义:比如,每个具体同事类都应该知道中介者对象,也就是每个同事对象都会持有中介者对象的引用,这个功能可定义在这个类中。
//抽象国家
abstract class Country
{
protected $mediator;
public function __construct(UnitedNations $_mediator)
{
$this->mediator = $_mediator;
}
}
//具体国家类
//美国
class USA extends Country
{
function __construct(UnitedNations $mediator)
{
parent::__construct($mediator);
}
//声明
public function Declared($message)
{
$this->mediator->Declared($message,$this);
}
//获得消息
public function GetMessage($message)
{
echo "美国获得对方消息:$message<br/>";
}
}
//中国
class China extends Country
{
public function __construct(UnitedNations $mediator)
{
parent::__construct($mediator);
}
//声明
public function Declared($message)
{
$this->mediator->Declared($message, $this);
}
//获得消息
public function GetMessage($message)
{
echo "中方获得对方消息:$message<br/>";
}
}
//抽象中介者
//抽象联合国机构
abstract class UnitedNations
{
//声明
public abstract function Declared($message,Country $colleague);
}
//联合国机构
class UnitedCommit extends UnitedNations
{
public $countryUsa;
public $countryChina;
//声明
public function Declared($message, Country $colleague)
{
if($colleague==$this->countryChina)
{
$this->countryUsa->GetMessage($message);
}
else
{
$this->countryChina->GetMessage($message);
}
}
}
$UNSC = new UnitedCommit();
$c1 = new USA($UNSC);
$c2 = new China($UNSC);
$UNSC->countryChina = $c2;
$UNSC->countryUsa =$c1;
$c1->Declared("姚明的篮球打的就是好");
$c2->Declared("谢谢。");
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: