上一篇我们介绍了设计模式的特性并且详细讲解了4种创建型模式,创建型模式是负责如何产生对象实例的,现在我们继续来给大家介绍结构型模式。

一、什么是结构型模式?

结构型模式是解析类和对象的内部结构和外部组合,通过优化程序结构解决模块之间的耦合问题。

结构型模式的种类:

适配器模式

桥接模式

装饰模式

组合模式

外观模式

享元模式

代理模式


1、适配器模式(Adapter)

将一个类的接口转换成客户希望的另一个接口,适配器模式使得原本的由于接口不兼容而不能一起工作的那些类可以一起工作。

应用场景:老代码接口不适应新的接口需求,或者代码很多很乱不便于继续修改,或者使用第三方类库

代码实现

<?php/***优才网公开课示例代码**适配器模式**@author优才网全栈工程师教研组*@seehttp://www.ucai.cn*///老的代码classUser{private$name;function__construct($name){$this->name=$name;}publicfunctiongetName(){return$this->name;}}//新代码,开放平台标准接口interfaceUserInterface{functiongetUserName();}classUserInfoimplementsUserInterface{protected$user;function__construct($user){$this->user=$user;}publicfunctiongetUserName(){return$this->user->getName();}}$olduser=newUser('张三');echo$olduser->getName()."\n";$newuser=newUserInfo($olduser);echo$newuser->getUserName()."\n";?>


注意点:这里的新接口使用了组合方式,UserInfo内部有一个成员变量保存老接口User对象,模块之间是松耦合的,这种结构其实就是组合模式。不要使用继承,虽然UserInfo继承User也能达到同样的目的,但是耦合度高,相互产生影响。


2、桥接模式

将抽象部分与它的实现部分分离,使它们都可以独立变化

特点:独立存在,扩展性强

应用:需要不断更换调用对象却执行相同的调用方法,实现扩展功能

代码实现

<?php/***优才网公开课示例代码**桥接模式**@author优才网全栈工程师教研组*@seehttp://www.ucai.cn*/abstractclassPerson{abstractfunctiongetJob();}classStudentextendsPerson{publicfunctiongetJob(){return'学生';}}classTeacherextendsPerson{publicfunctiongetJob(){return'老师';}}classBridgeObj{protected$_person;publicfunctionsetPerson($person){$this->_person=$person;}publicfunctiongetJob(){return$this->_person->getJob();}}$obj=newBridgeObj();$obj->setPerson(newStudent());printf("本次桥接对象:%s\n",$obj->getJob());$obj->setPerson(newTeacher());printf("本次桥接对象:%s\n",$obj->getJob());?>


3、装饰模式

动态地给一个对象添加额外的职责。在原有的基础上进行功能增强。

特点:用来增强原有对象功能,依附于原有对象。

应用:用于需要对原有对象增加功能而不是完全覆盖的时候

代码实现

<?php/***优才网公开课示例代码**装饰模式**@author优才网全栈工程师教研组*@seehttp://www.ucai.cn*///产品abstractclassPerson{abstractfunctiongetPermission();}//被装饰者classUserextendsPerson{publicfunctiongetPermission(){return'公开文档';}}//装饰类classPermUserextendsPerson{protected$_user;protected$_special='';function__construct($user){$this->_user=$user;}publicfunctiongetPermission(){return$this->_user->getPermission().$this->_special;}}//装饰类产品classJavaUserextendsPermUser{protected$_special='java程序';}classCPlusUserextendsPermUser{protected$_special='c++程序';}$user=newUser();printf("permission:%s\n",$user->getPermission());$user=newJavaUser($user);printf("permission:%s\n",$user->getPermission());$user=newCPlusUser($user);printf("permission:%s\n",$user->getPermission());?>


大家想想装饰和继承的区别在哪?

如果是上面的例子,如果用继承,是CPlusUser继承JavaUser还是反过来呢?谁也不知道最终使用者需要哪一种。

在多层关系的情况下,装饰是和顺序无关并且随时增加装饰,而继承只能是特定的顺序,所以装饰模式会更加的灵活。


4、组合模式

将对象组合成树形结构表示“部分-整体”的层次结构。

特点:灵活性强

应用:对象的部分-整体的层次结构,模糊组合对象和简单对象处理问题

代码实现

