公司法
当前位置: 首页 法律大全

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

时间:2023-06-14 作者: 小编 阅读量: 1 栏目名: 法律大全

改进方案将调料内置到咖啡基类里,这样不会造成数量过多,当单品咖啡继承咖啡基类的时候,就都拥有了这些调料,同时,点没有点调料,要提供相应的方法,来计算是不是加了这个调料。也就是不像我们想的,给单品咖啡里加调料,而是反向思维,把单品咖啡拿到调料里来,决定对他的操作。这样的话,增加一个单品咖啡,或者增加调料,都不用改变其他地方。

一、背景

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

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

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

最差方案

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

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

改进方案

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

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

二、装饰者模式

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

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

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

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

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

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

其中 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{@Overridepublic 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;}@Overridepublic float cost() {//计算成本,拿到佐料自己的价格+本来一杯Drink的价格//这里注意调用的是drink.cost不是drink.getPrice,因为cost才是子类实现的,Drink类的getPrice方法默认是返回0return super.getPrice() + drink.cost();}@Overridepublic 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());}}

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

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

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

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

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

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);//具体操作

感觉真是完全一样呢。

    推荐阅读
  • 怎样烧红烧肉(家常红烧肉的做法)

    怎样烧红烧肉原料:精品五花肉、炖肉料包、葱、冰糖、茶叶。五花肉切条放入凉水中撇去血沫。焯水定型;捞出后晾凉切一样大的方块。锅中放少许油倒入白砂糖炒糖色。糖色的气泡由大变小迅速关火,倒入开水。加少许绍酒,加开水烧,熟得快,加入茶叶水,可以去腥味。改回炒锅大火,放冰糖,使汁粘稠即可出锅,香葱段点缀。

  • 2022杭州径山茶圣节时间、地点、活动一览

    最终集齐所有铜币的游客可至“大宋钱庄”兑换神秘礼物。今来茶韵生活01、陆羽说论坛为进一步挖掘径山茶宴有关历史文化,本届茶圣节特邀请茶学专家交流讨论如何更好保护和传承国家非物质文化遗产。为打造文化传播年,第二十一届中国茶圣节以春迎、夏凉、秋韵、冬福四大主题贯穿全年。

  • 《重生之门》给罗队发短信的人身份

    但是通过前文,不难推测应该是庄文杰发给罗队的短信,只是没有暴露自己的身份。罗坚来到青檀假日酒店排查,没有发现任何异常,庄文杰和许正清乔装改扮随后赶来,他们一出现就被人盯上,庄文杰和许正清来到地下停车场,庄文杰巧妙引开那些人,混进游客中进入酒店。这件事情把十二年前的洛神案串联起来了。

  • 爱情名著哪个好看(随侃名著佳作第6期)

    言下之意,他主动向周晓白提出分手。钟跃民成为一个军人,上了战场,并且是在战斗中受伤,被送到战地医疗帐篷内救治。而周晓白和钟跃民在时隔十多年后的相遇一刻,也是被编剧以及导演,安排得相当的特别,并不是那种悲情欲绝又或者是感动无比的相遇时刻。

  • 板栗可以保存多久 板栗怎么能保存时间长

    如果是晒干的板栗可以存放3-4个月,生板栗在常温下合理贮存可以存放1-2个月,煮熟的栗子大概可以放一周,熟板栗放冰箱冷冻能保存30天左右,熟板栗放冰箱冷藏保存可以存放5天。

  • 贾宝玉与红楼梦的关系(贾宝玉的春梦到底在暗示什么)

    贾宝玉与红楼梦的关系?要知道,贾琏这个人极其好色,而且好的就是熟女,那么从这个曲折的描述中,我们可以推断出,秦可卿应该是那种熟女中的极品。这个问题在书中得不到直接的答案,因为在后面的文章中,秦可卿一共只出现三个镜头:介绍弟弟秦钟与贾宝玉相见,秦可卿病后王熙凤带贾宝玉去探病,秦可卿临死前在梦里向王熙凤交代后事。

  • 简单又好看的剪纸适合儿童(孩子能学会的幼儿简单剪纸教程)

    接下来我们就一起去研究一下吧!简单又好看的剪纸适合儿童幼儿园的孩子经常要做各种各样的手工,通过做手工,提高孩子的审美能力,锻炼孩子的动手能力,培养孩子的专注力和耐心,让孩子更聪明。用蓝天白云绿色的草地,太阳、小兔子和小蘑菇,可以贴出一幅画,也可以用这个画面编出一个小故事,带孩子度过愉快的亲子时光。欢迎关注,学习更多幼儿小手工。

  • 摩尔庄园钓鲤鱼的最佳方法(摩尔庄园钓鲤鱼的有什么最佳方法)

    以下内容希望对你有帮助!摩尔庄园钓鲤鱼的最佳方法工具/原料:华为手机、安卓系统、摩尔庄园游戏。进入游戏后操纵游戏角色进行移动了。去商店购买钓鱼的诱饵。来到池塘边进行的钓鱼。等待的水面出现波动即可钓到鲤鱼了。

  • 国外的懒人产品(歪国产品咖在用哪些可爱的小工具)

    quotes=trueUsabilityHub我通常使用UsabilityHub来帮助确定设计方案。

  • 一年四季水果时间表(一年四季的时令水果是什么)

    3月(春季):枇杷、红香蕉、樱桃、杨桃、番荔枝、青枣、甘果蔗、草莓、番石榴、牛奶蕉、柑桔、观赏南瓜、果桑、鹤首瓜。12月(冬季):樱桃、番茄、红香蕉、鸡蛋果、木瓜、草莓、百香果、杨桃、无花果、番石榴、牛奶蕉、鹤首瓜、观赏南瓜、果蔗、台湾青枣、黑提子、人心果、柠檬、菠萝、油梨、柑橘、橙子。