1.概念:
適配器模式(Adapter Pattern)[GOF95]把一個類的接口變換成客戶端所期待的另一種接口,從而使原本因接口不匹配而無法在一起工作的兩個類能夠在一起工作。
2.兩種形式
a.類的適配器模式 b.對象的適配器模式
3.模擬問題:
現在假設我們的程序已經設計了接口Request接口,但是現在有一個特殊的接口SpecificRequst能更好的完成我們的功能,但是它和我們現有的Request接口不匹配。那我們如何將它們一起工作呢?看下面的實例:
3.圖示實例1:a.類的適配器模式
目標角色:
public interface Target {
? public void request();
? }
源角色:
public class Adaptee {
public void specificRequest(){
System.out.println("實現所需功能");
}
}
適配器角色:
public class ClassAdapter extends Adaptee implements Target {
public void request() {
this.specificRequest();
}
}
用戶角色:
public class TestClassAdapter {
public static void main(String args[]){
ClassAdapter adapter = new ClassAdapter();
adapter.request();
}
}
運行結果:
引用
實現所需功能
3.圖示實例2:b.對象的適配器模式
實例代碼:
目標角色,源角色代碼不變。
適配器角色:
public class ObjectAdapter implements Target {
private Adaptee adaptee;
public ObjectAdapter(Adaptee adaptee){
this.adaptee = adaptee;
}
public void request() {
adaptee.specificRequest();
}
}
用戶角色:
public class TestOjbectAdapter {
public static void main(String arg[]){
Adaptee adaptee = new Adaptee();
ObjectAdapter adapter = new ObjectAdapter(adaptee);
adapter.request();
}
}
將一個類的接口轉換成客戶希望的另外一個接口。Adapter模式使得原本由於接口不兼容而不能一起工作的那些類可以一起工作。 --- 《設計模式》GOF
Adapter模式的宗旨就是,基於現有類所提供的服務,向客戶端提供接口,以滿足客戶的期望。---《java設計模式 》
對軟件系統中,如果要複用以前的“現存對象”,但是這些對象所提供的接口並不一定能適應我們的新環境,我們就要將其轉換成我們需要的接口,來供我們調用。 Adapter模式通過定義一個新的接口(對要實現的功能加以抽象),和一個實現該接口的Adapter(適配器)類來透明地調用外部組件。這樣替換外部 組件時,最多隻要修改幾個Adapter類就可以了,其他源代碼都不會受到影響。
簡單理解就是:我們需要實現某個功能,而現在實現這個功能的組件不必我們自己開發,可以通過第三方的組件(即別人的代碼或者自己曾經寫過的代碼)來實現, 但第三方組件的接口與現在所定義的接口不一致(即類名,方法名不一樣),那麼在不修改兩方接口的情況下,可以通過採用適配器模式來解決這一問題。
Target 爲用戶請求接口 |
Target 即客戶端給出的接口(此處不是java語言中的接口類型,而是指類名,方法名等等),也就是客戶端需要調用的組件。
Adapter 即適配器
Adaptee 即第三方組件
根據Target是否是java接口類型,適配器可以分爲類適配器和對象適配器。
類適配器:
此時Target是一個java接口,其中定義了其所期望的功能,而此時的Adapter則通過繼承Adaptee類並實現Target接口來完成。即
- class Adapter extends Adaptee implements Target{}
假設現在Target的內容爲:
- interface operation {
- public int add(int a , int b); -à 返回類型爲整形
- }
Adaptee的內容爲:
- public class Adaptee{
- public int addOpe(int a ,int b){
- return a+b;
- }
- }
那麼爲了利用Adaptee類,類適配器Adapter可以寫成:
- public class Adapter extends Adaptee implements Target{
- ? public int add(int a , int b) {
- ? return addOpe(a,b);
- ? }
- ? }
對象適配器:
此時Target可能是一個普通類,那麼Adapter的實現可以通過繼承Target,並將具體實現委託給Adaptee來完成。同樣是前面的add例子:
1. class Target{
2. public int add(int a , int b){}
3. }
4.
5. Public class Adapter extends Target{
6. Adaptee adaptee;
7. Adapter(Adaptee adaptee){
8. this.adaptee = adaptee ;
9. }
10. public int add(int a,int b){
11. return adaptee.addOpe(a,b);
12. }
13. }
總結:個人覺得適配器模式用到了面嚮對象語言中的多態的特性,根據客戶端給出的組件是接口還是類,適配器通過實現接口或者繼承類的方式來實現多態。 如果是實現接口,那麼適配器則可以繼承第三方組件,通過調用父類方法來完成功能。如果是繼承,因爲java中不支持多繼承,適配器將具體操作委派給第三方 組件來完成。這是根據客戶端給出的組件類型來區分,個人認爲即使客戶端給出的組件是接口,也可以通過將具體實現委派給第三方組件來完成,因爲設計模式的原 則是:優先使用對象組合而不是類繼承。這樣適配器模式就很容易理解,就是客戶端通過多態調用適配器,適配器通過使用第三方對象來完成具體功能。
適配器模式(Adapter Pattern)(另稱-變壓器模式):
把一個類的接口變換成客戶端所期待的另一種接口,從而使原本因接口不匹配而無法在一起工作的兩個類能夠在一起工作
1、(類適配器)模式所涉及的角色有:
1/目標(Target)角色:這就是所期待得到的接口。由於是類適配器模式,因此目標不可以是類。
2/源(Adaptee)角色:現有需要適配的接口。
3/適配器(Adapter)角色:適配器類是本模式的核心。適配器把源接口轉換成目標接口。顯然,這一角色不可以是接口,而必須是具體類。
- //目標角色類
- public interface Target{
- //源類有的方法
- void sampleOperation1();
- //源類沒有的方法
- void sampleOperation2();
- }
- 源類(具體類)
- public class Adaptee{
- //源類含有的方法sampleOperation1()
- public void sampleOperation1(){}
- }
- //適配器角色
- public class Adapter extends Adaptee implements Target{
- public void sampleOperation2(){}
2(對象適配器)
1):模式所涉及的角色有:
1/目標(Target)角色:這就是所期待的接口,目標可以是具體的或抽象的類
2/源(Adaptee)角色:現有需要適配的接口
3/適配器(Adapter)角色:適配器類是本模式的核心。適配器把源接口轉換成目標接口,這一角色必須是具體類 }
1. //Target類
2. public interface Target{
3. //源類有的方法
4. void sampleOperation1();
5. //源來沒有的方法
6. void sampleOperation2();
7. }
8. 源類(具體類)
9. public class Adaptee{
10. //源類含有的方法sampleOperation1()
11. public void sampleOperation1(){}
12. }
13. //適配器類
14. public class Adapter implements Target{
15. private Adaptee adaptee;
16.
17. public Adapter(Adaptee adaptee){
18. super();
19. this.adaptee = adaptee;
20. }
21. //源類有的方法,適配器直接委派就可以了
22. public void sampleOperation1(){
23. adaptee.sampleOperation();
24. }
25. //源類沒有,需要補充
26. public void sampleOperation2(){
27. //............
28. }
29. }
適配器模式的用意是將接口不同而功能相同或者相近的兩個接口加以轉換,這裏麪包括適配器角色補充了一個源角色沒有的方法。
4、對象適配器模式的效果
1)一個適配器可以把多種不同的源適配到同一個目標,換言之,同一個適配器可以把源類和它的子類都適配到目標接口。
2)與類的適配器模式相比,要想置換源類的方法就不容易。如果一定要置換掉源類的一個或多個方法,就只好先做一個源
類的子類,將源類的方法置換掉,然後再把原來的子類當做真正的源進行適配。
3)雖然要想置換源類的方法不容易,但是要想增加一些新的方法則方便的很,而且新增加的方法可同時適用於所有的源。
5、在什麼情況下使用適配器模式
1)系統需要使用現有的類,而此類的接口不符合系統的需要。
2)想要建立一個可以重複使用的類,用於與一些彼此之間沒有太大關聯的一些類,包括一些可能在將來引進的類一起工作。
這些源類不一定有很複雜的接口。
3)(對對象適配器模式而言)在設計裏,需要改變多個已有的子類的接口,如果使用類的適配器模式,就要針對每一個子類做
一個適配器,而這不太實際
- //Itermeration類
- import java.util.Iterator;
- import java.util.*;
- import java.util.Enumeration;
- public class Itermeration implements Enumeration{
- private Iterator it;
- public Itermeration(Iterator it){
- this.it = it;
- //是否存在下一個元素
- public boolean hasMoreElements(){
- return it.hasNext();
- }
- //返還下一個元素
- public Object nextElement() throws NoSuchElementException{
- return it.next();
- }
- }
- }
- //Enuterator類
- import java.util.Iterator;
- import java.util.*;
- import java.util.Enumeration;
- public class Enuterator implements Iterator{
- Enumeration enum;
- public Enuterator(Enumeration enum){
- this.enum = enum;
- }
- //是否存在下一個元素
- public boolean hasNext(){
- return enum.hasMoreElements();
- }
- //返還下一個元素
- public Object next() throws NoSuchElementsException{
- return enum.nextElement();
- }
- //刪除當前的元素(不支持)
- public void remove(){
- throw new UnsupportedOperationException();
- }
- }
- --------------------------------------------------
- //立方體類
- public class Cube{
- private double width;
- public Cube(double width){
- this.width = width;
- }
- //計算體積
- public double calculateVolume(){
- return width*width*width;
- }
- //計算面積
- public double calculateFaceArea(){
- return width*width;
- }
- //長度的取值方法
- public double getWidth(){
- return this.width;
- }
- //長度的賦值方法
- public void setWidth(double width){
- this.width = width;
- }
- }
- //目標接口角色
- public interface BallIF{
- //計算面積
- double calculateVolume();
- //半徑的取值方法
- double getRadius();
- //半徑的賦值方法
- void setRadius(double radius);
- }
- //適配器類角色
- public class MagicFinger implements BallIF{
- private double redius = 0;
- private static final double PI = 3.14D;
- private Cube adaptee;
- public MagicFinger(Cube adaptee){
- super();
- this.adaptee = adaptee;
- radius = adpatee.getWidth();
- }
- //計算面積
- public double calculateArea(){
- return PI*4.0D*(radius);
- }
- public double calculateVolume(){
- return PI*(4.0D/3.0D)*(radius*radius*radius);
- }
- //半徑取值方法
- public double getRadius(){
- return radius;
- }
- public void setRadius(double radius){
- this.radius = radius;
- }
- }
6、本模式在實現的時候有以下這些值得注意的地方;
1) 目標接口可以省略。此時,目標接口和源接口實際上是相同的。由於源是一個接口,而適配器類是一個類(或抽象類)
因此這種做法看似平庸而並並平庸,它可以使客戶端不必實現不需要的方法。
2)適配器類可以是抽象類,這可以在缺省適配情況下看到。
3)帶參數的適配器模式。使用這種方法可以根據參數返還一個合適的實例給客戶端
7、適配器模式與其他模式的關係
1)適配器模式與橋樑模式的關係
橋樑模式的用意是要把實現和它的接口分開,以便它們可以獨立地變化。橋樑模式並不是用來把一個已有的對象接到不相
匹配的接口上的。當一個客戶端只知道一個特定的接口,但是有必須與具有不同接口的類打交道時,就應當使用適配器模式。
2)適配器模式與裝飾模式的關係
一個裝飾類也是位於客戶端和另外一個Compontent對象之間的,
在它接到客戶端的調用後把調用傳給一個或幾個Component對象。
一個純粹的裝飾類必須與Compotent對象在接口上的完全相同,並增強後者的功能。
與適配器類不同的是,裝飾類不能改變它所裝飾的Compotent對象的接口。
3)適配器模式與缺省適配模式的關係
場景
相信很多人都知道什麼是顯卡,也有很多人知道顯卡的本名——圖形適配器。恩,是的,
正好這回說說Apater模式,就拿顯卡來例子來分析一下Adapter模式。
我們知道顯示器(Client)是用來顯示圖形的,它是不能顯示數據,它只能夠接受來自圖形發送設備Target的信號。可是我們手頭上只有 CPU(Adaptee)這個產生各種描述圖形的數據的數據發送器。我們需要將這些數據讓顯示器進行顯示,可是這兩個部件卻是不兼容的。於是我們需要一個 中間設備,它能夠將CPU“適配”於顯示器,這便是我們的顯卡——圖形適配器(Adapter)。
· // 圖形發送設備
? public class Target {
? /**
? * 傳送圖形信號
? */
? public String request() {
? return "Graphic sender";
? }
? }
// 顯示器
public class Client {
public static void main(String[] args) {
Target target = new Targete();
System.out.println(target.request());
}
}
可是我們的CPU(Adaptee)只能輸出0/1數據,他是個計算器,而不是圖形發送設備(Target)。
- // CPU
- public class Adaptee {
- /**
- * CPU輸出的數據
- */
- public String getData() {
- return "CPU data";
- }
- }
這個時候我們的顯卡(Adapter)的作用便體現出來了,它負責對CPU進行適配,通過將CPU傳過來的數據轉換成圖形信號,從而將CPU僞裝成一個圖形發送設備。
- // 顯卡,即我們的適配器
- public class Adapter extends Target {
- // 被代理的設備
- private Adaptee apt = null;
- /**
- * 裝入被代理的設備
- */
- public Adapter(Adaptee apt) {
- this.apt = apt;
- }
- /**
- * 被代理的設備傳過來的數據轉換成爲圖形輸出
- */
- public String request() {
- return apt.getData();
- }
- }
這樣,我們的電腦的顯示流程就變成CPU-顯卡-顯示器:
- public class Client {
- public static void main(String[] args) {
- // CPU經過顯卡的適配後“變”成了圖形發送裝置了
- Target target = new Adapter(new Adaptee());
- System.out.println(target.request());
- }
- }
圖形發送設備 Target,顯示器 Client,CPU Adaptee ,顯卡(Adapter)
上面的這種依賴於對象組合的Adapter模式叫做對象適配器(Object Adapter)。它的特徵是繼承/實現某一方的類(Target),如這裏的圖形發送器,同時內部包含一個被適配的類(Adaptee),如這裏的CPU。通過重寫其父類的方法來進行適配。
另一種的Adapter實現
對於Adapter模式,還有另外一種實現方式,這種適配方式叫做類適配器(Class
Adapter)。它與Object Adapter的不同之處在於它繼承被適配的對象。
- public class Adapter extends Targer, Adaptee { -- 多繼承(C++中合法)
- ......
- }
這樣的代碼在C++中是合法的,但是在Java中規定最多隻能繼承一個父類,而可以實現多個接口。所以我們需要建立一個IAdaptee的接口,然後將我們的Adapter繼承Target同時實現IAdaptee。
1. // IAdaptee接口
2. public interface IAdaptee {
3.
4. String getData();
5. }
6. // Adaptee 實現IAdaptee
7. public class Adaptee implements IAdaptee {
8. ......
9. }
10. public class Adapter extends Target implements IAdaptee {
11.
12. private IAdaptee apt = null;
13.
14. public Adapter(IAdaptee apt) {
15. this.apt = apt;
16. }
17.
18. public String request() {
19. return apt.getData();
20. }
21.
22. public String getData() {
23. return apt.getData();
24. }
25. }
對於我們的顯示器(Client)方面,Class Adapter跟Object Adapter一樣,所以不需要進行修改。對於Class Adapter,大家也看見了,在Adapter中因爲是實現了IAdaptee接口,因此需要實現getData()的接口。一旦Target和IAdaptee擁有相同的方法時,會出現麻煩的。所以儘量優先使用Object Adapter的模式。