<?php/***优才网公开课示例代码**组合模式**@author优才网全栈工程师教研组*@seehttp://www.ucai.cn*///继承模式classUserBaseInfo{private$name;function__construct($name){$this->name=$name;}publicfunctiongetName(){return$this->name;}}classUserextendsUserBaseInfo{private$login=false;publicfunctionsetLogin($islogin){$this->login=$islogin;}publicfunctionisLogin(){return$this->login;}}$user=newUser('张三');$user->setLogin(true);if($user->isLogin()){echo$user->getName()."已经登录了\n";}else{echo$user->getName()."还没有登录\n";}//组合模式classLoginInfo{protected$user;protected$login=false;publicfunctionsetLogin($user,$isLogin){$this->user=$user;$this->login=$isLogin;}publicfunctionisLogin(){return$this->login;}}$user=newUser('张三');$login=newLoginInfo();$login->setLogin($user,true);if($login->isLogin()){echo$user->getName()."已经登录了\n";}else{echo$user->getName()."还没有登录\n";}//部分可以更换,用继承则不行classAdmin{protected$level;function__construct($level){$this->level=$level;}functiongetLevel(){return$this->level;}}$admin=newAdmin(1);$login->setLogin($admin,true);if($login->isLogin()){printf("级别为%d的管理员已经登录了\n",$admin->getLevel());}else{printf("级别为%d的管理员还没有登录\n",$admin->getLevel());}?>


上面的例子分别展示了使用继承和组合来处理新功能,在简单的情况下看似区别不大,但在项目后期越来越复杂的时候组合模式的优势就越来越明显了。

例如上面的登录信息,如果要增加登录次数、最后登录时间、登录ip等信息,登录本身就会变成一个比较复杂的对象。如果以后有新的需求比如好友信息、用户的访问信息等,再要继承的话,用户类就会变得非常庞大,难免各父类之间没有冲突的变量和方法,而外部访问用户类的众多方法也变得很费劲。采用组合模式后,一个类负责一个角色,功能区分非常明显,扩展方便。


5、外观模式(门面模式)

为了系统中的一组接口提供一个一致的界面

特点:向上抽取,有共性

应用:内部接口众多,由统一的接口来调用

<?php/***优才网公开课示例代码**外观模式,也叫门面模式**@author优才网全栈工程师教研组*@seehttp://www.ucai.cn*/classOperation{publicfunctiontestPlus(){printf("plus:%s\n",(1+2==3?'true':'false'));}publicfunctiontestMinus(){printf("minus:%s\n",(3-2==2?'true':'false'));}publicfunctiontestTimes(){printf("times:%s\n",(2*3==6?'true':'false'));}}classTester{protected$_operation;function__construct(){$this->_operation=newOperation();}publicfunctiontestAll(){$this->_operation->testPlus();$this->_operation->testMinus();$this->_operation->testTimes();}}//测试用例,测试全部接口$tester=newTester();$tester->testAll();?>


门面模式估计大家在实际代码中都已经使用到了,接口较多时把相似功能的接口封装成一个接口供外部调用,这就是门面模式。


6、享元模式

运用共享技术有效地支持大量细粒度对象,采用一个共享来避免大量有相同内容对象的开销。这种开销中最直观的就是内存的损耗。

特点:高效性,共享性

应用:系统底层的设计。例如字符串的创建。如果两个字符串相同,则不会创建第二个字符串,而是第二个的引用直接指向第一个字符串。$str1=”abc”,$str2=”abc”.则内存存储中只会创建一个字符串“abc”而引用$str1.$str2都会指向它。


7、代理模式

为其他对象提供一个代理来控制对这个对象的访问,就是给某一对象提供代理对象,并由代理对象控制具体对象的引用。能够协调调用者和被调用者,能够在一定程度上降低系统的耦合性。

特点:低耦合性,独立性好,安全性

应用:客户访问不到或者被访问者希望隐藏自己,所以通过代理来访问自己。

代码实现

<?php/***优才网公开课示例代码**代理模式**@author优才网全栈工程师教研组*@seehttp://www.ucai.cn*///内部对象classUser{publicfunctiongetName(){return'张三';}publicfunctiongetType(){return'付费用户';}}//代理接口定义,例如开放平台interfaceUserInterface{functiongetName();}//代理对象classUserProxyimplementsUserInterface{protected$_user;function__construct(){$this->_user=newUser();}publicfunctiongetName(){return$this->_user->getName();}}//内部调用$user=newUser();printf("username:%s\n",$user->getName());printf("usertype:%s\n",$user->getType());//外部调用//$user=newUserProxy();//printf("username:%s\n",$user->getName());//printf("usertype:%s\n",$user->getType());//不能访问,及时知道内部对象有这个方法?>


二、总结

代理模式、适配器模式、门面模式、装饰模式的区别

相同之处:都封装一个内部对象,调用内部对象的方法

不同之处:各自有各自的特性和应用场景,不能相互替代。所以用的时候要仔细分析用那种合适。

关于模式的选用问题

模式的选用要根据实际的业务需求,通过对业务逻辑的仔细分析,再根据模式具有的特性和应用场景进行合理的选择和区分。大部分情况下业务的场景决定了哪种模式,而不是选择哪个模式去实现一个业务,少数情况几种模式确实都能解决问题,那主要就是考虑以后的扩展了。

到这里我们已经了解了7种结构型模式,下一篇我们继续给大家介绍设计模式的行为型模式,先预览一下行为型模式的种类吧:

模版方法模式

命令模式

迭代器模式

观察者模式

终结者模式

备忘录模式

解释器模式

状态模式

策略模式

职责链模式

访问者模式