您的位置:首页 > 编程语言 > Java开发

Java 设计模式 接口型模式 之 适配器Adapter模式

2014-06-02 00:45 525 查看


作者 :万境绝尘 
转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/19077139
.

适配器模式的意图 : 使用不同接口的类所提供的服务为客户端提供其所希望的接口;

-- 问题解决场景 : 在 类A 中实现了接口中的抽象方法, 客户端B 已经定义好了方法的调用, 但是调用的方法 与 类A 中的方法名不同,
这时我们就需要适配器模式了;

-- eg : 类A 实现了接口A1, 类B 实现了接口B1, 这里C调用 A 和 B 希望 A 和 B 能提供相同方法的接口, 这时我们需要使用适配器模式;

1. 接口适配

(1) 接口适配简介

接口适配

-- 问题场景
: 客户端需要调用 客户端类接口 中提供的 requiredMethod()的方法, 但是工具类中只提供了一个 existMethod() 方法, 显然客户端接口 与 工具类中提供的方法名称不匹配;

-- 适配方案 :  创建一个 适配器类,
适配现有的代码 工具类,  该类实现客户端接口的 requiredMethod()抽象方法, 与客户端接口是实现关系, 同时该实现类继承 工具类, 可以调用工具类中的方法, 与工具类的关系是
继承关系;

-- 方法委托 : 通过接口适配, 就将 客户端类的requiredMethod() 方法 委派给了 existMethod()方法;



(2) 接口适配实例

.

接口适配需求

-- 客户端提供接口 : 需要研发一种M1坦克, 需要实现接口 getCaliber() 获取火炮口径, fire() 开火, run()移动 等方法; 

-- 现有接口 : 现有的坦克 有 getGunCaliber() 获取火炮口径, GunFire() 火炮开火, Move() 移动 等方法;

-- 适配要求 : 写一个适配类, 这个类实现 Panzer 接口,
继承 Tanker 类, 将Panzer接口的动作委托给 Tanker 类;



接口类

[java]
view plaincopyprint?





package shuliang.han.displaytest;  
  
public interface Panzer {  
  
    public double getCaliber();  
    public void fire();  
    public void run();  
      
}  

package shuliang.han.displaytest;

public interface Panzer {

public double getCaliber();
public void fire();
public void run();

}

实体类

[java]
view plaincopyprint?





package shuliang.han.displaytest;  
  
  
public class Tanker {  
  
    private double caliber = 125.0;  
      
    public double getGunCaliber() {  
        return caliber;  
    }  
      
    public void gunFire() {  
        System.out.println("Fire in the hole !!!");  
    }  
      
    public void move() {  
        System.out.println("Move move !!");  
    }  
      
}  

package shuliang.han.displaytest;

public class Tanker {

private double caliber = 125.0;

public double getGunCaliber() {
return caliber;
}

public void gunFire() {
System.out.println("Fire in the hole !!!");
}

public void move() {
System.out.println("Move move !!");
}

}


分析

-- 名称不匹配 : Tanker类中的方法可以执行 Panzer 接口中需要的动作, 但是它们的方法名称不匹配;

-- 变量维护 : 如果创建一个 M1A2SEP 类, 需要在类中维护一个 Tank 对象, 在 Panzer 实现类中调用 对应的 Tank 对象方法;

M1A2SEP 类

[java]
view plaincopyprint?





package shuliang.han.displaytest;  
  
public class M1A2SEP extends Tanker implements Panzer  {  
  
    @Override  
    public double getCaliber() {  
        return getGunCaliber();  
    }  
  
    @Override  
    public void fire() {  
        gunFire();  
    }  
  
    @Override  
    public void run() {  
        move();  
    }  
  
}  

package shuliang.han.displaytest;

public class M1A2SEP extends Tanker implements Panzer  {

@Override
public double getCaliber() {
return getGunCaliber();
}

@Override
public void fire() {
gunFire();
}

@Override
public void run() {
move();
}

}


接口适配总结

--
客户端接口存在 : 如果客户端接口中定义了客户端所期待的行为, 可以运用适配器模式, 适配器继承现有类, 并实现客户端接口;

--
客户端接口不存在 : 如果客户端没有定义接口, 可以使用对象适配器, 对象适配器相当于 子类适配器;

2. 对象适配

(1) 对象适配简介

类适配 : 上面的接口适配方式就是类适配, 适配器类需要
实现客户端接口, 继承 现有实体类;

对象适配
: 对象适配器采用了委派, 并非是继承; 创建一个对象适配器,
继承客户端类, 在类中维护一个现有类实例对象, 满足客户端类需求方法; 

-- 需要场景 : 如果适配的客户端方法没有被定义在接口中, 就需要对象适配;



对象适配的方法

-- 适配器类继承客户端类 : 对象适配的适配器类 继承客户端类对象, 适配器类 的 实例 也是 客户端类的实例, 因为适配器类是客户端类的子类;  

-- 适配器类使用现有类
: 适配器类中定义一个 现有类对象作为成员变量, 通过调用 现有类对象中的方法 来实现客户端类方法的需求;

(2) 对象适配实例

客户端类 : 现在有客户端类 Panzer 装甲车, 提供
获取火炮口径方法 getCaliber(),
移动方法 run(), 开火方法 fire(); 

现有类 : 现有类 Tank 坦克, 提供
获取火炮口径方法 getGunCaliber(),
移动方法 move(), 开火方法 gunFire();

客户端类代码 : 客户端类代码中没有指定建模所需的接口;

[java]
view plaincopyprint?





package shuliang.han.adapter;  
  
public class Panzer {  
  
    public double getCaliber(){  
        return 0;  
    }  
      
    public void fire(){  
        //TODO  
    }  
      
    public void run(){  
        //TODO  
    }  
}  

package shuliang.han.adapter;

public class Panzer {

public double getCaliber(){
return 0;
}

public void fire(){
//TODO
}

public void run(){
//TODO
}
}

现有类代码

[java]
view plaincopyprint?





package shuliang.han.adapter;  
  
public class Tank {  
  
    private double caliber = 125.0;  
      
    public double getGunCaliber(){  
        return caliber;  
    }  
      
    public void gunFire() {          System.out.println("Fire in the hole !!!");  
    }  
      
    public void move() {  
        System.out.println("Move Move !!!");  
    }  
}  

package shuliang.han.adapter;

public class Tank {

private double caliber = 125.0;

public double getGunCaliber(){
return caliber;
}

public void gunFire() {
System.out.println("Fire in the hole !!!");
}

public void move() {
System.out.println("Move Move !!!");
}
}


UML图




适配器类


[java]
view plaincopyprint?





package shuliang.han.adapter;  
  
public class M1A2 extends Panzer {  
      
    private Tank tank;  
      
    public M1A2() {  
        tank = new Tank();  
    }  
      
    @Override  
    public double getCaliber() {  
        return tank.getGunCaliber();  
    }  
  
    @Override  
    public void fire() {  
        super.fire();  
        tank.gunFire();  
    }  
      
    @Override  
    public void run() {  
        super.run();  
        tank.move();  
    }  
      
}  

package shuliang.han.adapter;

public class M1A2 extends Panzer {

private Tank tank;

public M1A2() {
tank = new Tank();
}

@Override
public double getCaliber() {
return tank.getGunCaliber();
}

@Override
public void fire() {
super.fire();
tank.gunFire();
}

@Override
public void run() {
super.run();
tank.move();
}

}


(3) 脆弱的对象适配

对象适配比类适配要脆弱

-- 没有规范接口 : 对象适配的类中没有规范的接口, 如果客户端类出现了变化,
运行时可能出现错误;

-- 客户端类不可预知
: 对象适配类 继承客户端类, 首先客户端类需要将方法 和 变量声明为 protected, 即使这样, 这些类的方法也可能不符合子类意图;

3. Jtable 对数据适配

(1) Jtable 与 TableModel AbstractTableModel模型 

JTable适配数据方法 : JTable类可以将实现了TableModel抽象类的数据显示到图形界面中;

-- 数据不确定性 : Java中的Swing 提供了JTable控件用以显示列表,
JTable不知道我们要显示什么数据

-- 适配器
: 将数据交给JTable控件并显示出来, 需要一个适配器, 这些数据要经过一个适配器接口, 这个接口是 TableModel 抽象类;

TableModel子类实现


-- 抽象方法多 : Jtable定义了许多抽象方法, 其子类必须实现所有的抽象方法, 这样会很麻烦; 

-- TableModel的桩 : JDK中提供了另一个抽象类 AbstractTableModel 类, AbstractTableModel 继承了 TableModel 类, 并实现了绝大部分方法,
我们可以定义一个类 去 继承 AbstractTableModel 类, 并实现我们感兴趣的方法, 不必实现所有的方法了;

-- 数据封装 : 创建一个类 继承 AbstractTableModel 类, 然后呢实现感兴趣的接口;

(2) 实例

实现过程 : 使用JTable 绘制坦克相关数据, 需要创建一个TankTableModel类
继承 AbstractTableModel 类, 然后将 Tank 类封装在 TankTableModel 中,
当做其成员变量;

使用对象适配的原因

-- AbstractTableModel 抽象类
: 该抽象类提供了适配器对象需要实现的接口 (抽象方法), 该抽象类又实现了客户端 JTable类 期待的接口, 适配器对象必须继承抽象类;

-- 组合第三对象
: 适配器对象还需要重用第三个对象, 重用对象的方法只能是 继承 和 组合, Java是单继承机制, 只能使用组合方式, 即将第三个对象当做适配器类的成员变量;

UML图



Tank代码


[java]
view plaincopyprint?





package shuliang.han.jtable;  
  
public class Tank {  
  
    private double caliber;  
    private double speed;  
    private String name;  
      
    public Tank(double caliber, double speed, String name) {  
        this.caliber = caliber;  
        this.speed = speed;  
        this.name = name;  
    }  
  
    public double getCaliber() {  
        return caliber;  
    }  
  
    public double getSpeed() {  
        return speed;  
    }  
  
    public String getName() {  
        return name;  
    }  
      
      
}  

package shuliang.han.jtable;

public class Tank {

private double caliber;
private double speed;
private String name;

public Tank(double caliber, double speed, String name) {
this.caliber = caliber;
this.speed = speed;
this.name = name;
}

public double getCaliber() {
return caliber;
}

public double getSpeed() {
return speed;
}

public String getName() {
return name;
}

}


TankTableModel代码

[java]
view plaincopyprint?





package shuliang.han.jtable;  
  
import javax.swing.table.AbstractTableModel;  
  
public class TankTableModel extends AbstractTableModel {  
  
    private Tank tanks[];  
    private String names[];  
      
    public TankTableModel(Tank[] tanks, String[] names) {  
        this.tanks = tanks;  
        this.names = names;  
    }  
      
    @Override  
    public int getRowCount() {  
        return tanks.length;  
    }  
  
    @Override  
    public int getColumnCount() {  
        return names.length;  
    }  
  
    @Override  
    public String getColumnName(int column) {  
        return names[column];  
    }  
      
    @Override  
    public Object getValueAt(int rowIndex, int columnIndex) {  
        switch(columnIndex){  
            case 0 :  
                return tanks[rowIndex].getName();  
            case 1 :  
                return new Double(tanks[rowIndex].getCaliber());  
            case 2 :  
                return new Double(tanks[rowIndex].getSpeed());  
            default :  
                return null;  
        }  
    }  
  
}  

package shuliang.han.jtable;

import javax.swing.table.AbstractTableModel;

public class TankTableModel extends AbstractTableModel {

private Tank tanks[];
private String names[];

public TankTableModel(Tank[] tanks, String[] names) {
this.tanks = tanks;
this.names = names;
}

@Override
public int getRowCount() {
return tanks.length;
}

@Override
public int getColumnCount() {
return names.length;
}

@Override
public String getColumnName(int column) {
return names[column];
}

@Override
public Object getValueAt(int rowIndex, int columnIndex) {
switch(columnIndex){
case 0 :
return tanks[rowIndex].getName();
case 1 :
return new Double(tanks[rowIndex].getCaliber());
case 2 :
return new Double(tanks[rowIndex].getSpeed());
default :
return null;
}
}

}

