designPattern

Design Pattern(12) - Adaptor

以下文章是閱讀 深入淺出Design Pattern 還有 聖經還有Source making的筆記 圖片截圖自lynda.com的Foundations of Programming: Design Patterns 要更深入的理解一定要去看這兩本書

轉接器

跟我們常用的電源轉接器一樣 電腦需要的電壓 跟你牆壁電源提供的電壓不一樣 你就需要一個AC Adaptor來改變電壓 讓電腦可以跟生活用電相容

OO Adaptor也一樣 從一個介面轉到另一個介面 讓兩者可以相容

Alt text

掛羊頭賣狗肉

羊會叫 也會走

public interface Goat {
  public void makeBleatSound();
  public void walk();
}

public class WildGoat implements Goat {
  public void makeBleatSound() {
    System.out.println("bleat");
  }
  public void walk() {
    System.out.println("I'm walking");
  }
}

狗會叫 也會跑

public interface Dog {
  public void makeSound();
  public void run();
}

public class Maltese implements Dog {
  public void makeSound() {
    System.out.println("Woof");
  }
  public void run() {
    System.out.println("I'm running");
  }
}

我們今天想要掛羊頭賣狗肉的話 我們需要一個狗轉接器 這個轉接器需要實作羊 因為接下來client會把這個class生成的物件當作羊來用

public class DogAdapter implements Goat {
  Dog dog;

  public DogAdapter(Dog dog) {
    this.dog = dog;
  }

  public void makeBleatSound() {
    dog.makeSound();
  }

  public void walk() {
    dog.run();
  }
}

Client怎麼用呢

public class GoatTest {
  public static void main(String[] args) {
    Maltese dog = new Maltese();
    
    WildGoat goat = new WildGoat();
    Goat dogAdapter = new DogAdapter(dog);

    testGoat(goat);
    testGoat(dogAdapter);
  }

  static void testGoat(Goat goat) {
    goat.makeBleatSound();
    goat.walk();
  }
}
bleat
I'm walking
Woof
I'm running

這就是最基本的物件轉接器

轉接器模式

將一個類別的介面 轉換成另一個類別的介面供客戶使用 讓介面不相容的類別可以合作

物件轉換器結構

Alt text

物件轉換器優缺點

1.一個Adaptor就可以處理所有的Adaptee和Adaptee的子類別

2.Adaptee可以是介面也可以是類別 Target其實也可以是介面也可以是類別 四種排列組合都可以

不信

不信我實作給你看 剛剛的DogAdaptor是兩個都是介面(實作1)

下面的code Adaptee是介面(Dog) Target是類別(WildGoat) (實作2)

public class DogWildGoatAdapter extends WildGoat {
  Dog dog;

  public DogWildGoatAdapter(Dog dog) {
    this.dog = dog;
  }

  public void makeBleatSound() {
    dog.makeSound();
  }

  public void walk() {
    dog.run();
  }
}

下面的code Adaptee是類別(Maltese) Target是介面(Goat) (實作3)

public class MalteseAdapter implements Goat {
  Maltese mal;

  public MalteseAdapter(Maltese mal) {
    this.mal = mal;
  }

  public void makeBleatSound() {
    mal.makeSound();
  }

  public void walk() {
    mal.run();
  }
}

下面的實作是Adaptee是類別(Maltese) Target是類別(WildGoat) (實作4)

public class MalteseWildGoatAdapter extends WildGoat {
  Maltese mal;

  public MalteseWildGoatAdapter(Maltese mal) {
    this.mal = mal;
  }

  public void makeBleatSound() {
    mal.makeSound();
  }

  public void walk() {
    mal.run();
  }
}

因為在實作Adaptor的時候 對於Target的函式我們用了繼承 對於拿到Adaptee的函式我們用了合成(Composition)

因為用的是合成 所以Adaptee是越父類別越好 因為Adaptee的子類別也可以用父類別的Adaptor

實作2就是硬生生比實作4好用 實作1就是比實作3清爽

類別轉換器結構

還有另一種方法可以達到一樣效果 就是adaptor利用多重繼承 同時實作那兩個介面 然後再overwrite Target的函式 讓它去call Adaptee的函式 Alt text

類別轉換器優缺點

1.類別轉換器因為Adaptor直接繼承了Adaptee 所以Adpator無法使用在Adaptee的子類別的函式

2.但是Apaptor可以overwrite Adaptee的函式

3.Adaptee必須是類別(才能繼承) java並不支援多重繼承 所以Target必須要是介面

public class MalteseGoatClassAdapter extends Maltese implements Goat{
  public MalteseGoatClassAdapter() {}

  public void makeBleatSound() {
    this.makeSound();
  }

  public void walk() {
    this.run();
  }
}

奇怪了 你剛剛的實作1實作2的Adaptee也都用介面 那現在怎麼說不能用介面?

做學問要追根究柢 要用也可以 只是Dog介面裡面的函數還是必須要有人來實作 你就被逼的只能在Adaptor實作

public class DogClassWildGoatAdapter extends WildGoat implements Dog{

  public DogClassWildGoatAdapter() {}
  //實作Dog的函式
  public void makeSound() {
    System.out.println("Woof");
  }
  public void run() {
    System.out.println("I'm running");
  }
  //overwrite WildGoat的函式
  public void makeBleatSound() {
    this.makeSound();
  }

  public void walk() {
    this.run();
  }
}

一個類別就該做一件事 Adaptor類別不應該去實作狗狗介面 所以類別轉換器還是乖乖繼承狗類別