php设计模式笔记--总结篇,php设计模式--_PHP教程
php设计模式笔记--总结篇,php设计模式--
一、引入
设计模式的一般定义不再说,只大概说一下我理解的设计模式,我理解的设计模式的主要目的是利用面向对象(类、接口等)特点,让代码更加易于扩展,易于重用,易于维护。这三个特点也就要求我们不要将太多功能积攒到一个类里面,而是分配到更多的类里面。所以,二十种乃至更多的设计模式主要是围绕上述四个目的进行设计的。
php设计模式这一本书讲了19种设计模式,但其实有大部分设计模式思想上或者设计上是一样的思维与形式,我将在下面进行归类和总结,以便于大家更好地理解这本书,但大家最好看一下这本书,里面的使用的例子比较简单,看书的时候记住这本书为了代码上更简单,有些应该通过实现接口来更加规范的地方没有加接口。
(注意:由于本文是对《php设计模式》这本书进行总结,所以时间原因书中的例子我有所提到但是并没有写在本文中,大家可以通过查看电子书,以后我也会在分别介绍各个模式的文章或者本文中补充例子)
二、创建类的时候需要考虑的模式
面向对象最终要的概念就是类,从面向过程转换到对象,类是重要的步骤。那么创建一个类,各个类之间的依赖如何来实现,就是一个很重要的问题,下面的几个模式就是针对这一问题的。
1、建造者模式
我们应该在代码中尽量减少new的出现,这是创建类的一个重要方式没错,但是我们应该让他尽量出现在建造者或者说工厂中,而不应该出现在一个类的方法中。原因一,可能这个类的初始化方式比较麻烦,不光需要new,还需要往其new的构造函数里面传参,那么我们首先要建立那些参数,而那些参数中可能还有对象需要new,这个初始化操作就特别长;原因二,很可能这个类以后需求可能会改变,那么初始化操作我们就需要改变,改变的操作也是在别的类里进行的,这不符合各个类之间独立的原则。
2、工厂模式--延伸的建造者模式
当很多代码和框架中说道工厂模式的时候,其实它做的事情就是建造者模式,我认为工厂模式的意义更多的是让建造者模式更有弹性的抽象工厂。接着建造者模式的概念,如果有一些类有着一套标准得创建过程,那么我们可以先建一个抽象工程,然后各个具体类继承这个类,pizza工厂就是一个很好的例子。
3、补充:服务的概念--众多php框架的核心,依赖注入的实现方式。依赖注入比较重要,我将在别的文章里进行说明。
如果我们看php的两大框架symfony和zend framework我们会发现有个很重要的概念叫做依赖注入(dependency injection),也就是如果解决类之间依赖的问题,实现的方式有个很重要的概念叫做 service,或者说service container。在我的理解上这和工厂模式很类似,zf中也可以将一个服务指向一个具体的工厂,而在工厂的类中我们也可以实现抽象工厂,所以说这两个是概念上类似,但是并不冲突的方式。之后会单独介绍。
三、当需求改变或增加功能时
以下模式能够方便我们重用代码
1、适配器模式
我们说当功能变化的时候,我们应该保证类的接口不变,这样可以保证一个类变化的时候,它使用或者使用它的类的代码不需要变化。但是,毕竟会有违背这个原则的代码和类出现,比如书中所举例子,本来分解errorobject的功能应该由发生需求改变的类logtocsvadapter来实现,但是可能出于很多原因(比如代码是不可更改,不开源的),没有这么做。而适配器模式就是用来处理这种情况,他可以通过extend(比如书中的用法),也可以通过将其作为成员变量的方式,重新实现一个符合要求的接口。
2、装饰器模式
形式:将被装饰的类作为其成员,通过成员调用方法。
当你想为一个类添加功能却又害怕改坏了一个类的时候可以使用这种模式,但这种想法终究不能从根本上解决问题。
另一种常用的场景是如果你只想展示给别人这个类的某一些功能而不是全部的功能。
3、中介者模式(类似事件模式)
形式:多个有联系的类将中介者类作为其成员,调用某个方法会通过中介者来改变所有在中介者中注册的类的状态,所以注册的类需要实现一个接口供中介者调用。
和观察者类似。
结构上类似事件模式,只不过注册是写死(hardcode)在中介者(对应事件中的注册机)中,但是其解决问题的思路是事件的一个特例,所以叫做中介者模式。
假设有一个类,又有一个类在概念中和第一个类是平行的(可能是一开始就有的类,也可能是随着需求的变化又出现的一个类),这两个类有联系,一个类的改变需要改变另一个类的状态,这个时候需要中介者。具体例子会在事件模式中介绍。
四、将功能分散到更多的类中
实际项目中,维护一个有很多功能的“大”类是很麻烦的,这不符合高内聚低耦合的原则,一个类如果功能越多,那么就越面向过程,各个功能的联系越紧密,其实面向对象要解决的主要问题也是如何用更易于管理的方式将功能分布到更多的类中,听起来简单,实际上如果使用不好的方式分出来的类反而会更乱,从某种意义上这也是设计模式出现的原因。还有比如说一个人负责维护一个类,负责这个类的测试,如果结果每当一个功能发生变化他都要更改这个类的测试代码,如果我们将功能分布到更多的类中,就可以保证该类的测试代码不发生变化。
1、事件模式(会单独写一篇文章详细介绍,这里简介)
很多人包括我对事件的印象留在窗口进程中,我们单击鼠标或者按钮会有一个事件,它会改变所有注册了该事件的对象,可能是视图发生变化,也可能是我们自己定义的一个函数,中介者模式也类似事件模式,只不过是所有在中介者中注册的都是平等的关系,比如一个表格和一个柱状图,我们改变表格的内容会引起柱状图的改变,我们缩短加长柱状图会改变表格的内容。但是在一般的应用逻辑中事件模式和中介者模式有什么用?
我们要抛开上面所说的事件的意义,上面只是事件的一个特例,这里说明一下事件更一般的用法。其实事件本质上是中介者模式,中介者是把注册的容器放在了需要被改变的类中,相当于事件中传入event中的对象,但有些类在一开始并没有设计成中介者模式,也就是说类中没有保存一个中介者的成员,可能是因为没想到这个需求,也可能是因为觉得在类中加入这个机制比较麻烦,而是选择了可以在类外实现相同功能的事件模式。(所以说中介者模式是可以转换成事件模式的,虽然事件模式免去了在类内的麻烦,但是却需要提供一套事件的机制,相对于中介者在类外更加麻烦的,但由于分离到类外,所以有很多现成的中介者实现,比如各个框架基本都实现了这一机制,symfony更是可以将这一组件用在不是symfony的框架中。
在除了上面视图界面的操作中,别的例子可能不是那么直观,不那么符合事件这个名字,它的主要作用就像大标题中所写的那样,是通过事件模式的代码结构来更好的分割代码的功能使其更易管理。所以要理解就要忘记事件的一般含义。思考一个逻辑比较复杂的例子,一个电话公司,会针对用户的通话记录计算其花费,众所周知这是一套很复杂的逻辑,根据用户的不同套餐和使用地区都会有差别,那么那么一长串逻辑判断代码我们难道要写在一个类中么?肯定不符合我们的原则,那么我们就利用事件模式的形式(反复强调只是形式),来实现将逻辑分到不同的类中。我们把所有的电话服务都作为listener,进行注册,每一条通话记录作为subscriber,都通过if语句来判断属于哪一个业务,然后对向那些业务dispatch,listener获取账单对象,对其进行更改,最后的账单对象就是最终的账单信息。通过事件模型,我们将不同的处理逻辑分出来成为listener对象,更方便以后的扩展。
上面的例子是通过listener修改对象,也可以仅仅是利用获得对象信息进行相关操作,比如博客园发布一篇博文会有很多的操作,比如需要存数据库,需要进行倒排操作,需要进行标签操作,如果要发布到首页也需要另外的额一系列操作,可以通过一些listener来监听发布博文这一个操作,本身并没有修改Post这个对象,而是利用Post对象信息进行操作。
回到更一般的说法,事件模式主要应用在插件(plugin),当然是更广义的插件。这是symfony的介绍Consider the real-world example where you want to provide a plugin system for your project. A plugin should be able to add methods, or do something before or after a method is executed, without interfering with other plugins. This is not an easy problem to solve with single and multiple inheritance (were it possible with PHP) has its own drawbacks.
从上面的意义上讲,事件是以一种更利于扩展的方式处理和加工对象的机制,在你想利用这个机制的时候都可以用事件,并且借助现有框架中的事件组件,你可以将观察者和中介者都转化为事件模式,所以事件其实很常用,在c#中事件的重视中也可以体现。
也可以看一下symfony事件文档来学习一下事件的运用。
2、观察者模式
形式:$this->obserber->update($this),而中介者的形式是$this->mediator->chage($this,array('band'=>$newname))。可以看出来只不过是为了完成两种不同的功能需求。而这两者都可以通过事件来代替,前者发送一个update事件,后者发送一个changeBandName事件,然后通过查看传入对象的name来实现change。
3、委托模式
通过分配或者委托至其他对象,委托设计模式能够去除核心对象中判决(if语句)和复杂的功能性。和c#中委托的概念相似,c#中是用函数的形式,将委托作为参数传到别的函数中,从而将功能性分到别的方程。面向对象里的委托是把功能性分到委托类中。
形式:被委托类中有一个委托类的成员变量。
4、策略模式
在能够创建应用于基于对象的、由自包含算法组成的可互换对象时,最佳做法是使用策略模式。
和委托模式基本一致,无论是形式还是意义上,只不过书中的例子委托模式是把$this->playlist传递给了委托模式,但是策略模式把整个$this都传给了策略模式。
策略模式还有一个重要的意义,如果一个类的某个方法不经常用,可以转换成策略模式,这样就不必在类的测试中每次都要测试这个类了。
5、代理模式
代理模式可以有不同的形式,只要是被赋予了“代理”这个意义。书中通过装饰器的继承形式实现了代理。
五、其他
以下都是很常见的模式
1、面向对象与生俱来的模式--模板模式
就不详细介绍了,和抽象类和接口是一个思想。
2、外观模式(facade)
facade这个单词在一些框架中类的命名中很常见,类似面向过程中function的作用,把一系列针对某个特定功能的代码封装到一个类中一个static方法中,通常该类也只有这一个方法。
3、公共方法抽象成类--数据访问对象模式
公共方法抽象成类是面向对象的基本原则,不仅是一个实体,如果是一个功能可以被多个实体或者功能所使用,那么我们应该为他单独成类,其他类如果使用它那也就是上文所说的依赖注入(dependency injection)。
数据访问对象模式也是这一模式的基本实践。可以结合zend famework的guide http://framework.zend.com/manual/current/en/in-depth-guide/preparing-db-backend.html 进行理解。基本上是为了封装数据的操作,tablegateway也是这一领域的成熟的模式,基本上是一个表一个类,但这样也就限制了数据库本身的连接操作,我们只能通过类之间的操作来实现连接,具体我会在其他文章中写。
4、系统中只需要一个-单元素模式
这个模式也很常见
六、书中关键句子笔记
1、不同对象的连接才是简化的目标