设计模式学习总结(笔记)

我是那种非科班出身的程序员,很少完整阅读成本的技术类书籍,感觉很多书的内容都有太多罗嗦的地方。但是最近看到一本书《Guide to PHP Design Patterns》(中文名<PHP实际模式指南>)让我重温了久违的阅读享受。

这是一本系统介绍设计模式在PHP编程中应用的书,基本回答了两个问题:1、什么是设计模式,2、有哪些常用设计模式;还有一个附加问题:常用设计模式在 PHP中的具体实现。全书语言流畅,用例明晰,虽然看完后你会发现有一些设计模式其实你早已经无意识的使用了,但是它仍然会让你有顿悟的体会。

围绕这本书,我用了3天的时间研究了一下关于设计模式的一些概念,最后又用了2天来总结,就有了这篇文章。总结的主要目的是让自己更深刻的理解设计模式,也可以说是个全面一点的学习笔记,文字力求通俗,避免概念陷阱。

很多文字写的可能有点武断、大胆。肯定也会有些错误的认识,等到有更深刻的理解时再来完善把!

1 关于设计模式

设计模式可以理解为对开发中常见问题/需求的解决方案的高度抽象,这些问题/需求有具体的细节的,也有全局的结构性的。

在层次上,它应该是一个介于代码库和设计指导思想之间的一个东西,比代码库要抽象地多,比指导思想原则又要具体的多。

个人认为对于设计模式的学习应该是这样的一个目的,即“知道这件事情原来可以这样解决”,而不是“这件事情应该这样解决”。似乎还可以应验了“无招胜有招”这句经典武侠用语,你应该学习、理解这些设计模式,然后忘掉这些乱七八糟的东西。

当然,这些也许都是我无知无畏的狂妄。

2 常见设计模式

2.1 创建型

2.1.1 Singleton (单件/单子模式)

特性: 确保对一个类(对象)的创建请求在整个应用中只会返回同一个实例。

要点: 阻止construct,提供一个getInstance静态方法,作为实例化的唯一方式。

评论: 实际上使用Facorty时可以更方便的实现这一效果,但是似乎不如Singleton来的彻底。

典型应用: 数据连结、以及其他全局性的对象

典型代码: $obj = & class:: getInstance()

2.1.2 Factory (工厂模式)

特性: 简单说就是利用函数或类来进行对象的创建,而不是仅仅用new

要点: 没有什么特别的要点,可以根据需要使用函数或者类来实现

相关名词: AbstractFactory 用于处理一系列相关的组件构造型模式(GOF)
Builder简化复杂对象创建的组件构造型模式(GOF)
FactoryMethod 使用类来封装工厂,通过类中的方法完成对象创建如果工厂类,继承自一个或者多个抽象的根类,这时就可以叫AbstractFactory了。

评论: Factory 非常简单,以至于我觉得这个东东几乎不能用模式这个词来命名。
Facotory如此的不可或缺,以至于任何一个PHPer都有意或无意的使用过。
Factory又是如此强大多变。它可以响应参数(Parameterized Factory参数化工厂);可以具有分支结构,能够选择性的创建对象(促进多态);可以根据需要include进必要的文件(Lazy Loading延迟加载);等等很多作用。
BTW:以上关于Facory的很多名词看着眼花缭乱,但真正理解了就会发现本质上其实很简单,我甚至怀疑,是不是有人故意搞出这么多名词来装酷的:)
但是我还知道,理解是一回事,用的上,用的好又是另外一回事。

2.1.3 AbstractFactory (抽象工厂)

特性: 使一簇具有相关性的对象的建立更加容易。

要点: 存在一簇具有相关性的对象,这些对象一般具有相同根类或者接口
这些对象往往又可以划分为几个独立的系列
使用几个Factory来分别处理这几个系列,而这些Factory也应该具有相同根类或者接口

评论: 这个描述清楚很难,而且感觉也不是很实用,前置条件太多。

2.1.4 Builder (生成器)

特性: 将一个复杂对象的构建与他的表示相分离,使得同样的构建过程可以创建不同的表示。

评论: 仅仅罗列 未能深入

2.2 结构型

2.2.1 Handle-Body (结构型)

