您的位置:首页 > 编程语言 > PHP开发

什么是设计模式

2012-03-01 01:49 1671 查看

假如面向对象是兵器,那么,设计模式是一套武术套路,是一本编程人员均需要的葵花宝典。
假如面向对象是一个算盘,那么,设计模式就如同是珠算口诀。学珠算是要记口诀的。但会打算盘的是不用口诀的。并不是不用,而是在用时,己没有文字或模式的具体概念干扰了。
假如面向对象是五笔字型,那么,设计模式,就是五笔字型的口诀。但会打字的,则是想字读码,或看字读码,已不再需要口诀了。

学习设计模式,成为一个PHP核心代码编写高手,需要的就象前述的那种境界。要达到这样的境界,是要明白,看设计模式示例代码,最终目标是能让自己遇到问题时立刻能够给出类与类之间的结构设计,而不是找出设计模式参考,看哪个模式适合。如同学五笔时记口诀的目标是看字读码或想字读码。而不是每次用口诀去对。

回到正题,设计模式是什么?设计模式是用来组织应用的结构的。使用设计模式,能够使你的应用拥有一个清晰的,可维护,可扩展,松耦合的应用架构。对于一个应用,特别是大型的WEB应用,我们希望它有一个清晰的架构。为了实现这一点,首先我们要做的是:要把核心代码与非核心代码分开。那么,什么是核心代码?什么是非核心代码呢?
核心代码是,大家都需要用到的,在应用中会有很多地方调用的,不涉及具体问题的代码。
非核心代码则与具体业务需求相关的代码。

当我们要把要把核心代码与非核心代码分开时,会遇到以下两种情况:
第一、是由业务代码部分调用核心的。这种情况下,就需要把核心代码封装成具体业务代码可以方便调用的类即可。
第二,是由核心的部分调用非核心的。这种情况是比较复杂的。因为非核心部涉及具体的业务需求,会因不同的具体需求而变化。这种情况相对复杂。

PHP5,作为完全面向对象的语言,提供了面向对象中针对这一方面需求有的所有的功能。这些功能包括:抽象方法,抽象类,最终方法,最终类,接口。这些功能的基本用途如下:
接口:如果核心代码需要调用一个具体的类,这时,我们可以把这个具体的类先定义为接口,这就是说,一个只有接口名(相当于类名)和所有未实现具体代码的函数(方法)构成的一个结构。这样做的目的,是要告诉那些所有实现具体代码的人,这个类该怎样做,核心代码才能正确调用。
抽象类:假如,接口中存在已实现的方法代码,那么,就不能再用接口进行定义,而是要用抽象类,将没有具体实现的方法定义类抽象方法。
最终方法:如果某一个方法在抽象类中实现后,不可以在子类中重写,则需定义为最终方法。
最终类,如果某一个类,不允许被继承,则此类可定义为最终类保护起来。
由此可见,抽象方法,抽象类,最终方法,最终类,接口这些功能,目的是让你核心代码调用业务代码时,为业务代码实现一个约定,保证业务代码实现时遵守这个约定,从而保证核心代码能调用成功。

设计模式,实际上基本都是针对核心代码调用具体代码的场景的。理解了这一点,那么,学会并真正领悟设计模式就相当简单多了。我们根据这一点,就能从设计模式的例子代码中搜索线索:
这一模式中定义了哪些接口与抽象类?
这一模式中的核心代码调用具体代码的调用关系是什么?然后,你不再需要记住这些代码是如何实现的。而是遇到具体问题时,你能够不用考滤就自然用上了某一模式了。

