设计模式之装饰模式

作者: 愚公要移山 | 来源:发表于2019-07-13 21:45 被阅读1次

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

一、认识装饰模式

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

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

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

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

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

相关文章

  • JavaScript 设计模式核⼼原理与应⽤实践 之 结构型设计

    JavaScript 设计模式核⼼原理与应⽤实践 之 结构型设计模式 装饰器模式,又名装饰者模式。它的定义是“在不...

  • 聊聊Context

    在上篇文章《设计模式之装饰模式》中我们谈到了装饰模式,在 Android 中关于 Context 的设计就用到了装...

  • 9、结构型模式-装饰器设计模式

    1、如虎添翼的设计模式-装饰器设计模式 简介:讲解-装饰器设计模式介绍和应用场景 装饰器设计模式(Decorato...

  • Java设计模式之装饰器模式

    Java设计模式之装饰器模式 本文仅是个人观点,如有错误请指正 简介 装饰器模式(Decorator Patter...

  • Golang 设计模式之-装饰模式

    Golang 设计模式之-装饰模式 最近在温习设计模式,虽然面向对象不是go的特长,但用go实现的设计模式,比ja...

  • 设计模式笔记汇总

    目录 设计原则 “依赖倒置”原则 未完待续... 设计模式 设计模式——策略模式 设计模式——装饰者模式 设计模式...

  • 修饰模式

    维基百科-修饰模式 php 设计模式 之 装饰模式 hero博客——装饰模式 动态的给一个对象添加一些额外的职责,...

  • Java设计模式之 —— 装饰者(Decorator) — 点

    Java设计模式之 —— 装饰者(Decorator) — 点炒饭 下面会用做炒饭的例子来描述一下装饰者设计模式,...

  • 设计模式之-装饰模式

    好了,昨天我们说完了单利模式,趁着现在想写博客就多写几篇吧. 下面开始说这个装饰模式 装饰模式其实还有做成一个类,...

  • 设计模式之装饰模式

    将所有功能建立在继承体系上会导致系统中的类爆炸式的增多。更糟糕的是当你尝试继承数上不同的分支做相似的修改时,代码会...

网友评论

    本文标题:设计模式之装饰模式

    本文链接:https://www.haomeiwen.com/subject/idqvkctx.html