装饰者模式的应用场景(java装饰者模式)

一、背景

来看一个项目需求:咖啡订购项目。

咖啡种类有很多:美式、摩卡、意大利浓咖啡;
咖啡加料:牛奶、豆浆、可可。

要求是,扩展新的咖啡种类的时候,能够方便维护,不同种类的咖啡需要快速计算多少钱,客户单点咖啡,也可以咖啡+料。

最差方案

直接想,就是一个咖啡基类,然后所有的单品、所有的组合咖啡都去继承这个基类,每个类都有自己的对应价格。

问题:那么多种咖啡和料的组合,都相当于是售卖的咖啡的一个子类,全都去实现基本就是一个全排列,显然又会类爆炸。并且,扩展起来,多一个调料,都要把所有咖啡种类算上重新组合一次。

改进方案

将调料内置到咖啡基类里,这样不会造成数量过多,当单品咖啡继承咖啡基类的时候,就都拥有了这些调料,同时,点没有点调料,要提供相应的方法,来计算是不是加了这个调料。

问题:这样的方式虽然改进了类爆炸的问题,但是属性内置导致了耦合性很强,如果删了一个调料呢?加了一个调料呢?每一个类都要改,维护量很大。

二、装饰者模式

装饰者模式:动态的将新功能附加到对象上,在对象功能扩展方面,比继承更有弹性。

具体实现起来是这样的,如下类图所示:

装饰者模式的应用场景(java装饰者模式)

可以看到,在装饰者里面拥有一个 Component 对象,这是核心的部分。

也就是不像我们想的,给单品咖啡里加调料,而是反向思维,把单品咖啡拿到调料里来,决定对他的操作。

如果 ConcreteComponent 很多的话,甚至还可以再增加缓冲层。

用装饰者模式来解决上面的咖啡订单问题,类图设计如下,考虑到具体单品咖啡的种类,增加了一个缓冲层,最基本的抽象类叫 Drink

装饰者模式的应用场景(java装饰者模式)

其中 Drink 就相当于是前面的 Component,Coffee 是缓冲层,下面的不同 Coffee 就是上面的ConcreteConponent。

费用的计算方式一改正常思路的咖啡中,而是在调料中,因为 cost 在 Drink 类里也有,所以到最终的计算,其实是带上之前的 cost 结果,如果多种装饰者进行装饰,比如一个coffee加了很多料,那么其实是 递归的思路计算 最后的 cost 。

这样的话,增加一个单品咖啡,或者增加调料,都不用改变其他地方。

代码如下,类比较多,但是每个都比较简单:

/*
    抽象类Drink,相当于Component;
    getset方法提供给子类去设置饮品或调料的信息
    但是:cost方法留给调料部分实现
*/
public abstract class Drink {
    public String description;
    private float price = 0.0f;
    //价格方法
    public abstract float cost();
    public float getPrice() {
        return price;
    }
    public void setPrice(float price) {
        this.price = price;
    }
    public String getDescription() {
        return description +":"+ price;
    }
    public void setDescription(String description) {
        this.description = description;
    }
}

接着就是Coffe缓冲层以及下面的实现类,相当于ConcreteComponent:

public class Coffee extends Drink{
    @Override
    public float cost() {
        return super.getPrice();
    }
}
public class MochaCoffee extends Coffee{
    public MochaCoffee() {
        setDescription(" 摩卡咖啡 ");
        setPrice(7.0f);
    }
}
public class USCoffee extends Coffee{
    public USCoffee() {
        setDescription(" 美式咖啡 ");
        setPrice(5.0f);
    }
}
public class ItalianCoffee extends Coffee {
    public ItalianCoffee(){
        setDescription(" 意大利咖啡 ");
        setPrice(6.0f);
    }
}

然后是装饰核心,Decorator,和Drink是继承+组合的关系:

本文转载自:https://www.gylmap.com