那么既然设计模式是针对核心代码调用具体代码的场景,对其进行总结归纳,在此基础上得出的一套实用的编程套路。并且,这些套路,是以一些基本的架构原则前提,保证架构的清晰的,可维护,可扩展,松耦合。这一些基本原则,现在被简称为SOLID原则。(这是因为,这五个原则的第一个字母组合起来,正好是SOLID这个单词)。
SOLID原则是指:
SRP:The Single Responsibility Principle     单一责任原则   功能多少
OCP:The Open Closed Principle                 开放封闭原则  应对变化
LSP: The Liskov Substitution Principle        里氏替换原则  分类明确
ISP:  The Interface Segregation Principle    接口分离原则  方便组合
DIP:  The Dependency Inversion Principle  依赖倒置原则   面向对象的功能
以上首字母简写,即是SOLID
实际上,面向对象的原则中,还有:
CARP: The Composite/Aggregate Reuse Principle 合成聚合复用原则
LoD:       The Law of Demeter 迪米特法则
IoC:        The Inversion of Conntrol 控制倒置原则
因为,这些都是面向对象应用必须遵循的基本原则,所以,要真正学会设计模式,必须要先弄懂它们。以下进行分述。

SRP:The Single Responsibility Principle   单一责任原则
单一责任原则,要求,一个函数只完成一个功能,一个类,只实现一类具体的对象。
这样说仍很抽象。但更具体一些,对于一个函数,应当是一个输入与输出单元。即有明确的输入数据,算法,输出数据的结构。
对于一个类,在核心代码层面,它应当是一个明确的算法实体,或者是功能实体。如果是在应用的具体层面,应当是与实际对象相对应的实体,或值对象。
这实际上是与具体的需求也是分不开的。
比如,数据库操作,如果只写一个类,是否符合SRP?假如核心代码面对的应用不会操作另一种数据库。那它是符合SRP的。
但是,如果应用大了,涉及到与其它应用集成,就会产生要访问其它类型的数据的问题。这时,就需要将统一接口类与驱动分开。数据库核心给应用提供统一接口,但底层针对不同数据库要有不同的驱动类。
再进一步,当访问量加大,则需要多数据库支持,因而,就需要将链接管理独立出来,将表访问管理独立出来,实现多数据库与多表调度访问。
所以,SRP是由设计者把控的。好的设计者,应当清楚潜在需求,从而使类,函数均符合SRP。

OCP:The Open Closed Principle   开放封闭原则
开放闭合原则是指,对扩展开放,对修改关闭。这里的扩展,不单指简单的继承一个类,而有可能通过增加类的方式扩展。
这是针对一个类的组合而言。任意一个类的组合实现之前,我们先要考虑的是,其中哪一些部分虽然属于与具体数据无关,但仍与具体的实际情况的变化有关。这就是说,这一部分中,仍要将其分类核心与具体两部分。所谓具体的,则就是有可能会增加的,有可能会变化的部分。而这些具体的部分,就需要分析它是什么样的调用模式,是具体调用核心,那具体情况下,只要继承此类,对其扩展即可。如果是核心调用具体,则我们要用面向对象的抽象类,接口将其定义出来。这样的代码就能从容应对变化。
比如前述的数据库操作,统一接口是核心代码中的核心,而针对不同数据库的驱动,则是具体的问题。因而,将其独立到一个类中,就可以实现方便的扩展。

LSP: The Liskov Substitution Principle   里氏替换原则
里氏替换原则是指:当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系。
这里所讲的仍是变与不变的关系。哪些东西维持不变才能符合LSP呢?看一下不符合LSP的情况更易于了解:
当子类中不存在父类规定要实现的方法时,就不能替的父类的实例。同样,当子类中方法返回的数据类型与父类同一方法返回的数据类型不一致。等等等等。
由此,这就需要维持接口的一致性,否则,我们就会给调用方带来很多的if else, switch case,而这些都是不必要的。
很显然,这也会造成程序逻辑的混乱,难以维护与扩展。

ISP:  The Interface Segregation Principle    接口分离原则
接口分离原则是指:不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。
这里牵涉到里氏替换原则中的问题,既然里氏替换原则中为允许有这样的变化,那么,这样的变化如何处理?
PHP不同于C++,不允许多继承,即不允许同时继承两个类。但可以继承的同时再实现相关的接口,一个类,可以同时实现多个接口。因此,多个接口可以在需要时实现组合,这样用户就不会被强迫依赖那些他们不使用的接口。

