您现在的位置是:首页 >技术交流 >设计模式之装饰器模式网站首页技术交流
设计模式之装饰器模式
我们现在来看一个业务场景,星巴克卖咖啡
,一开始只有四种咖啡,分别是:Decaf无卡咖啡、Espresso浓缩咖啡、DrakRoast焦糖咖啡、HouseBlend混合咖啡
每种咖啡,都有描述,都有价格。 这是所有咖啡的共性,既然是共性,就要上提到父类Beverage
,饮料类中。
@Data
abstract class Beverage {
private String description;
public Beverage(String description) {
this.description = description;
}
// 花费
public abstract double cost();
}
class Decaf extends Beverage {
public Decaf() {
super("无卡咖啡");
}
@Override
public double cost() {
return 1;
}
}
class Espresso extends Beverage {
public Espresso() {
super("浓缩咖啡");
}
@Override
public double cost() {
return 5;
}
}
class DrakRoast extends Beverage {
public DrakRoast() {
super("焦糖咖啡");
}
@Override
public double cost() {
return 15;
}
}
class HouseBlend extends Beverage {
public HouseBlend() {
super("混合咖啡");
}
@Override
public double cost() {
return 10;
}
}
---------------------------------------------------------------
public class AppTest {
public static void main(String[] args) {
Beverage decaf = new Decaf();
Beverage espresso = new Espresso();
Beverage drakRoast = new DrakRoast();
Beverage houseBlend = new HouseBlend();
System.out.println(String.format("咖啡描述:%s,咖啡价格:%.2f", decaf.getDescription(), decaf.cost()));
System.out.println(String.format("咖啡描述:%s,咖啡价格:%.2f", espresso.getDescription(), espresso.cost()));
System.out.println(String.format("咖啡描述:%s,咖啡价格:%.2f", drakRoast.getDescription(), drakRoast.cost()));
System.out.println(String.format("咖啡描述:%s,咖啡价格:%.2f", houseBlend.getDescription(), houseBlend.cost()));
}
}
代码好像并没有什么问题,可是现在变化来了,星巴克老板为了提高竞争力,需要往咖啡里面加调料,假设之前的代码是作者写死了的,我们无法改变
调料有摩卡mocha、泡沫bubble、豆浆soy、牛奶milk,我们现在想要给上面写的四个类加上牛奶,但是由于我们不能修改源代码并且符合开闭原则,我们只能通过继承的方式
// 为牛奶的Decaf咖啡创建一个类
class DecafWithMilk{
}
// 为加牛奶的Espresso咖啡创建一个类
class EspressonWithMilk{
}
...
好像感觉到不对劲了,这才是一种调料的组合,如果我需要摩卡 + 白糖
、摩卡 + 牛奶
等等组合?还维护的下去吗?现在会造成类爆炸
的问题,有多少组合就会产生多少类
如果你非常强大,硬着头皮写完了所有类,现在老板说又要加一种调料,之前是四种,组合有15中组合,现在五种调料,有31中,六种调料有63种,类,爆炸了!! 程序员,疯了!!
为了解决上述问题,作者,尝试这样来解决:给每一种调料加上boolean属性
// 优点,没有类爆炸!
class Beverage {
private String description;
private boolean milk;
private boolean soy;
private boolean mocha;
private boolean bubble;
public Beverage(String description) {
super();
this.description = description;
}
public double cost() {
double cost = 0;
if(milk) {
cost += 0.2;
}
if(soy) {
cost += 0.2;
}
if(mocha) {
cost += 0.3;
}
if(bubble) {
cost += 0.1;
}
return cost;
}
public boolean ismilk() {
return milk;
}
public void setmilk(boolean milk) {
this.milk = milk;
}
public boolean isSoy() {
return soy;
}
public void setSoy(boolean soy) {
this.soy = soy;
}
public boolean isMocha() {
return mocha;
}
public void setMocha(boolean mocha) {
this.mocha = mocha;
}
public boolean isbubble() {
return bubble;
}
public void setBubble(boolean bubble) {
this.bubble = bubble;
}
}
class DarkRoast extends Beverage {
public DarkRoast(String descriptoin) {
super(descriptoin);
}
public double cost() {
return super.cost() + 10;
}
}
class Decaf extends Beverage {
public Decaf(String description) {
super(description);
}
@Override
public double cost() {
return super.cost() + 8;
}
}
class Espresso extends Beverage {
public Espresso(String description) {
super(description);
}
@Override
public double cost() {
return super.cost() + 12;
}
}
class HouseBlend extends Beverage {
public HouseBlend(String description) {
super(description);
}
@Override
public double cost() {
return super.cost() + 15;
}
}
--------------------------------------------------------------
//自己扩展一种饮料茶也没问题,没有违反开闭原则
class Tea extends Beverage {
public Tea(String description) {
super(description);
}
public double cost() {
return super.cost() + 9;
}
}
public class Test {
public static void main(String[] args) {
DarkRoast dr = new DarkRoast("焦炒咖啡");
dr.setMocha(true);
dr.setSoy(true);
System.out.println(dr.cost());
HouseBlend hb = new HouseBlend("混合咖啡");
hb.setBubble(true);
System.out.println(hb.cost());
Tea tea = new Tea("凉茶");
tea.setSoy(true);
System.out.println(tea.cost());
}
}
但是现在又加了一种调料:枸杞,这时怎么办呢?又不能改源码。我们用装饰器模式完成这个功能:
abstract class Beverage {
private String description;
public Beverage(String description) {
super();
this.description = description;
}
public abstract double cost();
public String getDescription() {
return description;
}
}
class DarkRoast extends Beverage {
public DarkRoast(String descriptoin) {
super(descriptoin);
}
public double cost() {
return 10;
}
}
class Decaf extends Beverage {
public Decaf(String description) {
super(description);
}
@Override
public double cost() {
return 8;
}
}
class Espresso extends Beverage {
public Espresso(String description) {
super(description);
}
@Override
public double cost() {
return 12;
}
}
class HouseBlend extends Beverage {
public HouseBlend(String description) {
super(description);
}
@Override
public double cost() {
return 15;
}
}
/**
* 这里我们让调料类继承自饮料类,显然违背了继承中的"is a"关系,但是在装饰器模式中这个原则就是需要违背
* 尽管调料不是饮料,但是为了解决问题,我们也只能让调料去继承饮料
*/
abstract class CondimentDecorator extends Beverage {
// 不仅需要继承饮料还需要关联饮料,让调料类关联饮料
protected Beverage beverage;
public CondimentDecorator(Beverage beverage) {
super("调料");
this.beverage = beverage;
}
}
class Milk extends CondimentDecorator {
public Milk(Beverage beverage) {
super(beverage);
}
@Override
public double cost() {
return beverage.cost() + 0.2;
}
@Override
public String getDescription() {
return beverage.getDescription() + " 牛奶";
}
}
class Mocha extends CondimentDecorator{
public Mocha(Beverage beverage) {
super(beverage);
}
@Override
public double cost() {
return beverage.cost() + 0.3;
}
@Override
public String getDescription() {
return beverage.getDescription() + " 摩卡";
}
}
class Soy extends CondimentDecorator {
public Soy(Beverage beverage) {
super(beverage);
}
@Override
public double cost() {
return beverage.cost() + 0.2;
}
@Override
public String getDescription() {
return beverage.getDescription() + " 豆浆";
}
}
class Bubble extends CondimentDecorator {
public Bubble(Beverage beverage) {
super(beverage);
}
@Override
public double cost() {
return beverage.cost() + 0.1;
}
@Override
public String getDescription() {
return beverage.getDescription() + " 奶泡";
}
}
-------------------------------------------------------------------------
class GouQi extends CondimentDecorator {
public GouQi(Beverage beverage) {
super(beverage);
}
@Override
public double cost() {
return beverage.cost() + 0.4;
}
@Override
public String getDescription() {
return beverage.getDescription() + " 枸杞";
}
}
public class Demo03 {
public static void main(String[] args) throws IOException {
Beverage b = new DarkRoast("焦炒咖啡");
//加牛奶
Beverage b2 = new Milk(b);
//加豆浆
Beverage b3 = new Soy(b2);
//加泡沫
Beverage b4 = new Bubble(b3);
//加第二份牛奶
Beverage b5 = new Milk(b4);
//加枸杞
Beverage b6 = new GouQi(b5);
System.out.println(b6.getDescription() + ":" + b6.cost());
/**
* 焦炒咖啡 牛奶 豆浆 奶泡 牛奶 枸杞:11.099999999999998
*/
}
}
上述的代码做到了在不改动源代码的前提下,完成了动态添加调料的功能!
我们重点看这几行代码:
// 现在想要加牛奶
Milk milk = new Milk(decaf);
// 加摩卡
Mocha mocha = new Mocha(milk);
这几行就是精华了,我们称之为想要添加什么功能就用对应的类装饰就行了!
,我们把这种类称为ConcreteDecorator(具体装饰类)
我们不能用传统的继承关系看上面的代码,认为 Milk
是不能继承Condiment
的,我们仔细看 Milk
到底做了啥
class Milk extends Condiment {
public Milk(Beverage beverage) {
super(beverage);
}
...
}
可以看到Milk
不仅继承了Beverage
饮料类,还关联了饮料类,关联是想要获取想要装饰的对象,继承是为了在原有的基础上对装饰对象的装饰
总结一下装饰器模式的几个角色
- Component(抽象构件角色):它是具体构件和抽象装饰类的共同父类,以规范准备接受附加责任的对象(Beverage)
- ConcreteComponent(具体构件):抽象构件角色的子类(或实现),具体的组件对象,装饰器可以给它增加额外的职责(decaf、Espresso、DrakRoast、HouseBlend)
- Docorator(装饰器):也是抽象构件角色的子类,持有一个抽象构件角色的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的(Condiment)
- ConcreteDecorator(具体装饰类):具体的装饰类,实现为需要装饰的构件添加新的职责(Milk、Mocha)
记住关键步骤:
-
继承 + 依赖:继承需要装饰的类的父类,拥有共性;组合想要装饰的类,用于装饰(加强)
-
无限套娃:通过套娃,对需要装饰的类进行装饰、加强
装饰器模式的优缺点
优点:
- 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能
缺点:
- 多层装饰比较复杂