opmvpc / patrons
设计模式示例
Requires
- php: ^7.4
- bartlett/umlwriter: 2.0.0-rc.2
Requires (Dev)
- friendsofphp/php-cs-fixer: ^2.16
- phpunit/phpunit: ^9.3
- vimeo/psalm: ^3.11
This package is auto-updated.
Last update: 2024-09-08 07:45:42 UTC
README
PHP中实现设计模式的示例。
该包使用psalm静态代码分析器编写。代码尽可能使用php 7.4提供的新特性。代码覆盖率达到100%。
类图UML的不同图是从代码生成的。该包不表示类之间的关系。
⚙ 要求
- php ^7.4
- composer
用于生成类图UML
- php imagick扩展
- graphviz安装(+ windows的PATH环境变量)
🛠 安装
您可以通过composer安装该包
git clone composer update
📚 目录
注意,内容正在建设中!
创建型
单例
强制一个类只能实例化一次。要实现此模式,我们调用静态属性并将构造函数的可见性设置为private。
在PHP中,我们将确保不能使用魔术方法__construct()、__clone()和__wakeup()。
单例经典
问题:静态变量$instance被所有扩展Singleton的类共享
单例通用
问题:PHP中还没有泛型类型。我们可以使用Psalm的@template T标签来模拟。不幸的是,它不能用于静态属性。
解决方案:我们可以将实例存储在一个数组中。
https://blog.cotten.io/how-to-screw-up-singletons-in-php-3e8c83b63189
如何向一个类添加Singleton功能。
解决方案:我们可以使用反射来重新创建类,并为其添加单例功能。SingletonFactory的创建示例。
https://patrick-assoa-adou.medium.com/a-generic-php-singleton-the-long-of-it-661b1ead3981
解决方案:或者通过singletonize函数实现
https://patrick-assoa-adou.medium.com/a-generic-php-singleton-1985f17eeb6f
注意:有些人认为依赖注入比Singleton更好,因为它被认为是一种反模式
在大型项目中使用模式
常用用法
- DAO(数据库访问)
- Logger
- 应用锁文件
示例
- Laravel ServiceContainer
用于多语言字典的模式
可以实例化多个扩展DictSingleton的类。这违反了Singleton模式的基本原则。
作用:我们仍然可以确保不同的字典只能实例化一次,并且不允许修改这些对象。
抽象工厂
允许使用类似的API创建不同的对象。例如,这里使用热力学和电动汽车工厂。
使用
- 当需要管理多个产品系列时
- 根据操作系统使用不同的文件读取器
原型
克隆对象而不是通过"new ObjectClass()"操作创建对象。当需要多个相似对象时,这通常更经济,并且往往需要更少的代码。注意,需要实现clone方法以进行深拷贝。
用途
- 游戏视频级别创建的图形界面。
- CAD软件?
练习:使用一个抽象类,它使用数组来存储属性的值,并提供一个clone方法来复制属性。
PHP奖励:我们实现了ArrayAccess接口,以便使用与数组相同的语法访问我们对象中的$attributes属性的值(例如:$floor['floorId'])。
结构型
代理
允许通过仅操作一个将负责创建和管理昂贵对象的代理对象来隐藏昂贵资源对象。
用途
- 我们需要控制对对象的访问
- 我们需要在访问对象时添加功能。
- 访问权限
- 代理可以用作其他对象的替身。
代理类型
- 保护代理(访问资源控制)
- 虚拟代理(例如占位符视频)
- 远程代理(本地对象,它隐藏对服务器的远程调用)
来源
https://zh.wikipedia.org/wiki/Proxy_pattern
https://www.geeksforgeeks.org/zh/proxy-design-pattern/
装饰器
允许动态地向对象添加功能。是创建子类的替代方案。
比较代理和装饰器
它们的结构相似。它们都使用接口来创建具有兼容和链式方法的对象。
代理模式的目标是“代表”或“代替”另一个对象,而装饰器模式的目标是在执行时向对象添加新功能。
ScrollDecorator和3DDecorator的问题
3D效果应用于窗口,但未应用于滚动条。
解决方案
- 强制类实例只能被引用一次。(我不确定我是否正确理解了😥)
https://zh.wikipedia.org/wiki/Decorator_pattern#Motivation
组合
练习:使用组合模式管理文件。
- 创建文件和目录
- 删除文件和目录
- 移动文件和目录
- 复制文件和目录
- 检查文件和目录是否相同/具有相同的结构
- 检查两个文件是否具有相同的结构
解决方案
- 添加一个FileManager类,它可以简化API
- 一些额外的方法(find($name), goTo($path))
- 注意,磁盘写入尚未实现。为此,只需完成某些方法即可。
练习
如何使用组合模式使用访问者模式?
待办事项
如何使用组合模式使用PlayerRole模式来实现将叶子/container转换为组件的转换?
待办事项
PHP改进
我们可以在Component类中使用接口
- (SplObjectStorage)[https://php.ac.cn/manual/fr/class.splobjectstorage.php] 在Component类中存储子对象
- (RecursiveIterator)[https://php.ac.cn/manual/fr/class.recursiveiterator.php] 用于在子对象中导航
- (可寻址迭代器)[https://php.ac.cn/manual/zh/class.seekableiterator.php] 要找到子项(逻辑已经写好,只需更改方法名称)或在 FileManager 中从根目录遍历树。
外观
目标
- 为子系统提供一组接口提供一个统一的接口。
- 门面定义了一个高级接口,使得子系统更容易使用。
- 隐藏功能实现的“脏”细节。
桥接
允许解耦接口实现,并解耦我们的代码与接口。这样我们可以在运行时更改实现。
示例:一个可以根据使用的 renderer 实现渲染 Html 或 Json 的响应系统。
练习
添加 TailwindCSS 实现,并观察对架构的影响
- 没有明显影响。我们只需创建一个新的类来实现 RendererImplementation
RendererImplementation 是一个接口。如何使用抽象类(请参阅模板模式)来改进它
- 待办事项
如何让 Abstract Factory 有助于桥接模式?
我们可以使用抽象工厂来根据每个工厂的需求选择使用哪种实现。
享元
- 避免在内存中创建大量相似的对象
示例
可以应用于文本的不同样式的文本处理。我们希望避免为每个字母创建一个对象,例如。
如何?
我们使用工厂来创建 Lettre 对象。如果对象尚未实例化,则工厂将创建一个对象字母;否则,它将使用现有对象的引用。这允许 Lettre 对象对它们被使用的上下文具有透明性(并且可以在多个不同的上下文中重用)。至于字母的样式,我们将将其存储在一个容器对象(例如 Mot)中。
行为型
观察者
用途
- 当一个对象的变化要求改变其他对象时
- 当必须能够通知其他对象而不与这些对象耦合时(广播通知)
- 允许根据事件或另一个类中的数据更改更新类
- 允许将一个大类分解为多个类,这些类将是分解类的观察者。
在 PHP 中,我们将使用 SplObserver 和 SplSubject 接口(PHP 中的语言包含)来专门实现观察者模式。
我们还将使用 SplObjectStorage 数据结构(PHP 中的语言包含)来存储 Observed 类中的观察者。
练习
通过避免不必要的更新(例如,数据没有更改)来改进模式
- 在 Observed 类的 setAttribute() 方法中添加检查
- 如果观察者在观察者中保存前一个状态,则可以查看状态的变化
使用观察者模式与状态模式一起使用,以便类能够意识到某些对象的状态转换。
例如:对象 x 必须通知对象 y 当前状态的每次更改。
待办事项
拦截器
-
在执行期间添加或删除功能(插件系统)
-
在执行期间修改功能
-
自动触发功能(插件安装)
-
- 可扩展性
-
- 灵活性
-
- 分离关注点
-
- 可重用性
-
- 副作用
-
- 处理递归事件(例如,TCP 发送的消息被机制捕获并重新发送...无限循环)的困难
-
- 难以预测框架的未来扩展
模板
目标
- 定义一个算法的框架,以简化具有共同行为的多个类的因子化。
如何?
- 定义一个具有共同方法实现的抽象类
- 具体化该类的类将重新定义具有不同行为的方法的行为
状态
可以将有限状态机抽象为类。
- 使用定义不同转换的接口。
- 通过一个具体类实现接口,该类将使用其他对象来执行操作(类似于代理模式)。
- 一个抽象类,其方法会抛出异常。
- 这个抽象类通过一个按状态实现的具体类来实现,其中只重写了必要的方法(这样如果转换不可行,会自动抛出异常)。
- 具体类拥有一个当前对象(一个状态),其方法(转换)将在其上调用。
优点
- 有效地组织代码结构。
- 使转换明确。
策略
目标
- 定义一组算法,封装它们,并使它们可交换。
- 该模式允许
如何
- 定义算法类之间的公共接口。
优点
- 分解技术。
- 根据上下文替代子类。
- 消除条件语句。
缺点
- 客户端必须了解可用的不同策略以及它们之间的差异,包括它们的优点和缺点。
- 所有策略都使用相同的接口,无论实现它的不同算法的复杂度如何。例如,不是所有策略都使用所有参数。
备忘录
目标
在不违反封装原则的情况下,捕获并外部化对象的内部状态,以便在需要时恢复该状态。
如何
- 声明一个没有通过包含状态的对象实现的方法的接口。
- 使用另一个专门为此目的设计的类来管理对象。
- 应用程序拥有一个对象不同状态的集合,因此可以在需要时回到以前的状态。
命令
目标
- 封装命令调用。
- 允许 undo/redo 功能。
- 创建由多个命令组成的宏命令。
示例
- 文本编辑器中的 undo/redo。
- 命令行界面工具。
好处
- 将调用命令的对象与知道如何生成预期结果的对象解耦。
- 命令成为可以扩展或序列化的对象。
- 可以将命令组装成复合命令。
- 可以轻松添加新的命令,只需添加一个新类。
- 是简化事务过程实现的一个有趣的概念。
解释器
允许定义语言的运算语义。为此,我们将用表示抽象语法树的类集合来表示我们的抽象语法树。Node 类具有一个 "interpret" 方法,它定义了抽象语法树元素的运算语义。此 interpret 方法修改执行环境(上下文)。该模式在语法简单且效率不是主要考虑因素时效果更好。如果是这种情况,我们更喜欢创建一个完整的编译器。 
访问者
目标
- 允许解耦对数据结构进行的操作。
- 解耦不相关的任务。
如何?
使用一个可以按所需处理方式(例如 X86Visitor、DocHtmlVisitor...)实现的抽象访问者类。
优点
- 易于添加新功能。
- 使用累积器(例如符号表)。如果没有该模式,累积器应该传递给所有方法或使用全局变量。
缺点
- 当添加 Node 类型时,必须在每个访问者中添加用于处理的方法(除非使用每个访问者具有默认行为的抽象类,如 ANTLR)。
- 打破封装(需要将所有方法公开,用户可能需要全部内容)
- AST的遍历由其表示的程序决定。我们不选择遍历的方式。
配置
目标
优点
- 隐藏并提取对象配置的逻辑
- 简化文档编写
- 能够观察配置
改进
待办事项
- 按类别划分(行为、创建、结构)
- 为单例模式添加clone和wakeup方法
- 根据代码生成类图
- 创建目录
- 将课程中学习的模式添加进来(+测试、描述、问题回答和类图、更新目录)
- 单例
- 抽象工厂
- 代理
- 装饰器
- 组合
- 原型
- 桥接
- 观察者
- 拦截器
- 享元
- 状态
- 访问者
- 模板
- 策略
- 配置
- 命令
- 外观
- 备忘录
- Playerrole
- 解释器
参考文献
PHP设计模式(包含许多模式及其代码示例和类图)
https://designpatternsphp.readthedocs.io/en/latest/README.html
测试
composer test
Psalm(静态分析)
composer psalm
生成类图
php generateUmlCD.php
更改日志
请参阅 更改日志 了解最近的变化。
贡献
请参阅 贡献指南 了解详细信息。
安全漏洞
请审查 我们的安全策略 了解如何报告安全漏洞。
鸣谢
许可
MIT许可(MIT)。请参阅 许可文件 了解更多信息。



















