欢迎来到星巴兹咖啡
Beverage是店里所有饮料的抽象类,下面是饮料的不同口味。
在日常生活中,你在购买时,可能还会加一些小料(凑单满减),例如燕奶(Steamed Milk)、豆浆(Soy)、摩卡(Mocha)等,在付款时,电脑的订单系统会根据你点的饮料和加的小料计算出总的价钱。
我们现在就是要设计一个能自动计算价格的订单系统。
最简单的方法,就是所有的饮料包括小料都写一个实现类,但是这样在后期就是一个维护噩梦。不说可能有几百种饮料几百种实现方式,如果后期稍微改动其中一款小料的价格,那么你就需要到一个一个的实现类里面去进行修改,严重违反了软件的设计原则。
tips:
代码应该如同晚霞中的莲花一样地关闭(免于改变),如同晨曦中的莲花一样地开放(能够扩展)。
设计原则:
类应该对扩展开放,对修改关闭。
这样的设计具有弹性可以应对改变,可以接受新的功能来应对改变的需求。
注意:在选择需要被扩展的代码部分时要小心。每个地方都采用开放-关闭原则,是一种浪费,也没有必要,还会导致代码变得复杂且难以理解,要找到平衡点。
认识装饰者模式
在上面星巴兹咖啡的设计中,实现类数量爆炸、设计死板、以及基类加入新功能不适用于所有的子类。
用装饰者模式进行设计
-
拿一个深色烘焙咖啡(
DarkRoast
)对象 -
以摩卡(Mocha)对象装饰它
-
以奶泡(Whip)对象装饰它
-
调用cost()方法,并委托(delegate)将调料的价钱加上去
简单来讲就是将对象一层一层包起来,在调用的时候,先一层一层进去,之后一层一层计算结果出来。
定义装饰者模式
说明:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
使用装饰者模式设计星巴兹类图
注意:这里使用继承是达到“类型匹配”的目的(!!!),而不是利用继承来获得行为。
新咖啡师傅特训
如果有一张单子点的是:“双倍摩卡豆浆奶泡拿铁咖啡”,进行设计实现。
流程
核心代码实现
总抽象类,装饰类与被装饰类都实现此类,达到类型匹配
/**
* @Description 抽象类饮料
* @Author lh
* @Date 2022/12/6 19:31
*/
public abstract class Beverage {
public String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
被装饰类,不同口味饮料
/**
* @Description 意大利浓缩咖啡
* @Author lh
* @Date 2022/12/6 19:37
*/
public class Espresso extends Beverage {
public Espresso() {
description = "Espresso";
}
/**
* @Description 家庭混合咖啡
* @Author lh
* @Date 2022/12/6 19:39
*/
public class HouseBlend extends Beverage {
public HouseBlend() {
description = "House Blend Coffee";
}
装饰抽象类
/**
* @Description 装饰类调料抽象类
* @Author lh
* @Date 2022/12/6 19:35
*/
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
装饰类实现
/**
* @Description 调料摩卡
* @Author lh
* @Date 2022/12/6 19:41
*/
public class Mocha extends CondimentDecorator{
private final Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
/**
* @Description 调料奶泡
* @Author lh
* @Date 2022/12/6 19:51
*/
public class Whip extends CondimentDecorator{
private final Beverage beverage;
public Whip(Beverage beverage) {
this.beverage = beverage;
}
实现 双倍摩卡豆浆奶泡拿铁咖啡
/**
* @Description 星巴兹计算
* @Author lh
* @Date 2022/12/6 20:10
*/
public class StarbuzzCoffee {
public static void main(String[] args) {
Beverage beverage = new HouseBlend();
System.out.println(beverage.getDescription() + ":" + beverage.cost() + "元");
Beverage beverage1 = new HouseBlend();
beverage1 = new Mocha(beverage1);
beverage1 = new Mocha(beverage1);
beverage1 = new Whip(beverage1);
System.out.println(beverage1.getDescription() + ":" + beverage1.cost() + "元");
}
}
真实世界的装饰者:Java I/O
下面是一个典型的对象集合,用装饰者来将功能结合起来,以读取文件数据。
和星巴兹的设计相比,java.io
其实并没有多大的差距。
核心代码示例
/**
* @Description 获取文本行数
* @Author lh
* @Date 2022/12/7 19:34
*/
public class LowerNumberInputStream extends FilterInputStream {
public LowerNumberInputStream(InputStream in) {
super(in);
}
public int read() throws IOException {
int c = super.read();
return (c == -1 ? c : Character.toLowerCase(c));
}
public int read(byte[] b, int offset, int len) throws IOException {
int result = super.read(b, offset, len);
for (int i = offset; i < offset + result; i++) {
b[i] = (byte) Character.toLowerCase(b[i]);
}
return result;
}
}
/**
* @Description IO测试
* @Author lh
* @Date 2022/12/7 19:40
*/
public class InputTest {
public static void main(String[] args) throws FileNotFoundException {
int c;
try {
InputStream in = new LowerNumberInputStream(new BufferedInputStream(new FileInputStream("text.txt")));
while ((c = in.read()) >= 0) {
System.out.println(c);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
总结
-
装饰者和被装饰者对象有相同的超类型(类型匹配)。
-
你可以用一个或多个装饰者包装一个对象。
-
既然装饰者和被装饰者有相同的超类型,所以在任何需要原始对象(被包装的)场合,可以用装饰过的对象代替它。
-
装饰者可以在所委托被装饰者的行为之前与之后,加上自己的行为,以达到特定的目的。
-
对象可以在任何时候被装饰,所以可以在运行时动态地、不限量得使用你喜欢的装饰者来装饰对象。
代码地址