一、问题的产生
开一家咖啡店,售卖各式咖啡,
1、咖啡店1.0版
image.png
如果仅仅是售卖这4种咖啡,这个订单系统也能满足要求,但是,如果想加入各种调料,如:奶(Milk),摩卡(Mocha),或者覆盖上奶泡,订单系统就需要考虑到调料的部分,根据所加入的调料来计算不同的费用。
简单粗暴的修改订单系统:
image.png
这种方法很明显不行,如果牛奶价格上涨了怎么办?新增了一种调料怎么办?
2、咖啡店2.0版
利用实例变量和继承来设计调料部分
image.png
public class Beverage {
//为milkCost,mochaCost等声明实例变量
public double cost() {
float condimentCost = 0.0;
if (hasMilk()) {
condimentCost += milkCost;
}
if (hasMocha()) {
condimentCost += mochaCost;
}
…
return condimentCost;
}
}
public class DarkRoast extends Beverage {
public double cost() {
return 1.99 + super.cost();
}
}
当调料价格改变、出现了新的调料,都需要改变超类Beverage中的方法,如果以后售卖新的饮料,如:茶(Tea),某些调料及不适合它了,但是Tea仍然会继承那些不适合的方法,这些都明显违反了开闭原则。
3、咖啡店3.0版
采用新想法:以饮料为主体,在运行时用调料来“装饰”饮料。
例如,摩卡和奶泡深焙咖啡,需要做的是:
A、拿一个深焙咖啡(DarkRoast)对象
B、以摩卡(Mocha)对象装饰它
C、以奶泡(Whip)对象装饰它
D、调用cost()方法,并依赖委托(delegate)将调料的价钱加上去
image.png
二、装饰者模式
装饰者模式:动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
装饰者和被装饰对象有相同的超类
可以用一个或多个装饰者包装一个对象
装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定目的
对象可以在任何时候被装饰,可以在运行动态的、不限量的使用装饰者来装饰对象
1、类图
image.png
2、运用到咖啡店上
image.png
3、新的设计
双倍摩卡豆浆奶泡拿铁咖啡怎么设计呢?
image.png
4、咖啡店3.0版的实现
A、设计抽象组件
image.png
//饮料Beverage是个抽象类,有两个方法,方法getDescription自己实现,方法cost 需要子类实现
public abstract class Beverage {
String description="Unknown Beverage";
public String getDescription(){
return description;
}
public abstract double cost();
}
B、实现具体组件
image.png
//实现深焙咖啡DarkRoast
public class DarkRoast extends Beverage{
public DarkRoast (){
description = "DarkRoast ";
}
//返回的是Espresso的价格,无需考虑调料的价格
@Override
public double cost() {
return 1.99;
}
}
//实现浓缩咖啡Espresso
public class Espresso extends Beverage{
…
}
…//实现其他口味的咖啡
C、设计抽象装饰者
image.png
//由类图可看出,抽象装饰者也是扩展自Beverage类
public abstract class CondimentDecorator extends Beverage() {
public abstract String getDescription();
}
D、实现具体装饰者
image.png
//摩卡Mocha是一个装饰者,扩展自CodimentDecorator(CodimentDecorator扩展自Beverage)
public class Mocha extends CodimentDecorator{
//使Mocha能够引用到Beverage,需要定义一个Beverage的实例变量
private Beverage beverage;
//还需要把这Beverage实例传递到Mocha的构造器中
public Mocha(Beverage beverage){
this.beverage = beverage;
}
//描述的饮料需要带上调料的描述
@Override
public String getDescription() {
return beverage.getDescription()+" Mocha";
}
//先把具体口味饮料Beverage的价格获取到,然后加上调料Mocha的价格,为最终的结果
@Override
public double cost() {
return beverage.cost()+0.55;
}
}
//实现牛奶调料
public class Milk extends CodimentDecorator{
…
}
…//其他调料
E、测试代码Main
public class Main {
public static void main(String[] args) {
//订一杯深焙咖啡DarkRoast,不加调料,打印出描述和价格
Beverage b1 = new DarkRoast();
System.out.println(b1.getDescription()+" cost:"+b1.cost());
//制造出一个浓缩咖啡Espresso对象
Beverage b2 = new Espresso();
//用摩卡Mocha装饰它
b2 = new Mocha(b2);
//用牛奶Milk装饰它
b2 = new Milk(b2);
System.out.println(b2.getDescription()+" cost:"+b2.cost());
Beverage b3 = new …//其他口味及添加各种调料的饮料
}
}
Tips:小优化
对于各种口味的饮料DarkRoast,Espresso等,以及各种调料Mocha,Milk等,可以使用工厂模式来实现
三、在Java中的应用
读取文件数据
image.png
网友评论