一、认识装饰模式
我们先给出装饰模式的概念,再去分析一下:
装饰模式又名包装模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。有透明和半透明两种,大部分都是半透明的,半透明的装饰模式是介于装饰模式和适配器模式之间的。
概念看起来有点懵,我们通过奶茶例子来解释一下:
从上面这张图我们可以看到,我们去店里买一杯奶茶有10种,还有6种口味可供选择,如果我们把不同的奶茶都当成一个类,那我们就需要new出来6*10个类。想想这样也太多了,使用了装饰器模式,使用18个类(10个类表示奶茶,6个类表示口味、其他两个分别是奶茶和口味的接口抽象)就能生产任何奶茶了,是不是很方便。
现在来看看透明和半透明的区别:
透明的装饰模式,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。意思是奶茶接口、珍珠奶茶和蜂蜜奶茶、调料的这些接口一样。下面代码中会有体现。相反,如果装饰角色的接口与抽象构件角色接口不一致,那就是半透明的了。
我们来看看装饰模式的类图:
从上图我们可以看到,装饰模式一共有四个角色。
(1)Component(抽象构建):这里指的奶茶抽象接口,表示规范准备接收附加责任的对象。
(2)ConcreteComponent(具体构建):这里指珍珠奶茶、蜂蜜奶茶等具体的奶茶。
(3)Decorator(装饰器):定义一个与抽象构件接口一致的接口,这里表示各种口味。
(4)ConcreteDecorator(具体装饰器):具体不同的口味,给奶茶一些附加的特色。
现在有了这个例子,我们再回头看一看装饰模式的概念,加深理解。
装饰器动态地给一个对象添加一些额外的功能。相比起继承子类来说,他的功能更加的灵活。不改变接口的前提下,增强所考虑的类的性能。
什么时候我们去考虑使用装饰模式呢?下面列出了三种常见情况:
1)需要扩展一个类的功能,或给一个类增加附加功能。
2)需要动态的给一个对象增加功能时(可随时撤销)。
3)类似于奶茶的例子,排列组合产生的类太多,继承不现实的情况。
下面我们就是用代码来验证一下:
二、代码实现装饰模式
代码实现也比较简单,就是创建上面几个角色而已:
第一步:定义奶茶接口(Component)
public interface MilkTea { //奶茶名字 public String milkTeaName(); //奶茶价格 public int milkTeaPrice(); }
第二步:定义两种不同种类的奶茶:珍珠奶茶和蜂蜜奶茶(ConcreteComponent)
首先看珍珠奶茶
public class PearlMilkTea implements MilkTea { @Override public String milkTeaName() { return "珍珠奶茶"; } //珍珠奶茶一杯15块 @Override public int milkTeaPrice() { return 15; } }
接下来看蜂蜜奶茶
public class HoneyMilkTea implements MilkTea { @Override public String milkTeaName() { return "蜂蜜奶茶"; } //蜂蜜奶茶一杯20块 @Override public int milkTeaPrice() { return 20; } }
第三步:定义口味(Decorator)
//装饰器:为奶茶添加不同的口味 public class Taste implements MilkTea { @Override public String milkTeaName() { return "具体的名字让子类去定义"; } @Override public int milkTeaPrice() { return 0; } }
第四步:具体口味(ConcreteDecorator):加冰和加咖啡
首先是加冰的口味:
public class AddIceTaste extends Taste { private String description = "奶茶加冰。。。。"; private MilkTea milkTea=null; public AddIceTaste( MilkTea milkTea) { this.milkTea = milkTea; } //我们让相应的奶茶去增加这个口味 public String milkTeaName() { return milkTea.milkTeaName()+" "+description; } //同理,奶茶的价格也要提高一点:增加5块钱吧 public int milkTeaPrice() { return milkTea.milkTeaPrice()+5; } }
然后是加咖啡的:
public class AddCoffeeTaste extends Taste { private String description = "奶茶加咖啡。。。。"; private MilkTea milkTea=null; public AddCoffeeTaste( MilkTea milkTea) { this.milkTea = milkTea; } //我们让相应的奶茶去增加这个口味 public String milkTeaName() { return milkTea.milkTeaName()+" "+description; } //同理,奶茶的价格也要提高一点:增加15块钱吧 public int milkTeaPrice() { return milkTea.milkTeaPrice()+15; } }
第五步:用户买奶茶
public class User { public static void main(String[] args) { MilkTea honeyMilkTea=new HoneyMilkTea(); System.out.println("买了一杯蜂蜜奶茶,价格是"+honeyMilkTea.milkTeaPrice()); honeyMilkTea = new AddCoffeeTaste(honeyMilkTea); System.out.println("加咖啡,价格是"+honeyMilkTea.milkTeaPrice()); MilkTea pearlMilkTea=new HoneyMilkTea(); System.out.println("买了一杯珍珠奶茶,价格是"+pearlMilkTea.milkTeaPrice()); pearlMilkTea = new AddIceTaste(pearlMilkTea); System.out.println("加冰块,价格是"+pearlMilkTea.milkTeaPrice()); } } //output //买了一杯蜂蜜奶茶,价格是20 //加咖啡,价格是35 //买了一杯珍珠奶茶,价格是20 //加冰块,价格是25
ok,以上就是装饰模式的代码实现,
我们来进行一个小结:
(1)装饰模式用于动态地给一个对象增加一些额外的职责,是一种对象结构型模式。
(2)装饰模式包含四个角色。
(3)比生成子类实现更为灵活。
(4)装饰模式可分为透明装饰模式和半透明装饰模式。
现在我们基本上对装饰模式有了基本的了解了,不过这还不够,在文中一开始我们提到过,上面的只是对透明类型的装饰模式进行了介绍,下面就来看看什么是半透明的。
三、分析装饰模式
1、半透明装饰模式
在上面我们已经对透明的装饰模式进行了代码的演示,但是还需要介绍一下半透明的装饰模式。
半透明装饰模式可以给系统带来更多的灵活性,设计相对简单,使用起来也非常方便;但是其最大的缺点在于不能实现对同一个对象的多次装饰,而且客户端需要有区别地对待装饰之前的对象和装饰之后的对象。那它是怎么实现的呢?我们来看看透明装饰模式和半透明装饰模式的区别。
我们开的车可以定义为一个抽象构建,其具体构件可以有小轿车、越野车、跑车等等,他们都具有在马路上行驶的能力,但是突然出现了一种车,不仅能在地上跑了,也能在天上飞了,这时候我们就需要在我们的装饰器里面新增一个fly方法,过了没多久我们发现又出现了一种车更牛,不仅能在地上跑,还能在水里游了,这时候我们有需要在我们的装饰器里面新增一个swim方法。这些fly和swim是车接口没有的方法,问题就在这。也就是说果装饰角色的接口(装饰器)与抽象构件角色接口(车接口)中的方法不一致。这就是透明装饰模式和半透明装饰模式的区别。
2、与适配器模式区别
(1)概念区分
适配器模式,一个适配允许通常因为接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中。
装饰器模式,原有的不能满足现有的需求,对原有的进行增强。
(2)继承特点
适配器模式是用新接口来调用原接口,原接口对新系统是不可见或者说不可用的。
装饰者模式原封不动的使用原接口,系统对装饰的对象也通过原接口来完成使用。(增加新接口的装饰者模式可以认为是其变种--“半透明”装饰者)
(3)包装
装饰模式包装的是自己的兄弟类,隶属于同一个家族(相同接口或父类),
适配器模式则修饰非血缘关系类,把一个非本家族的对象伪装成本家族的对象,注意是伪装,因此它的本质还是非相同接口的对象。
总结:装饰模式分为透明和半透明两种,半透明要记住和适配器模式的区分。
OK,今天的文章就先到这里。如有问题还请批评指正。