美文网首页设计模式
23种设计模式之装饰模式

23种设计模式之装饰模式

作者: 小二小二小二 | 来源:发表于2020-06-21 19:25 被阅读0次

    意图

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

    别名

    装饰模式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种设计模式之装饰模式

        本文链接:https://www.haomeiwen.com/subject/blraxktx.html