iT邦幫忙

2021 iThome 鐵人賽

DAY 14
0
Software Development

也該是時候學學 Design Pattern 了系列 第 14

Day 14: Structural patterns - Decorator

目的

使用包覆(Wrapper)的方式,可以動態地給物件增添新的功能,或是重新定義既有的功能,達到擴充目的。

說明

當原本的程式需要配合需求新增功能,新功能之間彼此不互斥,可以疊加時。簡單的做法是:

  • 功能 A
    • 功能 A 具有功能 B
      • 功能 A 具有功能 B、功能 C
    • 功能 A 具有功能 C
      • 功能 A 具有功能 C、功能 D
    • 功能 A 具有功能 D
      • 功能 A 具有功能 E

隨著新功能功能不斷地增加,簡單的做法必須建立符合各種可能的物件,將導致管理不易。

因此,可以換個想法,讓新功能包覆物件後形成新的物件,且新功能可以包覆其他新功能,如此一來,物件便擁有新功能。而管理上,只要確認包覆的順序即可,減少推測多種可能結果的麻煩。

作法是:

  1. 建立擁有基本功能的物件,視為該模式的祖先代。
  2. 建立新功能的親代,本身繼承祖先代,負責親代的規格細節。
  3. 建立新功能的子代,除了親代的功能之外,可以覆寫、新增功能。

UML 圖

Decorator Pattern UML Diagram

使用 Java 實作

本體物件,祖先代:Commuter

public class Commuter {
    private String name;

    private String destination;

    public Commuter() {
    }

    public Commuter(String name, String destination) {
        this.name = name;
        this.destination = destination;
    }

    public void claimDestination() {
        System.out.println("目的地: " + destination + " 已經抵達,準備下車");
    }

    public void commute() {
        System.out.println("我是 " + name + ",是一位通勤者");
    }
}

活動親代,繼承本體物件:Activity

public class Activity extends Commuter {
    protected Commuter commuter;

    public Activity() {
    }

    public Activity(Commuter commuter) {
        this.commuter = commuter;
    }

    public void commuterDecorate(Commuter commuter) {
        this.commuter = commuter;
    }

    @Override
    public void claimDestination() {
        if (commuter != null) {
            commuter.claimDestination();
        }
    }

    @Override
    public void commute() {
        if (commuter != null) {
            commuter.commute();
        }
    }
}

活動子代:InADazeListeningMusicObservingOthersStopEverythingWatchingYouTube

public class InADaze extends Activity {
    public InADaze() {
    }

    public InADaze(Activity activity) {
        super(activity);
    }

    public void dream() {
        System.out.println("似乎想到什麼,讓人想「A Ha」一下");
    }

    @Override
    public void commute() {
        super.commute();
        System.out.println("什麼都不想做,發呆中");
        dream();
    }
}

public class ListeningMusic extends Activity {
    public ListeningMusic() {
    }

    public ListeningMusic(Activity activity) {
        super(activity);
    }

    public void feel() {
        System.out.println("這首歌真讚啊");
    }

    @Override
    public void commute() {
        super.commute();
        System.out.println("專注在聽音樂");
        feel();
    }
}

public class ObservingOthers extends Activity {
    public ObservingOthers() {
    }

    public ObservingOthers(Activity activity) {
        super(activity);
    }

    public void alert() {
        System.out.println("警戒中");
    }

    @Override
    public void commute() {
        super.commute();
        alert();
        System.out.println("觀察他人的衣著、行為");
    }
}

public class StopEverything extends Activity {
    public StopEverything() {
    }

    public StopEverything(Activity activity) {
        super(activity);
    }

    @Override
    public void commute() {
        super.commute();
        System.out.println("停止一切事物");
    }
}

public class WatchingYouTube extends Activity {
    public WatchingYouTube() {
    }

    public WatchingYouTube(Activity activity) {
        super(activity);
    }

    public void detail() {
        System.out.println("這部影片可是今天的發燒啊");
    }

    @Override
    public void commute() {
        super.commute();
        System.out.println("專注在 YT 上");
        detail();
    }
}

測試:CommuterDecoratorSample

