之前买过个ipad玩,当用其充电器充电时遇到了点麻烦(有的同学应该知道),家里没有适合它的插座。还好,卖家送了个接头转换部件,问题轻松解决。这就是适配器的作用。
- <?php
- interface IUserInfo{
- public function getUserName();
- public function getUserId();
- public function getUserInfo();
- }
- class UserInfo implements IUserInfo{
- public function getUserId() {
- return 123;
- }
- public function getUserName() {
- return 'XXXX';
- }
- public function getUserInfo() {
- return array( 'userId' => 123, 'userName' => 'XXXX' );
- }
- }
- interface IOuterUser{
- public function getOutUserId();
- public function getOutUserName();
- public function getOutUserInfo();
- }
- class OutUser implements IOuterUser{
- public function getOutUserId() {
- return array( 'id' => 111 );
- }
- public function getOutUserName() {
- return array( 'name' => 'OOOO' );
- }
- public function getOutUserInfo() {
- return array( 'id' => 111, 'name' => 'OOOO' );
- }
- }
- ?>
看下这段代码,有两个获取用户信息的接口,一个内部,一个外部,可以发现,两个接口的实现类的返回类型有完全不同。好比,现在内部的接口就是“插座”,外部的接口就是ipad,现在需要一个“适配器”将这个外部的接口给转化成内部的。类图如下:
OutUserInfo就是我的适配器,代码如下:
- class OutUserInfo extends OutUser implements IUserInfo{
- public function getUserId() {
- $return = $this->getOutUserId();
- return $return['id'];
- }
- public function getUserName() {
- $return = $this->getOutUserName();
- return $return['name'];
- }
- public function getUserInfo() {
- $userInfo = $this->getOutUserInfo();
- return array( 'userId' => $userInfo['id'], 'userName' => $userInfo['name'] );
- }
- }
- $outUserInfo = new OutUserInfo();
- var_dump( $outUserInfo->getUserId() );
- var_dump( $outUserInfo->getUserName() );
- var_dump( $outUserInfo->getUserInfo() );
- 运行结果:
- int(111)
- string(4) "OOOO"
- array(2) {
- ["userId"]=>
- int(111)
- ["userName"]=>
- string(4) "OOOO"
- }
- [Finished in 0.3s]
有人会问,为什么不让内部的转化成外部的?相像一下,你家里现在所有的插座都是只能插两角的(ipad是三角的),现在全部转化成三角,那手机等等其他用两角的怎么充电?
适配器模式的定义
将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。适配器模式由三个角色构成:
1、Target目标角色
该角色定义把其他类转换为何种接口,也就是我们的期望接口,例子中的IUserInfo接口中就是目标角色。
2、Adaptee源角色
就是被转换的角色。例子中的OutUser
3、Adapter适配器角色
适配器的核心角色,其他的两个角色都是已经存在的角色,而适配器角色是需要新建立的,它的职责非常简单,把源角色转换为目标角色。
适配器模式的优点
1、适配器模式可以让两个没有任何关系的类在一起运行,只要适配器这个角色能够搞定他们就成。
2、增加了类的透明性
想想看,我们访问的Target目标角色,但是具体的实现都委托给了源角色,而这些对高层次模块是透明的,也是它不需要关心的。
3、提高了类的复用度
4、灵活性非常好
某天,我的ipad坏了,扔了,因此适配器拔掉就可以了。太方便了
适配器模式的使用场景
适配器模式的应用场景只要记住一点就足够了:你有动机修改一个已经上线的接口时,适配器模式可能是最适合你的模式。
适配器模式的注意事项
适配器模式最好在详细设计阶段不要考虑它,它不是为了解决还处在开发阶段的问题,而是解决正在服役的项目问题,没有一个系统分析师会在做详细设计的时候考虑使用适配器模式,这个模式使用的主要场景是扩展应用中。项目一定要遵守依赖倒置原则和里氏替换原则,否则即使在适合使用适配器的场合下,也会带来非常大的改造。
适配器模式的扩展
上面的例子是比较幸运的情况,试想一下,如果外部不是一个接口,而是多个,该如何?
- <?php
- interface IUserInfo{
- public function getUserName();
- public function getUserId();
- }
- class UserInfo implements IUserInfo{
- public function getUserId() {
- return 123;
- }
- public function getUserName() {
- return 'XXXX';
- }
- }
- interface IOuterUserId{
- public function getOutUserId();
- }
- interface IOuterUserName{
- public function getOutUserName();
- }
- class OutUserId implements IOuterUserId{
- public function getOutUserId() {
- return array( 'id' => 111 );
- }
- }
- class OutUserName implements IOuterUserName{
- public function getOutUserName() {
- return array( 'name' => 'OOOO' );
- }
- }
- ?>
代码所示,现外部是由两个接口组成,我们知道无论是java还是php都是不支持多继承的!既然这样,那就用类关联吧。
适配器代码:
- class OutUserInfo implements IUserInfo{
- private $outUserid = null;
- private $outUserName = null;
- public function __construct($outUserid,$outUserName){
- $this->outUserid = $outUserid;
- $this->outUserName = $outUserName;
- }
- public function getUserId() {
- $return = $this->outUserid->getOutUserId();
- return $return['id'];
- }
- public function getUserName() {
- $return = $this->outUserName->getOutUserName();
- return $return['name'];
- }
- }
- $outUserid = new OutUserId();
- $outUserName = new OutUserName();
- $outUserInfo = new OutUserInfo($outUserid,$outUserName);
- var_dump( $outUserInfo->getUserId() );
- var_dump( $outUserInfo->getUserName() );
- 运行结果:
- int(111)
- string(4) "OOOO"
- [Finished in 0.1s]
这种适配器叫做对象适配器,而上面的例子则是类适配器。
类适配器是类间继承,对象适配器是对象的合成关系,也可以说是类的关联关系。
适配器模式是一个补偿模式,通常用来解决接口不相容的问题,在百分之百的完美设计中是不可能使用到的,但真的有百分之百的完美设计吗?因此我们需要这么这种补偿方式来处理这完美中的不足!