17.在NBA我需要翻译 - 适配器模式(Adapter Pattern)

17.1 在NBA 我需要翻译!

  • 时间: 4月22日13点   地点: 小区外饭馆   人物: 小菜, 大鸟

     周日, 小菜与大鸟上午在家刚看完 NBA 季后赛的第一场比赛, 出去吃饭.

     “大鸟, 今天火箭开门红, 赢得真是爽呀,” 小菜感慨万分.
     “是呀, 希望能把这种势头保持到最后, 那就可以有所突破了.” 大鸟肯定地说.
     “……”

17.2 适配器模式

     “这个模式叫做适配器模式.”

适配器模式(Adapter), 讲一个类的接口转换成客户希望的另外一个接口. Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作.[DP]

     “适配器模式主要解决什么问题呢?”
     “简单地说, 就是需要的东西在眼前, 但却不能用, 而短时间内无法改造它, 于是我们就想到了适配器.”
     “前面听懂了, 有东西不能用, 又不能改造它. 但想办法 ‘适配’ 是什么意思?”
     “其实这个次最早的是出现在电工学里, 有些国家110v的电压, 而我们国家是220v的电压, 但我们的电器, 比如笔记本电脑是不能什么电压都能使用的, 但国家不同, 电压不同也是事实, 于是就用一个电源适配器, 只要是电, 不管多少伏, 动能把电源变成需要的电压, 这就是电源适配器的作用. 适配器的作用就是是一个东西适合零一个东西的东西.”
     “这个我明白, 但是和适配器有什么关系?”
     “哈, NBA 篮球运动员都会打球, 姚明也会打球….”
     “废话”
     “你小子, 别打岔. 但是要明确不会说英语, 要在美国NBA打球, 不会英语如何交流?没有交流如何理解教练和同伴的意图? 又如何让他们理解自己的想法? 不能沟通就打不好求了. 于是就有了第三个办法, 第一, 让姚明学英语, 你看如何?”
     “这个不符合实际啊, 姚明刚到NBA打球, 之前又没有在学校里认知学习英语, 马上学到可以听懂说英语的地步是很困难的.”
     “说的不错, 第二种方法, 让教练和球员学会中文?”
     “哈, 大鸟又在搞笑了.”
     “不可能, 你说怎么办?”
     “给姚明找个翻译. 哦, 我明白了, 你的意思是翻译就是适配器.”
     “对的, 在我们不能改变球队教练, 球员和姚明的前提下, 我们能做到的就是想办法找个适配器, 在软件开发中, 也就是系统的数据和行为都正确, 单接口不对, 我们应该考虑用适配器, 目的是控制范围之外的一个原有对象与某个接口适配. 适配器模式主要用于希望复用的一些现存类, 但是接口与复用环境要求不一致的情况. 比如在需要对早期的代码复用一些功能等应用上很有实际价值.”
     “在 GoF 的设计模式中, 对适配器模式讲了两种类型, 类适配器模式和对象适配器模式, 由于类适配器模式通过多重继承对一个接口与另一个接口适配, 而 C#, VB.NET, JAVA 等语言都不支持多重继承(C++支持), 也就是一个类只有一个父类, 所以我们这里主要讲的是对象适配器.”



     Target (这是客户所期待的接口. 目标可以是具体或抽象的类, 也可以是接口) 代码如下.

1
2
3
4
5
public class Target {
public void request() {
System.out.println("普通请求!");
}
}

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

1
2
3
4
5
public class Adaptee {
public void sepcificRequest() {
System.out.println("特设请求!");
}
}

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

1
2
3
4
5
6
7
8
9
10
11
public class Adapter extends Target {
// 建立一个私有的 adaptee 对象
private Adaptee adaptee = new Adaptee();
@Override
public void request() {
// 这样就可以把表面上调用 request()
// 方法变成实际调用 specificRequest()
adaptee.sepcificRequest();
}
}

     客户端代码如下:

1
2
3
4
5
public static void main(String[] args) {
// 对客户端来说, 调用的就是 target 的 request()
Target target = new Adapter();
target.request();
}

17.3 何时使用适配器模式

     “你的意识是不是说, 在想使用一个已经存在的类, 但如果它的接口, 也就是方法和你要求的不相同时, 就应该考虑用适配器模式?
     “对的, 两个类所做的事情相同或相似, 但是具有不同的接口时需要使用它. 而且由于类都共享同一个接口, 使得客户端代码如何?”
     “客户端代码可以统一调用统一接口就行了, 这样应该可以更简单, 更直接, 更紧凑
     “很好, 其实用适配器模式也是无奈之举, 很有点 ‘亡羊补牢’ 的感觉, 没办法呀, 软件就有维护的一天, 就有可能因为不同的开发人员, 不同的产品, 不同的厂家而造成功能类似而接口不同的情况, 此时就是适配器模式大展拳脚的时候了.”
     “你的意思是说, 我们通常是在软件开发后期或维护期在考虑使用适配器模式?”
     “如果是在设计阶段, 你有必要把类似的功能类的接口设计成不同的吗”
     “话是这么说, 但不同的程序员定义的方法名称可能不相同啊”
     “首先, 在公司内部, 类和方法的明明应该有规范, 最好前期就设计好, 然后如果真的如你说, 借口不相同时, 首先不应该考虑使用适配器, 而是应该考虑一下通过重构统一接口.”
     “明白了, 就是要在双方都不太容易修改的时候再使用适配器模式适配, 而不是一有不同时就是用他. 那有没有设计之初就考虑使用适配器的时候.”
     “当然有, 比如公司设计一系统时考虑使用第三方开发组件, 而这个组件的接口与我们自己系统的接口是不同的, 而我们也完全没有必要为了迎合他们的接口而修改自己的接口, 此时尽管是在开发阶段, 也是可以考虑使用适配器模式来解决问题的.” 大鸟解释说, “好了, 说了这么多, 你都没有联系一下, 来来来, 试着把火箭队的比赛, 教练叫暂停时给后卫, 中锋, 前锋分配进攻和防守任务的代码模拟出来.”