public class CommuterDecoratorSample {
    public static void main(String[] args) {
        System.out.println("---第一位通勤者---");
        Commuter victor = new Commuter("維特", "臺北車站");
        Activity watchingYouTube = new WatchingYouTube();
        watchingYouTube.commuterDecorate(victor);
        Activity stopEverything = new StopEverything(watchingYouTube);
        stopEverything.commute();
        stopEverything.claimDestination();

        System.out.println("\n---第二位通勤者---");
        Commuter sandy = new Commuter("珊迪", "市府站");
        Activity listeningMusic = new ListeningMusic();
        listeningMusic.commuterDecorate(sandy);
        listeningMusic.commute();
        listeningMusic.claimDestination();

        System.out.println("\n---第三位通勤者---");
        Commuter johnny = new Commuter("強尼", "象山站");
        Activity inADaze = new InADaze(stopEverything);
        inADaze.commuterDecorate(johnny);
        Activity observingOthers = new ObservingOthers(inADaze);
        observingOthers.commute();
        observingOthers.claimDestination();
    }
}

使用 JavaScript 實作

本體物件,祖先代:Commuter

class Commuter {
  constructor(name, destination) {
    this.name = name;
    this.destination = destination;
  }

  claimDestination() {
    console.log("目的地: " + this.destination + " 已經抵達,準備下車");
  }

  commute() {
    console.log("我是 " + this.name + ",是一位通勤者");
  }
}

活動親代,繼承本體物件:Activity

class Activity extends Commuter {
  constructor(commuter) {
    super(null, null);
    this.commuter = commuter;
  }

  commuterDecorate(commuter) {
    this.commuter = commuter;
  }

  /** @override */
  claimDestination() {
    if (this.commuter != null) {
      this.commuter.claimDestination();
    }
  }

  /** @override */
  commute() {
    if (this.commuter != null) {
      this.commuter.commute();
    }
  }
}

活動子代:InADazeListeningMusicObservingOthersStopEverythingWatchingYouTube

class InADaze extends Activity {
  constructor(activity) {
    super(activity);
  }

  dream() {
    console.log("似乎想到什麼,讓人想「A Ha」一下");
  }

  /** @override */
  commute() {
    super.commute();
    console.log("什麼都不想做,發呆中");
    this.dream();
  }
}

class ListeningMusic extends Activity {
  constructor(activity) {
    super(activity);
  }

  feel() {
    console.log("這首歌真讚啊");
  }

  /** @override */
  commute() {
    super.commute();
    console.log("專注在聽音樂");
    this.feel();
  }
}

class ObservingOthers extends Activity {
  constructor(activity) {
    super(activity);
  }

  alert() {
    console.log("警戒中");
  }

  /** @override */
  commute() {
    super.commute();
    this.alert();
    console.log("觀察他人的衣著、行為");
  }
}

class StopEverything extends Activity {
  constructor(activity) {
    super(activity);
  }

  /** @override */
  commute() {
    super.commute();
    console.log("停止一切事物");
  }
}

class WatchingYouTube extends Activity {
  constructor(activity) {
    super(activity);
  }

  detail() {
    console.log("這部影片可是今天的發燒啊");
  }

  /** @override */
  commute() {
    super.commute();
    console.log("專注在 YT 上");
    this.detail();
  }
}

測試:commuterDecoratorSample

const commuterDecoratorSample = () => {
  console.log("---第一位通勤者---");
  const victor = new Commuter("維特", "臺北車站");
  const watchingYouTube = new WatchingYouTube();
  watchingYouTube.commuterDecorate(victor);
  const stopEverything = new StopEverything(watchingYouTube);
  stopEverything.commute();
  stopEverything.claimDestination();

  console.log("\n---第二位通勤者---");
  const sandy = new Commuter("珊迪", "市府站");
  const listeningMusic = new ListeningMusic();
  listeningMusic.commuterDecorate(sandy);
  listeningMusic.commute();
  listeningMusic.claimDestination();

  console.log("\n---第三位通勤者---");
  const johnny = new Commuter("強尼", "象山站");
  const inADaze = new InADaze(stopEverything);
  inADaze.commuterDecorate(johnny);
  const observingOthers = new ObservingOthers(inADaze);
  observingOthers.commute();
  observingOthers.claimDestination();
};

commuterDecoratorSample();

總結

Decorator 可以用俄羅斯娃娃來聯想,每一次的包覆,除了維持既有功能之外,還能新增點什麼,好實踐需求。但是,為了達成「有條有理」的繼承、覆寫、新增,必須在動手寫程式碼之前就定義好哪些行為可以擴充,這部分的設計複雜、不容易實踐,可能到最後找不出共同點而罷休。

很明顯地,Decorator 模式也是屬於特殊要求下的解。

明天將介紹 Structural patterns 的第五個模式:Facade 模式。


上一篇
Day 13: Structural patterns - Composite
下一篇
Day 15: Structural patterns - Facade
系列文
也該是時候學學 Design Pattern 了31

尚未有邦友留言

立即登入留言