设计模式之装饰模式(结构型)--- 10

  • 导语
  • 怎么用
    1.样例背景
    2.UML类图
    3.代码示例
        3.1 不用设计模式时,以及此时存在的问题
        3.2 使用设计模式后的情况
  • 优缺点
  • 使用场景
    1.概括描述
    2.现存知名产品中的使用示例
  • 与其他设计模式的对比
  • 参考

导语

装饰(者)模式(Decorator,也叫包装器模式),在不改变原有对象的基础之上,动态的将功能附加到对象上。
提供了比继承更有弹性的替代方案(扩展原有对象的功能)

如果比较全的话要有以下几个角色:
1.抽象被装饰者
2.实际被装饰者
3.抽象装饰者
4.实际装饰者

特别注意:装饰模式的装饰顺序很重要,比如加密数据过滤词汇都可以是数据持久化之前的装饰功能,但若是先加密了数据后过滤词汇就会出问题,最理想的情况,是保证装饰类之间彼此独立,这样他们就可以以任意顺序组合了。

怎么用

共有2个示例,代码详见访问链接
下面以example1举例说明

1. 样例背景

吃早点,买煎饼
有的人愿意加个鸡蛋,有的人愿意加两个鸡蛋,有的人愿意加火腿,有的人……

2. UML类图

AbstractBatterCake -------------- 抽象被装饰者
BatterCake ---------------- 实际被装饰者
BatterCakeDecorator ------ 抽象装饰者(这里只是没有定义成抽象类)
SausageDecorator ----------------- 实际装饰者
EggDecorator ----------------- 实际装饰者

example1 使用装饰模式后 UML类图

3. 代码示例

3.1 使用装饰模式之前

/**
 * @Description: 煎饼
 */
public class BatterCake {
    protected String getDesc(){
        return "煎饼";
    }
    protected int cost(){
        return 8;
    }
}

/**
 * @Description: 加了鸡蛋的煎饼
 */
public class BatterCakeWithEgg extends BatterCake {
    @Override
    protected String getDesc() {
        return super.getDesc() + "加了一个鸡蛋";
    }

    @Override
    protected int cost() {
        return super.cost() + 2;
    }
}

/**
 * @Description: 加了鸡蛋和火腿的煎饼
 */
public class BatterCakeWithEggSausage extends BatterCake {
    @Override
    protected String getDesc() {
        return super.getDesc() + "加了一个鸡蛋,又加了一个火腿";
    }

    @Override
    protected int cost() {
        return super.cost() + 4;
    }
}

/**
 * @Description: 测试类
 */
public class Test {
    public static void main(String[] args) {
        beforeDecorator();
    }

    /**
     * @Description: 使用装饰者模式之前
     *          在现有条件下只能满足这两种需求
     *          如果客户还有别的需求怎么办?比如,需要加5个火腿2个鸡蛋
     */
    public static void beforeDecorator(){
        // 要一个加鸡蛋的煎饼
        BatterCake batterCake1 = new BatterCakeWithEgg();
        System.out.println(batterCake1.getDesc() + " 销售价格:" + batterCake1.cost());

        // 要一个加鸡蛋和一个火腿的煎饼
        BatterCake batterCake2 = new BatterCakeWithEggSausage();
        System.out.println(batterCake2.getDesc() + " 销售价格:" + batterCake2.cost());
    }
}

3.2 使用装饰模式之后

/**
 * @Description: 煎饼
 */
public abstract class AbstractBatterCake {
    protected abstract String getDesc();
    protected abstract int cost();
}

/**
 * @Description: 具体煎饼(具体的被装饰类)          
 */
public class BatterCake extends AbstractBatterCake{
    @Override
    protected String getDesc(){
        return "煎饼";
    }
    @Override
    protected int cost(){
        return 8;
    }
}

/**
 * @Description: 抽象的装饰者类
 *          因为你不知道将来的客户到底需要什么样的煎饼
 *
 *          如果 那些具体的装饰类中,有一些基础的共性的动作,那么可以把该类定义为抽象类,
 *          并添加基础公共的方法
 */
public class BatterCakeDecorator extends AbstractBatterCake{
    private AbstractBatterCake abstractBatterCake;

    public BatterCakeDecorator(AbstractBatterCake abstractBatterCake) {
        this.abstractBatterCake = abstractBatterCake;
    }

    @Override
    protected String getDesc() {
        return abstractBatterCake.getDesc();
    }

    @Override
    protected int cost() {
        return abstractBatterCake.cost();
    }
}

/**
 * @Description: 加了鸡蛋的装饰类(具体的装饰类)
 */
public class EggDecorator extends BatterCakeDecorator {

