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

一、背景 来看一个项目需求:咖啡订购项目。 咖啡种类有很多:美式、摩卡、意大利浓咖啡;咖啡加料:牛奶、豆浆、可可。 要求是,扩展新的咖啡种类的时候,能够方便维护,不同种类的咖啡需要快速计算多少钱,客户单点咖啡,也可以咖啡+料。 最差方案 直接想,就是一个咖啡基类,然后所有的单品、所有的组合咖啡都去继承这个基类,每个类都有自己的对应价格。 问题:那么多种咖啡和料的组合,都相当于是售卖的咖啡的一个子类…

一、背景

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

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

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

最差方案

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

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

改进方案

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

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

二、装饰者模式

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

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

设计模式:装饰者模式介绍及代码示例&JDK里关于装饰者模式的应用

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

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

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

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

设计模式:装饰者模式介绍及代码示例&JDK里关于装饰者模式的应用

其中 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是继承+组合的关系:

/*
    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());
    }
}
设计模式:装饰者模式介绍及代码示例&JDK里关于装饰者模式的应用

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

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

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

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

设计模式:装饰者模式介绍及代码示例&JDK里关于装饰者模式的应用

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

设计模式:装饰者模式介绍及代码示例&JDK里关于装饰者模式的应用

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年5月9日 下午1:50
下一篇 2022年5月9日 下午1:52

相关推荐

  • qq群营销技巧有哪些,qq群营销的注意事项

    QQ群营销是互联网最重要的网络推广方式之一,掌握QQ群营销,对你的互联网生意至关重要。很多人会说 ,现在谁还用QQ,大家都在微信做营销,这一点我不否认,只不过qq群在人员的管理,资料的共享、群视频等方面相对微信群功能更强大。用好之后效果不可估量,大家都知道营销的核心是流量,而成交的核心是信任,而QQ群营销正是建立在流量和信任双重保障的前堤下而存在。 通过QQ群营销,我们无需要太多推广成本,只需发布…

    2022年7月13日
    570
  • win10时间显示秒的工具有哪些,win10状态栏时间显秒教程

    Win10系统任务栏右下角的系统时间一般是显示到分钟,但也可以设置为显示到秒,那么如何让系统时间显示秒钟呢?本文就给大家分享Win10系统时间显示秒的方法。 操作步骤 1.使用Windows+R快捷键打开“运行”,输入regedit打开注册表编辑器,定位到: 2、在右侧窗口右键点击新建DWORD(32位)值,并命名为ShowSecondsInSystemClock,双击打开将数值数据改为1,然后重…

    2022年7月4日
    860
  • 如何融资创业,手把手教你创业融资的方法

    很多人都有创业梦,但很多人的创业梦又被资金缺乏现实撞碎。如何借助内外力量实现“巧”融资呢?下面介绍几种方法。 创业融资方法一、定期存单抵押贷款。 如果急需用钱时而手头现金不足,定期储蓄存款又尚未到期,提前支取将损失不少利息。此时,个人定期储蓄存款小额抵押贷款可解难。该贷款额度起点为1000元,每笔不超过抵押存单面额的80%,最高限额为10万元,贷款期限不能超过存单到期日。 创业融资方法二、保单质押…

    2022年7月15日
    730
  • 成功人士的创业故事分享,看看他们是怎样创业成功的

    1、寂寞变成好生意 2008年,有个叫陈齐明的小伙子坐在台北车站前的星巴克。窗外人人擦肩而过,也许这辈子再也不会相遇。突然,他像被苹果打到头:如果有个网站让大家记录今天去了哪里,也许回家上网,会发现彼此下午3点曾在同一个地方。 一个月后,他开始架设想象中的网站。这个命名为“地图日记”的网站有点像群体博客,他与“谷歌地图”合作,以地图为主轴,网友依照不同的地点写上日记、放上照片。例如,你在这个网站上…

    2022年6月20日
    650
  • 折叠电动车品牌排行榜(口碑最好的六款折叠电动车)

    最近几年由于城市污染问题严重,所以特别推崇健康绿色的出行方式,电动车也自然而然成为了大家的首选工具,骑行方便又舒适。那么今天小编就来为大家列出性价比高的折叠电动车,感兴趣的小伙伴快来看看吧。 1、G-force电动自行车 详细介绍:G-force这款电动车的设计非常时尚,很适合年轻人,想要绿色出行会是一个不错的选择。在锂电池的配置方面也是比较稳定的,还有智能化的仪表,能够清晰查看骑行数据。 参考价…

    2022年10月24日
    650
  • 实体店开店步骤及流程,第一次开店忌讳和注意事项说明

    配图:旺铺招租 想要开好店做好生意,首先要做到心中有数,要知道如何开店,开店步骤是什么。那么,开店创业有哪些步骤呢?下面小编将为您详细解说一下,如果您有创业的打算,或者已经在进行中了,不妨一起看看。 第一步占地利,选个好位置 店址对于店铺生意的好坏起着主要作用。俗话说,店址差一寸,营业差一丈。好店铺就是人流、财流、信息流交换得最快、最活的地方。根据传统经验,一条南北对开的商业街,面南的要旺过面北的…

    2022年6月29日
    880
  • 淘宝论坛推广方法是什么,17种免费推广方法教程介绍

    很多卖家都有这么一个问题,淘宝开好店铺后,店铺没有什么客流量,特别是很多新手卖家,遇到这种情况大都一筹莫展,不知道怎么进行新店推广,提升淘宝店铺流量。其实最好的解决办法就是给店铺做推广。那么淘宝推广方法有哪些, 淘宝推广方法有站内推广和站外推广。 淘宝站内推广方法:淘宝自然搜索排名,直通车,淘宝客,淘宝活动,钻石展位、淘宝社区等。站内推广方法大都是付费的。 淘宝站外推广方法:百度推广,百度网络联盟…

    2022年8月28日
    550
  • 新产品的推广方法及策略,新品牌如何打开市场

    这个标题是微信群里面一个童鞋问的,索性我就直接拿来做标题了。我知道很多人都会有推广方面的疑问,包括我自己,也都遇到过推广的难题。 在做网络推广的时候,难的不是没有方法,难的是领导不给予资金支持,也不给予人力支持,然后就想让你用一个人创造出10个人的价值,最好把网站或者产品推广到全世界去。 在回答这个问题之前,其实我们最应该先了解的是,一个公司的产品和人群定位,以及公司的推广人员配置情况,还有就是公…

    2022年5月18日
    760
  • 高中生在手机赚钱方法有哪些,高中生正规赚钱方法分享

    手机任务赚钱 可以在兼职app里雇主发布一些注册下载小任务,然后我们用手机app接任务,完成雇主确认即可赚钱。这样做任务赚钱就很简单了。 自媒体平台 这个比较适合有一些文字功底的人群,可以在微信或者今日头条、微博等开个自己的账号,做自媒体,依靠广告收入赚钱。 摆地摊赚钱 比如说可以在学习和学校里摆地摊,卖一些生活用品等等也是非常赚钱,据说,有些人靠着摆地摊每年收入几十万的大有人在。 diy手工饰品…

    2022年8月11日
    520
  • 邮件归档什么意思,邮件归档后的意义介绍

    整洁是保持工作高效的基础, 而邮件归档功能是使电子邮件保持整洁最有效地解决方案。 邮件归档功能可以轻松将你的电子邮件进行归档, 集邮件收集、分类、记录、存储、检索、还原、恢复于一体。 简单便捷,让你的电子邮件多一份保障,避免数据丢失带来的不便。 在这个互联网飞速发展的时代,伴随着邮件的日常应用,电子邮件在办公中的重要作用不断显现,每个公司都会通过互发邮件进行业务上交流往来,邮件归档的重要性也逐渐为…

    2022年7月24日
    980

发表回复

登录后才能评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信