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 模式。


<<:  【Day14-字串】浅谈python中最常用到的str处理方式——格式化、寻找、取代、分割、合并

>>:  [GBC] 一个可以客制调整元件、支援通用渲染的Genero Client

30. 铁人赛心得

铁人赛心得 这是第二次参加铁人赛,第一次主题是javasript的入门学习,第二次是比较有一点难度的...

DAY2-JAVA的变数与资料型态

第一天有讲到变数的部分,今天就让我们来深入了解一下吧! 首先,JAVA的资料型态可以分为原始资料型态...

Day 19 - 写一个含状态的 Button

既然今天是连假第二天,咱们今天来点简单的,把前面复习一下加一点新知识,整合一个有各种状态的按钮样式...

Day 28 -- Rails 实作 Ransack 简易搜寻及排序功能

承接上一篇 实作 Rails-i18n语言选项在Bootstrap4 导览列,我们同样可以 在导览列...

[使用者提问的问题]请问一下, 用VPN连入,\\电脑名称 找不到 \\ip 可以用 请问是什麽原因?

答: 查对方电脑名称的条例有 DNS WINS NetBIOS over vpn tcp/ip LM...