特性: 一个包含“目标对象引用”的设计模式的集合名词。
本质就是在一个对象(Handler)中保存一个目标对象(subject)的引用,并通过Handler传递对Subject的访问

评论: 一个基本的模式,Proxy、Decorator、Adapter都可以归入这一模式。
以全面模拟对象,并改变内部行为为目的的就是Proxy;以改变目标对象输出结果为目的的就是Decorator;以改变目标接口为目的就是Adapter。
实际上开发过程中,往往这些目的会组合出现,很难也“没有必要”去区分自己用的到底是Proxy、Decorator还是Adapter。

2.2.2 Proxy (代理)

特性: Proxy 的本质就是在一个对象(Proxyer)中保存一个目标对象(subject)的引用,然后把对Proxyer类方法的调用传递给目标对象。

要点: 典型情况下Proxyer中应该编写所有Subject中公开的方法,并把调用传递给Subject。,在传递过程中可以进行选择、过滤、预处理、缓存、延迟加载(Lazy Proxy)、监视状态等等附加操作。
利用PHP5的__call(),Proxyer 可以进行自动重定向。
PHP5中的SoapClient其实就是一种远程代理(Remote Proxy)。

评论: 提供了一个很好的包装现有对象的机制,特别在延迟加载、远程代理、监视状态等方面提供了一个不修改原有类的解决方案。

典型应用: 远程代理,权限检测,延迟加载

典型代码:

[coolcode]

class Proxy{

var $subject

function Proxy() {

$this->subject =& new Subject;

}

function someMethod() {

//do something

$this->subject->someMethod();

//do something

}

}[/coolcode]

2.2.3 Decorator (装饰)

特性: Decorator的本质就是在一个对象(Decorator)中保存一个目标对象(subject)的引用,然后把对Decorator类方法的调用传递给目标对象。
注意:以上特性描述和Proxy几乎完全一样,Decorator和Proxy有时很难区分,似乎Decorator仅仅用于对Subject部分方法的修饰和处理,而且其重点在于提供一个可嵌套的机制,即对同一Subject的Decorator是可以多层包装的。

要点: 保存一个目标对象(subject)的引用,代理并装饰(改变)部分方法

评论: 实际上Decorator和Proxy确实很难区分,Proxy一样应该是可嵌套得,甚至我认为

典型代码:

$a_Invalid_Labeled_text_input =

& new InvalidInput(new LabeledInput($labelTitle,new TextInput($inputName)));

$output = $a_Invalid_Labeled_text_input ->paint();

2.2.4 Adapter (适配器)

特性: Adapter的目的是要改变目标对象(Subject)的方法接口,改变方法(API)的名称和参数接受方式。其重点在于改变接口方式而不是改变对象行为。

要点: Adapter至少有两种实现方式:组合、继承
组合:很大程度上也像一个代理,本质仍然是在一个对象(Adapter)中保存一个目标对象(subject)的引用,然后编写一些新的方法作为接口,并把转换后的调用传递给目标对象。
继承:通过类的继承,一样可以实现Adapter的目的,两者取舍就看各人喜好和具体代码了。

评论: 在需要改变对象接口,更换Lib时,提供了一个快速简单的兼容方式。

典型应用: 提供兼容、改变/标准化第三方程序库

2.2.5 Composite (组合)

特性: 用于管理一个对象集合,集合中的每个“部分”都可以代表“整体”。典型地用于组织一个树型分层结构。

评论: 仅仅罗列 未能深入

2.3 行为型

2.3.1 MonoState (状态监视器/单态模式/Stealth Singletons)

特性: 一个类的所有实例都共享一个全局状态

要点: 把类的属性绑定到对全局变量的引用上

评论: 当同一个类的实例的所有属性都相同时应该认为他们是相同的实例,所以当利用单态模式来实现类的所有属性全局共享时,我认为其效果是和Singleton完全相同的,而效率应该是低于Singleton的。
但是另一方面:我认为单态模式应该是可以用于仅部分属性需要全局同步的情况, 而且似乎仅仅在这种状态下才有其存在意义。但GOPDP中认为:MonoState的任何实例都返回完全相同的信息,此为未解之处。

