380 likes | 557 Views
The Adaptor Pattern (结构模式之适配器模式). 现实生活中的适配器. 现实生活中的适配器. 面向对象软件系统的适配问题. 假设我们已经有一个软件系统,原来使用了一个第三方类库 A 。现在有一个新的第三方类库 B ,其功能等各方面都更加强大。我们希望用 B 来替换 A ,以改善我们的系统。但是 B 的接口与 A 不一样。那则么办呢?. B. System. A. New System. 办法之一. B. New System. 办法之一. B. System. A. Adapter. 办法之二. B. System. Adapter.
E N D
面向对象软件系统的适配问题 • 假设我们已经有一个软件系统,原来使用了一个第三方类库A。现在有一个新的第三方类库B,其功能等各方面都更加强大。我们希望用B来替换A,以改善我们的系统。但是B的接口与A不一样。那则么办呢?
B System A New System 办法之一
B New System 办法之一
B System A Adapter 办法之二
B System Adapter 第二种方案的优点 不需要修改代码 新代码 不需要修改代码
B B’ System 办法之三
下面我们来看一个实际的例子 • 简化的鸭子接口和类 public interface Duck { public void quack(); public void fly(); } 鸭子接口Duck,定义了鸭子具有“鸣叫”和“飞行”方法
MallardDuck类 public class MallardDuck implements Duck { public void quack() { System.out.println("嘎嘎嘎..."); } public void fly() { System.out.println("我在飞哦!"); } } MallardDuck类简单地实现了Duck接口。
现在有一种新家伙 public interface Turkey { public void gobble(); public void fly(); }
WildTurkey public class WildTurkey implements Turkey { public void gobble() { System.out.println("咕咕咕..."); } public void fly() { System.out.println("我在飞,不过飞不远。"); } }
我们原来有一个程序使用鸭子对象,现在想让它使用火鸡对象,但是火鸡与鸭子的接口不同,不能直接使用。我们原来有一个程序使用鸭子对象,现在想让它使用火鸡对象,但是火鸡与鸭子的接口不同,不能直接使用。 • 写一个火鸡适配器,让火鸡看起来像鸭子
火鸡适配器 public class TurkeyAdapter implements Duck { Turkey turkey; public TurkeyAdapter(Turkey turkey) { this.turkey = turkey; } public void quack() { turkey.gobble(); } public void fly() { for(int i=0; i < 5; i++) { turkey.fly(); } } } 火鸡适配器包装了一个火鸡对象,同时实现了鸭子接口。这样就可以像使用鸭子一样使用火鸡了。
使用适配器 public class DuckTestDrive { public static void main(String[] args) { MallardDuck duck = new MallardDuck(); WildTurkey turkey = new WildTurkey(); Duck turkeyAdapter = new TurkeyAdapter(turkey); System.out.println("火鸡说..."); turkey.gobble(); turkey.fly(); System.out.println("\n鸭子说..."); testDuck(duck); System.out.println("\n火鸡适配器说..."); testDuck(turkeyAdapter); } static void testDuck(Duck duck) { duck.quack(); duck.fly(); } } 在需要鸭子对象的地方使用了火鸡适配器对象,火鸡适配器对象包装了一个火鸡对象,所以实际使用的是火鸡对象。 需要使用鸭子对象
目标接口:鸭子接口 适配器 把火鸡装扮成鸭子 被适配者 火鸡接口 客户 要使用鸭子对象的程序 适配器模式详解 两者无耦合 彼此不必知道对方的存在 两者无耦合 彼此不必知道对方的存在
试试看 • 现在,如果希望把鸭子包装成火鸡该怎么做? • 写出你的代码DuckAdapter
DuckAdapter参考答案 import java.util.Random; public class DuckAdapter implements Turkey { Duck duck; Random rand; public DuckAdapter(Duck duck) { this.duck = duck; rand = new Random(); } public void gobble() { duck.quack(); } public void fly() { if (rand.nextInt(5) == 0) { duck.fly(); } } }
Adapter模式 • 定义 • 将一个类的接口转换成客户端所期望的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。 • 别名 • 包装器Wrapper
Adapter模式 • 动机 • 有时,为复用而设计的工具箱类不能够被复用的原因仅仅是因为它的接口与专业应用领域所需要的接口不匹配。 • 我们可以改变工具箱类使它兼容专业领域中的类的接口,但前提是必须有这个工具箱的源代码。然而即使我们得到了这些源代码,修改工具箱也是没有什么意义的;因为不应该仅仅为了实现一个应用,工具箱就不得不采用一些与特定领域相关的接口。
Adapter模式 • 动机(续) • 我们可以不用上面的方法,而定义一个适配器类,由它来适配工具箱的接口和专业应用的接口。我们可以用两种方法做这件事: • 1) 继承专业应用类的接口和工具箱类的实现。这种方法对应A d a p t e r模式的类版本(多继承) • 2) 将一个工具箱类的实例作为适配器类的组成部分,并且使用工具箱的接口实现适配器类。这种方法对应A d a p t e r模式的对象版本。
Adapter模式 • 适用性 以下情况使用A d a p t e r模式 • 你想使用一个已经存在的类,而它的接口不符合你的需求。 • 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。 • (仅适用于对象A d a p t e r)你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。
Adapter模式 • 结构(类版本)
Adapter模式 • 结构(对象版本)
基于类的Adapter模式 • 基于类的Adapter模式的一般结构如下:Adaptee类为Adapter的父类,Adaptee类为适配源,适配目标(接口)也是Adapter的父类;基于类的Adapter模式比较适合应用于Adapter想修改Adaptee的部分方法的情况。
基于对象的Adapter模式 • 基于对象的Adapter模式的一般结构如下:Adaptee类对象为Adapter所依赖,适配目标(接口)是Adapter的父类; • 基于对象的Adapter模式比较适合应用于Adapter想为Adaptee添加新的方法的情况。但在Adaptee类的方法与Adapter类的方法不同名而实现相同功能的情况下,我们一般也使用基于对象的Adapter模式,
Adapter模式 • 参与者 • Ta r g e t • 定义C l i e n t使用的与特定领域相关的接口。 • C l i e n t • 与符合Ta rg e t接口的对象协同。 • A d a p t e e • 定义一个已经存在的接口,这个接口需要适配。 • A d a p t e r • 对A d a p t e e的接口与Ta rg e t接口进行适配
Adapter模式 • 协作 • Client在A d a p t e r实例上调用一些操作(请求)。接着适配器调用A d a p t e e的操作实现这个请求。
Adapter模式 • 效果(类适配器和对象适配器有不同的权衡) • 类适配器 • 用一个具体的A d a p t e r类对A d a p t e e和Ta rg e t进行匹配。结果是当我们想要匹配一个类以及所有它的子类时,类A d a p t e r将不能胜任工作。 • 使得A d a p t e r可以重定义A d a p t e e的部分行为,因为A d a p t e r是A d a p t e e的一个子类。 • 仅仅引入了一个对象,并不需要额外的指针以间接得到a d a p t e e。
Adapter模式 • 效果(类适配器和对象适配器有不同的权衡) • 对象适配器则 • 允许一个A d a p t e r与多个A d a p t e e—即A d a p t e e本身以及它的所有子类(如果有子类的话)同时工作。A d a p t e r也可以一次给所有的A d a p t e e添加功能。 • 使得重定义A d a p t e e的行为比较困难。这就需要生成A d a p t e e的子类并且使得A d a p t e r引用这个子类而不是引用A d a p t e e本身。
???? • 前面的实例是A d a p t e r模式的那种版本?
分析一下例子,确定各个类的角色 public class WebFormDesigner extends JFrame { void jbInit() throws Exception { text.addActionListener(new WebFormDesigner_text_actionAdapter(this)); } void text_actionPerformed(ActionEvent e) { TextDialog createText = new TextDialog(); createText.setVisible(true); createText.setTitle("TEXT"); createText.setBounds(100, 100, 500, 300); createText.setModal(true); createText.show(); } }
分析一下例子,确定各个类的角色---续 class WebFormDesigner_text_actionAdapter implements java.awt.event.ActionListener { WebFormDesigner adaptee; WebFormDesigner_text_actionAdapter(WebFormDesigner adaptee) { this.adaptee = adaptee; } public void actionPerformed(ActionEvent e) { adaptee.text_actionPerformed(e); } }
分析 WebFormDesigner在本例中是Adaptee, WebFormDesigner_text_actionAdapter在本例中是Adapter, java.awt.event.ActionListener在本例中是适配目标。 适配目标为一接口,代码如下: public interface ActionListener extends EventListener { /** * Invoked when an action occurs. */ public void actionPerformed(ActionEvent e); } 适配目标中只有一个方法:actionPerformed()。由于WebFormDesigner_text_actionAdapter 实现了java.awt.event.ActionListener,所以要求实现actionPerformed()方法。WebFormDesigner_text_actionAdapter实现actionPerformed()方法是引用了WebFormDesigner这个adaptee来完成的。从代码中可以看出,WebFormDesigner是WebFormDesigner_text_actionAdapter的方法的参数,所以WebFormDesigner_text_actionAdapter依赖WebFormDesigner。 Adaptee类(WebFormDesigner)的方法(text_actionPerformed)与Adapter类(WebFormDesigner_text_actionAdapte)的方法(actionPerformed)不同名而实现相同功能,这就是我们上面分析的基于对象的Adapter模式。
作业(下周上机完成) • 请设计一个Kittie的具体类,并实现miao(),run(),sleep(),catchRat()等方法。 • 再设计一个Puppie接口,要求有wao(), fetchBall(), run(), sleep()等方法。 • 设计一个适配器,将一只猫kittie伪装成小狗puppie。 • 编写一个测试主类,测试你的程序。 • 实验后一周内将实验报告发至hhzjut@gmail.com