Head First 设计模式学习——装饰者模式

本文深入讲解装饰者模式的概念与应用场景,通过买饮料的例子演示如何利用装饰者模式提高代码的扩展性和灵活性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

设计模式是进阶高级开发的必经之路。掌握设计模式,才能提升编码能力,设计出可复用、可扩展、可维护的软件系统。了解设计模式,才能更好理解开源类库的实现原理,解决问题。
装饰者模式(Decorate Pattern)是《Head First 设计模式》介绍的第三个模式,只要你认真读完本篇博文,一定能很好地掌握此模式。

定义

装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

应用场景举例

  • 在网上买手机。同样型号的手机,通过添加不同的选项,不同的配置,最后计算出不同的价格。过程中手机一直在变化,但本质上还是一个手机。
  • 买饮料,可以选择水、奶茶、咖啡。而每一种都有不同口味,加不同的调料,选择不同的分量,最后计算不同的价格,虽然不同的人最后买到的饮料不一样,但都有个超类型——饮料。

分析

用java 实现买饮料计算价格的行为。抽象一个基类饮料Beverage,店里所有的饮料都继承此类。饮料卖出都要计算价格,而且计算要根据添加的调料计算。因此,有一个抽象方法cost(),需要子类具体实现,代码如下:

代码

abstract class Beverage{
    String description="基础饮料";
    //计算价格的方法,由子类实现
    abstract double cost();
}

于是,原味奶茶MilkyTea :

class MilkyTea extends Beverage{
    public MilkyTea(){
        description = "原味奶茶";
    }
    double cost(){
        return 3.5;
    }
}

而珍珠奶茶PearlMilkyTea :

class PearlMilkyTea extends Beverage{
    public PearlMilkyTea (){
        description = "珍珠奶茶";
    }
    double cost(){
    //珍珠奶茶是在原味基础上加珍珠
        return 3.5+2;
    }
}
class OtherMilkyTea extends Beverage{
    double cost(){
    //多次计算
        return 3.5+2+6+9+...+1;
    }
}

此外还有香草、芒果等奶茶,都是如此。还有香草芒果搭配来的奶茶。假设店里现在有5个品种的奶茶,我们需要准备5个子类,对应每个品种,并实现独有的计算价格的方法。幸福的时光总是短暂的,换来的确是无穷无尽的痛苦和悔恨——至尊宝。
原材料上涨,奶茶、珍珠等价格都上调,我们需要调整每个子类中的cost方法。增加新品种,我们需要增加新的子类满足需求。还有其他的需求都会导致牵一发动全身。此处我们要考虑到一个新的原则:类应该对扩展开放,对修改关闭。此处当然不是指每一句代码都要如此,而是一种编程、设计时遵循的原则。

改造

分析可得,成品奶茶都是由前一步骤的奶茶通过添加调料组合而来,而且还能继续被组合。装饰者设计模式刚好能解决此类问题。
首先需要定义一个顶层的抽象类型,此处就是Beverage,原味奶茶就是扩展出来的组件component,我们要新增一个类型——装饰者类型(Decorate抽象类或者接口)。通过把需要装饰的组件传入、改造,返回原类型。就像原味奶茶传入装饰者后,返回的还是奶茶一样。因此装饰者类也继承自顶层抽象类型Beverage。Decorate中需要一个引用来保存传入的组件,所有的装饰者都要如此。另外可能还有些装饰者共同的操作,因此我们将Decorate抽象并加入抽象方法,让真正的装饰者来实现

abstract class Decorate extends Beverage{
//对象被装饰之后肯定会有变化,我们之前设置了description 变量,这里我们让所有装饰者实现此方法来获取它们装饰之后对象变成什么样了
    String getDescrpition();
}
//原味奶茶不用改动
class MilkyTea extends Beverage{
    public MilkyTea(){
        description = "原味奶茶";
    }
    double cost(){
        return 3.5;
    }
}

//实现一个珍珠装饰者,功能是给传入的饮料加上珍珠调料,并加上价格
public class PearlDecorate extends Decorate{
//保存一个传入的被装饰者,用超类类型引用
    Beverage beverage;
    public PearlDecorate (Beverage beverage){
        description  = "珍珠";
        this.beverage = beverage;
    }
    //打印传入的饮料+当前装饰者的改变:+珍珠
    @Override
    String getDescription() {
        String currentDes = beverage.getDescription()+"+"+description;
        System.out.println(currentDes);
        return currentDes;
    }
    //计算价格,原来的价格+当前珍珠价格
    @Override
    double cost() {
        double result = beverage.cost()+2.5;
        System.out.println("加"+description+"价格:"+beverage.cost()+2.5+"="+result);
    }

}

同样的,香草装饰者VanillaDecorate、芒果MangoDecorate等装饰者代码类似,略。客户端调用如下:

    public static void main(String[] args) {
        //点一杯原味
        Beverage milkyTea = new MilkyTea();
        //加珍珠
        Beverage pearlTea = new PearlDecorate(milkyTea);
        //加香草
        Beverage vanillalTea = new VanillaDecorate(pearlTea);
        //加芒果
        Beverage mangoTea = new MangoDecorate(vanillalTea);
        mangoTea.getDescription();
        mangoTea.cost();
    }

控制台输出(其他信息清除):

原味奶茶
原味奶茶+珍珠
原味奶茶+珍珠+香草
原味奶茶+珍珠+香草+芒果
原味奶茶价格:3.5
加珍珠价格:3.5+2.5=7.5
加香草价格:7.5+4.5=11.5
加芒果价格:11.5+5.2=16.7

如此重构之后,虽然类的数量没有减少(多少个调料就有多少个装饰类),但扩展性大大提高。比如原来珍珠香草,需要一个具体的子类,现在只需要通过两个装饰类即可获得。另外,如果要开发新的组合,也只用根据顺序装饰即可。而且如果某种调料的行为、价格发生变化,只需要更改其自己的内部逻辑即可。

JDK中的应用

JDK 的java.io包中有许多的io类。相信许多人都会被这一堆类弄晕,分不清何时该哪个。此处就是运用装饰者模式。看源代码可知,最顶层的类是:InputStreamOutputStream,其他的操作流都继承于此类,如:

//顶层抽象类
class FileInputStream extends InputStream{}
class FileOutputStream extends OutputStream{}

而其他种类繁多的操作类,则是装饰者,它们扩展自FilterInputStreamFilterOutputStream,当然它们也必须扩展自顶层基类:

//装饰者抽象类
class FilterInputStream extends InputStream {}
class FilterOutputStream extends OutputStream{}

而剩下的则是装饰者的具体实现了,他们覆盖父类中的方法,提供更多功能,比如:

class BufferedOutputStream extends FilterOutputStream{}
//通过重写父类中的write()方法,以提供更多功能。具体查看源码

总结

  • 当一个类型会经历多种变化但仍能保持本心时(还是我们熟悉的样子),我们就可以使用装饰者模式。
  • 需要一个顶层的抽象类型,以保证装饰者和被装饰者在抽象层面可以相互替代
  • 原则:类应该对扩展开发,都修改关闭
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值