小白自我提高学习设计模式笔记—装饰者模式

Posted 好人静

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了小白自我提高学习设计模式笔记—装饰者模式相关的知识,希望对你有一定的参考价值。

前言

        结合着android源码把所有的设计模式总结一下。

        小白自我提高学习设计模式笔记(一)—代理模式

        小白自我提高学习设计模式笔记(二)—装饰者模式

       小白自我提高学习设计模式笔记(三)—装饰者模式在Android开发的小试

        小白自我提高学习设计模式笔记(四)—责任链模式


一 装饰者模式

1.定义

        动态给对象添加一些额外的属性或行为,从对象的外部给对象添加功能。通过动态组合只改变对象的外观,并没有改变其内核。

        通常有四部分组成:

  • (1)Component(组件):一个抽象类或一个接口。装饰类和被装饰类共同的父类,用来标准或模版化对象的基本功能;
  • (2)Concrete Component(被装饰类 动态添加功能的对象):继承或实现Component。实现对象的基本功能,是被装饰的原始对象,就是给这个类的对象动态添加功能;
  • (3)Decorator(装饰类):一个抽象类或者接口,需要继承或实现Component,并且持有Component对象。是所有具体装饰类的父类,用来标准或模版化动态添加的功能。
  • (4)Concrete Decorator( 具体装饰类):具体装饰类,继承或实现Decorator。主要就是通过Component对象调用到被装饰类,不改变被装饰对象,而只是在基础上添加功能,用来扩展Concrete Component。

        对应的UML如图:

2.优缺点

  • 优点

        (1)比继承更加灵活:继承为静态,一旦继承所有的子类的功能是一样的;而装饰者模式把功能分配到每个装饰类中,在运行时动态的组合功能来决定被装饰者对象有哪些功能。

        (2)功能复用容易:把复杂的功能分散到每个装饰类中,一般每个装饰类只实现一个功能,可以为一个对象增加多个装饰类,也可以把一个装饰类用在多个对象上,实现装饰类的复用;

        (3)简化父类定义:通过组合装饰类,为对象添加任意功能;所以在最顶层父类只需要定义基本功能,在需要的时候通过组合装饰类的方式完成所需要的功能

  • 缺点

        (1)会产生较多的细粒度对象。装饰者模式把一系列复杂的功能分散到每个装饰类,一般情况下每个装饰类只实现一个功能,所以就会有很多细粒度的对象,功能越复杂,对象越多。

3. 与代理模式区别

        代理模式关注对对象的控制访问,对外隐藏对象的具体信息。代理模式详见小白自我提高学习设计模式笔记(一)—代理模式

        而装饰者模式关注的是为对象动态添加功能。

4.与适配器模式区别

        类和被装饰类需要实现同一个接口,或者装饰类是被装饰类的子类;适配器模式和被适配的类具有不同的接口

5. 与继承对比

        通过继承父类对子类扩展。但是如果父类带有数据、信息、属性的话,子类无法增强;子类继承了父类,功能固定,不方便任意组合,复用率低。

二 应用场景

        在不影响其他对象的情况下,通过对象组合的方式给对象动态增加职责;

1.简单的实例

        经常喝CoCo的人都知道,对于一杯珍珠奶茶,不仅分大中小杯的价格是一样的,而且还可以单独去加珍珠、椰果等配料。对于不同的顾客,可能就会有选择的选择不同的尺寸以及不同的配料,那么对于如何设计这个代码架构来实现顾客需要付钱的金额呢?

        如果从继承的角度来分析:需要将大小中对应一个计算价格的类,配料需要对应一个计算价格的类,并且每次在计算顾客选择的情况来选择对应的尺寸、配料的价格来计算出最终的价格,这样会产生很多很多的类,这还是一种饮料对应的价格计算,对于不同的饮料又要有各种类来支持这个功能,维护起来相当麻烦。

        这就需要装饰者模式隆重出场。

  • (1)定义Component

        用来标准化该饮料的基本功能:就是知道饮料的名字和价格,代码如下:

public interface CocoComponent 
    String getCocoName();
    double cost();
  • (2)定义被装饰类

        用来实现一杯珍珠奶茶的基本价格,后面就是对该对象进行装饰,给该对象增加配料以及大中小杯规格,代码如下:

public class MilkTeaConcreteComponent implements CocoComponent 

    @Override
    public String getCocoName() 
        return "珍珠奶茶";
    

    @Override
    public double cost() 
        return 13;
    

    @Override
    public String toString() 
        return String.format("%s\\n价格为:%.2f", getCocoName(), cost());
    
  • (3)定义装饰类的基类

        用来抽象需要为被装饰类的功能 ,在装饰者模式下,由于是通过动态组合来为对象添加不同的功能,所以每个装饰类最好是实现一个功能。所以这里分别定义两个装饰类的基类:一个实现杯子的规格,另外一个实现配料的种类,代码如下:

public abstract class SizeDecorator implements CocoComponent 
    CocoComponent cocoComponent;

    public SizeDecorator(CocoComponent component) 
        this.cocoComponent = component;
    

    public abstract String addSizeDescription();

    @Override
    public String toString() 
        return String.format("%s\\n原来价格为:%.2f\\n升级%s 价格为:%.2f", getCocoName(), cocoComponent.cost(), addSizeDescription(), cost());
    

public abstract class BatchingDecorator implements CocoComponent 

    CocoComponent cocoComponent;

    public BatchingDecorator(CocoComponent component) 
        this.cocoComponent = component;
    

    /**
     * 添加的配料的描述
     *
     * @return
     */
    public abstract String addBatchingDescription();

    @Override
    public String toString() 
        return String.format("%s\\n原来价格为:%.2f\\n添加%s 价格为:%.2f", getCocoName(), cocoComponent.cost(), addBatchingDescription(), cost());
    


  • (4)定义具体的装饰类

        为抽象的装饰类来实现具体的功能: 

        1)定义BigSizeConcreteDecorator、MiddleSizeConcreteDecorator(代码省略,可从源码中查看 )实现SizeDecorator得到一杯珍珠奶茶升级到大杯和中杯所需要的价格,代码如下:

public class BigSizeConcreteDecorator extends SizeDecorator 

    public BigSizeConcreteDecorator(CocoComponent coco) 
        super(coco);
    

    @Override
    public String addSizeDescription() 
        return "大杯";
    

    @Override
    public String getCocoName() 
        return String.format("%s&%s", cocoComponent.getCocoName(), addSizeDescription());
    

    @Override
    public double cost() 
        return 20 + cocoComponent.cost();
    

        从这代码中可以看到,具体的装饰类本身需要持有一个被装饰者对象,本身并没有对被装饰者对象cocoComponent内容进行修改,而是在cocoComponent基础上进行增强,并且额外增加了addSizeDescription()的功能。

        这里也可以看出和代理模式的一个区别:代理模式的代理类虽然也持有被代理类的对象,并且代理类和被代理类都实现同样的接口,但代理模式仅仅是限制了外部对对象的访问,对对象的调用过程进行预处理和后处理,并没有动态为对象添加功能。

        2)配料的实现类XianyuMilkTeaConcreteDecorator、YeguoMilkTeaConcreteDecorator(代码省略,可从源码中查看 )实现了添加鲜芋、椰果之后的珍珠奶茶的价格,代码如下:

public class XianyuMilkTeaConcreteDecorator extends BatchingDecorator 

    public XianyuMilkTeaConcreteDecorator(CocoComponent component) 
        super(component);
    

    @Override
    public String addBatchingDescription() 
        return "鲜芋";
    

    @Override
    public String getCocoName() 
        return String.format("%s&%s", cocoComponent.getCocoName(), addBatchingDescription());
    

    @Override
    public double cost() 
        return 1 + cocoComponent.cost();
    

        同样也是没有对被装饰类的对象cocoComponent进行修改, 而是在这基础上增加了addBatchingDescription()功能。

  • (5)运行

        由于具体的装饰类中含有被装饰类的对象,可以比较方便计算通过对象组合的方式得到一杯添加了椰果升级成大杯的珍珠奶茶的价格。

public class DecoratorMain 
    public static void main(String[] args) 
        //默认的为小杯珍珠奶茶
        System.out.println("======默认的为小杯珍珠奶茶========");
        CocoComponent cocoComponent = new MilkTeaConcreteComponent();
        System.out.println(cocoComponent.toString());
        //直接为MilkTeaConcreteComponent添加鲜芋
        System.out.println("=======添加鲜芋=======");
        CocoComponent zhenzhu = new XianyuMilkTeaConcreteDecorator(cocoComponent);
        System.out.println(zhenzhu.toString());
        //添加MilkTeaConcreteComponent椰果
        System.out.println("======添加椰果========");
        CocoComponent yeguo = new YeguoMilkTeaConcreteDecorator(cocoComponent);
        System.out.println(yeguo.toString());
        //为YeguoMilkTeaConcreteDecorator升级大杯
        System.out.println("=======升级大杯=======");
        CocoComponent bigYeguo = new BigSizeConcreteDecorator(yeguo);
        System.out.println(bigYeguo.toString());
        //为YeguoMilkTeaConcreteDecorator升级中杯
        System.out.println("=======升级中杯=======");
        CocoComponent middleYeguo = new MiddleSizeConcreteDecorator(yeguo);
        System.out.println(middleYeguo.toString());
    

        运行结果如下:

======默认的为小杯珍珠奶茶========
珍珠奶茶
价格为:13.00
=======添加鲜芋=======
珍珠奶茶&鲜芋
原来价格为:13.00
添加鲜芋 价格为:14.00
======添加椰果========
珍珠奶茶&椰果
原来价格为:13.00
添加椰果 价格为:15.00
=======升级大杯=======
珍珠奶茶&椰果&大杯
原来价格为:15.00
升级大杯 价格为:35.00
=======升级中杯=======
珍珠奶茶&椰果&中杯
原来价格为:15.00
升级中杯 价格为:25.00

         具体代码已经上传到github:地址为https://github.com/wenjing-bonnie/pattern.git的com.android.pattern.decorator下的相关代码。

        上面的是一杯珍珠奶茶被顾客添加配料或者升级杯的规格之后的价格。那如果CoCo在添加一种饮料,只需要定义该饮料的被装饰类即xxxConcreteComponent,实现CocoComponent接口,那么再将该对象依次传入杯子规格或者配料的装饰具体类,就轻而易举的得到具体的价格,有很高的复用率。 

2.Java IO的装饰者模式的体现

(1)Component

        InputStream:抽象类,定义了read()这个基本功能。

public abstract class InputStream implements Closeable 
    public abstract int read() throws IOException;
......

(2)Concrete Component 被装饰类:

        继承InputStream, 实现具体的read()功能,有下面几种常见的被装饰类:

  • 例如FileInputStream:File作为输入流,从File来读取对应的数据。
public class FileInputStream extends InputStream 
    public FileInputStream(File file) throws FileNotFoundException 
    ........
    
    public int read() throws IOException 
        // Android-changed: Read methods delegate to read(byte[], int, int) to share Android logic.
        byte[] b = new byte[1];
        return (read(b, 0, 1) != -1) ? b[0] & 0xff : -1;
    
.......
  • 例如ByteArrayInputStream,以byte[]作为输入流,从byte[]读取对应的数据。
