17.2适配器模式

适配器模式(Adapter),将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器的作用:
在软件开发中,也就是系统的数据和行为都正确,但接口不符时,我们应该考虑用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况,比如需要对早期代码复用一些功能等应用上很有实际价值。
在GoF的设计模式中,对适配器模式讲了两种类型,类适配器模式和对象适配器模式,由于类适配器模式通过多重继承对一个接口与另一个接口进行匹配,而C#、VB.NET、JAVA等语言都不支持多重继承(C++支持),也就是一个类只有一个父类,所以我们这里主要讲的是对象适配器。
适配器模式(Adapter)结构图 第十七章 在NBA我需要翻译——适配器模式 - 图1代码示例
Target(这是客户所期待的接口。目标可以是具体的或抽象的类)代码如下:

  1. class Target
  2. {
  3. public function Request()
  4. {
  5. echo '普通请求!' . PHP_EOL;
  6. }
  7. }

Adaptee(需要适配的类)代码如下:

  1. class Adaptee
  2. {
  3. public function SpecificRequest()
  4. {
  5. echo '特殊请求!' . PHP_EOL;
  6. }
  7. }

Adapter(通过在内部包装一个Adaptee对象,把源接口转换成目标接口)代码如下:

  1. class Adapter extends Target
  2. {
  3. private $adaptee = null;
  4. public function __construct()
  5. {
  6. $this->adaptee = new Adaptee();
  7. }
  8. public function Request()
  9. {
  10. $this->adaptee->SpecificRequest();
  11. }
  12. }

客户端代码如下:

  1. public function adapterDemo()
  2. {
  3. $target = new AdapterDemo();
  4. $target->Request();
  5. }

17.3何时使用适配器模式

适配器模式的使用场景

  • 在想使用一个已经存在的类,但如果它的接口,也就是它的方法和你的要求不相同时,就应该考虑用适配器模式。
  • 两个类所做的事情相同或相似,但是具有不同的接口时要使用它。而且由于类都共享同一个接口,客户代码可以统一调用同一接口就行了,这样应该可以更简单、更直接、更紧凑。
  • 通常实在软件开发后期或维护期再考虑使用适配器模式。
  • 在双方都不太容易修改的时候再使用适配器模式适配,而不是一有不同时就使用。
  • 当设计一个系统是考虑使用第三方开发组件,而这个组件的接口与自己的系统接口不相同时,考虑使用适配器模式(软件设计之初适配器模式的使用场景)。

    17.4篮球翻译适配器

    代码实现
    球员类

    1. abstract class Player
    2. {
    3. protected $name = '';
    4. public function __construct($name)
    5. {
    6. $this->name = $name;
    7. }
    8. abstract function Attack();
    9. abstract function Defense();
    10. }

    后卫、中锋、前锋类

    1. //前锋
    2. class Forwards extends Player
    3. {
    4. public function __construct($name)
    5. {
    6. parent::__construct($name);
    7. }
    8. public function Attack()
    9. {
    10. echo '前锋 ' . $this->name . ' 进攻' . PHP_EOL;
    11. }
    12. public function Defense()
    13. {
    14. echo '前锋 ' . $this->name . ' 防守' . PHP_EOL;
    15. }
    16. }
    17. //中锋
    18. class Center extends Player
    19. {
    20. //与前锋代码类似,略
    21. }
    22. //后卫
    23. class Guards extends Player
    24. {
    25. //与前锋代码类似,略
    26. }

    外籍中锋类

    1. //外籍中锋
    2. class ForeignCenter
    3. {
    4. private $name = '';
    5. public function setName($name)
    6. {
    7. $this->name = $name;
    8. }
    9. public function Attack()
    10. {
    11. echo '外籍中锋 ' . $this->name . ' 进攻' . PHP_EOL;
    12. }
    13. public function Defense()
    14. {
    15. echo '外籍中锋 ' . $this->name . ' 防守' . PHP_EOL;
    16. }
    17. }

    翻译者类

    1. //翻译者
    2. class Translator extends Player
    3. {
    4. private $wjzf = null;
    5. public function __construct($name)
    6. {
    7. parent::__construct($name);
    8. $this->wjzf = new ForeignCenter();
    9. $this->wjzf->setName($name);
    10. }
    11. public function Attack()
    12. {
    13. $this->wjzf->Attack();
    14. }
    15. public function Defense()
    16. {
    17. $this->wjzf->Defense();
    18. }
    19. }

    客户端代码

    1. public function adapterImp()
    2. {
    3. $b = new Forwards('巴蒂尔');
    4. $b->Attack();
    5. $m = new Guards('麦克格雷迪');
    6. $m->Attack();
    7. $ym = new Translator('姚明');
    8. $ym->Attack();
    9. $ym->Defense();
    10. }

    显示结果

    1. 前锋 巴蒂尔 进攻
    2. 后卫 麦克格雷迪 进攻
    3. 外籍中锋 姚明 进攻
    4. 外籍中锋 姚明 防守

    代码结构图 第十七章 在NBA我需要翻译——适配器模式 - 图217.5适配器模式的.NET应用
    比如在.NET中有一个类库已经实现的、非常重要的适配器,那就是DataAdapter。DataAdapter 用作 DataSet 和数据源之间的适配器以便检索和保存数据。DataAdapter 通过映射Fill(这更改了 DataSet 中的数据以便与数据源中的数据相匹配)和 Update(这更改了数据源中的数据以便与 DataSet 中的数据相匹配)来提供这一适配器。由于数据源可能是来自SQL Server,可能来自Oracle,也可能来自Access、DB2,这些数据在组织上可能有不同之处,但我们希望得到统一的 DataSet(实质是XML数据),此时用 DataAdapter 就是非常好的手段,我们不必关注不同数据库的数据细节,就可以灵活的使用数据。