我们讲都都是设计一个类,设计一个类,设计。
装饰者模式中的”装饰“的意思是礼物盒的装饰那种装饰。
其实就是不断地“装饰组件”嵌套“组件”的过程,只不过不是用继承技术,而是用组合和委托技术。
无论是哪种模式都至少有两层向上抽象,一般都是三层抽象以上,普遍有中介者来结构,而这个中介者很多时候也是抽象出来的抽象。
利用组合思想来代替继承,利用委托(多态)来实现组合,其实就是多态,本质也是抽象。但是该如何抽象,抽象出来的东西该放在哪里?抽象出来的东西是否还需要继续抽象?抽象出来的用途是什么?等等一系列的回答不同就组成了不同的设计模式。也就有根据不同的场景有了不同的术语。于是,抽象的思想基本上贯穿了面向对象设计思想的全部,所以针对接口编程也就贯穿了全部。
装饰者相对继承的优势在于:
装饰者可以在new一个对象的时候(具体使用的时候),传递一个组件的引用作为参数进来,在使用时动态地组合对象。而继承必须在设计阶段就考虑每一个new对象的类,属于静态设计代码,然后你用数学的组合思想来算一下下面的需求,所需要设计的类。用装饰者模式的话,要设计4+3个类,用继承的话,要继承4+3*2*1个类。
如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰模式的目标。
抽象装饰者继承于抽象组件,在抽象组件基础上增加了行为(方法)。
具体组件继承并实现抽象组件,是一个可以使用的具体类(是类,不是实例)。
具体装饰者类,继承并实现于装饰者类,所以装饰者也是一个具体组件,只不过它由于继承抽象装饰,也就有了抽象装饰里的额外行为,所以装饰者就是有了额外行为的具体组件类。
那为什么在设计具体组件类的时候就不把额外的行为设计进去呢?就是因为当时没考虑到要额外的行为啊,不然怎么叫做额外的行为啊?现在亡羊补牢啊。我另外设计一个抽象装饰,抽象装饰者类,比修改系统原来就存在的大量代码要轻松得多啊,现在只是增加代码,不用做过多的修改啊。核心就是在装饰者组件里声明一个原来的具体组件的属性,从而对其扩展,以后要想有额外的行为就使用这个装饰者就好了,而以前系统存在的代码也不会受到影响啊。
装饰者模式的定义:
动态的将责任附加到对象上(嵌套)。若要扩展功能,装饰者提供了比继承更有弹性的设计方案。
装饰者模式里的术语(角色):
组件(抽象):
你用一下自下而上的抽象思想,把每一种具体的饮料,例如橙汁、苹果汁、奶茶、咖啡等等,抽象成饮料,这个“饮料”就是抽象组件了。每个组件都可以单独使用可,或者被装饰者包起来,即嵌套起来。
具体组件:
就是上面说的咯,也就是在这个系统中的销售对象咯。就是可以在具体使用时,动态地加上新行为的对象。(也就是实例化时)
抽象的装饰者类(可选):
继承于抽象组件,并包含具体组件的实例的引用,其子类可以扩展具体组件的功能。具体组件不是实例,是一个类。
也就是如果你想要递归地执行“每一层抽象组件里都要有”的某一个方法时,你就要为该方法设计一个(层)抽象类,用于递归(因为具体类必须实现该抽象方法嘛,也就是必须要具体子类去定义方法的具体代码咯)。而在具体的装饰者类里面,也声明了爸爸级别的引用,即定义抽象组件的引用。也就是在一个抽象类(或者接口)里面,声明了抽象组件,不是说就没有声明其他的东西了,可能也有声明抽象组件以外的方法。
具体装饰者:
也即是抽象装饰的子类,即具体类,实现抽象装饰的相关方法,并给具体构件对象添加附加的责任(行为)。
其实就是不断地“装饰组件”嵌套“组件”的过程,只不过不是用继承技术,而是用组合和委托技术。每个装饰者都“有一个”(包装一个)组件,也就是说,装饰者有一个实例变量以保存某个component的引用。嵌套之后的组件也还是组件,你叫它装饰过的组件也可以。
示例的需求:
开一家咖啡店,有不同的饮料,不同的饮料可以加不同的配料,于是你要设计这个咖啡店的(饮料)订单系统。假设我们现在的饮料有HouseBlend、DarkRoast、Decaf、Espresso;配料有Milk、Soy、Mocha、Whip。
需求分析:
如果你为每一杯饮料都设计一个类,那肯定是行不通的,你试试用数学的排列组合算一下?软件工程里好像有一个方法是从需求里面提取出名词和动词,找出关键的,然后再设计类,排除类,剩下有用的,哎,忘了。
设计原则: 设计类的时候,类应该对“扩展”开放,对“修改“关闭
这里的修改的意思不是说不能修改代码,而是尽量不要去修改设计好的类的定义,因为继承什么的,会牵涉到很多其他的类也要进行修改,牵一发而动全身,所以你可以设计新的类来继承或者装饰什么,就不要去修改已经定义好的类了(这也是对“扩展”开放的意思,继承或者装饰什么的)。
所以,我们设计类的目标是允许该类很容易地被扩展,在不修改现有的类定义里面的代码情况下,就可以为现有的类搭配新的行为。其实就是要设计一个新的类了咯,对旧的类继承,装饰什么的,就是新的了呀。这样的设计,就具有弹性,可以应对改变,可以接受新的功能来应对那些经常需要改变的需求。
也就是说,如果你旧的类已经写死了,不给别人一点点扩展的机会,那么当新的需求过来时,哦呵,你要重新由头到尾设计过整一个系统了,gg。
示例代码:
抽象组件(单纯)--饮料抽象类:
package s_AbstractClass;
/**
* 装饰者模式中的抽象组件,共同的超类
* 装饰者模式适用于 属于同一个超类的 不同组件之间需要不断地嵌套组合的场景;虽然嵌套了很多,但最终也还是这个超类的实例
* 装饰者模式理解的关键在于,共同的抽象接口(超类),好像其他模式的最顶层也至少有两层的抽象,针对接口编程。
* 装饰是指礼物盒外层的装饰那种。
* 被装饰者叫做 具体组件,超类叫做 抽象组件,有一个叫 抽象装饰者,也有一个叫 具体装饰者。
* @author mathew
*
*/
public abstract class Beverage {
protected String description = "Unkown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
抽象组件的装饰者类(嵌套与被嵌套)--配料抽象类:
为具体的装饰者再提供了一层抽象,供具体装饰者类遵循,具体装饰者类才是真正的设计了嵌套的组件的引用,其实我觉得不用这层抽象装饰者类也可以了,抽象组件类也就可以了。但是这里要多层递归抽象组件里面的方法,所以就需要再定义一层抽象,用于递归抽象组件里面的方法。
package s_AbstractDecoratorClass;
import s_AbstractClass.Beverage;
/**
* 抽象装饰者类,继承与具体组件共同的接口,目的是嵌套各个不同的组件时,大家有共同的方法属性,方便嵌套。这也是与广义组合思想的区别。
* 组合思想中,被组合者并不需要是共同接口的遵循者,只要是一个抽象的接口,可以利用多态就行,范围更广。
* @author mathew
*
*/
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
具体的组件类(被装饰者)--没加配料的饮料的具体类:
package s_ConcreteCompnentClass;
import s_AbstractClass.Beverage;
/**
* 具体组件类,这个具体组件就是不断的被装饰者拿去嵌套的,
* 注意装饰者嵌套组件时的类定义并不是具体组件的类定义,而是利用多态,即抽象组件类,就是共同的超类。
* @author mathew
*浓咖啡
*/
public class Espresso extends Beverage {
public Espresso() {
super.description = "Espresso";
}
@Override
public double cost() {
// TODO Auto-generated method stub
return 1.99;
}
}
具体的含装饰组件(装饰者类)--加了配料的饮料具体类:
package s_DecoratorClass;
import s_AbstractClass.Beverage;
import s_AbstractDecoratorClass.CondimentDecorator;
/**
* 装饰者类,摩卡
* 装饰者嵌套组合具体组件时,是在具体装饰者的类定义中定义抽象组件的,而不是在抽象装饰者的类定义中定义的。
* @author mathew
*
*/
public class Mocha extends CondimentDecorator {
//被嵌套的抽象组件,被装饰的对象。礼物
protected Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
// TODO Auto-generated method stub
return beverage.getDescription() + " , Mocha";
}
@Override
public double cost() {
// TODO Auto-generated method stub
return 0.21 + beverage.cost();
}
}
示例的执行:
package s_MainClass;
import s_AbstractClass.Beverage;
import s_ConcreteCompnentClass.DarkRoast;
import s_ConcreteCompnentClass.Espresso;
import s_ConcreteCompnentClass.HouseBlend;
import s_DecoratorClass.Mocha;
import s_DecoratorClass.Soy;
import s_DecoratorClass.Whip;
/**
* 模仿星巴克的饮料来实现装饰者模式。
* @author mathew
*
*/
public class StarbuzzCoffee {
public static void main(String[] args) {
// TODO Auto-generated method stub
//具体组件
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + " $ " + beverage.cost());
Beverage beverage2 = new DarkRoast();
Beverage beverage3 = new HouseBlend();
//装饰者,深度烘焙的加料饮料
//加两个摩卡,可以设置一个临时变量,注意参数和变量都是同一个引用
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription() + " $ " + beverage2.cost());
//装饰者,混合味的加料饮料
beverage3 = new Soy(beverage3);
beverage3 = new Mocha(beverage3);
beverage3 = new Whip(beverage3);
System.out.println(beverage3.getDescription() + " $ " + beverage3.cost());
}
}