public class ByteArrayInputStream extends InputStream 
    public ByteArrayInputStream(byte buf[]) 
        this.buf = buf;
        this.pos = 0;
        this.count = buf.length;
    
    public synchronized int read() 
        return (pos < count) ? (buf[pos++] & 0xff) : -1;
    
    .......

        后面具体的装饰类就是给这个类的对象动态添加功能。

(3)Decorator

        FilterInputStream:装饰类,所有具体装饰类的基类,继承Component,拥有被装饰类的对象。

public class FilterInputStream extends InputStream 
    protected volatile InputStream in;

    protected FilterInputStream(InputStream in) 
        this.in = in;
    

(4)Concrete Decorator

  • 例如BufferedInputStream

        具体装饰类,继承FilterInputStream。增加了缓存作用的,先通过fill()将数据读入到缓存buf[],然后从缓存中读取内容。

public class BufferedInputStream extends FilterInputStream 
    public BufferedInputStream(InputStream in) 
        .......
    
    public synchronized int read() throws IOException 
        if (this.pos >= this.count) 
            this.fill();
            if (this.pos >= this.count) 
                return -1;
            
        
       return this.getBufIfOpen()[this.pos++] & 255;
    
.......

        最终还是通过传入的具体的被装饰类对象来完成读操作,但是在这基础上增加了缓存功能。

(5)运行

        下面就是一个ConCrete Component (FileInputStream)传入到Concrete Decorator(BufferedInputStream),用来实现一个有缓存的读文件的操作。

    public static void main(String[] args) 
        BufferedInputStream bufferedInputStream = null;
        byte[] buffer = new byte[1024];
        try 
            //TODO 该路径应该是一个具体的文件路径
            String filePath = "";
            System.out.println(filePath);
            //将具体的被装饰类传入到具体的装饰类中
            bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
            bufferedInputStream.read(buffer);
            System.out.println(new String(buffer));
         catch (FileNotFoundException e) 
         catch (IOException e) 
        
    

2. Android源码的装饰者模式

(1)Context

        全局应用环境的抽象类,通过该类可以访问应用资源、启动Activity、Service以及接收广播。一些经常用到的代码简单如下:

public abstract class Context 
    public abstract Resources getResources();
    public abstract PackageManager getPackageManager();
    public abstract Context getApplicationContext();
    public abstract void startActivity(@RequiresPermission Intent intent);
     .......

        该抽象类,装饰者模式的Component,后面的装饰类和被装饰类都要继承该抽象类。 

(2) ContextImpl

        装饰者模式的被装饰类,实现该Component中的具体功能。继承Context,实现里面的抽象方法的功能。

class ContextImpl extends Context 
    @Override
    public AssetManager getAssets() 
        return getResources().getAssets();
    

    @Override
    public Resources getResources() 
        return mResources;
    

    @Override
    public PackageManager getPackageManager() 
        if (mPackageManager != null) 
            return mPackageManager;
        

        final IPackageManager pm = ActivityThread.getPackageManager();
        final IPermissionManager permissionManager = ActivityThread.getPermissionManager();
        if (pm != null && permissionManager != null) 
            // Doesn't matter if we make more than one instance.
            return (mPackageManager = new ApplicationPackageManager(this, pm, permissionManager));
        

        return null;
    
    @Override
    public Context getApplicationContext() 
        return (mPackageInfo != null) ?
                mPackageInfo.getApplication() : mMainThread.getApplication();
    
    @Override
    public void startActivity(Intent intent) 
        warnIfCallingFromSystemProcess();
        startActivity(intent, null);
    
     ........

        实现Context的抽象方法的功能。提供环境相关的各种操作。

(3)ContextWapper

        装饰者模式的装饰类,继承了Component,拥有被装饰类的对象mBase,用来给被装饰类的对象动态增加功能,但是在ContextWrapper基本上就是通过传入的mBase来完成对应功能的调用。

