设计模式之装饰模式

我们都去过奶茶店买过奶茶吧,一种奶茶可能有很多不同的产品,同一种产品也有很多不同的口味。我们去买的时候,都会发现我们的的奶茶是现场调制的,奶茶店会根据已有的很多奶茶,添加不同的口味。再比如新买的房子去装修,房子是不会变的,但是我们可以装修成不同的风格。这一过程就是装饰过程。其思想就是装饰模式。这篇文章将通过案例对装饰模式有一个了解和分析。

一、认识装饰模式

我们先给出装饰模式的概念,再去分析一下:

装饰模式又名包装模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。

有透明和半透明两种,大部分都是半透明的,半透明的装饰模式是介于装饰模式和适配器模式之间的。

概念看起来有点懵,我们通过奶茶例子来解释一下:

1-透明装饰模式.png

从上面这张图我们可以看到,我们去店里买一杯奶茶有10种,还有6种口味可供选择,如果我们把不同的奶茶都当成一个类,那我们就需要new出来6*10个类。想想这样也太多了,使用了装饰器模式,使用18个类(10个类表示奶茶,6个类表示口味、其他两个分别是奶茶和口味的接口抽象)就能生产任何奶茶了,是不是很方便。

现在来看看透明和半透明的区别:

透明的装饰模式,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。意思是奶茶接口、珍珠奶茶和蜂蜜奶茶、调料的这些接口一样。下面代码中会有体现。

相反,如果装饰角色的接口与抽象构件角色接口不一致,那就是半透明的了。

我们来看看装饰模式的类图:

2-类图.jpg

从上图我们可以看到,装饰模式一共有四个角色。

(1)Component(抽象构建):这里指的奶茶抽象接口,表示规范准备接收附加责任的对象。

(2)ConcreteComponent(具体构建):这里指珍珠奶茶、蜂蜜奶茶等具体的奶茶。

(3)Decorator(装饰器):定义一个与抽象构件接口一致的接口,这里表示各种口味。

(4)ConcreteDecorator(具体装饰器):具体不同的口味,给奶茶一些附加的特色。

现在有了这个例子,我们再回头看一看装饰模式的概念,加深理解。

装饰器动态地给一个对象添加一些额外的功能。相比起继承子类来说,他的功能更加的灵活。不改变接口的前提下,增强所考虑的类的性能。

什么时候我们去考虑使用装饰模式呢?下面列出了三种常见情况:

1)需要扩展一个类的功能,或给一个类增加附加功能。

2)需要动态的给一个对象增加功能时(可随时撤销)。

3)类似于奶茶的例子,排列组合产生的类太多,继承不现实的情况。

下面我们就是用代码来验证一下:
二、代码实现装饰模式

代码实现也比较简单,就是创建上面几个角色而已:

第一步:定义奶茶接口(Component)

public interface MilkTea {
    //奶茶名字
    public  String milkTeaName();
    //奶茶价格
    public  int milkTeaPrice();
}

第二步:定义两种不同种类的奶茶:珍珠奶茶和蜂蜜奶茶(ConcreteComponent)

首先看珍珠奶茶

public class PearlMilkTea implements MilkTea {
    @Override
    public String milkTeaName() {
        return "珍珠奶茶";
    }
    //珍珠奶茶一杯15块
    @Override
    public int milkTeaPrice() {
        return 15;
    }
}

接下来看蜂蜜奶茶

public class HoneyMilkTea implements MilkTea {
    @Override
    public String milkTeaName() {
        return "蜂蜜奶茶";
    }
    //蜂蜜奶茶一杯20块
    @Override
    public int milkTeaPrice() {
        return 20;
    }
}

第三步:定义口味(Decorator)

//装饰器:为奶茶添加不同的口味
public class Taste implements MilkTea {
    @Override
    public String milkTeaName() {
        return "具体的名字让子类去定义";
    }
    @Override
    public int milkTeaPrice() {
        return 0;
    }
}

第四步:具体口味(ConcreteDecorator):加冰和加咖啡

首先是加冰的口味:

public class AddIceTaste extends Taste {    
    private String description = "奶茶加冰。。。。";
    private MilkTea milkTea=null;
    
    public AddIceTaste( MilkTea milkTea) {
        this.milkTea = milkTea;
    }
    //我们让相应的奶茶去增加这个口味
    public String milkTeaName() {
        return milkTea.milkTeaName()+" "+description;
    }
    //同理,奶茶的价格也要提高一点:增加5块钱吧
    public int milkTeaPrice() {
        return milkTea.milkTeaPrice()+5;
    }
}

