内容简介:本文原创地址:jsbintask的博客(食用效果最佳),转载请注明出处!装饰器模式是为了运行时动态的扩展一个类的功能。它谨遵
本文原创地址:jsbintask的博客(食用效果最佳),转载请注明出处!
前言
装饰器模式是为了运行时动态的扩展一个类的功能。它谨遵 开闭原则
,它实现的关键在于 继承和组合的结合使用
,解耦对象之间的关系。
各种 设计模式 学习地址:
https://github.com/jsbintask22/design-pattern-learning
栗子
首先我们列举一个案例,并且按照面向对象的思想来对应实体之间的关系。
有一个咖啡店,销售各种各样的咖啡,拿铁,卡布奇洛,蓝山咖啡等,在冲泡前,会询问顾客是否要加糖,加奶,加薄荷等。这样不同的咖啡配上不同的调料就会卖出不同的价格。
V1
针对上面的栗子,我们很容易就抽象出对应的实现,如上图。接着,我们就要编写对应的类来实现对应的功能。在这个例子中,主题当然就是 咖啡
,并且它有一个属性是 名字
,一个行为 价格
,出于“面向对象”的思想,我们自然会设计出抽象类 Coffee
:
public abstract class Coffee { /** * 获取咖啡得名字 */ public abstract String getName(); /** * 获取咖啡的价格 */ public abstract double getPrice(); }
接着,按照继承的思想,我们要开始设计出具体的实现类,因为拿铁,卡布奇洛,蓝山搭配上不同的调料(上面三种)会有不同的价格,名字,所以我们至少得设计出 3 X 3 = 9 个类来分别对应它们的名字和价格:
嗯!我想不用说这样设计得缺陷也很明显了! 由于不同的咖啡和不同的调料得各种任意组合,使得出现了 类爆炸
的现象。既然有这么明显的缺陷,那我们当然得改! 我们可以考虑把各种调料当作属性加入到Coffee这个抽象类中,接着在实现类中计算价格和名字时,分别判断是否加入了各种调料包,得到不同的名字和价格!
按照上面的思想,我们的Coffee类现在变成了这样:
public abstract class Coffee { // 是否加了牛奶 protected boolean addedMilk; // 是否加了糖 protected boolean addedSugar; // 是否加了薄荷 protected boolean addedMint; /** * 获取咖啡得名字 */ public abstract String getName(); /** * 获取咖啡的价格 */ public abstract double getPrice(); }
接着,我们实现一种咖啡,蓝山咖啡:
public class BuleCoffee extends Coffee { @Override public String getName() { StringBuilder name = new StringBuilder(); name.append("蓝山"); if (addedMilk) { name.append("牛奶"); } if (addedMilk) { name.append("薄荷"); } if (addedSugar) { name.append("加糖"); } return name.toString(); } @Override public double getPrice() { double price = 10; if (addedMilk) { price += 1.1; } if (addedMilk) { price += 3.2; } if (addedSugar) { price += 2.7; } return price; } }
嗯!现在似乎比上面愉快多了。其实不然!我们仔细分析这种设计,会发现它似乎不太符合”封装的思想“,比如说针对拿铁,对于加薄荷而言,对他总是多余的! 而对于蓝山而言,牛奶又显得很多余! 所以这种设计也并不合理。 另外,我们假设coffee,拿铁等实体类来自第三方类库,我们并不能改动这些类的实现, 又要怎么得到名字和价格呢?
这个时候,我们就得使用 装饰器
模式来动态的扩展类行为! 所以我们设计出V3版本。
V3
开闭原则
首先,我们需要了解一个面向对象的一个基本设计原则: 开闭原则
,它指的是 类应该对修改关闭,对扩展开放
。
怎么理解呢? 就比如我们上方说的:假如cofee和它的一众实现拿铁,卡布奇洛,蓝山来自第三方类库,并且这个类库已经很”适合“,”实用“了。 而我们为了得到加入不同调料的咖啡的名字和价格,我们就得修改这些实现,而这样的修改,总是免不了 稳定性
的改变。对原本的系统来说也是一种风险! 所以我们应该 对修改关闭,对扩展开放
;
继承和组合
遵循开闭原则,那我们就得对外扩展,那怎么对外扩展呢? 这也是装饰器模式实现的关键,利用 继承和组合
的结合; 现在我们可以考虑设计出一个装饰类,它也继承自coffee,并且它内部有一个coffee的实例对象:
现在,我们多了一个 咖啡装饰器
: CoffeeDecorator:
public abstract class CoffeeDecorator implements Coffee { private Coffee delegate; public CoffeeDecorator(Coffee coffee) { this.delegate = coffee; } @Override public String getName() { return delegate.getName(); } @Override public double getPrice() { return delegate.getPrice(); } }
接着,我们将牛奶,薄荷作为抽象出一个类,继承自CoffeeDecorator,所以,现在类图就成了这样:
我们实现一个 MilkCoffeeDecorator
:
public class MilkCoffeeDecorator extends CoffeeDecorator { public MilkCoffeeDecorator(Coffee coffee) { super(coffee); } @Override public String getName() { return "牛奶, " + super.getName(); } @Override public double getPrice() { return 1.1 + super.getPrice(); } }
按同样的方法可以实现出 MintCoffeeDecorator
, SugarCoffeeDecorator
。接着我们写一个测试类:
public class App { public static void main(String[] args) { // 得到一杯原始的蓝山咖啡 Coffee blueCoffee = new BlueCoffee(); System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice()); // 加入牛奶 blueCoffee = new MilkCoffeeDecorator(blueCoffee); System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice()); // 再加入薄荷 blueCoffee = new MintCoffeeDecorator(blueCoffee); System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice()); // 再加入糖 blueCoffee = new SugarCoffeeDecorator(blueCoffee); System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice()); } }
从结果我们可以看出,随着不断加入各种调料,价格,名字都在改变! 这说明我们加入不同的调料,动态的改变了咖啡的名字和价格!
思考
从上面的最后的装饰器模式的实现来看,我们可以得出以下结论:
开闭原则
扩展
到现在,我们已经实现了一个自己的装饰器,我们来看看jdk中用到的装饰器实现.
IO
我们可以查看FilterInputStream:
它的主要是实现者为 BufferedInputStream
:
所以我们经常可以使用BufferedInputStream装饰一个InputStream,比如FileInputStream:
new BufferedInputStream(FileInputStream);
这就是装饰器模式的典型应用。
tomcat
在tomcat的HttpServletRequest的内部实现代码中, RequestFacde
继承自HttpServlet,而它内部的实现也是通过代理 Request
对象,而Request对象继承自HttpServlet,Request内部代理了 org.apache.coyote.Request
来实现的。
总结
装饰器模式充分展示了组合的灵活。利用它来实现扩展。它同时也是开闭原则的体现。 如果相对某个类实现运行时功能动态的扩展。 这个时候你就可以考虑使用装饰者模式!
关注我,这里只有干货!
谢谢你支持我分享知识
扫码打赏,心意已收
打开 微信 扫一扫,即可进行扫码打赏哦
以上所述就是小编给大家介绍的《从未这么明白的设计模式(二):装饰器模式》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 设计模式——订阅模式(观察者模式)
- 设计模式-简单工厂、工厂方法模式、抽象工厂模式
- java23种设计模式-门面模式(外观模式)
- 设计模式-享元设计模式
- Java 设计模式之工厂方法模式与抽象工厂模式
- JAVA设计模式之模板方法模式和建造者模式
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
随机密码生成器
多种字符组合密码
HEX CMYK 转换工具
HEX CMYK 互转工具