星巴兹咖啡
咖啡存在许多的种类,同时也有不同的调料。此时用户可以单点咖啡,也可以点咖啡+调料,请计算费用(这里咖啡和调料都属于
Drink
的一类)
简单实现
方案1
每出现一种组合就实现一个类,但是每次增加一个咖啡种类或者一个新的调料,类的数量就会倍增,出现类爆炸!
方案2
具体修改如下:
- 在基类中加上每个调料的实例变量,并且将其设置为
boolean
类型表示是否加入- 基类的
cost
方法不再是抽象方法,而是计算加入所有调料后的价格(为什么是计算所有调料???),子类会调用基类的cost
方法hasxxx
和setxxx
方法由于取得和设置调料的bool
值这样大大减少了类的数量,但是该方案会出现以下问题:
- 调整价钱就要修改代码
- 增加或者删除调料种类时,就要加上新方法并且修改基类的
cost
方法- 某些新饮料(子类)并不需要某些调料,但是仍要继承这些不需要的方法,比如
hashSoy
等
public class Drink{
String desc;
// mikeCost、soyCost的实例变量
// mike、soy的getter和setter方法
public double cost(){
float cost = 0.0;
if(hasMilk()){
cost += milkCost;
}
if(hasSoy()){
cost += soyCost;
}
return cost;
}
}
public class DarkRoast extends Drink{
public DarkRoast(){
desc = "好喝";
}
public double cost(){
return 2.0 + super.cost();
}
}
两个设计原则
- 利用组合和委托代替继承:
- 继承设计子类的行为是在编译时静态决定(即所有子类都会继承到相同的行为),而组合扩展对象行为时可以在运行时动态地扩展
- 通过动态组合对象可以在不修改代码的前提下添加新功能(下面提到的开闭原则)
- 尽量遵循开闭原则:
- 类应该对扩展开发,对修改关闭
- 不需要每个地方都遵循该原则,因为会让代码变得复杂
装饰者模式
什么是装饰者模式?
假设需要一杯摩卡(
Mocha
)+奶泡(whip
)的深焙咖啡(DarkRoast
),具体做法和计算价格的过程如下:
- 拿到一个
DarkRoast
对象- 使用
Mocha
对象装饰它- 使用
Whip
对象装饰它- 调用
cost
方法,并依赖委托将调料价钱加上
-
装饰者可以在被装饰者的行为前/后加上自己的行为(
Whip
是装饰者,则Mocha
是被装饰者,其他依次类推) -
装饰者模式可以动态将责任附加到对象上,即扩展功能时更具弹性
-
装饰者和被装饰者具有相同的父类(因为装饰者可能变为被装饰者,被装饰者也可能变为装饰者)
代码实现
// 需要被继承的公共父类
public abstract class Drink{
String desc = "Unknown";
public String getDesc(){
return desc;
}
public abstract double cost();
}
// 抽象装饰者类(目的在于让装饰者类必须实现getDesc方法)
public abstract class CondimentDecorator extends Drink{
public abstract String getDesc();
}
// Espresso类,即被装饰者类
public class Soy extends Drink{
public Soy(){
desc = "Soy"
}
public double cost(){
return 2.0;
}
}
// Mocha类,即装饰者类
public class Mochas extends CondimentDecorator{
Drink drink; // 组合
public Mochas(Drink drink){
this.drink = drink;
}
public String getDesc(){
return drink.getDesc() + ", Mocha";
}
public double cost(){
// 1.0是Mocha自己的价格
return 1.0 + drink.cost();
}
}
// 测试
public class CoffeeStore{
public static void main(String[] args){
// 点一杯加了豆浆的摩卡
Drink soy = new Soy();
Drink Mocha = new Mocha(espresso);
}
}
Java I/O
在
Java I/O
类中,同样使用了装饰者模式,但是也是有缺点的,比如会出现大量的小类
编写自己的Java I/O装饰者
编写一个装饰者,把输入流中的大写都转为小写
import java.io.*;
// FilterInputStream是抽象装饰者
public class LowerCaseInputStream extends FilterInputStream{
public LowerCaseInputStream(InputStream in){
super(in);
}
// 针对字节
public int read() throws IOException{
int c = super.read();
return (c == -1 ? c : Character.toLowerCase((char)c));
}
// 针对字节数组
public int read(byte[] b, int offset, int len) throws IOException{
int result = super.read(b, offset, len);
for(itn i = offset; i < offset + result; i++){
b[i] = (byte)Character.toLowerCase((char)b[i]);
}
return result;
}
}
// 测试
public class InputTest{
public static void main(String[] args) throws IOException{
int c;
try{
// 先用BufferdInputStream修饰FileInputStream
// 再用LowerCaseInputStream修饰BufferdInputStream
InputStream in = new LowerCaseInputStream(
new BufferdInputStream(new FileInputStream("test.txt")));
while((c = in.read()) >= 0){
System.out.print((char)c);
}
in.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
参考
Head First 设计模式-装饰者模式