23种设计模式之装饰模式

意图

动态地给一个对象添加一些额外的职责。

别名

装饰模式Decorator也称包装模式Wrapper

动机

有时我们希望给某个对象而不是整个类添加一些功能。

适用性

以下情况适用装饰模式:
1.在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
2.处理那些可以撤销的职责。
3.当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量的独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸式增长。另一种情况可能是因为类定义被隐藏或者类定义不能用于生成子类。

类图

image.png

角色介绍

Component:抽象组件。
可以是一个接口或抽象类,其充当的就是被装饰的原始对象。
ConcreteComponent:组件具体实现类。
该类是Component类的基本实现,也是我们装饰的原始对象。
Decorator:抽象装饰者。
顾名思义,其承担的职责就是为了装饰我们的组件对象,其内部一定要有一个指向组件对象的引用。在大多数情况下,该类为抽象类,需要根据不同的装饰逻辑实现不同的具体子类。当然,如果装饰逻辑单一,只有一个的情况下我们可以省略该类直接作为具体的装饰者。
ConcreteDecoratorA/ConcreteDecoratorB:装饰者具体实现类。
对抽象装饰者作出具体的实现。

生活中的示例

现地摊经济很火,假设我们摆了个地摊,卖山东杂粮煎饼和手抓饼, 以手抓饼为例:


image.png

从图上可以看到,原味手抓饼3元,然后可以加鸡蛋、肉松、热狗、黄瓜、培根、芝士等等配料,还可以加番茄酱,甜辣酱,甜面酱等等酱料。
如果我们要做一个收费系统,你会怎么做呢?从上面讲的装饰者的适用情况看,这里很适合用装饰模式-动态添加职责,我们将配料和酱料来装饰手抓饼,最后来算一个手抓饼的价格。

类图

image.png

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是很好的装饰模式的示例。

类图

image.png

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。

你可能感兴趣的:(23种设计模式之装饰模式)