前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >装饰者模式(动态组合)

装饰者模式(动态组合)

作者头像
幺鹿
发布2018-08-21 15:33:56
4180
发布2018-08-21 15:33:56
举报
文章被收录于专栏:Java呓语Java呓语

公告

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

定义

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

场景举例

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

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

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

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

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

程序员视角

拆分角色

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

重新描述该场景

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

如何实现

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

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

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

代码语言:javascript
复制
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 + '\'' +
                '}';
    }
}

运行结果:

代码语言:javascript
复制
添加身型{value='纤细'}
添加胸型{value='C罩杯'}
添加臀型{value='翘臀'}
添加脸型{value='瓜子脸'}

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

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

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

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

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

代码语言:javascript
复制
// 抽象装饰组件类型
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 + '\'' +
                '}';
    }

}

运行结果:

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

方案3 —— 装饰者模式

Bob编译执行看了下结果,发现自己还遗漏了一个TODO还未做。

他意识到一个重要的问题:

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

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

最终的方案:

代码语言:javascript
复制
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());
    }
}

运行结果如下:

代码语言:javascript
复制
===触发了装饰物的行为===
添加了身型{value='纤细'}
添加了臀型{value='翘臀'}
添加了胸型{value='C罩杯'}
添加了脸型{value='瓜子脸'}

总结

装饰者模式

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

应用装饰模式的注意点:

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

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

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

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


本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.02.05 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 公告
  • 定义
  • 场景举例
  • 程序员视角
    • 拆分角色
      • 重新描述该场景
        • 如何实现
          • 方案1 —— 最粗暴方式的实现
          • 方案2 —— 隔离变化,抽象类型
          • 方案3 —— 装饰者模式
        • 总结
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档