装饰者模式(动态组合)

公告

如果您是第一次阅读我的设计模式系列文章,建议先阅读设计模式开篇,希望能得到您宝贵的建议。

定义

装饰者模式:即动态的给一个对象添加一些额外的职责。

场景举例

现在是2187年,智能机器人已经发展到可以一个新的高度。全球知名厂商 XX 正在推广新一代“机器人”,这次它的宣传口号是 “除了不能生孩子其它都可以做”,这满足了无数宅男们无尽的幻想。

某天顾客Alice 进入你的店铺,正在准备挑选她的下一任“机器人女友”。Alice首先购买了机器人的原型,选择价格99999元的价位的机器人原型。机器人原型皮肤表面很光滑(想象下母鸡褪了毛的样子~~)。于是你顺理成章的向Alice介绍了,本店新推出的“外形套餐”。

Alice希望他的机器人拥有 “大胸”、“肤白”、“瓜子脸”、“大眼睛”……(省略300个要求)。好吧,Alice并不算本店遇到的最挑剔的客户,但幸好我们拥有多于3000个可选方案,可供Alice挑选。

在一番深思熟虑之后,Alice最终选择了,“C罩杯”、“瓜子脸”、“丰满”、“翘臀”的组合套餐,该“套餐包”额外收费3000元。确定付款之后。

后台的3D打印机正在迅速的制造中,经历10分钟的漫长等待。Alice取到了他满意的机器人女友。

程序员视角

拆分角色

  • 被装饰的对象(被装饰者) —— 机器人原型
  • 可用于装饰的对象(装饰者) —— 肤色、胸型、身型、脸型等
  • 客户端 —— 顾客Alice

重新描述该场景

客户端(Alice)被装饰的对象(机器人原型) 添加了许多 装饰对象(肤色、胸型、脸型、身型)

如何实现

Bob看到了你的门店生意火爆,同样是做机器人生意的他觉得有必要改良下他的传统售卖模式。

方案1 —— 最粗暴方式的实现

Bob一番思考后心想,暂时想不到啥好办法。我先做出来,再慢慢思考有没有好办法。不然客人都要被你抢光了。


public class Customer {

    public static void main(String[] args) {
        Machine machine = new Machine();
        machine.add(new Body("纤细"));
        machine.add(new Chest("C罩杯"));
        machine.add(new Butt("翘臀"));
        machine.add(new Face("瓜子脸"));
    }
}

public class Machine {

    public void add(Body value) {
        System.out.println(value);
    }

    public void add(Chest value) {
        System.out.println(value);
    }

    public void add(Butt value) {
        System.out.println(value);
    }

    public void add(Face value) {
        System.out.println(value);
    }

}

// 此处示意举例一个装饰对象,其他装饰对象与其结构一致
public class Body {

    public String value;

    public Body(String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "身型{" +
                "value='" + value + '\'' +
                '}';
    }
}

运行结果:

添加身型{value='纤细'}
添加胸型{value='C罩杯'}
添加臀型{value='翘臀'}
添加脸型{value='瓜子脸'}

方案2 —— 隔离变化,抽象类型

Bob很高兴的实现了方案1,但是总是有客户会提出新的奇葩的需求。每次客户提出新的需求的时候,Bob都要修改“机器人”的生产程序,来匹配外观的修改。才能把机器人的外观,预览给客户看。这样下来一天Bob只能服务几个客人,而对面的你的门店每天人进人出。

于是Bob心想是否可以不改变机器人的源代码程序,修改其外观元素

方案2有一个思想需要注意:把装饰物 装饰到 机器人身上,那么被装饰过的机器人依然是机器人。

所以无论是装饰物,还是被装饰物都需要实现同一个接口IDecorComponent

// 抽象装饰组件类型
public interface IDecorComponent {

}

public class Machine {

    public void add(MachineContainer container) {
        System.out.println("TODO:遍历容器,并Machine添加功能");
    }

}

// 提取装饰容器,并去掉了对具体类型的耦合
public class MachineContainer implements IDecorComponent {

    public void add(IDecorComponent component) {
        System.out.println("添加了" + component);
    }

}
// 装饰物实现了无方法的接口
public class Body implements IDecorComponent {

