八:適配器模式

從實現方式上分爲兩種,類適配器和對象適配器,這兩種的區別在於實現方式上的不一樣,一種採用繼承,一種採用組合的方式(繼承和實現接口方式)。設計模式

從使用目的上來講,也能夠分爲兩種,特殊適配器和缺省適配器,這兩種的區別在於使用目的上的不一樣,一種爲了複用原有的代碼並適配當前的接口,一種爲了提供缺省的實現,避免子類須要實現不應實現的方法。this

 

首先應該明白一點,適配器模式是補救措施,因此在系統設計過程當中請忘掉這個設計模式,這個模式只是在你迫不得已時的補救方式。spa

那麼咱們何時使用這個模式呢?場景一般狀況下是,系統中有一套完整的類結構,而咱們須要利用其中某一個類的功能(通俗點說能夠說是方法),可是咱們的客戶端只認識另一個和這個類結構不相關的接口,這時候就是適配器模式發揮的時候了,咱們能夠將這個現有的類與咱們的目標接口進行適配,最終得到一個符合須要的接口而且包含待複用的類的功能的類。設計

 接下來咱們舉一個例子,好比咱們在觀察者一章中就提到一個問題,就是說觀察者模式的一個缺點,即若是一個現有的類沒有實現Observer接口,那麼咱們就沒法將這個類做爲觀察者加入到被觀察者的觀察者列表中了,這實在太遺憾了。code

 在這個問題中,咱們須要獲得一個Observer接口的類,可是又想用原有的類的功能,可是咱們又改不了這個原來的類的代碼,或者原來的類有一個完整的類體系,咱們不但願破壞它,那麼適配器模式就是你的不二之選了。server

 咱們舉個具體的例子,好比咱們但願將HashMap這個類加到觀察者列表裏,在被觀察者產生變化時,假設咱們要清空整個MAP。可是如今加不進去啊,爲何呢?對象

 由於Observable的觀察者列表只認識Observer這個接口,它不認識HashMap,怎麼辦呢?blog

這種狀況下,咱們就可使用類適配器的方式將咱們的HashMap作點手腳,剛纔已經說了,類適配器採用繼承的方式,那麼咱們寫出以下適配器。繼承

複製代碼
public class HashMapObserverAdapter<K, V> extends HashMap<K, V> implements Observer{

    public void update(Observable o, Object arg) {
        //被觀察者變化時,清空Map
        super.clear();
    }

}
複製代碼

                 即咱們繼承咱們但願複用其功能的類,而且實現咱們想適配的接口,在這裏就是Observer,那麼就會產生一個適配器,這個適配器具備原有類(即HashMap)的功能,又具備觀察者接口,因此這個適配器如今能夠加入到觀察者列表了。接口

                 看,類適配器很簡單吧?那麼下面咱們來看看對象適配器,剛纔說了對象適配器是採用組合的方式實現。

                 爲何要採用組合呢?上面的方式不是很好嗎?

                 究其根本,是由於JAVA單繼承的緣由,一個JAVA類只能有一個父類,因此當咱們要適配的對象是兩個類的時候,你怎麼辦呢?你難道要將兩個類所有寫到extends後面嗎,若是你這麼作了,那麼編譯器會表示它的不滿的。

                 咱們仍是拿觀察者模式那一章的例子來講(觀察者模式比較慘,老要適配器模式擦屁股),好比咱們如今有一個寫好的類,假設就是個實體類吧。以下。

複製代碼
public class User extends BaseEntity{
    private Integer id;
    private String name;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
複製代碼

                 看到了吧,咱們的實體類大部分都是繼承自BaseEntity的,那如今你怎麼辦吧,你要想具備被觀察者的功能還要繼承Observable類,你說你怎麼繼承吧。

                 你是否是想說,那個人User不繼承BaseEntity不就完事了,我把BaseEntity裏面的東西所有挪動到User類,或者我不繼承Observable了,把Observable裏面的東西所有挪到User類裏面。