/*
    Decorator,反客为主去拿已经有price的drink,并加上佐料
    加佐料的时候是拿去了Drink对象,但是也是给Drink进行
*/
public class Decorator extends Drink{
    private Drink drink;
    //提供一个构造器
    public Decorator(Drink drink){
        this.drink = drink;
    }
    @Override
    public float cost() {
        //计算成本,拿到佐料自己的价格+本来一杯Drink的价格
        //这里注意调用的是drink.cost不是drink.getPrice,因为cost才是子类实现的,Drink类的getPrice方法默认是返回0
        return super.getPrice() + drink.cost();
    }

    @Override
    public String getDescription() {
        //自己的信息+被装饰者coffee的信息
        return description + " " + getPrice() + " &&" + drink.getDescription();
    }
}

以及Decorator的实现类,也就是ConcreteDecorator:

public class Milk extends Decorator{
    public Milk(Drink drink) {
        super(drink);
        setDescription(" 牛奶:");
        setPrice(1.0f);
    }
}
public class Coco extends Decorator{
    public Coco(Drink drink) {
        super(drink);
        setDescription(" 可可:");
        setPrice(2.0f);//调味品价格
    }
}
public class Sugar extends Decorator {
    public Sugar(Drink drink) {
        super(drink);
        setDescription(" 糖:");
        setPrice(0.5f);
    }
}

注意,对于具体的Decorator,这里就体现了逆向思维,拿到的 drink 对象,调用父类构造器得到了一个drink,然后 set 和 get 方法设置调料自己的price和description,父类的方法 cost 就会计算价钱综合。

那里面的 super.getPrice() + drink.cost() 中的 cost(),就是一个递归的过程。

最后我们来写一个客户端测试:

public class Client {
    public static void main(String[] args) {
        //1.点一个咖啡,用Drink接受,因为还没有完成装饰
        Drink usCoffee = new USCoffee();
        System.out.println("费用:"+usCoffee.cost()+" 饮品信息:"+usCoffee.getDescription());
        //2.加料
        usCoffee = new Milk(usCoffee);
        System.out.println("加奶后:"+usCoffee.cost()+" 饮品信息:"+usCoffee.getDescription());
        //3.再加可可
        usCoffee = new Coco(usCoffee);
        System.out.println("加奶和巧克力后:"+usCoffee.cost()+" 饮品信息:"+usCoffee.getDescription());
    }
}

装饰者模式的应用场景(java装饰者模式)

可以看到,调用的时候,加佐料只要在原来的 drink 对象的基础上,重新构造,将原来的 drink 放进去包装(装饰),最后就达到了效果。

并且,如果要扩展一个类型的 coffee 或者一个调料,只用增加自己一个类就可以。

三、装饰者模式在 JDK 里的应用

java 的 IO 结构,FilterInputStream 就是一个装饰者。

装饰者模式的应用场景(java装饰者模式)

2.1 这里面 InputStream 就相当于 Drink,也就是 Component 部分;
2.2 FileInputStream、StringBufferInputStream、ByteArrayInputStream 就相当于是单品咖啡,也就是ConcreteComponent,是 InputStream 的子类;
2.3 而 FilterInputStream 就相当于 Decorator,继承 InputStream 的同时又组合了InputStream;

装饰者模式的应用场景(java装饰者模式)

2.4 BufferInputStream、DataInputStream、LineNumberInputStream 相当于具体的调料,是FilterInputStream的子类。

我们一般使用的时候:

DataInputStream dataInputStream = new DataInputStream(new FileInputStream("D://test.txt"));

或者:

FileInputStream fi = new FileInputStream("D:test.txt");
DataInputStream dataInputStream = new DataInputStream(fi);
//具体操作

这里面的 fi 就相当于单品咖啡, datainputStream 就是给他加了佐料。

更贴合上面咖啡的写法,声明的时候用 InputStream 接他,就可以:

InputStream fi = new FileInputStream("D:test.txt");
fi = new DataInputStream(fi);
//具体操作

