常见的面向对象设计模式大约有23种,但是自从接触PHP后,渐渐发现常见的设计模式好像少了很多,网络上的资料也比较少,身边的PHP同事们有的甚至没有听说过设计模式,这也有可能是PHP的发展所带来的,因为PHP对面向对象支持的比较晚,好多PHP程序员还按照面向过程的思想写代码。于是,我决定把原来用C#写的面向对象设计模式用PHP改写。
经常听见其他程序员(、 C#)等说PHP是不是运行在浏览器端的脚本语言,其实我在接触PHP之前也这样认为过,后来发现PHP是用C语言开发出来的一种语言,C语言是真正意义 上跨平台的语言,这也注定PHP是跨平台的,PHP是可运行在Windows Server或Linux操作系统的服务器上的语言,它和Java以及C#一样,代码存储并运行在服务器端,它将浏览器端可执行的HTML以及脚本发送给 浏览器执行,PHP相对Java和C#对于面向过程的封装更多,减少部分数据类型的支持。
PHP是面向对象的,PHP能够得到这样快速的发展,能够被互联网大企业所广泛应用,证明PHP是经得住考验的,随着移动互联网的不断发展,PHP还会更广阔的天地。
什么是设计模式?
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式于己于他人于系统都是多赢的;设计模式使代码编制真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。
设计模式的优点:
在接下来的篇幅里会详细介绍PHP面向对象设计模式,本系列博客以设计模式的原理为核心,列举最简单的例子,让只要有一点面向对象基础的读者都能看明白。
一般认为遵从以下六大原则的代码是易扩展可复用的代码:
这六大原则任何面向对象的语言都应该遵守的,要想让你的代码易扩展高服用就尽量去满足这六大原则吧,不一定严格按照某种设计模式,但是如果你的代码符合这六大原则,那么你的代码就是好代码了,好的代码不一定是严格按照设计模式写的代码。
1.单一职责
定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。
场景:类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障,关系如下图:
修改:遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险,结构如下图:
优点:
1)、可以降低类的复杂度,一个类只负责一项职责,逻辑简单;
2)、提高类的可读性,提高系统的可维护性;
3)、变更引起的风险降低,变更是必然的。
2.里氏代换原则
定义:所有引用基类的地方必须能透明地使用其子类的对象,也就是说子类可以扩展父类的功能,但不能改变父类原有的功能
场景:有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障,如下图:
CountPriceByJKL类继承于CountPrice类,CountPriceByJKL重写了Count()方法,这样可能影响到原来Count方法的功能。
修改:当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。
3.依赖倒置原则
定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
此处理解起来是最困难的,一般会在项目框架的搭建的时候用到,例如,业务逻辑层相对于数据层是高层模块,因为业务逻辑层需要调用数据层去连接,但是要做到可扩展高复用,尽量不要让业务逻辑层依赖数据层,可以在数据层抽象出一个接口,让业务逻辑层依赖于这个抽象接口。
场景:类A(高层模块)直接依赖类B(低层模块),假如要将类A改为依赖类C(低层模块),则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
AutoSystem类直接依赖于HondaCar与FordCar两个类,这样就产生了一个高耦合,AutoSystem类想操控HondaCar或者FordCar必须直接创建相应对象。
修改:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率,如下图:
经过此番修改,Honda与Ford实现ICar接口,提供了Run、Stop以及Turn功能方法,AutoSystem依赖ICar接口,这样迫使AutoSystem依赖抽象接口,这就使得AutoSystem类能够应对更多的需求变化。
优点:
1)、低层模块尽量都要有抽象类或接口,或者两者都有。
2)、变量的声明类型尽量是抽象类或接口。
3)、使用继承时遵循里氏替换原则。
4.接口隔离原则
定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
场景:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法,如下图:
修改:将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
注意:
1)、接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性 是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
2)、为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
3)、提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。
5.迪米特法则(最少知道原则)
定义:一个对象应该对其他对象保持最少的了解。
场景:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
简单的理解就是高内聚,一个类尽量减少对其他对象的依赖,并且这个类的方法和属性能用私有的就尽量私有化。
注意:
1)、只与直接的朋友通信,不要和陌生人说话。
2)、过分的使用该原则,将导致系统复杂度变大。所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合。
6.开闭原则
定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
场景:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。
建议:当软件需求变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
前面两节内容介绍了什么是设计模式以及六大原则,相信看完前两节内容大家对设计模式已经有了初步的认识,接下来说一下设计模式的分类。
一般将面向对象设计模式分为三类:创建型、结构型、行为型三种。
创建型:创建对象时,不再由我们直接实例化对象;而是根据特定场景,由程序来确定创建对象的方式,从而保证更大的性能、更好的优势。创建型模式主要有简单工厂模式(并不是23种设计模式之一)、工厂方法、抽象工厂模式、单例模式、生成器模式、原型模式。
结构型:用于帮助将多个对象组织成更大的结构。结构型模式主要有适配器模式、桥接模式、组合器模式、装饰器模式、门面模式、亨元模式和代理模式。
行为型:用于帮助系统间各对象的通信,以及如何控制复杂系统中流程。行为型模式主要有命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板模式、访问者模式和职责链模式。
今天主要介绍创建型的第一种简单工厂模式。
注意:在阅读本系列博客的时候一定要有阅读UML类图、面向对象PHP编程基础。
简单工厂模式不属于23种常用面向对象设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。其实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。
角色及职责:
工厂(SimpleFactory)角色:简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。
抽象产品(IProduct)角色:简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
具体产品(Concrete Product)角色:是简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。
需求:根据提供相应的属性值由简单工厂创建具有相应特性的产品对象。
现根据以上UML类图编写如下PHP代码。
- <?php
- /**
- * Created by PhpStorm.
- * User: Jiang
- * Date: 2015/4/9
- * Time: 21:48
- */
- /**抽象产品角色
- * Interface IProduct 产品接口
- */
- interface IProduct
- {
- /**X轴旋转
- * @return mixed
- */
- function XRotate();
- /**Y轴旋转
- * @return mixed
- */
- function YRotate();
- }
- /**具体产品角色
- * Class XProduct X轴旋转产品
- */
- class XProduct implements IProduct
- {
- private $xMax=1;
- private $yMax=1;
- function __construct($xMax,$yMax)
- {
- $this->xMax=$xMax;
- $this->yMax=1;
- }
- function XRotate()
- {
- echo "您好,我是X轴旋转产品,X轴转转转。。。。。。";
- }
- function YRotate()
- {
- echo "抱歉,我是X轴旋转产品,我没有Y轴。。。。。。";
- }
- }
- /**具体产品角色
- * Class YProduct Y轴旋转产品
- */
- class YProduct implements IProduct
- {
- private $xMax=1;
- private $yMax=1;
- function __construct($xMax,$yMax)
- {
- $this->xMax=1;
- $this->yMax=$yMax;
- }
- function XRotate()
- {
- echo "抱歉,我是Y轴旋转产品,我没有X轴。。。。。。";
- }
- function YRotate()
- {
- echo "您好,我是Y轴旋转产品,Y轴转转转。。。。。。";
- }
- }
- /**具体产品角色
- * Class XYProduct XY轴都可旋转产品
- */
- class XYProduct implements IProduct
- {
- private $xMax=1;
- private $yMax=1;
- function __construct($xMax,$yMax)
- {
- $this->xMax=$xMax;
- $this->yMax=$yMax;
- }
- function XRotate()
- {
- echo "您好,我是XY轴都可旋转产品,X轴转转转。。。。。。";
- }
- function YRotate()
- {
- echo "您好,我是XY轴都可旋转产品,Y轴转转转。。。。。。";
- }
- }
- /**工厂角色
- * Class ProductFactory
- */
- class ProductFactory
- {
- static function GetInstance($xMax,$yMax)
- {
- if($xMax>1 && $yMax===1)
- {
- return new XProduct($xMax,$yMax);
- }
- elseif($xMax===1 && $yMax>1)
- {
- return new YProduct($xMax,$yMax);
- }
- elseif($xMax>1 && $yMax>1)
- {
- return new XYProduct($xMax,$yMax);
- }
- else
- {
- return null;
- }
- }
- }
测试代码:
- <?php
- /**
- * Created by PhpStorm.
- * User: Jiang
- * Date: 2015/4/9
- * Time: 21:54
- */
- require_once "./SimpleFactory/SimpleFactory.php";
- header("Content-Type:text/html;charset=utf-8");
- $pro=array();
- $pro[]=ProductFactory::GetInstance(1,12);
- $pro[]=ProductFactory::GetInstance(12,1);
- $pro[]=ProductFactory::GetInstance(12,12);
- $pro[]=ProductFactory::GetInstance(0,12);
- foreach($pro as $v)
- {
- if($v)
- {
- echo "<br/>";
- $v->XRotate();
- echo "<br/>";
- $v->YRotate();
- }
- else
- {
- echo "非法产品!<br/>";
- }
- echo "<hr/>";
- }
用浏览器访问测试代码,我们可以发现创建的对象依次是YProduct,XProduct,XYProduct,null。简单工厂的核心代码在于工厂 (ProductFactory)这个角色,这里根据传入的xMax与yMax值去创建不同的对象,这便是简单工厂的实质,而且我们在测试调用客户端根本 不知道具体的产品类是什么样,这样就做到了调用与创建的分离。
简单工厂的优点:让对象的调用者和对象创建过程分离,当对象调用者需要对象时,直接向工厂请求即可。从而避免了对象的调用者与对象的实现类以硬编码方式耦合,以提高系统的可维护性、可扩展性。
简单工厂的缺点:当产品修改时,工厂类也要做相应的修改,比如要增加一种操作类,如求M数的N次方,就得改case,修改原有类,违背了开放-封闭原则。
更多详情敬请关注我的视频课程: