【面向对象设计模式】 适配器模式 (二)
.
作者?:萬境絕塵?
轉載請注明出處?:?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 類;
接口類 :?
package shuliang.han.displaytest;public interface Panzer {public double getCaliber();public void fire();public void run();}
實體類 :?
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 類 :?
package shuliang.han.displaytest;public class M1A2SEP extends Tanker implements Panzer {@Overridepublic double getCaliber() {return getGunCaliber();}@Overridepublic void fire() {gunFire();}@Overridepublic void run() {move();}}
接口適配總結 :?
-- 客戶端接口存在 : 如果客戶端接口中定義了客戶端所期待的行為, 可以運用適配器模式, 適配器繼承現有類, 并實現客戶端接口;
-- 客戶端接口不存在 : 如果客戶端沒有定義接口, 可以使用對象適配器, 對象適配器相當于 子類適配器;
2. 對象適配
(1) 對象適配簡介
類適配 : 上面的接口適配方式就是類適配, 適配器類需要 實現客戶端接口, 繼承 現有實體類;
對象適配 : 對象適配器采用了委派, 并非是繼承; 創建一個對象適配器, 繼承客戶端類, 在類中維護一個現有類實例對象, 滿足客戶端類需求方法;?
-- 需要場景 : 如果適配的客戶端方法沒有被定義在接口中, 就需要對象適配;
對象適配的方法 :?
-- 適配器類繼承客戶端類 : 對象適配的適配器類 繼承客戶端類對象, 適配器類 的 實例 也是 客戶端類的實例, 因為適配器類是客戶端類的子類; ?
-- 適配器類使用現有類 : 適配器類中定義一個 現有類對象作為成員變量, 通過調用 現有類對象中的方法 來實現客戶端類方法的需求;
(2) 對象適配實例
客戶端類 : 現在有客戶端類 Panzer 裝甲車, 提供 獲取火炮口徑方法 getCaliber(), 移動方法 run(), 開火方法 fire();?
現有類 : 現有類 Tank 坦克, 提供 獲取火炮口徑方法 getGunCaliber(), 移動方法 move(), 開火方法 gunFire();
客戶端類代碼 : 客戶端類代碼中沒有指定建模所需的接口;
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 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圖 :?
適配器類 :?
package shuliang.han.adapter;public class M1A2 extends Panzer {private Tank tank;public M1A2() {tank = new Tank();}@Overridepublic double getCaliber() {return tank.getGunCaliber();}@Overridepublic void fire() {super.fire();tank.gunFire();}@Overridepublic 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代碼 :?
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代碼 :?
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;}@Overridepublic int getRowCount() {return tanks.length;}@Overridepublic int getColumnCount() {return names.length;}@Overridepublic String getColumnName(int column) {return names[column];}@Overridepublic 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代碼 :?
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
.
總結
以上是生活随笔為你收集整理的【面向对象设计模式】 适配器模式 (二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【重构】 代码的坏味道总结 Bad Sm
- 下一篇: 【Android 应用开发】分析各种An