模式概述
装饰器模式在java中很常见,其中心的本质思想就是动态组合,这种组合很精妙的实现了生活中类似于定制化的功能,可以按照客户的要求来满足客户的定制场景,同时又让编码变得没有那么的复杂,学好了装饰模式,能真正的理解面向对象编程的 本质,也会让人有一种豁然开朗的感觉。
java中常用的装饰模式的地方就是我们熟知的IO流,InputStream系列和OutputStream系列都是状态模式的设计体现。有了这种模式,我们就可以很方便的将一个对象选择写入到一个file中还是写成内存byte中,使用方可以根据自己的需求定制,而且不用再根据业务场景增加多余的类;
模式结构uml和类介绍
-
装饰器模式的uml结构图如下所示:
-
每个类的功能如下:
- Component : 组件对象的接口,可以给这些对象动态的添加职责
- ConcreteComponent :具体的对象,实现组件对象的接口,通常是装饰器的原始对象,后面的装饰器就是要给这些对象添加职责。
- Decorator : 所有装饰器对象的父类,需要和组件接口一致的接口,并且持有一个组件对象
- ConcreteDecorator :装饰器对象的具体实现,实现具体要添加的功能。
-
实现思路
装饰器模式在具体的实现中,首先要有一个组件对象,这是实现装饰器对象的基础,这个可以是一个接口,也可以是一个抽象类,其次,我们需要实现一个或者多个组件的实现类,座位被装饰的原始对象,原始对象的意思就是没有装饰之前的对象,比如IO中的file和byte两个流,这两个流就是同一个级别的原始对象。然后才是我们的重点,装饰器的设计:首先我们要实现一个抽象的Decorator对象,这个对象首先需要实现或者继承原来的装饰组件(Component),因为我们就是为了装饰那个组件的实现类,当然要有相同的方法;其次就是需要持有一个组件对象,这是因为我们完成了装饰逻辑以后,还是需要被装饰的对象去执行功能。抽象的装饰对象(Decorator)还是很有必要的,虽然我们可以直接实现装饰器的实现类,但是那样就需要在每个实现类中注入每个组件对象,没法统一规范,写这样一个对象,就可以统一规范注入什么样的组件对象,也规范了和组件对象相同的方法。
在具体的使用中,我们先创建被装饰的原始对象(ConcreteComponent),然后创建具体的装饰对象(ConcreteDecorator),将组件对象注入到装饰对象中,然后调用装饰对象的方法,就可以完成装饰工作。
使用场景示例
我们用促销的例子来举例,来实现一个具体的促销场景。为了演示装饰模式的扩展性,首先我们先假设该促销目前有两个活动,一个是团购活动,一个是节假日促销活动。实现思路和代码如下:
- 我们需要先实现一个抽象类AbstractSalePromotion,用来定义本次活动的顶层方法
/**
* 促销活动的抽象类
*/
public abstract class AbstractSalePromotion {
/**
* 获得活动价格
*
* @return :
*/
abstract double getPromotionPrice();
}
- 我们再实现一个没有活动的实现类,作为被装饰的原始对象
/**
* 原始产品
*/
public class OriginalProduct extends AbstractSalePromotion {
@Override
double getPromotionPrice() {
// 原始产品,没有折扣
return 100;
}
}
- 定义一个抽象的装饰器类,作为装饰器的父类,这个类需要继承AbstractSalePromotion类并拥有一个AbstractSalePromotion的对象
/**
* 装饰器的抽象类
*/
public abstract class AbstractDecorator extends AbstractSalePromotion {
private AbstractSalePromotion salePromotion;
public AbstractDecorator(AbstractSalePromotion salePromotion) {
this.salePromotion = salePromotion;
}
@Override
double getPromotionPrice() {
return salePromotion.getPromotionPrice();
}
}
- 分别实现两个装饰器类的具体实现类,一个是团购的,一个是节假日的装饰类
/**
* 团购活动价
*/
public class GroupSalePromotion extends AbstractDecorator{
public GroupSalePromotion(AbstractSalePromotion salePromotion) {
super(salePromotion);
}
@Override
double getPromotionPrice() {
double promotionPrice = super.getPromotionPrice();
System.out.println("团队折扣:打九折!!!");
return promotionPrice * 0.9;
}
}
/**
* 节假日折扣
*/
public class HolidaySalePromotion extends AbstractDecorator{
public HolidaySalePromotion(AbstractSalePromotion salePromotion) {
super(salePromotion);
}
@Override
double getPromotionPrice() {
double promotionPrice = super.getPromotionPrice();
System.out.println("节假日折扣:打八折!!!");
return promotionPrice * 0.8;
}
}
- 我们来写个测试类
public class Client {
/**
* 只有团队折扣
*/
@Test
public void testGroupSalePromotion() {
AbstractSalePromotion salePromotion = new GroupSalePromotion(new OriginalProduct());
System.out.println("最终价格:" + salePromotion.getPromotionPrice());
}
}
执行结果:
-------------------------------------------------------------
团队折扣:打九折!!!
最终价格:90.0
- 为什么说装饰器的本质是组合实现呢?我们在使用中就可以看出,目前有两个装饰器类,一个是团购的装饰器,一个是节假日的装饰器,这个时候,如果遇上一个顾客,既有团购,又是在节假日过来,我们就可以动态的实现一个团购,一个节假日的场景,用来满足顾客的要求,测试代码和结果如下:
/**
* 两个团队折扣
*/
@Test
public void testDoubleGroupSalePromotion() {
System.out.println("-------------------------------------------------------------");
AbstractSalePromotion salePromotion = new GroupSalePromotion(new GroupSalePromotion(new OriginalProduct()));
System.out.println("最终价格:" + salePromotion.getPromotionPrice());
}
-------------------------------------------------------------
团队折扣:打九折!!!
节假日折扣:打八折!!!
最终价格:72.0
要是有个极端的场景,一个客户拿到了两个团购的折扣,我们也可以轻松的实现,创建两个团购装饰类就行,而不是需要新建一个两个团购的装饰类,这就达到了根据客户动态定制的逻辑,测试代码和结果如下:
/**
* 两个团队折扣
*/
@Test
public void testDoubleGroupSalePromotion() {
System.out.println("-------------------------------------------------------------");
AbstractSalePromotion salePromotion = new GroupSalePromotion(new GroupSalePromotion(new OriginalProduct()));
System.out.println("最终价格:" + salePromotion.getPromotionPrice());
}
-------------------------------------------------------------
团队折扣:打九折!!!
团队折扣:打九折!!!
最终价格:81.0
- 为什么说装饰器模式有很好的扩展性呢?我们假设这个商场今年是特殊的一年:十周年店庆,十周年这一天,我们要加大力度,这个时候我们只需要实现一个十周年店庆的装饰实现类即可,这个时候就有了三个具体的装饰类,平时根据不同的优惠政策给客户定制化优惠就可以。十周年店庆的装饰类如下:
/**
* 十周年庆折扣
*/
public class TenYearSalePromotion extends AbstractDecorator{
public TenYearSalePromotion(AbstractSalePromotion salePromotion) {
super(salePromotion);
}
@Override
double getPromotionPrice() {
double promotionPrice = super.getPromotionPrice();
System.out.println("十周年折扣:打五折!!!");
return promotionPrice * 0.5;
}
}
如果一个客户既是团购,又是在节假日,还在店庆那天来,而商场又满足优惠可以同时享受,我们也不用怕,只需要三个具体的装饰类相互包装,就能实现具体的逻辑,测试代码和结果如下:
/**
* 团队、节假日、十周年折扣
*/
@Test
public void testTenYearGroupAndHolidaySalePromotion() {
System.out.println("-------------------------------------------------------------");
AbstractSalePromotion salePromotion = new TenYearSalePromotion(
new HolidaySalePromotion(new GroupSalePromotion(new OriginalProduct())));
System.out.println("最终价格:" + salePromotion.getPromotionPrice());
}
-------------------------------------------------------------
团队折扣:打九折!!!
节假日折扣:打八折!!!
十周年折扣:打五折!!!
最终价格:36.0
- 促销活动示例的uml类图如下:
总结
- 装饰模式的本质是动态组合,这种动态组合是为了让调用更加简单,给调用端做了一个定制化处理,不需要被调用端为了每种业务场景去实现不同的功能,只需要不断的组合就可以。
- 装饰模式、策略模式、组合模式、静态代理模式好像都是会持有一个对象,然后对对象进行加强或者其他的操作,好像很难分清楚,在这里我做一个我个人的理解:
- 首先,设计模式是一个思想层面的东西,本质是为了解决某一种特性场景的问题而生的,所以其区别就不是行为或者结构上面的区别,二是不同的思想层面的区别,也就是侧重点不一样,本质上来说,这四种实现结构是很像的,但是侧重点不一样。
- 装饰模式侧重于调用端的动态组合,是一种定制化的场景而生的,其一定会要求有原始对象,然后再依照这个实现自己的逻辑。
- 策略模式是为了执行不同的实现的时候能够用不同的策略来生的,其传入进去的时候是一个实现的策略对象,而且,策略模式一般是在方法调用的时候传入策略对象,装饰模式是构造的时候就传入组件对象。
- 组合模式更像是一种结构上的实现方法,只要能把对象传入,然后反向的调用到组合的对象就算,这样的话,这个更像是具体的实现的方式。
- 静态代理和装饰模式比较像,都是构造方法传入,实现了被代理的接口,但是代理强调的是控制被代理类或者增强被代理类,其目标是加强实现的逻辑,而装饰的本质是方便调用端的组合实现,虽然在实现上一样,但是在方向上有区别。
后记
个人总结,欢迎转载、评论、批评指正