public class ContextWrapper extends Context 
    @UnsupportedAppUsage
    Context mBase;
    @Override
    public Resources getResources() 
        return mBase.getResources();
    

    @Override
    public PackageManager getPackageManager() 
        return mBase.getPackageManager();
    
    @Override
    public void startActivity(Intent intent) 
        mBase.startActivity(intent);
    
.......

(4)具体装饰类ContextThemeWrapper

        该ContextThemeWrapper是一个具体的装饰类,是一个包含主题的具体装饰类,可通过构造函数传入主题。对应具体装饰的是Context中的setTheme()

public class ContextThemeWrapper extends ContextWrapper 
    public ContextThemeWrapper(Context base, @StyleRes int themeResId) 

    
    public ContextThemeWrapper(Context base, Resources.Theme theme) 

    

        Activity是ContextThemeWrapper的子类,继承ContextThemeWrapper。相对于ContextImpl增加了一些在Activity中的事件处理,像dispatchActivityCreated()等

public class Activity extends ContextThemeWrapper

(5)具体的装饰类Application

        具体的装饰类,继承装饰类ContextWrapper。相对于被实现类ContextImpl的基础上,增加了Application的生命周期如onCreate()等方法。

public class Application extends ContextWrapper

(6)具体的装饰类Service

        具体的装饰类,继承装饰类ContextWrapper。相对于被实现类ContextImpl的基础上,增加了Service的生命周期如onCreate()等方法。

public abstract class Service extends ContextWrapper 

三 总结

  • 1.装饰者模式有四部分组成:
    • (1)Component:组件。一个抽象类或者接口,用来制定对象的基本功能。被装饰类和装饰类都需要继承或实现组件。
    • (2)Concrete Component:被装饰类。继承或实现Component。用来实现该对象的具体功能。是最原始的对象,后面的装饰类就是对该对象进行装饰。
    • (3)Decorator:装饰类。继承或实现Component。拥有被装饰类对象,对被装饰类对象进行增加功能,但是不会改变被装饰类的具体内容。
    • (4)Concrete Decorator:具体的装饰类。继承或实现Decorator。实现装饰类为被装饰类对象增加的具体功能或属性。
  • 2.通常为被装饰类增加的每个功能为一个装饰类,方便通过组合对象的方式来决定被装饰类有哪些功能;
  • 3.每个装饰类有一个功能,这样就可以为被装饰类增加很多功能,实现装饰类的复用;
  • 4.代理模式注重的是对被代理对象的控制访问,可以在访问被代理对象前后增加预处理以及后处理逻辑;而装饰者模式注重对被装饰类对象的增加功能,可为被装饰类对象扩展多个具体装饰类对象的功能;
  • 5.与适配器模式的区别后面在去详细理解;
  • 6.Java中的IO操作就是一个装饰者模式的体现:
    • (1)Component:InputStream
    • (2)Concrete Component:FileInputStream、ByteArrayInputStream。分别可以传入File或者byte[]来实现读文件
    • (3)Decorator:FilterInputStream
    • (4)Concrete Decorator:BufferedInputStream:增加了缓存功能的读操作
  • 7.Android中的Context就是一个装饰者模式的体现:
    • (1)Component:Context
    • (2)Concrete Component:ContextImpl。Context功能具体实现
    • (3)Decorator:ContextWrapper。拥有ContextImpl对象,用来扩展不同功能的ContextImpl对象
    • (4)Concrete Decorator:Activity、Application、Service等,都是对ContexImpl的功能扩展。

        继续加油!!!下次看下适配器模式 

以上是关于小白自我提高学习设计模式笔记—装饰者模式的主要内容,如果未能解决你的问题,请参考以下文章

小白自我提高学习设计模式笔记—装饰者模式

小白自我提高学习设计模式笔记—模板模式

小白自我提高学习设计模式笔记—模板模式

小白自我提高学习设计模式笔记—模板模式

小白自我提高学习设计模式笔记—责任链模式

小白自我提高学习设计模式笔记—责任链模式