    public String value;

    public Body(String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "身型{" +
                "value='" + value + '\'' +
                '}';
    }

}

运行结果:

添加了身型{value='纤细'}
添加了胸型{value='C罩杯'}
添加了臀型{value='翘臀'}
添加了脸型{value='瓜子脸'}
TODO:遍历容器,并Machine添加功能 // 纳尼?这里怎么还没做?
机器人组装完成

方案3 —— 装饰者模式

Bob编译执行看了下结果,发现自己还遗漏了一个TODO还未做。
他意识到一个重要的问题:

无论是委托给 装饰容器还是机器人本身都避免不了要为机器人添加装饰物这事。

既然在劫难逃,那就只能勇敢面对了。

最终的方案:

public class Customer {

    public static void main(String[] args) {

        Machine machine = new Machine();
        // 配置装饰容器
        IDecorComponent component = new MachineContainer(machine);
        component = new Body(component, "纤细");
        component = new Butt(component, "翘臀");
        component = new Chest(component, "C罩杯");
        component = new Face(component, "瓜子脸");
        // 触发添加行为
        component.addBehiavor();
    }
}

public interface IDecorComponent {

    void addBehiavor();
}

public class Machine {
    // 对Machine 没有任何侵入
}

public class MachineContainer implements IDecorComponent {

    private Machine machine;

    public MachineContainer(Machine machine) {
        this.machine = machine;
    }

    public void add(IDecorComponent component) {
        System.out.println("添加了" + component);
    }

    @Override
    public void addBehiavor() {
        System.out.println("===触发了装饰物的行为===");
    }
}

// 新增的类,用于委托递归操作
public class Decor implements IDecorComponent {

    IDecorComponent component;

    public Decor(IDecorComponent component) {
        this.component = component;
    }

    @Override
    public void addBehiavor() {
        // 委托装饰的组件继续执行相关的行为
        this.component.addBehiavor();
    }
}

public class Body extends Decor {

    public String value;

    public Body(IDecorComponent component, String value) {
        super(component);
        this.value = value;
    }

    @Override
    public String toString() {
        return "身型{" +
                "value='" + value + '\'' +
                '}';
    }

    @Override
    public void addBehiavor() {
        // 触发了递归操作
        super.addBehiavor();
        System.out.println("添加了" + toString());
    }
}

运行结果如下:

===触发了装饰物的行为===
添加了身型{value='纤细'}
添加了臀型{value='翘臀'}
添加了胸型{value='C罩杯'}
添加了脸型{value='瓜子脸'}

总结

装饰者模式

装饰模式的本质:动态组合。

应用装饰模式的注意点:

各个装饰器之间最好是完全独立的功能,不要有依赖,这样在进行组合的时候才没有先后顺序的限制。否则会大大降低装饰组合的灵活度。

装饰模式不仅可以增加功能,可以增加功能的访问(这点可以参考:职责链模式)。也可以限制功能的执行的先后顺序(递归的思想)。

总之装饰模式是通过把复杂的功能简单化、分散化(注意:会产生比较多的子类)。然后再根据需求动态的组合这些功能(子类)。

建议用法:在不影响其他对象的情况下,透明的添加职责时。


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258

推荐阅读更多精彩内容

  • 1 场景问题# 1.1 复杂的奖金计算## 考虑这样一个实际应用:就是如何实现灵活的奖金计算。 奖金计算是相对复杂...
    七寸知架构阅读 3,781评论 4 66
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,083评论 18 139
  • 加密货币,特别是比特币,几乎从各个方面都得到了大量关注:规则、管理、税务、技术、产品创新等等,不胜枚举。“点对点(...
    简闻阅读 647评论 0 9
  • 代替惩罚的七个技巧如何应用: 一、请孩子帮忙。比如,我们带孩子去超市购物,但孩子却跑来跑去,我们此时可能会说,不要...
    贇贇妈说阅读 515评论 2 4
  • 酷热的暑假,时不时给人整一场暴雨,似乎就是来冲洗炎热积攒下来的疲惫。 昨天,正午未过,天就...
    西环房客阅读 134评论 1 3