设计模式-装饰者模式
在JDK
中,使用到了很多设计模式,例如装饰者模式、观察者模式,它广泛存在于Java I/O
中,说实话,刚学习Java I/O
时有点懵,他的类是在太多了,可供选择的就十多种,还有很多叫不上名字的IO类,当时一度怀疑自己不是学编程的选手。
今天重点所说的就是装饰者模式,也算是一个总结吧
场景
这个场景很简单,为某冷饮店制造一个收费系统。
他的业务是这样的,主要的为饮品收费,其他的收费项目为配料收费,例如在点了饮品的基础上增加了珍珠、椰果等配料,然后计算总的费用。
问题解决
大家的想法可能是这样:当购买哪一种类的饮料时,就创建出来这个对象,如果想另外增加配料,再创建出来配料类的对象,最后计算出价格。但是怎么实现呢,要讲弹性,以方便日后的扩展
最初可以设计出一个抽象类,将饮料抽象出来
1 2 3 4 5 6 7
| public abstract class Beverage { public String getDescription() { return "unknown beverage"; } public abstract float cost(); }
|
接下来就是围绕这个类展开,从而引出装饰者模式。在往下进行之前,先说一个设计原则:
对修改关闭,对扩展开放(开放封闭原则)
新创建的饮料可以完全继承自Beverage
类,并且实现其中的cost()
和覆盖getDescription()
,我们创建两个饮料类拿铁(Latte)和卡布奇诺(Cappuccino)
1 2 3 4 5 6 7 8 9 10 11
| public class Latte extends Beverage{ @Override public float cost() { return 12.1f; }
@Override public String getDescription() { return "Latte"; } }
|
1 2 3 4 5 6 7 8 9 10 11
| public class Cappuccino extends Beverage{ @Override public float cost() { return 7.21f; }
@Override public String getDescription() { return "Cappuccino"; } }
|
在装饰者模式中,这两类被称为被装饰者,而这个场景中,真正的装饰者是调料,我们看一下调料的抽象类
1 2 3 4 5 6
| public abstract class Flavour extends Beverage { @Override public String getDescription() { return "Flavour"; } }
|
另外再去实现两种调料珍珠(Pearl)、椰肉(Coconut)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Pearl extends Flavour{
private Beverage beverage;
public Pearl(Beverage beverage) { this.beverage = beverage; }
@Override public float cost() { return beverage.cost() + 2.1f; }
@Override public String getDescription() { return beverage.getDescription() + " Pearl"; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Coconut extends Flavour{
private Beverage beverage;
public Coconut(Beverage beverage) { this.beverage = beverage; }
@Override public float cost() { return beverage.cost() + 4.2f; }
@Override public String getDescription() { return beverage.getDescription() + " Coconut"; } }
|
目前我们就完成了所有所需的类,UML
类图如下
其中Pearl
与Coconut
装饰者类,负责装饰Latte
与Cappuccino
,且装饰者与被装饰者应该有相同的类型,即Beverage
如果我想点一份Latte,并且加上Pearl配料,应该如何计算金钱呢
1 2 3 4 5 6 7 8 9 10
| public class Consumption { public static void main(String[] args) { Latte latte = new Latte();
Pearl pearl = new Pearl(latte);
System.out.println("total cost: " + pearl.cost()); System.out.println(pearl.getDescription()); } }
|
最后打印出
1 2
| total cost: 14.200001 Latte Pearl
|
这样的扩展是否很灵活呢,通过装饰者对被装饰者的装饰,在不修改原有代码的前提下,完成了如此具有弹性的功能。
此时我想点一份Cappuccino加两份Coconut
1 2 3 4 5 6 7 8 9 10 11
| public class Consumption { public static void main(String[] args) { Cappuccino cappuccino = new Cappuccino();
Coconut coconut = new Coconut(cappuccino); coconut = new Coconut(coconut);
System.out.println("total cost: " + coconut.cost()); System.out.println(coconut.getDescription()); } }
|
输出
1 2
| total cost: 15.61 Cappuccino Coconut Coconut
|
I/O类中的装饰者模式
以输入流举例(输出流也是同理),InputStream
可以看成是抽象的组件,FilterInputStream
可以看成是抽象装饰者,而她下面的类就可以看成是具体的装饰者,他们都是对以及InputStream
的装饰
既然说的这么细了,那就进到FilterInputStream
的源码中看一看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class FilterInputStream extends InputStream { protected volatile InputStream in;
protected FilterInputStream(InputStream in) { this.in = in; }
public int read() throws IOException { return in.read(); } }
|
他和咱们上面饮品的例子结构差不多吧
Java I/O
也引出来了装饰者模式的一个缺点,利用装饰者模式,常常造成设计中有大量的小类,数量实在太多,可能会给使用此API
的程序员造成困扰。
我们在知道了Java I/O
类的原理后,可以自己动手写一个Java I/O
类的API
,功能是将文件中的全部大写转换为小写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class LowerFileInputStream extends FileInputStream {
private FileInputStream fileInputStream;
@Override public int read() throws IOException { int read = super.read(); return read == -1 ? read : Character.toLowerCase((char)read); }
@Override public int read(byte[] b) throws IOException { int read = super.read(b);
for (int i = 0; i < read; i++) { b[i] = (byte) Character.toLowerCase((char) b[i]); }
return read; }
public LowerFileInputStream(String name) throws FileNotFoundException { super(name); fileInputStream = new FileInputStream(name); } }
|
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static void main(String[] args) throws Exception { LowerFileInputStream lowerFileInputStream = new LowerFileInputStream("lower.txt");
int read = 0;
StringBuilder stringBuilder = new StringBuilder();
while((read = lowerFileInputStream.read()) != -1) { stringBuilder.append((char)read); }
lowerFileInputStream.close();
System.out.println(stringBuilder.toString()); }
|
输出
1 2 3 4 5
| # 原始文件内容 I am Programmer
# 输出内容 i am programmer
|
总结
本篇文章以某饮料公司的消费系统为场景,引出了装饰者模式,并且得到了一个重要的设计原则:
类只对扩展开放,对修改关闭(开放封闭原则)
还总结出Java IO中存在着大部分的装饰者模式,同时也指出了装饰者模式的不足之处。
最后自己动手实现了一个依靠装饰者模式自定义的IO类。