    public EggDecorator(AbstractBatterCake abstractBatterCake) {
        super(abstractBatterCake);
    }

    @Override
    protected String getDesc() {
        return super.getDesc() + " 加了一个鸡蛋";
    }

    @Override
    protected int cost() {
        return super.cost() + 2;
    }
}

/**
 * @Description: 加了火腿的装饰类(具体的装饰类)
 */
public class SausageDecorator extends BatterCakeDecorator {
    public SausageDecorator(AbstractBatterCake abstractBatterCake) {
        super(abstractBatterCake);
    }

    @Override
    protected String getDesc() {
        return super.getDesc() + " 加了一个火腿";
    }

    @Override
    protected int cost() {
        return super.cost() + 2;
    }
}

/**
 * @Description: 测试类
 * @Author: zqy
 * @CreateTime: 2019-07-20 19:55
 */
public class Test {
    public static void main(String[] args) {
        afterDecorator();
    }

    /**
     * @Description: 采用装饰者模式后
     *              以不变应万变
     */
    public static void afterDecorator(){

        AbstractBatterCake batterCakeBase = new BatterCake();

        // 要一个加鸡蛋的煎饼
        AbstractBatterCake batterCake1 = new EggDecorator(batterCakeBase);
        System.out.println(batterCake1.getDesc() + " 销售价格:" + batterCake1.cost());

        // 要一个加鸡蛋和一个火腿的煎饼
        AbstractBatterCake batterCake2 = new SausageDecorator(
                new EggDecorator(batterCakeBase));
        System.out.println(batterCake2.getDesc() + " 销售价格:" + batterCake2.cost());

        // 要二个加鸡蛋和一个火腿的煎饼
        AbstractBatterCake batterCake3 = new SausageDecorator(
                new EggDecorator(
                        new EggDecorator(batterCakeBase)));
        System.out.println(batterCake3.getDesc() + " 销售价格:" + batterCake3.cost());
    }
}

优缺点

  • 缺点
    1.会出现更多的代码,更多的类,增加程序的复杂性
    2.动态装饰时,多层装饰时会更复杂

  • 优点
    1.不改变原有对象的情况下给一个对象扩展功能——继承的有力补充,比继承灵活
    2.通过使用不同的装饰类以及他们的排列组合,可以实现多个不同的效果
    3.有效的把类的核心职责和装饰功能区分开,而且可以去除相关类中重复的装饰逻辑
    4.符合开闭原则

使用场景

1. 概括描述

  • 扩展一个类的功能或给一个类添加附加职责

2. 现存知名产品中的使用示例 todo

2.1 java.io.BufferedReader (jdk)

2.2 java.io.BufferedInputStream(jdk)

2.3 org.springframework.cache.transaction.TransactionAwareCacheDecorator

(spring)

2.4 org.apache.ibatis.cache.Cache (mybatis)

相关设计模式

  1. 装饰模式和代理模式
    装饰模式关注在一个对象上动态的添加方法;
    代理模式关注于控制对对象的访问;
    使用上二者的不同:
    代理模式中的代理类,可以对他的客户隐藏一个对象的具体信息,我们通常在使用代理模式的时候,常常在一个代理类中创建一个对象的实例,而当我们使用装饰模式的时候,我们通常会把原始对象作为一个参数,传给装饰者的构造器,这个是使用上的一些不同。
    从结构上来看和装饰模式类似,但代理模式是控制,更像是一种对功能的限制,而装饰模式是增加职责。

  2. 装饰模式和适配器模式
    他们都可以叫做包装模式(wrapper)
    装饰者和被装饰者可以实现相同的接口,或者装饰者是被装饰者的子类
    在适配器模式中,适配器和被适配的类具有不同的接口,当然也有可能是有部分接口是重合的。
    (如果在深入一点,装饰者模式还可以退化成半装饰者模式,也就是说,一个装饰者除了提供被装饰类的接口外,还提供了其他的方法,那就变成了一个半透明的装饰者,如果应用层代码想使用这个提供了特殊功能的方法的话,就要使用具体的装饰者类了。半装饰者在实际的场景中应用的比较少,重点关注装饰者就可以了。)

参考

  1. https://coding.imooc.com/learn/list/270.html(强烈推荐)
  2. https://en.wikipedia.org/wiki/Design_Patterns
  3. 大话设计模式,电子版下载链接,https://pan.baidu.com/s/17WOI3Bvp-JUoQXvaomHISg 密码:vw05
    (作者博客,https://www.cnblogs.com/cj723/archive/2007/12/30/1021314.html)
  4. https://www.cnblogs.com/geek6/p/3951677.html
  5. https://mp.weixin.qq.com/s/eAlPRqScG3-Acvi3HwYK3A
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容