2.3.2 ValueObject (值对象)

特性: 顾名思义把对象作为一个值来对待,换句话说就是防止因为引用而造成的意外关联。

要点: 保护属性,当属性变化时总是返回一个新实例

评论: PHP中对象的传递经常让人迷惑,有时会引起一些BUG,ValueObject的目的就是确保一些以关键值为基本特性的轻量对象不受引用的影响,始终以传值方式传递。
但是很遗憾,PHP不支持操作符重载,这种情况下实现ValueObject似乎成本有点高。

典型应用: 日期,数字,或货币等值的OOP封装。

2.3.3 Iterator (迭代器)

特性: 可以方便地操纵(循环遍历)一个对象集合(Collection)

要点: Iterator的实现有多种方式,常见的有GOF Iterator和SPL Iterator。
GOF Iterator :
集合类必须提供一个 Factory来创建你的 Iterator 实例。
Iterator 类定义了一个 first() 接口来转到集合的起始位置,next()在迭代时顺次移动到下一个项目,currentItem() 在迭代时从集合中取回当前项目,而 isDone() 则指出你已经迭代完了整个集合
简化的情况下,Iterator可以只提供一个next()方法,通过返回不同的值来操纵遍历。
SPL Iterator:
只有在激活了SPL的PHP5+中才可以使用这种方式
集合类按照标准PHP库(Standard PHP Library,SPL)的定义实现SPL Iterator接口,提供current()、next()、key()、valid() 和 rewind()五个方法。
最终是为了实现foreach($collection as $item){}的遍历形式。注意,$collection 是一个对象,而不是数组。

评论: Iterator对于我们如何操纵一个对象集合提供了很好的模式,需要认真学习体会。
Iterator不仅可以进行遍历,还可以在遍历过程中完成过滤、排序等操作,而且同一个对象集合可以有多个Iterator.

典型应用: 所有具有集合性质的对象

典型代码:

$lib = & new Library();

for ($it = $lib->getIterator(); !$it->isDone(); $it->next()) {

$output .= $it->currentItem()->name;

}

或者更简单的

$lib = & new Library();

$it = $lib->getIterator();

While($item = $it->next()){

$output .= item->name;

}

2.3.4 Registry (注册)

特性: Registry 设计模式就像一个“对象电话本”—— 一个目录 —— 用于存储和检索对象(数据)

要点: 提供set和get两个方法分别用于写和读
使用简单的$name<=>$value对应存储数据。

评论: 这也是一个简单但强大的模式,用处很多。GOPDP中似乎认为Resitry必须是全局,但我认为应该不受这个限制影响

典型应用: 缓存、配置数据

典型代码: $registry->set($name,$value); $registry->get($name);

2.3.5 Strategy (策略模式)

特性: 通过创建具有一致接口的对象允许在可选的算法之间切换。

要点: 一系列相同特性算法,每个都封装成一个单独的类,并提供统一的接口。容器对象根据需要选择性的绑定其中一个类

评论: 感觉这个模式的核心思想就是:算法也可以使用类来封装
Strategy 模式与其它几种模式相似。Strategy 模式和 State 模式的主要区别是,Strategy 只绑定一次,而State 模式的行为则随着实例变量值(对象的状态)的改变而改变。或则,换一种说法,Strategy 模式在构造期间改变对象的行为;State 在对象整个生命周期动态改变对象行为。

典型应用: 太多

典型代码:

switch (strtolower($type)) {

case ‘string’:

$strategy = ‘String’;

break;

case ‘numeric’:

$strategy = ‘Numeric’;

break;

case ‘serialize’:

default:

$strategy = ‘Serializing’;

}

$strategy .=’CacheWriter’;

$this->_type =& new $strategy;

….

2.3.6 Observer (观察者)

特性: 提供一个机制:当一个对象(被观察者/Observable/subject)的状态发生改变时,可以自动通知/触发其它一系列相关对象(观察者/Observers/Clients)。

要点: Observable提供attach()和detach()由于登记/删除Observers.
Observable通过notify()触发Observers的Update()操作。
Observable可以提供一个getState()方法,用于向Observers传递信息。

评论:

典型应用:

典型代码:

$loger = & new SomeObserver();

$state = & new SomeObservable();

$state->attach($loger);

//当$state中定义的某个状态发生变化时,将会自动触发$loger的update()操作。

2.3.7 Specification (规范)

特性: 提供一个灵活的验证/选择机制,以促使复杂的业务逻辑可以进一步分离重构,并进行灵活组合

要点: 建立一个对象(Specification),通过isSatisfiedBy()方法接受一个对象,并验证其是否符合定义的条件。
Specification可以接受参数(Parameterized Specification)
Specification可以通过提供内置的或外部的组合机制,利用and/or操作,构成复合规范(Composite Sepecification),构成复合规范的方法/对象被称为“策略工厂(Policy Factory)”。

评论: Specification 模式可以帮助你在你的应用程序的领域模型中更好地构建和组织业务逻辑。
但是在我看来,这种模式似乎也属于成本太高的模式,可能是我还没遇到真正复杂到需要做如此分离和封装的业务逻辑吧。

典型应用:

典型代码:

$menber = & new Menber($id);

$women = & new SexSpecification(‘female’);

If($women->isSatisfiedBy($menber)){

Echo $menber->name.’is a women’;

}

//now try Composite Sepecification

$admin = & new adminSpecification();

$women_admin = & new AndSpecification($women,$admin);

If($women_admin->isSatisfiedBy($menber)){

Echo $menber->name.’is a female admin’;

}

2.3.8 Visitor (访问者)

特性: 把一个算法定义成一个对象,这个对象通过访问一个集合的每个成员来执行一个操作。

评论: 仅仅罗列未能深入

2.3.9 TemplateMethod (模板方法)

特性: 把类方法的中的部分代码步骤,独立为一个子方法,并在子类中实现(改变)它

要点: 基类中的某个方法(被理解为Template)需要N步骤,其中一些步骤通过类中的其他方法(Sub Method)实现
子类重载了Sub Method

评论: 基本东西,简单,实用

典型代码:

Class a{

Function doSomething(){

//….

$this->amethod();

//…

}

}

Class b Extends a{

Function amehtod(){

//….

}

}

2.3.10 MockObject (模拟对象)

特性: 这实际上是测试驱动开发中用到的一个模式,用于模拟一个对象以提供给一个测试过程使用。

要点: 动态定义,可以设定对象的属性、属性值、方法名、方法返回值

评论: 似乎仅仅在测试驱动开发包中有存在意义。

典型应用: 测试

典型代码: Mock::generate(‘Accumulator’);
$amount =& new MockAccumulator();
$amount->setReturnValue(‘total’, 200);
$amount->total();//200

2.4 数据访问

2.4.1 ActiveRecord

特性: 创建一个对象以包装一个记录行,提供一次一行的数据库访问,可以进行CRUD(创建、读取、更新、删除/Create、Read、Update、Delete)操作,并且还可以封装相关的业务逻辑。

要点: 以对象来封装一个数据行
对象属性对应相应的字段
对象的典型方法包括save(), add(), findById(), findBySomething(), delete()等,其中save()方法一般是集成了Create和Update两个操作。

评论: 简单,便于理解,对象关系清晰。
但是只能处理一个记录行,不能进行批量操作,成为了它的最大缺点。

典型代码:

$book = & new Book($bookId);

$book->setNum(4);

$book->save();

2.4.2 TableDataGateway

特性: 一个充当了数据库表网关的对象,提供对多行记录的访问。
TableDataGateway 模式关注的是表 — 记录集 — 而非单个记录行, 目的是方便地操纵一个数据库表和其中的所有记录

要点: 对象的典型方法包括add(), update($aRecord), findAll(), findBySomething(), delete()等。
返回结果或者传递数据行参数时可以使用“联合数组”或者“可迭代的对象集合”。

评论: 这几乎是目前WEB开发中用的最多的数据访问模式了。

典型代码:

$gateway = new BookmarkGateway($aDatabaseConnection);

