一、什么是结构型模式?
结构型模式是解析类和对象的内部结构和外部组合,通过优化程序结构解决模块之间的耦合问题。
二、结构型模式的种类:
适配器模式
桥接模式
装饰模式
组合模式
外观模式
享元模式
代理模式
1、 适配器模式(adapter)
将一个类的接口转换成客户希望的另一个接口,适配器模式使得原本的由于接口不兼容而不能一起工作的那些类可以一起工作。
应用场景:老代码接口不适应新的接口需求,或者代码很多很乱不便于继续修改,或者使用第三方类库。
代码实现
/** * 适配器模式 */ //老的代码 class user { private $name; function __construct($name) { $this->name = $name; } public function getname() { return $this->name; } } //新代码,开放平台标准接口 interface userinterface { function getusername(); } class userinfo implements userinterface { protected $user; function __construct($user) { $this->user = $user; } public function getusername() { return $this->user->getname(); } } $olduser = new user('张三'); echo $olduser->getname().n; $newuser = new userinfo($olduser); echo $newuser->getusername().n; 复制代码
注意点:这里的新接口使用了组合方式,userinfo内部有一个成员变量保存老接口user对象,模块之间是松耦合的,这种结构其实就是组合模式。不要使用继承,虽然userinfo继承user也能达到同样的目的,但是耦合度高,相互产生影响。
2、 桥接模式
将抽象部分与它的实现部分分离,使它们都可以独立变化
特点:独立存在,扩展性强
应用:需要不断更换调用对象却执行相同的调用方法,实现扩展功能
代码实现
/** * * 桥接模式 * */ abstract class person { abstract function getjob(); } class student extends person { public function getjob() { return '学生'; } } class teacher extends person { public function getjob() { return '老师'; } } class bridgeobj { protected $_person; public function setperson($person) { $this->_person = $person; } public function getjob() { return $this->_person->getjob(); } } $obj = new bridgeobj(); $obj->setperson(new student()); printf(本次桥接对象:%sn, $obj->getjob()); $obj->setperson(new teacher()); printf(本次桥接对象:%sn, $obj->getjob()); 复制代码
3、 装饰模式
动态地给一个对象添加额外的职责。在原有的基础上进行功能增强。
特点:用来增强原有对象功能,依附于原有对象。
应用:用于需要对原有对象增加功能而不是完全覆盖的时候
代码实现
/** * * 装饰模式 * */ //产品 abstract class person { abstract function getpermission(); } //被装饰者 class user extends person { public function getpermission() { return '公开文档'; } } //装饰类 class permuser extends person { protected $_user; protected $_special = ''; function __construct($user) { $this->_user = $user; } public function getpermission() { return $this->_user->getpermission() . $this->_special; } } //装饰类产品 class javauser extends permuser { protected $_special = ' java程序'; } class cplususer extends permuser { protected $_special = ' c++程序'; } $user = new user(); printf(permission:%sn, $user->getpermission()); $user = new javauser($user); printf(permission:%sn, $user->getpermission()); $user = new cplususer($user); printf(permission:%sn, $user->getpermission()); 复制代码
大家想想装饰和继承的区别在哪?
如果是上面的例子,如果用继承,是cplususer继承javauser还是反过来呢?谁也不知道最终使用者需要哪一种。
在多层关系的情况下,装饰是和顺序无关并且随时增加装饰,而继承只能是特定的顺序,所以装饰模式会更加的灵活。
4、组合模式
将对象组合成树形结构表示“部分-整体”的层次结构。
特点:灵活性强
应用:对象的部分-整体的层次结构,模糊组合对象和简单对象处理问题
代码实现
/** * 组合模式 * */ //继承模式 class userbaseinfo { private $name; function __construct($name) { $this->name = $name; } public function getname() { return $this->name; } } class user extends userbaseinfo { private $login = false; public function setlogin($islogin) { $this->login = $islogin; } public function islogin() { return $this->login; } } $user = new user('张三'); $user->setlogin(true); if ($user->islogin()) { echo $user->getname().已经登录了n; } else { echo $user->getname().还没有登录n; } //组合模式 class logininfo { protected $user; protected $login = false; public function setlogin($user, $islogin) { $this->user = $user; $this->login = $islogin; } public function islogin() { return $this->login; } } $user = new user('张三'); $login = new logininfo(); $login->setlogin($user, true); if ($login->islogin()) { echo $user->getname().已经登录了n; } else { echo $user->getname().还没有登录n; } //部分可以更换,用继承则不行 class admin { protected $level; function __construct($level) { $this->level = $level; } function getlevel() { return $this->level; } } $admin = new admin(1); $login->setlogin($admin, true); if ($login->islogin()) { printf(级别为 %d 的管理员已经登录了n, $admin->getlevel()); } else { printf(级别为 %d 的管理员还没有登录n, $admin->getlevel()); } 复制代码
上面的例子分别展示了使用继承和组合来处理新功能,在简单的情况下看似区别不大,但在项目后期越来越复杂的时候组合模式的优势就越来越明显了。
例如上面的登录信息,如果要增加登录次数、最后登录时间、登录ip等信息,登录本身就会变成一个比较复杂的对象。如果以后有新的需求比如好友信息、用户的访问信息等,再要继承的话,用户类就会变得非常庞大,难免各父类之间没有冲突的变量和方法,而外部访问用户类的众多方法也变得很费劲。采用组合模式后,一个类负责一个角色,功能区分非常明显,扩展方便。
5、 外观模式(门面模式)
为了系统中的一组接口提供一个一致的界面
特点:向上抽取,有共性
应用:内部接口众多,由统一的接口来调用
代码实现
class operation { public function testplus() { printf(plus: %sn, (1 + 2 == 3 ? 'true' : 'false')); } public function testminus() { printf(minus: %sn, (3 - 2 == 2 ? 'true' : 'false')); } public function testtimes() { printf(times: %sn, (2 * 3 == 6 ? 'true' : 'false')); } } class tester { protected $_operation; function __construct() { $this->_operation = new operation(); } public function testall() { $this->_operation->testplus(); $this->_operation->testminus(); $this->_operation->testtimes(); } } //测试用例,测试全部接口 $tester = new tester(); $tester->testall(); 复制代码
门面模式估计大家在实际代码中都已经使用到了,接口较多时把相似功能的接口封装成一个接口供外部调用,这就是门面模式。
6、 享元模式
运用共享技术有效地支持大量细粒度对象,采用一个共享来避免大量有相同内容对象的开销。这种开销中最直观的就是内存的损耗。
特点:高效性,共享性
应用:系统底层的设计。例如字符串的创建。如果两个字符串相同,则不会创建第二个字符串,而是第二个的引用直接指向第一个字符串。$str1=”abc”,$str2=”abc”.则内存存储中只会创建一个字符串“abc”而引用$str1.$str2都会指向它。
7、 代理模式
为其他对象提供一个代理来控制对这个对象的访问,就是给某一对象提供代理对象,并由代理对象控制具体对象的引用。能够协调调用者和被调用者,能够在一定程度上降低系统的耦合性。
特点:低耦合性,独立性好,安全性
应用:客户访问不到或者被访问者希望隐藏自己,所以通过代理来访问自己。
代码实现
//内部对象 class user { public function getname() { return '张三'; } public function gettype() { return '付费用户'; } } //代理接口定义,例如开放平台 interface userinterface { function getname(); } //代理对象 class userproxy implements userinterface { protected $_user; function __construct() { $this->_user = new user(); } public function getname() { return $this->_user->getname(); } } //内部调用 $user = new user(); printf(user name:%sn, $user->getname()); printf(user type:%sn, $user->gettype()); //外部调用 // $user = new userproxy(); // printf(user name:%sn, $user->getname()); // printf(user type:%sn, $user->gettype()); //不能访问,及时知道内部对象有这个方法 复制代码
三、总结
1、代理模式、适配器模式、门面模式、装饰模式的区别
a、 相同之处:都封装一个内部对象,调用内部对象的方法
b、 不同之处:各自有各自的特性和应用场景,不能相互替代。所以用的时候要仔细分析用那种合适。
2、 关于模式的选用问题
模式的选用要根据实际的业务需求,通过对业务逻辑的仔细分析,再根据模式具有的特性和应用场景进行合理的选择和区分。大部分情况下业务的场景决定了哪种模式,而不是选择哪个模式去实现一个业务,少数情况几种模式确实都能解决问题,那主要就是考虑以后的扩展了。
到这里我们已经了解了7种结构型模式,下一篇我们继续给大家介绍设计模式的行为型模式,先预览一下行为型模式的种类吧:
u 模版方法模式
u 命令模式
u 迭代器模式
u 观察者模式
u 终结者模式
u 备忘录模式
u 解释器模式
u 状态模式
u 策略模式
u 职责链模式
u 访问者模式