简介
装饰者模式,简而言之,可以针对已经存在的行为基础进行装饰。在进行装饰后达到自己想要的实现的功能。
同样,我们引入现实例子对装饰者模式进行层层分析。
现在,假如我们开了一家咖啡点,想要做一套点咖啡的系统,系统主要是根据选择的各种原料进行计算咖啡的价格;
直接进入设计代码环节吧。
public abstract class Coffee {
public String descriprion;
public String getDescription(){
return descriprion;
};
public abstract int cost();
}
public class KaBuQiNuo extends Coffee{
@Override
public String getDescription() {
// TODO Auto-generated method stub
descriprion = "kabuqinuo";
return super.getDescription();
}
@Override
public int cost() {
// TODO Auto-generated method stub
return 1000;
}
}
public class JiaoTang extends Coffee{
@Override
public String getDescription() {
// TODO Auto-generated method stub
descriprion = "JiaoTang ";
return super.getDescription();
}
@Override
public int cost() {
// TODO Auto-generated method stub
return 2000;
}
}
上面直接定义了 Coffee 基类,同时对应不同的类似于 KaBuQiNuo(卡布奇诺)、JiaoTang 咖啡种类直接继承父类并实现相应 Description 和 cost 方法即可,当我们需要计算的时候进行 Cost 叠加。
上述写法看上去没有什么问题,但是当要对咖啡进行拓展的时候,我们需要加 YeGuo(椰果)、YanMai(燕麦) 之类的配料之后就要重新新建子类(YeGuoCoffee)并且进行实现cost方法,甚至如果我们要区分亚洲和美洲口味的 Coffee。最后组合起来子类就会越来越多,简直就是类的大集合大爆炸,这样肯定是不行的 。
本着希望不需要太多类的原则,对基类 Coffee 进行优化,抽取基本原材料:
public abstract class Coffee {
public String descriprion;
public Milk milk;
public YeGuo yeGuo;
public YanMai yanmai;
public String getDescription(){
return this.descriprion;
}
public void setMiik(Milk milk){
this.milk = milk;
}
public boolean hasMilk(){
if(milk != null){
return true;
}
return false;
};
public void setYeGuo(YeGuo yeGuo){
this.yeGuo = yeGuo;
}
public boolean hasYeGuo(){
if(yeGuo != null){
return true;
}
return false;
};
public void setYanMai(YanMai yanmai){
this.yanmai = yanmai;
}
public boolean hasYanMai(){
if(yanmai != null){
return true;
}
return false;
};
public int cost (){
int cost = 0;
if(hasMilk()){
cost += 1000;
}
if(hasYeGuo()){
cost += 2000;
}
if(hasYanMai()){
cost += 3000;
}
}
}
对基类 Coffee 进行改造,将 Coffee 相关基本原材料放在基类中,并对应判断是否有相关原料,在子类中仅仅需要去实现 cost 在基类基础上加上新增加原材料的价格即可。
public class ChinaCoffee extends Coffee{
@Override
public String getDescription() {
// TODO Auto-generated method stub
descriprion = "kabuqinuo";
return super.getDescription();
}
@Override
public int cost() {
// TODO Auto-generated method stub
return super.cost() + 50;
}
}
Coffee 的基本材料固定在基类中进行相关处理,而 Coffee 的种类根据各种搭配则可能进行变化。针对变化的情况。子类只需要实现相应的 cost 方法即可。
到这里,看上去这个点咖啡的系统已经不错了,如果我们新增了美式咖啡,只需要在子类对应增加相应价格。而不要对应再去关心是否有 YanMai 或者 YeGuo 等。
但是再仔细想想,如果提出下面几个问题,这么系统是否还很方便呢?
1 调整现有的 Coffee 的基本原料的价格(需要更改基类现有代码)
2 增加 Coffee 的基本原料 (需要更改基类代码)
3 假如需要推出南美口味 Coffee 新品,可能不再需要 Milk 原料了。子类却依然会继承到 hasMilk 方法。
这样看来,系统仍然不够强壮。每次修改或者增加依然会修改到现有的代码。需要继续优化,使得这个系统尽量不去修改原有代码,而对外进行扩展。
这里就涉及到设计模式的一个重要原则:
类应该对扩展开发过,对修改关闭
接下里正式进入到装饰者模式。
把装饰者模式先按照案例进行拆分:
1 先拿到一个 Coffee 的对象
2 Milk 进行装饰
3 YeGuo 进行装饰
3 依次调用 cost 方法,依赖 委托方式 将调料的价格加上去
类似于上述图中,进行一层层地把 Coffee 基类进行包装,并层层调用 cost 方法,最后进行累加。
1 首先调用最外层 YeGuo 的 cost 方法。
2 YeGuo 调用 Milk 的 cost 方法。
3 Milk 调用 Coffee 的 cost 方法。
4 Coffee 返回价格。
5 Milk 在结果上加上自己的价格。
6 YeGuo 在结果上加上自己的价格,返回总价格。
分析完成步骤,接下来具体看看实现方式。
先从 Coffee 类来写:
public abstract class Coffee {
public String descriprion = "UnKnow Coffee";
public String getDescription(){
return descriprion;
}
public abstract double cost();
}
接下来实现一个装饰者的基类:
public abstract class SimpleDecorator extends Coffee {
public abstract String getDescription();
}
有了基类,可以实现一些地方的 Coffee 。
public class ChinaCoffee extends Coffee {
public ChinaCoffee() {
// TODO Auto-generated constructor stub
descriprion = "ChinaCoffee";
}
@Override
public double cost() {
// TODO Auto-generated method stub
return 2.99;
}
}
接下来,我们就用 Milk 装饰 Coffee。
public class Milk extends SimpleDecorator{
Coffee coffee;
public Milk(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription() + ",Milk";
}
@Override
public double cost() {
return 3.99+ coffee.cost();
}
}
再加一个 YeGuo 类进行装饰:
public class YeGuo extends SimpleDecorator{
Coffee coffee;
public YeGuo (Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription() + ",YeGuo ";
}
@Override
public double cost() {
return 4.99+ coffee.cost();
}
}
装饰对象 Milk 实现了简单的装饰基类,同时在内部维护了 Coffee 的引用类型。这里维护的 Coffee 引用类型保证了我们的装饰效果层层进行调用。
最后我们来看看装饰者模式的实现情况。
public class Main {
public static void main(String args []){
Coffee coffee = new ChinaCoffee();
System.out.println(coffee.getDescription()+"RMB" + coffee.cost());
coffee = new Milk(coffee);
System.out.println(coffee.getDescription()+"RMB" + coffee.cost());
coffee = new YeGuo(coffee);
System.out.println(coffee.getDescription()+"RMB" + coffee.cost());
}
}
可以看到上述代码,先定了一杯 ChinaCoffee,然后直接用 Milk 和 YeGuo 进行装饰。 运行上述代码:
ChinaCoffee RMB2.99
ChinaCoffee,Milk RMB6.98
ChinaCoffee,Milk,YeGuo RMB11.97
得到运行结果。
结合 UML 图再看下装饰者模式:
UML 图.png就这样通过装饰者模式,完成了一套稳定的 Coffee 点单系统。
再回到上面提到的三个问题。
1 修改价格,直接在 ChinaCoffee 或者对应装饰原料 Milk 中修改,不必修改基类;
2 如果要新增原料,继承 SimpleDecorator 实现对应方法。
3 不必在继承不必要的方法。
同时看到,对内 cost 的修改进行了封闭,同时可以进行拓展新的品种 JapanCoffee 或者 新的原料 YanMai 等。
Java 中其实也有装饰者模式的类,例如 InputStream 和它的相关子类。具体可自行在 Java 源码中进行查看。
网友评论