$gateway->add( ‘http://www.php.net/’, ‘PHP’, ‘The main page for PHP’, ‘php’ );

$rs = $gateway->findByName(‘PHP’);

Foreach($rs as $bookmark){

$bookmark[‘name’] = $bookmark[‘name’].’ Updated’;

$gateway->update($bookmark);

}

2.4.3 DataMapper (数据映射)

特性: 领域对象和数据库表之间的中间层,从数据表提取数据创建领域对象,并可以根据现有的领域对象更新/新建数据记录;同时它还可以处理表字段与领域对象属性的转换关系。

要点: DataMapper在本质上可以理解为TableDataGateway高级版本,更加的OOP和更加的具有自适应能力,相对TableDataGateway:

  • 〉DataMapper总是用一个封装好的对象(领域对象)作为数据行的返回值和参数接受形式
  • 〉DataMapper应该可以处理字段与领域对象属性的对应关系,即属性名称与字段名称并不总是相等的,甚至必要时也应该可以处理属性值与字段值之间的简单转换关系
  • 〉上述对应关系应该可以通过一个定义好的配置文件格式(XML)来方便的修改

评论: Data Mapper 的目的是为了解除了对象属性和保存它们的表字段之间的耦合。但是目前来看这种模式的成本似乎有点太高,特别是对于WEB应用程序。

2.5 MVC

2.5.1 Model-View-Controlle

特性: Model-View-Controller(模型-视图-控制器,MVC) 模式,一个应用程序分层模式,它分离了你的领域模型、表现逻辑以及应用程序流程之间的关系。

要点: 将你的软件组织并分解成三个截然不同的角色:
Model 封装了你的应用数据、应用流程和业务逻辑。
View 获取数据并格式化数据以进行显示。
Controller 控制程序流程,接收输入,并把它们传递给 Model 和 View。

评论: 只要你与 MVC 模式打过交道,你就会对它的实用性心存感激,这是目前已经非常成熟的WEB程序开发的框架模式,学习并理解这种模式对PHP开发是非常有帮助的。
与其它设计模式不同,MVC 模式并没有一个明确的类结构或实例,相反,MVC 更像一个概念上的指导原则或范型。要知道很多MVC的框架在具体实现上有极大的不同。
对于用了很长时间MVC模式的开发者来说,还要注意MVC并不是WEB开发唯一高效的框架模式,比如基于事件处理的Event Handling模式。

2.5.2 Model(模型)

特性: Model 包含了你的应用业务逻辑、底层数据处理,在你的应用程序中,它很可能是主要的值驱动器。

要点: Model 没有任何与表现层相关的特性,而且也和 HTTP 请求处理职责中完全无关。(作为一个经验方法,PHP Model 中决不要出现 HTML 标签或 $_GET 超全局变量)

评论: 领域模型和模型到底是什么关系目前我还没搞清,暂不评论

2.5.3 DomainModel (领域模型)

特性: 一个包含了数据和行为的业务逻辑的对象模型。
Domain Model 是一个对象层,是对现实世界逻辑、数据和你应用程序所处理的问题的抽象。Domain Model 可分为两大类:Simple Domain Model 和 Rich Domain Model。

2.5.4 View(视图)

特性: View 用于处理所有表现层方面的问题。View 获取数据,并可以把它格式化成用于 web 页的 HTML,用于 web 服务的 XML,或用于 email 的文本等,任何属于可视的表现的东西。

要点: 接受数据,格式化数据,表现数据
在MVC中尽管 View 有权访问 Model,但是让 View 调用 Model 的方法来改变它的状态是一种很不好的方式 —— 更新只应该由 Controller 来执行。View 调用的 Model 方法应该是没有副作用的只读数据检索方法。

评论: View层并不一定要用模板技术技术,有两种设计模式经常在 Views 中使用:Template View 和 Transform View。而用了模板技术的程序也未必就是MVC的结构。
鉴别你是否已经把你的View层成功地分离的一个好方法就是,试着去取代(至少从概念上)另一个产生完全不同输出的 View。举例来说,如果你有一个 web 应用程序,要让程序使用 PHP CLI 在命令行方式下运行,你将必须做何更改?

2.5.5 TemplateView (模板视图)

特性: Template View 是在 web 应用程序的 View 中使用的主要模式。这个模式使用一个包含特殊标记的模板文件(通常是 HTML),当 Template View 被执行的时候,这些标记会被接受的数据所替换。

要点: 有一个固定的模板语法(直接用PHP作模板也是有语法的,那就是PHP的语法)和格式,并据此编写模板
Contrller会向View发送数据并请求渲染某个模板
View负责接受数据,并根据数据渲染出指定的模板

评论: 没什么好说的,一直很热门,捧的骂的都不少,但是真的很实用。
Smarty、PHPTAL、WACT等有很多现成的模板引擎,PHP 本身也可以认为是一个特殊的 Template View 的例子。

2.5.6 TransformView (转换视图)

特性: 先处理领域数据,然后继续把它转换成某种输出形式。

要点: Transform View 从模型中取得数据,然后把数据转换为需要的输出格式。从本质上来讲,它就是用一种语言逐个遍历你的数据元素,并顺便生成输出。
Template View 和 Transform View 的区别就在于数据流向。在 Template View 中,你首先定义一个输出框架,然后把领域数据插入其中。Transform View 则从数据着手,然后从它建立输出。
实现 Transform View 的主要技术是 XSLT。

评论: 没用过,不好评论。

2.5.7 ViewHelper (视图助手)

评论: 一个帮助视图从模型(Model)收集数据的对象。

2.5.8 CustomTag (自定义标签)

评论: 通过把内容封装成新的 HTML 标签,从而改善表现层的分离。

2.5.9 Controller (控制器)

特性: 控制程序流程,接收输入,并把它们传递给 Model 和 View。

要点: 负责接受各种形式的输入,分析 HTTP 响应选择并控制程序流程,选择View,
决定应用程序应该如何响应请求

评论: Controller 是大多数 PHP MVC 框架主要关注的 MVC 角色之一。这主要是考虑到,Model 对于应用来说是特定的,而几乎每个开发人员都已经有了他们所喜爱的模板引擎(View 的一个主要组件)。

2.5.10 ApplicationControlle (应用控制器)

特性: 一个应用程序处理导航的集中点,典型地实现在一个 index.php 文件中,基于 URL 查询参数进行分发。

要点: 典型情况下:接受访问参数(一般为URL),解析参数,分发操作(一般可以通过Command传递参数)

评论: 很典型很实用的Contrller实现方式,没什么好说的

2.5.11 FrontController (前端控制器)

特性: 把应用程序流程控制集中在一个地方通常是很有用的。集中化可以帮助你了解一个复杂系统是如何运行的,而且它还提供了一个单独的地方,在那里你可以插入全局代码,比如一个 Intercepting Filter。Front Controller 对于集中化处理是一个非常好的选择。

要点: 在程序流程进入实际的派发/运行之前提供一个在运行 FrontController 的实际工作前(产生页面,分发,等等),我们可以依次应用 preFilter() 方法

评论: 可以理解为在常规Controller外面包上一层,它一般还是要把请求转发给Controller.

2.5.12 Command (命令)

特性: 把一个请求封装为一个对象。

评论: 控制器里的一种常用模式。用于封装请求,参数化请求。

3 关于《Guide to PHP Design Patterns》

《Guide to PHP Design Patterns》(中文名<PHP实际模式指南>)是一本“有用”的书,会让你不管是从细节上还是从结构上对编程都有一个提高,最差情况下也是一个深层次的总结。

这本书的另一个特色是贯穿全书的测试驱动理念,我个人对测试驱动的开发理念不是很感冒,但是这本书很好的演示了测试驱动开发的具体实现。这有些偏离书的主题,如果你对测试驱动开发已经有了比较深入地了解,你完全可以掠过书中所有关于测试驱动的内容,这丝毫不会影响你对设计模式的学习。

所以强烈推荐PHPer阅读这本书。

这本书的英文原版是由php|architect’s出版,Jason E. Sweat著。因为懒,我下载了一个中文译本,由AnSon翻译,为了方便阅读和打印,我又对AnSon的中文译本进行了简单排版。原版的PDF和排版后的中文译本可以在我的Blog(http://lee.kometo.com )下载。

下载:PHP 设计模式指南

(10)

“设计模式学习总结(笔记)”的一个回复

发表评论

电子邮件地址不会被公开。