感觉真是完全一样呢。

秒鲨号所有文章资讯、展示的图片素材等内容均为注册用户上传(部分报媒/平媒内容转载自网络合作媒体),仅供学习参考。用户通过本站上传、发布的任何内容的知识产权归属用户或原始著作权人所有。如有侵犯您的版权,请联系我们反馈!本站将在三个工作日内改正。
(0)

大家都在看

  • 狗一斤多少钱(现在肉狗能卖多少钱一斤)

    很多人喜欢狗狗们年幼时,毛绒绒的可爱模样,但是作为生物的它们,始终都会长大,而一旦它们长大之后,不仅模样可能会大变样,性格,行为等等都可能会有不同程度上的改变,并且,成年犬的危险性…

    2022年6月3日 专栏投稿
  • pe系统盘怎么用(pe系统盘制作教程)

    创作立场声明:站在一个普通玩家及普通消费者的情况下分享以下内容,文中所提到的所有商品及软件内容与小夜无任何利益关系。如果您觉得我整理的资料和建议对您有帮助,请点赞、关注、留言、分享…

    2022年6月7日 专栏投稿
  • iphone怎么同步(iphone两个设备同步)

    换新手机后,让大家比较头疼的一件事就是数据同步的问题,如果是同一品牌的手机,借助云端同步数据是可以将旧手机上一些比较重要的照片、通讯录、备忘等内容步到新手机上,但如果手机品牌不同的…

    2022年4月6日
  • flsh播放器是干嘛的(flash实用播放器使用方法)

    据外媒AppleInsider报道,现在已经是2022年了,Adobe不再支持Flash播放器,所以是时候把它从Mac上卸载了。下面则是如何处理老互联网浏览时代的遗留问题。Flas…

    2022年3月16日
  • 采摘多少钱(草莓怎样采摘一般多少钱)

    寒冬腊月,草莓开始“崭露头角”,采摘草莓便成了冬季家庭出行的必备选项。近日,记者前往官仓街道花牌坊社区的草莓采摘园进行了打探。 从赵镇街道到官仓街道花牌坊社区的路上,记者发现了很多…

    2022年5月18日
  • 计算机病毒分析(反病毒分析具体内容)

    静态分析:静态分析方法是在没有运行计算机病毒时, 对其进行分析的相关技术。常用的分析工具有VirusTotal、strings、IDA pro等。 动态分析:动态分析方法则需要运行…

    2022年5月15日
  • 锤子怎么样(现在锤子手机怎么样)

    坚果这个手机在当年可谓独树一帜,各方面都不错,但随着锤子的倒下,坚果的知名度也有所下降,不过坚果手机依然在市面上活跃,品质还是非常不错的。由于今年的安卓旗舰普遍采用骁龙888,在发…

    2022年4月28日 专栏投稿
  • 怎么变漂亮(如何让颜值快速变高)

    再有一个多月就是过年了,过年大家都希望自己可以美美的,漂漂亮亮的。 今天,小鱿鱼就来和大家分享,如何在过年前一个月改变自己?做好下文10个小细节,在一个月左右,让我们变美、变漂亮!…

    2022年4月4日 专栏投稿
  • 海带怎么吃(为什么晚上不能吃海带)

    海带是大家耳熟能详的一种海鲜蔬菜,因为营养价值高、有养生功效,又被称为长寿菜,但你知道海带不能乱吃吗? 甲亢病人要慎吃海带 甲状腺功能亢进症(甲亢)是一种促进人体活动亢进和新陈代谢…

    2022年4月7日
  • 联想乐pad的价格(平板电脑推荐)

    数码新时代,改变商品属性,平板取代笔记本电脑,成为更全面的pc电脑,当手机厂商们扬言要进入PC领域争夺商务笔记本的领域,平板电脑已悄然成为商务人士最爱的产品,平板凭借着优秀的性能和…

    2022年6月11日 专栏投稿
品牌推广 在线咨询
返回顶部