意图
动态地给一个对象添加一些额外的职责。
别名
装饰模式Decorator也称包装模式Wrapper
动机
有时我们希望给某个对象而不是整个类添加一些功能。
适用性
以下情况适用装饰模式:
1.在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
2.处理那些可以撤销的职责。
3.当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量的独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸式增长。另一种情况可能是因为类定义被隐藏或者类定义不能用于生成子类。
类图
角色介绍
Component:抽象组件。
可以是一个接口或抽象类,其充当的就是被装饰的原始对象。
ConcreteComponent:组件具体实现类。
该类是Component类的基本实现,也是我们装饰的原始对象。
Decorator:抽象装饰者。
顾名思义,其承担的职责就是为了装饰我们的组件对象,其内部一定要有一个指向组件对象的引用。在大多数情况下,该类为抽象类,需要根据不同的装饰逻辑实现不同的具体子类。当然,如果装饰逻辑单一,只有一个的情况下我们可以省略该类直接作为具体的装饰者。
ConcreteDecoratorA/ConcreteDecoratorB:装饰者具体实现类。
对抽象装饰者作出具体的实现。
生活中的示例
现地摊经济很火,假设我们摆了个地摊,卖山东杂粮煎饼和手抓饼, 以手抓饼为例:
从图上可以看到,原味手抓饼3元,然后可以加鸡蛋、肉松、热狗、黄瓜、培根、芝士等等配料,还可以加番茄酱,甜辣酱,甜面酱等等酱料。
如果我们要做一个收费系统,你会怎么做呢?从上面讲的装饰者的适用情况看,这里很适合用装饰模式-动态添加职责,我们将配料和酱料来装饰手抓饼,最后来算一个手抓饼的价格。
类图
Cake:饼,对应于Component;
HandGraspingCake/GrainFriedCake:手抓饼和杂粮煎饼对应于ConcreteComponent;
CondimentDecorator:配料类对应于Decorator;
Bacon/Cheese/Cucumber/Egg/ChillSauce:培根/芝士/黄瓜/鸡蛋/辣椒酱对应于ConcreteDecoratorA/ConcreteDecoratorB等等。
运行结果:
老板,来一个手抓饼,加一个鸡蛋、培根、黄瓜、辣椒酱
老板来一个杂粮煎饼,加两个鸡蛋,两个培根,不要辣椒酱
HandGraspingCake,鸡蛋,培根,黄瓜,辣椒酱:7.5
GrainFriedCake,鸡蛋,鸡蛋,培根,培根:12.0
代码:
Client.java
package com.sunny.disignpattern.decorator;
public class Client {
public static void main(String[] args) {
Cake cake = new HandGraspingCake();
//老板,来一个手抓饼,加一个鸡蛋、培根、黄瓜、辣椒酱
cake = new Egg(cake);
cake = new Bacon(cake);
cake = new Cucumber(cake);
cake = new ChilliSauce(cake);
System.out.println(cake.getDescription()+":"+cake.cost());
//老板来一个杂粮煎饼,加两个鸡蛋,两个培根,不要辣椒酱
Cake cake2 = new GrainFriedCake();
cake2 = new Egg(cake2);
cake2 = new Egg(cake2);
cake2 = new Bacon(cake2);
cake2 = new Bacon(cake2);
System.out.println(cake2.getDescription()+":"+cake2.cost());
}
}
Cake.java
package com.sunny.disignpattern.decorator;
public abstract class Cake {
protected String description = "Unknown Cake";
public String getDescription(){
return description;
}
public abstract double cost();
}
GrainFriedCake.java
package com.sunny.disignpattern.decorator;
/**
* @ProjectName: DesignPattern23
* @Package: com.sunny.disignpattern.decorator
* @ClassName: GrainFriedCake
* @Description: 杂粮煎饼
* @Author: Sunny
* @CreateDate: 2020/6/21 5:22 PM:
*/
public class GrainFriedCake extends Cake {
public GrainFriedCake() {
description = "GrainFriedCake";
}
public double cost() {
return 5;//原味杂粮煎饼5块一个
}
}
HandGraspingCake.java
package com.sunny.disignpattern.decorator;
/**
* @ProjectName: DesignPattern23
* @Package: com.sunny.disignpattern.decorator
* @ClassName: HandGraspingCake
* @Description: 手抓饼
* @Author: Sunny
* @CreateDate: 2020/6/21 5:17 PM
*/
public class HandGraspingCake extends Cake {
public HandGraspingCake() {
description = "HandGraspingCake";
}
@Override
public double cost() {
return 3;//原味手抓饼3块一个
}
}
CondimentDecorator.java
package com.sunny.disignpattern.decorator;
/**
* @ProjectName: DesignPattern23
* @Package: com.sunny.disignpattern.decorator
* @ClassName: Condiment
* @Description: 调料
* @Author: Sunny
* @CreateDate: 2020/6/21 5:25 PM:
*/
public abstract class CondimentDecorator extends Cake{
public abstract String getDescription();
}
Bacon.java
package com.sunny.disignpattern.decorator;
/**
* @ProjectName: DesignPattern23
* @Package: com.sunny.disignpattern.decorator
* @ClassName: Bacon
* @Description: 培根
* @Author: Sunny
* @CreateDate: 2020/6/21 5:30 PM:
*/
public class Bacon extends CondimentDecorator
{
private Cake cake;
public Bacon(Cake cake){
this.cake = cake;
}
@Override
public String getDescription() {
return cake.getDescription()+",培根";
}
@Override
public double cost() {
return cake.cost()+2;//培根的价格为2元
}
}
Cheese.java
package com.sunny.disignpattern.decorator;
/**
* @ProjectName: DesignPattern23
* @Package: com.sunny.disignpattern.decorator
* @ClassName: Cheese
* @Description: 芝士
* @Author: Sunny
* @CreateDate: 2020/6/21 5:34 PM:
*/
public class Cheese extends CondimentDecorator{
private Cake cake;
public Cheese(Cake cake){
this.cake = cake;
}
@Override
public String getDescription() {
return cake.getDescription()+",芝士";
}
@Override
public double cost() {
return cake.cost()+2;//芝士2块钱
}
}
ChilliSauce.java
package com.sunny.disignpattern.decorator;
/**
* @ProjectName: DesignPattern23
* @Package: com.sunny.disignpattern.decorator
* @ClassName: ChilliSauce
* @Description: 辣椒酱
* @Author: Sunny
* @CreateDate: 2020/6/21 5:37 PM:
*/
public class ChilliSauce extends CondimentDecorator{
private Cake cake;
public ChilliSauce(Cake cake){
this.cake = cake;
}
@Override
public String getDescription() {
return cake.getDescription()+",辣椒酱";
}
@Override
public double cost() {
return cake.cost()+0;//加辣椒酱免费
}
}
Cucumber.java
package com.sunny.disignpattern.decorator;
/**
* @ProjectName: DesignPattern23
* @Package: com.sunny.disignpattern.decorator
* @ClassName: Cucumber
* @Description: 黄瓜
* @Author: Sunny
* @CreateDate: 2020/6/21 5:33 PM:
*/
public class Cucumber extends CondimentDecorator{
private Cake cake;
public Cucumber(Cake cake){
this.cake = cake;
}
@Override
public String getDescription() {
return cake.getDescription()+",黄瓜";
}
@Override
public double cost() {
return cake.cost()+1;//黄瓜1块钱
}
}
Egg.java
package com.sunny.disignpattern.decorator;
/**
* @ProjectName: DesignPattern23
* @Package: com.sunny.disignpattern.decorator
* @ClassName: Egg
* @Description: 鸡蛋
* @Author: Sunny
* @CreateDate: 2020/6/21 5:28 PM:
*/
public class Egg extends CondimentDecorator{
private Cake cake;
public Egg(Cake cake){
this.cake = cake;
}
@Override
public String getDescription() {
return cake.getDescription()+",鸡蛋";
}
@Override
public double cost() {
return cake.cost()+1.5;
}
}
Android源码中的示例
Android源码中Context、ContextImpl、ContextWrapper、Activity、Application、Service是很好的装饰模式的示例。
类图
Context在Android中被称为“上帝对象”,它本质是一个抽象类,其在装饰模式中相当于抽象组件-Component,而在其内部定义了大量的抽象方法,比如我们经常会用到的startActivity()方法:
public abstract class Context {
//......省略一些代码......
public abstract void startActivity(@RequiresPermission Intent intent);
public abstract void startActivity(@RequiresPermission Intent intent,
@Nullable Bundle options);
//......省略一些代码......
}
而其真正的实现是在ContextImpl中完成的,ContextImpl继承自Context抽象类,并实现了Context中的抽象方法:
class ContextImpl extends Context {
//......省略一些代码......
@Override
public void startActivity(Intent intent) {
warnIfCallingFromSystemProcess();
startActivity(intent, null);
}
@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
// Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
// generally not allowed, except if the caller specifies the task id the activity should
// be launched in. A bug was existed between N and O-MR1 which allowed this to work. We
// maintain this for backwards compatibility.
final int targetSdkVersion = getApplicationInfo().targetSdkVersion;
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
&& (targetSdkVersion < Build.VERSION_CODES.N
|| targetSdkVersion >= Build.VERSION_CODES.P)
&& (options == null
|| ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intent, -1, options);
}
//......省略一些代码......
}
这里ContextImpl就相当于ConcreteComponent-组件具体实现类。
public class ContextWrapper extends Context {
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
//......省略一些代码......
@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}
@Override
public void startActivity(Intent intent, Bundle options) {
mBase.startActivity(intent, options);
}
//......省略一些代码......
ContextWrapper就相当于Decorator-装饰抽象类,在ContextWrapper中有一个Context的引用以及抽象组件的方法。
public class Application extends ContextWrapper implements ComponentCallbacks2 {
//......省略一些代码......
}
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
//......省略一些代码......
}
public class ContextThemeWrapper extends ContextWrapper {
//......省略一些代码......
}
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback, WindowControllerCallback,
AutofillManager.AutofillClient {
//......省略一些代码......
}
Activity/Service/Application就相当于装饰模式里面的ConcreteDecoratorA/ConcreteDecoratorB的角色。
题外话:在一个应用中,一共存在多少个Context对象?
一个Application对象会有一个Context,而Application在应用中是唯一的,同时一个Activity或Service又分别表示一个Context,因此,一个应用中Context对象的个数=Activity对象和Service对象个数之和再加上一个Application。那么四大组件的另外两个BroadcastReceiver和ContentProvider没有保持Context对象吗?BroadcastReceiver并非直接或间接继承于Context,但是每次接收广播的时候,onReceive()方法都会收到一个Context对象,该Context对象是ReceiverRestrictedContext的一个实例;而在ContentProvider中你可以调用其getContext()方法获取一个Context对象。这些Context都直接或间接来自于Application、Activity和Service。