17.4 篮球翻译适配器

     “哈, 这有何难. 后卫, 中锋, 前锋都是球员, 所以应该有一个球员抽象类, 有进攻和防守的方法.”
     球员类

1
2
3
4
5
6
7
8
9
10
11
public abstract class Player {
protected String name;
public Player(String name) {
this.name = name;
}
// 进攻和防守的方法
public abstract void attack();
public abstract void defense();
}

     后卫, 中锋, 前锋类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 前锋
public class Forwards extends Player {
public Forwards(String name) {
super(name);
}
@Override
public void attack() {
System.out.println("Forwards" + name + "attack");
}
@Override
public void defense() {
System.out.println("Forwards" + name + "defense");
}
}
// 中锋, 后卫代码类似, 省略

     客户端代码如下

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
Player b = new Forwards("Battier");
b.attack();
Player m = new Guards("MacGrady");
m.attack();
Player c = new Center("姚明");
// 姚明问, attack 和 defense 是什么意思呢
c.attack();
c.defense();
}

     结果显示

1
2
3
4
Forwards Battier attack
Guards MacGrady attack
Center 姚明 attack
Center 姚明 defense

     “注意, 姚明刚来到 NBA, 他身高够高, 球技够好, 但是他那是还不懂英语, 也就是说, 他听不懂教练的战术安排, Attach 和 Defence 是什么意思都不知道. 你这样的写法是有问题的. 事实上, 当时是如何解决这个矛盾的?”
     “姚明说 ‘我需要翻译.’ 我知道你的意思了, 姚明是一个外籍中锋, 需要有翻译这类来 ‘适配’”.

     外籍中锋

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ForeignCenter {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// 外籍中锋只懂得中文进攻
public void 进攻() {
System.out.println("外籍中锋 " + name + " 进攻");
}
// 只懂得中文防守
public void 防守() {
System.out.println("外籍中锋 " + name + " 防守");
}
}

     翻译者类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 翻译者
public class Translator extends Player {
// 声明并实例化一个内部的'外籍中锋', 表明翻译者与外籍中锋有关
private ForeignCenter foreignCenter = new ForeignCenter();
public Translator(String name) {
super(name);
foreignCenter.setName(name);
}
@Override
public void attack() {
// 翻译者将 'attack' 告诉外籍中锋
foreignCenter.进攻();
}
@Override
public void defense() {
// 翻译者将 'defense' 告诉外籍中锋
foreignCenter.防守();
}
}

     客户端代码如下:

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
Player b = new Forwards("Battier");
b.attack();
Player m = new Guards("MacGrady");
m.attack();
// 翻译者告诉姚明, 教练要你 '进攻','防守'
Player c = new Translator("姚明");
c.attack();
c.defense();
}

     结果显示:

1
2
3
4
Forwards Battier attack
Guards MacGrady attack
外籍中锋 姚明 进攻
外籍中锋 姚明 防守



17.5 适配器模式的 .net 应用

     “这模式当然好用, 我现在现实中也很常用吧.”
     “当然, 比如在 .net 中有一个类库已经实现, 非常重要的适配器, 那就是 DataAdapter. Dataadapter 作用 DataSet 和数据源之间的适配器以便检索和保存数据. DataAdapter 通过映射 Fill (这更改了 DataSet 中的数据以便于数据源中的数据相配) 和Update(者更改了数据源中的数据以便与DataSet 中的数据相配) 来提供这一适配器[MSDN]. 由于数据源可能来自 SQL Server, 可能来自 Oracle, 也可能来自Access DB2, 这些数据在组织上可能有不同之处, 但我们希望得到统一的DataSet(实质是 XML 数据库), 此时用 DataAdapter 就是非常好的手段, 我们不必关注不同的数据库的数据细节, 就可以灵活的使用数据.”
     “啊, DataAdapter 我都用了无数次了, 原来他就是适配器模式的应用啊, 太棒了. 我喜欢这个模式. 我要经常的使用它.”
     “NO, NO, NO! 模式乱用不如不用.”

~感谢捧场,您的支持将鼓励我继续创作~