                 這並非不行,可是這是個很大的隱患,好比咱們項目到時候要針對BaseEntity的子類進行掃描,用來作一些事情,這時候若是User沒繼承BaseEntity,那麼你就會遺漏掉這個類,這會破壞你的繼承體系,付出太大了。

                 相反,若是你不繼承Observable,那麼你的User類看起來會很是雜亂,並且假設我如今不只User類能夠被觀察了,個人Person類,Employee都能被觀察了,你難道要把Observable的代碼COPY三次到這三個類裏面嗎?

                不要忘了剛纔說的,適配器模式就是爲了幫助咱們複用代碼的,這裏使用適配器模式就能夠幫咱們複用Observable的代碼或者說功能。

                基於上面LZ的討論,咱們作出以下適配器,這裏採用的對象適配器。

複製代碼
//咱們繼承User,組合Observable.
public class ObservableUser extends User{
    
    private Observable observable = new Observable();

    public synchronized void addObserver(Observer o) {
        observable.addObserver(o);
    }

    public synchronized void deleteObserver(Observer o) {
        observable.deleteObserver(o);
    }

    public void notifyObservers() {
        observable.notifyObservers();
    }

    public void notifyObservers(Object arg) {
        observable.notifyObservers(arg);
    }

    public synchronized void deleteObservers() {
        observable.deleteObservers();
    }

    protected synchronized void setChanged() {
        observable.setChanged();
    }

    protected synchronized void clearChanged() {
        observable.clearChanged();
    }

    public synchronized boolean hasChanged() {
        return observable.hasChanged();
    }

    public synchronized int countObservers() {
        return observable.countObservers();
    }
    
    
}
複製代碼

              咱們繼承User,而不是繼承Observable,這個緣由剛纔已經說過了,咱們不能破壞項目中的繼承體系,因此如今可觀察的User(ObservableUser)依然處於咱們實體的繼承體系中,另外若是想讓ObservableUser具備User的屬性,則須要將User的屬性改成protected。

              這下好了,咱們有了可觀察的User了。不過LZ早就說過,設計模式要活用,這裏明顯不是最好的解決方案。由於咱們要是還有Person,Employee類都要具備可觀察的功能的話,那其實也至關慘,由於下面那些Observable的方法咱們還要再複製一遍。

              提示到這裏,不知各位想到更好的解決方案了嗎?尤爲是新手能夠好好思考下。

              LZ這裏給出最終相對來講比較好的解決方案,那就是咱們定義以下可觀察的基類。

複製代碼
//咱們擴展BaseEntity,適配出來一個可觀察的實體基類
public class BaseObservableEntity extends BaseEntity{

    private Observable observable = new Observable();

    public synchronized void addObserver(Observer o) {
        observable.addObserver(o);
    }

    public synchronized void deleteObserver(Observer o) {
        observable.deleteObserver(o);
    }

    public void notifyObservers() {
        observable.notifyObservers();
    }

    public void notifyObservers(Object arg) {
        observable.notifyObservers(arg);
    }

    public synchronized void deleteObservers() {
        observable.deleteObservers();
    }

    protected synchronized void setChanged() {
        observable.setChanged();
    }

    protected synchronized void clearChanged() {
        observable.clearChanged();
    }

    public synchronized boolean hasChanged() {
        return observable.hasChanged();
    }

    public synchronized int countObservers() {
        return observable.countObservers();
    }
    
}
複製代碼

              這下好了,如今咱們的User,Person,Employee要是想具備可被觀察的功能,那就改去繼承咱們適配好的BaseObservableEntity就行了,並且因爲BaseObservableEntity繼承了BaseEntity,因此他們三個依然處於咱們實體的繼承體系中,並且因爲咱們的BaseObservableEntity是新增的擴展基類,因此不會對原來的繼承體系形成破壞。

              適配器模式的用法仍是比較清晰的,咱們以上兩種方式都是爲了複用現有的代碼而採用的適配器模式,LZ剛纔說了,根據目的的不一樣,適配器模式也能夠分爲兩種,那麼上述即是第一種,可稱爲定製適配器,還有另一種稱爲缺省適配器