ShowTankData代码

[java]
view plaincopyprint?





package shuliang.han.jtable;  
  
import java.awt.Component;  
import java.awt.Dimension;  
import java.awt.Font;  
  
import javax.swing.JFrame;  
import javax.swing.JScrollPane;  
import javax.swing.JTable;  
import javax.swing.UIManager;  
  
public class ShowTanksData {  
  
    public static void main(String[] args) {  
        setFrame();  
        JTable jTable = new JTable(getTankTableModel());  
        jTable.setRowHeight(36);  
        JScrollPane pane = new JScrollPane(jTable);  
        pane.setPreferredSize(new Dimension(300, 100));  
        display(pane, "坦克数据");  
    }  
      
    private static void setFrame() {  
        Font font = new Font("Dialog", Font.PLAIN, 18);  
        UIManager.put("Table.font", font);  
        UIManager.put("TableHeader.font", font);  
    }  
      
    private static TankTableModel getTankTableModel() {  
        Tank tank1 = new Tank(120.0, 50.0, "99式");  
        Tank tank2 = new Tank(150.0, 2.0, "KV");  
        return new TankTableModel(new Tank[]{tank1, tank2}, new String[]{"名称", "火炮口径 ", "速度"});  
    }  
      
    private static void display(Component component, String tittle) {  
        JFrame frame = new JFrame(tittle);  
        frame.getContentPane().add(component);  
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
        frame.pack();  
        frame.setVisible(true);  
    }  
  
}  

package shuliang.han.jtable;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;

public class ShowTanksData {

public static void main(String[] args) {
setFrame();
JTable jTable = new JTable(getTankTableModel());
jTable.setRowHeight(36);
JScrollPane pane = new JScrollPane(jTable);
pane.setPreferredSize(new Dimension(300, 100));
display(pane, "坦克数据");
}

private static void setFrame() {
Font font = new Font("Dialog", Font.PLAIN, 18);
UIManager.put("Table.font", font);
UIManager.put("TableHeader.font", font);
}

private static TankTableModel getTankTableModel() {
Tank tank1 = new Tank(120.0, 50.0, "99式");
Tank tank2 = new Tank(150.0, 2.0, "KV");
return new TankTableModel(new Tank[]{tank1, tank2}, new String[]{"名称", "火炮口径 ", "速度"});
}

private static void display(Component component, String tittle) {
JFrame frame = new JFrame(tittle);
frame.getContentPane().add(component);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}

}


效果图



4. 识别适配器

MouseAdapter 为 MouseListener 接口提供桩的实现;



在使用MouseAdapter的时候, 就相当于使用了适配器 : 用户操作鼠标的时候, 将swing组件接收到的鼠标操作适配给相应的动作处理类中,
即将GUI时间适配给应用程序接口, 使用了Swing适配类, 将一个接口方法委派给一个类的方法去执行;

5. 适配器模式总结

适配器总结 : 适配器模式可以重用一个现有类, 满足客户端需求, 将客户端的调用转化为现有方法的调用;

-- 类适配器 : 客户端的需求通过接口表达出来, 可以创建一个实现了该接口的适配类, 适配类同时还要继承现有类;

-- 对象适配 : 客户端没有指定接口, 创建一个新适配器类, 实现
继承客户端类, 在该类中维护一个现有类的实例对象作为成员变量;

JTable适配器模式 : 通过定义TableModel接口, JTable组件将客户端需要的表信息存储到自身中, 通过自定义适配器对象, 将任何数据适配到表中;

JTable不适用类适配原因

-- 继承数量限制 : JTable适配器需要继承 AbstractTableModel类, 这样就无法继承现有类, 因为只能继承一个类;

-- 需要维护多个对象 : JTable需要大量数据, 一般是从多个对象中采集的;

设计适配器模式 : 当我们设计软件的时候, 充分考虑程序的灵活性, JTable 的设计就是一个很好的范例;

.

作者 :万境绝尘 
转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/19077139
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  设计模式