大话设计模式书中,作者举了一个穿衣服的例子来为我们引入装饰器模式。
概念
定义
装饰模式在书中的定义是:
动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更灵活。
这句话直接去理解可能会有点抽象,我结合书中的例子来讲讲自己的理解。假设有一天,女朋友要你陪她去逛商场,她今天要做很多事情:
- 去干洗店拿干洗的衣服
- 去服装店取定制地衣服
- 去做头发
- 去美甲
- 去美食街吃夜宵
- ......
她去做这些事情地顺序是不确定的,而且这些事情之间也没有什么太大的关联。那我们在程序中去把这些事情串联起来呢?简单的为每一种操作写一个类,然后在主函数中排列组合吗?这样做也不是不可以,但是这就破坏了我们程序当中的封装性。用书中的话说就是:
你光着身子, 当着大家的面,先穿T恤,再穿裤子,再穿鞋,仿佛在跳穿衣舞。难道你穿衣服都是在众目睽睽下穿的吗?
实际上,大多数时候我们不希望将太多的细节暴露给用户,因此面对这种情况,我们可能就需要用到装饰模式。让我们先来看看它的结构图。
结构
这是作者在书中给出的原图。同样,直接看图可能很难让我们理解什么是策略模式。我们继续用上面那个例子来类比举例。
Component是定义一个对象接口,可以给这些对象动态地添加职责。
这句话怎么理解,其实在上面这个例子中,Conponent其实就相当于“人”。为什么这么说,因为只有“人”才能完成拿衣服,买衣服,做头发等等这些事情(不要抬杠哈,你懂我意思)。那这个Operation()方法就相当于是做完了的事情(可以是空的或者多件事情的排列组合)。
ConcreteComponent是定义了一个具体的对象,也可以给这个 对象添加一些职责。
这句话又怎么理解呢,你可以把ConcreteComponent看成是女朋友,她是一个具体的人,是她要去完成这些行为。同样Operation()方法就相当于是她已经做完了的事情(可以是空的或者多件事情的排列组合)。
Decorator,装饰抽象类,继承了Component, 从外类来扩展Component类的功能,但对于Component来说,是无需知道Decorator的存在的。至于ConcreteDecorator就是具体的装饰 对象,起到给Component添加职责的功能。
这句话比较长,也比较抽象。什么是装饰抽象类,在这个例子中还真不是很好解释,你可以理解为一个“盒子”,里面装的是“前一刻的女朋友”(可能比较牵强哈)。或者更好的理解是把他当成一种规则:你这个人在进我们店之前是什么样的,带了哪些东西;你带来的东西我们原封不动,我们店给你提供了一些服务之后,你还是完完整整的一个人从我这里走出去。所有的店都必须遵循这个规则(不然就是黑店了)。ConcreteDecorator就相当于是遵循这些规则的具体的干洗店,服装店,理发店,美甲店,小吃店等等了。
那么整个流程是怎么样的呢。
- 女朋友出门饿了,先来小吃店吃了小吃,变成了“吃了小吃的女朋友”
- “吃了小吃的女朋友”吃太饱了,准备去干洗店先拿一下衣服,变成了“拿了衣服的吃了小吃的女朋友”
- “拿了衣服的吃了小吃的女朋友”觉得走累了,先去理发店做个于是头发休息一下,变成了“做了头发的拿了衣服的吃了小吃的女朋友”
- ......
- 最后女朋友做完了所有的事情
经过这个流程,大家应该能够理解了具体装饰类的作用了吧。比如装饰类A的作用就是,把B的C变成A的B的C。
优缺点
优点:
在书中对于装饰模式的优点是这样说的:
装饰模式是利用SetComponent来对对象进行包 装的。这样每个装饰对象的实现就和如何使用这个对象分离开了, 每个装饰对象只关心自己的功能,不需要关心如何被添加到对象链当中。
其实我觉得它的主要优点还是增强了扩展对象功能的灵活性。并且减少了细节的暴露。
缺点:
主要的缺点就是:可能会增加程序的复杂性,因为它的结构相较于直接在主程序中“跳穿衣舞”更加难以理解,因为它需要将对象一层一层的包裹起来,如果过度使用的话可能会让程序变得复杂。
例子
我们尝试用Java实现一下上面的例子。并对其中的一些细节进行相应的优化。
- 由于整个事情中,出现的人只有女朋友一个,因此可以将人这个父类省略。女朋友类的代码如下:
/** * @Author yirui * @Date 2024/4/17 20:58 * @Version 1.0 */ public class GirlFriend { public void Operation(){ System.out.println("女朋友"); } }
-
由于没有人这个父类(父子类的概念参考简单工厂模式中所介绍的),那修饰抽象类只能继承女朋友类了,规则变成:女朋友在进店之前是什么样的,带了哪些东西;带来的东西我们原封不动,我们店给女朋友提供了一些服务之后,女朋友还是完完整整的一个人从我这里走出去。(其实这个例子中,这个类都可以不要,直接让具体修饰类继承女朋友类就可以了,但是为了方便理解,还是把它写出来了):
/** * @Author yirui * @Date 2024/4/17 21:03 * @Version 1.0 */ public class Decorator extends GirlFriend{ GirlFriend girlFriend; public void setGirlFriend(GirlFriend girlFriend) { this.girlFriend = girlFriend; } @Override public void Operation() { girlFriend.Operation(); } }
-
接下来就是具体修饰类了,继承修饰抽象类。(这里为了省事,只写干洗店和小吃店。)
/** * @Author yirui * @Date 2024/4/17 21:08 * @Version 1.0 */ public class ClothingStore extends Decorator{ private void buyCloth(){ System.out.println("买了一件衣服。"); } @Override public void Operation() { super.Operation(); buyCloth(); } } /** * @Author yirui * @Date 2024/4/17 21:11 * @Version 1.0 */ public class FoodStore extends Decorator{ private void eatFood(){ System.out.println("吃了一碗麻辣烫。"); } @Override public void Operation() { super.Operation(); eatFood(); } }
-
主程序:
/** * @Author yirui * @Date 2024/4/17 21:13 * @Version 1.0 */ public class Program { public static void main(String[] args) { GirlFriend girlFriend = new GirlFriend(); ClothingStore clothingStore = new ClothingStore(); FoodStore foodStore = new FoodStore(); clothingStore.setGirlFriend(girlFriend); clothingStore.Operation(); System.out.println("**********************"); foodStore.setGirlFriend(clothingStore); foodStore.Operation(); } }
-
结果如图,可以看到,女朋友先去了服装店,变成了女朋友买了一件衣服。这个时候,买了一件衣服的女朋友又去了小吃店,最终变成了,女朋友买了一件衣服,吃了一碗麻辣烫。