然后是加咖啡的:

public class AddCoffeeTaste extends Taste { 
    private String description = "奶茶加咖啡。。。。";
    private MilkTea milkTea=null;
    
    public AddCoffeeTaste( MilkTea milkTea) {
        this.milkTea = milkTea;
    }
    //我们让相应的奶茶去增加这个口味
    public String milkTeaName() {
        return milkTea.milkTeaName()+" "+description;
    }
    //同理,奶茶的价格也要提高一点:增加15块钱吧
    public int milkTeaPrice() {
        return milkTea.milkTeaPrice()+15;
    }
}

第五步:用户买奶茶

public class User {
    public static void main(String[] args) {    
        MilkTea honeyMilkTea=new HoneyMilkTea();
        System.out.println("买了一杯蜂蜜奶茶,价格是"+honeyMilkTea.milkTeaPrice());
        honeyMilkTea = new AddCoffeeTaste(honeyMilkTea);
        System.out.println("加咖啡,价格是"+honeyMilkTea.milkTeaPrice());
        
        MilkTea pearlMilkTea=new HoneyMilkTea();
        System.out.println("买了一杯珍珠奶茶,价格是"+pearlMilkTea.milkTeaPrice());
        pearlMilkTea = new AddIceTaste(pearlMilkTea);
        System.out.println("加冰块,价格是"+pearlMilkTea.milkTeaPrice());
    }
}
//output
//买了一杯蜂蜜奶茶,价格是20
//加咖啡,价格是35
//买了一杯珍珠奶茶,价格是20
//加冰块,价格是25

ok,以上就是装饰模式的代码实现,我们来进行一个小结:

(1)装饰模式用于动态地给一个对象增加一些额外的职责,是一种对象结构型模式。

(2)装饰模式包含四个角色。

(3)比生成子类实现更为灵活。

(4)装饰模式可分为透明装饰模式和半透明装饰模式。

现在我们基本上对装饰模式有了基本的了解了,不过这还不够,在文中一开始我们提到过,上面的只是对透明类型的装饰模式进行了介绍,下面就来看看什么是半透明的。

三、分析装饰模式

1、半透明装饰模式

在上面我们已经对透明的装饰模式进行了代码的演示,但是还需要介绍一下半透明的装饰模式。

半透明装饰模式可以给系统带来更多的灵活性,设计相对简单,使用起来也非常方便;但是其最大的缺点在于不能实现对同一个对象的多次装饰,而且客户端需要有区别地对待装饰之前的对象和装饰之后的对象。那它是怎么实现的呢?我们来看看透明装饰模式和半透明装饰模式的区别。

我们开的车可以定义为一个抽象构建,其具体构件可以有小轿车、越野车、跑车等等,他们都具有在马路上行驶的能力,但是突然出现了一种车,不仅能在地上跑了,也能在天上飞了,这时候我们就需要在我们的装饰器里面新增一个fly方法,过了没多久我们发现又出现了一种车更牛,不仅能在地上跑,还能在水里游了,这时候我们有需要在我们的装饰器里面新增一个swim方法。这些fly和swim是车接口没有的方法,问题就在这。也就是说果装饰角色的接口(装饰器)与抽象构件角色接口(车接口)中的方法不一致。这就是透明装饰模式和半透明装饰模式的区别。

2、与适配器模式区别

(1)概念区分

适配器模式,一个适配允许通常因为接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中。

装饰器模式,原有的不能满足现有的需求,对原有的进行增强。

(2)继承特点

适配器模式是用新接口来调用原接口,原接口对新系统是不可见或者说不可用的。

装饰者模式原封不动的使用原接口,系统对装饰的对象也通过原接口来完成使用。(增加新接口的装饰者模式可以认为是其变种--“半透明”装饰者)

(3)包装

装饰模式包装的是自己的兄弟类,隶属于同一个家族(相同接口或父类),

适配器模式则修饰非血缘关系类,把一个非本家族的对象伪装成本家族的对象,注意是伪装,因此它的本质还是非相同接口的对象。

总结:装饰模式分为透明和半透明两种,半透明要记住和适配器模式的区分。

OK,今天的文章就先到这里。如有问题还请批评指正。

需要计算机系列的各种视频教程与书籍,还请关注我的微信公众号:java的架构师技术栈

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

推荐阅读更多精彩内容