              首先咱們得先說下缺省適配器爲何要出現,由於適配器模式大部分狀況下是爲了補救,因此既然補救,那麼確定是歷史緣由形成的咱們須要使用這個模式。

              咱們來看看缺省適配器的歷史來由,不知各位仍是否記得在第一章總綱中,LZ曾經提到過一個原則,最小接口原則。

              這個原則所表達的思想是說接口的行爲應該儘可能的少,那麼還記得LZ當時說若是你沒作到的話會產生什麼狀況嗎?

              結果就是實現這個接口的子類,極可能出現不少方法是空着的狀況,由於你的接口設計的過大,致使接口中本來不應出現的方法出現了,結果如今子類根本用不上這個方法,但因爲JAVA語言規則的緣由,實現一個接口必須實現它的所有方法,因此咱們的子類不得不被迫寫一堆空方法在那,只爲了編譯經過。

              因此爲了解決這一問題,缺省適配器就出現了。好比咱們有以下接口。

複製代碼
public interface Person {
    
    void speak();
    
    void listen();
    
    void work();
    
}
複製代碼

                 這是一我的的接口,這個接口表示了人能夠說話,聽和工做,假設是兩年前的LZ,還在家待業呢,LZ沒工做啊,可是LZ也是我的啊,因此LZ要實現這個接口,因此LZ只能把work方法抄下來空着放在那了,假設LZ是個聾啞人,好吧,三個方法都要空着了,可是LZ表示,LZ是人,LZ必定要實現Person接口。

                 固然,上述只是舉個例子,可是真實項目當中也會出現相似的狀況,那麼怎麼辦呢?

                 這下來了,咱們的缺省適配器來了,以下。

複製代碼
public class DefaultPerson implements Person{

    public void speak() {
    }

    public void listen() {
    }

    public void work() {
    }

}
複製代碼

                 咱們創造一個Person接口的默認實現,它裏面都是一些默認的方法,固然這裏由於沒什麼可寫的就空着了,實際當中可能會加入一些默認狀況下的操做,好比若是方法返回結果整數,那麼咱們在缺省適配器中能夠默認返回個0。

                 這下好了,LZ只要繼承這個默認的適配器(DefaultPerson),而後覆蓋掉LZ感興趣的方法就好了,好比speak和listen,至於work,因爲適配器幫咱們提供了默認的實現,因此就不須要再寫了。

                 這種狀況其實蠻多的,由於接口設計的最小化只是理想狀態,不免會有一些實現類,對其中某些方法不感興趣,這時候,若是方法過多,子類也不少,而且子類的大部分方法都是空着的,那麼就能夠採起這種方式了。

                 固然,這樣作違背了里氏替換原則,可是上面的作法本來就違背了接口的最小化原則,因此咱們在真正使用時要權衡兩者的利弊,到底咱們須要的是什麼。因此今後也能夠看出來,原則只是指導,並不必定也不可能所有知足,因此咱們必定要學會取捨。

                 總結下兩種實現方式的適配器所使用的場景,二者都是爲了將已有類的代碼複用而且適配到客戶端須要的接口上去。

 1,第一種類適配器,通常是針對適配目標是接口的狀況下使用。

 2,第二種對象適配器,通常是針對適配目標是類或者是須要複用的對象多於一個的時候使用,這裏再專門提示一下,對象適配器有時候是爲了將多個類一塊兒適配,因此纔不得不使用組合的方式,並且咱們採用對象適配器的時候,繼承也不是必須的,而是根據實際的類之間的關係來進行處理,上述例子當中必定要直接或間接的繼承自BaseEntity是爲了避免破壞咱們原來的繼承體系,但有些狀況下這並非必須的。

3,第三個缺省適配器,通常是爲了彌補接口過大所犯下的過錯,可是也請注意衡量利弊,權衡好之後再考慮是否要使用缺省適配器。

                 好了,本次適配器模式的分享就到此結束了,但願各位能夠從中獲得點收穫

相關文章
相關標籤/搜索