DIP:  The Dependency Inversion Principle  依赖倒置原则
依赖倒置原则是指:1. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象;2. 抽象不应该依赖于细节,细节应该依赖于抽象
其中的高层模块,实际是指核心代码,低层模块是指具体代码。很显然,这里涉及到的是核心代码调用具体代码的模式。要让高层模块不应该依赖于低层模块,那就是核心代码中不能涉及具体的一切,不能因任一具体在代码中增加任何的if else类似的控制结构。
二者都应该依赖于抽象,则是指,应当将具体的直接纳入到具体的类中,二者之间通过抽象类或接口定义好约定的调用关系。
抽象不应该依赖于细节,细节应该依赖于抽象,实际是指,上面的这些模式,首先要把握,哪些是细节,是具体问题,哪些是核心代码。将变与不变划分清楚。
我们可以看出,如果没有这些原则,很多时间,我们对应用的功能模块划分是很困难的。但如果划分完成,接下来,编写这样的代码也是有难度的。因为,很多时候,我们要面向的是抽象,是一种面向抽象编程。要能够更好的面向抽象,设计模式的运用是必不可少的技能。

下面再简单介绍一下其它面向对象的原则。

CARP: The Composite/Aggregate Reuse Principle 合成聚合复用原则
合成聚合复用原则的定义:在一个新对象中使用一些已有的对象,使之成为新对象的一部分。新的对象通过向这些对象的委派达到复用的目的。
提出这一原则的根本点在于,很多编码的问题是滥用继承。通过SOLID原则,我们可能分出了较多的类。
如果最终我们仍是使用SOLID原则将这些类最终继承成为一个类,一下子将我们的松耦合的目标又破坏了。这是因为,继承实现是静态的,很难应对父类的变化。而使用合成聚合复用,则是动态的。
所以,这一原则简单来说可以这样理解,如果SOLID把一个类分成了多个类,则不要设法通过继承让其再成为一个类,而要使用合成聚合复用的手段。这就是说另行定义一个新类,或使用其中一个类,调用其它被分出的类。

LoD: The Law of Demeter 迪米特法则
迪米特法则是:不要和陌生人说话。
这实际上是与单一职责原则已经对此有所强制。而这一法侧更强调细节。
迪米特法测中明确规定了哪些是朋友。除朋友之外,所有算法均不可以与朋友之外的数据对话。迪米特法则朋友的条件有:
1)当前对象本身
2)以参数形式传入本对象中的对象
3)当前对象的实例变量直接引用的对象
4)当前对象如果是一个聚集,那么,聚集中的元素也是朋友
5)当前对象所创建的对象
可以看出,迪米特法则有助于我们在编码时清楚界定核心与具体。并协助我们创建最松的耦合关系。

IoC: The Inversion of Conntrol 控制倒置原则
控制倒置原则是指:不要调用我,让我来调用你。实际上就是指核心调用具体的一个模式。
这就是说,核心调用具体,并不是天生就有的,而是我们针对具体问题而做的设计。
这里,实际上产生了两个控制权的倒转:
第一,调用控制权倒转。由核心代码获得。
第二,流程控制权与具体实现控制权倒转,由具体代码获得。
具体而言,如果是让用户直接使用的类,调用控制权上由具体代码掌控的。而流程控制权与具体实现控制权已不存在,或者说已被类的编写者使用。
但当你要继承一个抽象类,或实现一个接口,则就产生了对应的控制倒置。
有人说,控制倒置原则与依赖倒置原则是同义原则。实际上二者并不是等同的。因为,当然,这二者所说的实际是同一问题的两个不同的层面。
控制倒置原则的要求是,交还具体代码的控制权,并强行使用调用控制权。这就是说,凡不属于核心代码的流程控制的一切,均交给具体代码处理。这样一来,我们就更易于处理核心调用具体时对抽象类或接口的定义。

有人说,弄通SOLID原则,或者再加上这三大原则,那么,23个设计模式根本不需要再看了。理论上来讲是这样的。但实际上,如果不背九九乘法表,能计算乘法的,此人是天才。接下来,我们要开始进一步讨论具体的23个设计模式了。不过,有些不同的是,这里的讨论,是没有代码示例的。如同几何老师只讲定理,不给出例题。因为,相对于设计模式,所有“例题”都是与实际脱钩的。我们的目标,是希望没有“例题”的讨论,更加有利于能够运用到实际当中。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息