文章目录
前言
初学设计模式,看的韩顺平老师的视频,记录一下。
一、装饰者模式的引入
问题分析
以点咖啡为例,我们一般会点一杯咖啡加上一些调料,如果按照正常思维我们会大致做出如下类图:
常规思路存在的问题
但是这样会出现问题就是组合种类很多,比方说:n种咖啡、m种调料,就会有n*m种组合来计算花费,而且每增加一种类就会增加非常多的新组合,导致类爆炸。
换一种解决方案就是在类里直接聚合调料字段,UML类图如下:
这种方案的思路是:在Drink类中直接增加调料字段,这样在每个单品咖啡子类(ShortBlack、LongBlack、Espresso)计算花费时,会根据是否有调料来计算花费(hasMilk…),这种方案是减少了类的个数(因为调料都变成了字段),但是存在问题就是在增加或删除调料时代码的维护量很大(因为每个单品咖啡的cost方法都会由与调料相关的代码,增加或删除调料字段都会导致很多类要修改)。
因此,引出了装饰者模式。
二、什么是装饰者模式?
1.装饰者模式定义
动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)
2.如何理解?
先来看一张图:
说明:先大致说一下这张图,这张图其实可以理解为4个部分:
- 第一个部分就是Drink抽象类,这个类是被其它类直接或间接的继承,其实就是我们常规思维创建的基类。
- 第二个部分是装饰者类Decorator,这个类体现了装饰者模式的核心思想,就是增加了一个父类字段(组合关系),这个字段十分重要!它是用来装饰对象(下面理解部分会讲解)。
- 第三个部分是普通类,也就是组合对象。有时候会再抽象出一个基类,但仍然可以视为一个部分。
- 第四部分类似第三部分。
理解:简单来说就是装饰者类Decorator聚合了父类Drink,这样每次增加调料就可以直接将原来的对象作为新的对象的Drink字段。举个例子:我原本点了一杯意大利咖啡,那就是创建一个Espresso对象,记为order。然后我又点了巧克力调料,那就是创建一个Chocolate对象。不过,这里要将原来的order对象作为新对象的字段,new Chocolate(order),这样就是将原来的对象包裹(装饰)起来。至于怎么将两者的价格计算起来,其实很好理解,就是将obj的价格(原来的单品咖啡价格)和当前对象的价格(Chocolate的价格)加和。
三、代码部分
1.Drink 代码:
public abstract class Drink {
public String des; // 描述
private float price = 0.0f;
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public abstract float cost();
}
2.Decorator 代码:
public class Decorator extends Drink {
private Drink obj;
public Decorator(Drink obj) { //组合关系
this.obj = obj;
}
@Override
public float cost() {
// getPrice 获取的是当前对象的价格
return super.getPrice() + obj.cost();
}
@Override
public String getDes() {
// obj.getDes() 获取的是当前对象的描述
return super.des + " " + getPrice() + " && " + obj.getDes();
}
}
3.Espresso 代码:
public class Espresso extends Coffee {
public Espresso() {
setDes(" 意大利咖啡 ");
setPrice(6.0f);
}
}
4.Chocolate 代码:
public class Chocolate extends Decorator { //间接继承了Drink类
public Chocolate(Drink obj) {
super(obj);
setDes(" 调料-巧克力 ");
setPrice(3.0f); //价格
}
}
5.测试类 CoffeeBar 代码:
public class CoffeeBar {
public static void main(String[] args) {
// TODO Auto-generated method stub
// 装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack
// 1. 点一份 LongBlack
Drink order = new LongBlack();
System.out.println("费用1=" + order.cost());
System.out.println("描述=" + order.getDes());
// 2. order 加入一份牛奶
order = new Milk(order);
System.out.println("order 加入一份牛奶 费用 =" + order.cost());
System.out.println("order 加入一份牛奶 描述 = " + order.getDes());
// 3. order 加入一份巧克力
order = new Chocolate(order);
System.out.println("order 加入一份牛奶 加入一份巧克力 费用 =" + order.cost());
System.out.println("order 加入一份牛奶 加入一份巧克力 描述 = " + order.getDes());
// 3. order 加入一份巧克力
order = new Chocolate(order);
System.out.println("order 加入一份牛奶 加入2份巧克力 费用 =" + order.cost());
System.out.println("order 加入一份牛奶 加入2份巧克力 描述 = " + order.getDes());
System.out.println("===========================");
Drink order2 = new DeCaf();
System.out.println("order2 无因咖啡 费用 =" + order2.cost());
System.out.println("order2 无因咖啡 描述 = " + order2.getDes());
order2 = new Milk(order2);
System.out.println("order2 无因咖啡 加入一份牛奶 费用 =" + order2.cost());
System.out.println("order2 无因咖啡 加入一份牛奶 描述 = " + order2.getDes());
}
}
6.装饰者模式图解:下订单两份巧克力+一份牛奶的LongBlack
四、装饰者模式在jdk中的应用
1.FilterInputStream的UML类图:
2.模拟代码-装饰者
public class Decorator {
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
//说明
//1. InputStream 是抽象类, 类似我们前面讲的 Drink
//2. FileInputStream 是 InputStream 子类,类似我们前面的 DeCaf, LongBlack
//3. FilterInputStream 是 InputStream 子类:类似我们前面 的 Decorator 修饰者
//4. DataInputStream 是 FilterInputStream 子类,具体的修饰者,类似前面的 Milk, Soy 等
//5. FilterInputStream 类 有 protected volatile InputStream in; 即含被装饰者
//6. 分析得出在jdk 的io体系中,就是使用装饰者模式
DataInputStream dis = new DataInputStream(new FileInputStream("d:\\abc.txt"));
System.out.println(dis.read());
dis.close();
}
}
五、总结
个人理解:
装饰者模式从代码层面来看,就是在装饰者类聚合了其基类(也就是被装饰者),从思想层面上是装饰(也可以理解为套娃,旧对象聚合在新对象),通过这种“包装”思想,就可以达到减少类的数量和提高维护性。
PS:目前初学,理解还不到位!