30

java设计模式之装饰器模式

 3 years ago
source link: http://www.cnblogs.com/liu-yi/p/13975429.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

装饰器模式的定义:

装饰器模式也叫作包装器模式,指在不改变原有对象的基础上,动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活,属于结构性设计模式。

装饰器模式提供了比继承更有弹性的替代方案(扩展原有对象的功能)将功能附加到对象上,因此装饰器模式的核心是扩展功能,使用装饰器模式可以透明且动态地扩展类地功能。

装饰器模式地应用场景:

  • 用于扩展一个类地功能,或者给一个类添加职责。
  • 动态地给一个对象添加功能,这些功能可以再动态地撤销。
  • 需要为一批平行的兄弟类进行改装或加装功能。

装饰器模式的UML类图:

YJjEJfz.png!mobile

由上图可以看到,装饰器模式主要包含以下4个角色:

  • 抽象组件(Component):可以是一个接口或者抽象类,充当被装饰类的原始对象,规定了被装饰对象的行为。
  • 具体组件(ConcreteComponent):实现/继承Component的一个具体对象,即被装饰对象。
  • 抽象装饰器(Decorator):通用的装饰concreteComponent的装饰器,其内部必然有一个属性指向Component;其实现一般是一个抽象类,主要是为了让其子类按照其构造形式传入一个Component,这是强制的通用行为。如果系统中装饰逻辑单一,则并不需要实现许多的装饰器,可以直接省略该类,而直接实现一个具体的装饰器即可。
  • 具体装饰器(ConcreteDecorator):Decorator的具体实现类,理论上,每个ConcreteDecorator都扩展了Component对象的一种功能。

装饰器模式的通用写法:


package com.liuyi.designmode.structure.decorator;

import javax.swing.*;

public class Client {
    public static void main(String[] args){
        Component c1 = new ConcreteComponent (); //首先创建需要被装饰的原始对象(即要被装饰的对象)
        Decorator decoratorA = new ConcreteDecoratorA(c1); //给对象透明的增加功能A并调用
        decoratorA .operation();
        Decorator decoratorB = new ConcreteDecoratorB(c1); //给对象透明的增加功能B并调用
        decoratorB .operation();
        Decorator decoratorBandA = new ConcreteDecoratorB(decoratorA);//装饰器也可以装饰具体的装饰对象,此时相当于给对象在增加A的功能基础上在添加功能B
        decoratorBandA.operation();
    }

    //抽象组件
    static interface  Component {
        /**
         * 示例方法
         */
        public void operation();
    }

    //具体组件
    static class ConcreteComponent implements Component {
        public void operation() {
            //相应的功能处理
            System.out.println("处理业务逻辑");
        }
    }

    static abstract class Decorator implements Component {
        /**
         * 持有组件对象
         */
        protected Component component;

        /**
         * 构造方法,传入组件对象
         * @param component 组件对象
         */
        public Decorator(Component component) {
            this.component = component;
        }

        public void operation() {
            //转发请求给组件对象,可以在转发前后执行一些附加动作
            component.operation();
        }
    }

    //具体装饰器A
    static class ConcreteDecoratorA extends Decorator {
        public ConcreteDecoratorA(Component component) {
            super(component);
        }
        private void operationFirst(){ } //在调用父类的operation方法之前需要执行的操作
        private void operationLast(){ } //在调用父类的operation方法之后需要执行的操作
        public void operation() {
            //调用父类的方法,可以在调用前后执行一些附加动作
            operationFirst(); //添加的功能
            super.operation();  //这里可以选择性的调用父类的方法,如果不调用则相当于完全改写了方法,实现了新的功能
            operationLast(); //添加的功能
        }
    }

    //具体装饰器B
    static class ConcreteDecoratorB extends Decorator {
        public ConcreteDecoratorB(Component component) {
            super(component);
        }
        private void operationFirst(){ } //在调用父类的operation方法之前需要执行的操作
        private void operationLast(){ } //在调用父类的operation方法之后需要执行的操作
        public void operation() {
            //调用父类的方法,可以在调用前后执行一些附加动作
            operationFirst(); //添加的功能
            super.operation();  //这里可以选择性的调用父类的方法,如果不调用则相当于完全改写了方法,实现了新的功能
            operationLast(); //添加的功能
        }
    }
}  
 

分析装饰器模式在IO流中的使用:

java中的IO流主要分为字符流和字节流,这里我们以常用的字符流的读(Reader)为例来分析。首先我觉得要理解装饰器模式在IO中的使用,首先我们必须要

明白怎么去区分哪些是装饰类,哪些是被装饰类。我们看上面的例子,发现具体的装饰类ConcreteDecoratorA和ConcreteDecoratorB的构造函数的参数都是顶层接

口的实例对象。我们先通过JDK API文档查看Reader接口有哪些实现类,然后我们根据这个规则去判断哪些实现类是装饰类,哪些实现类是被装饰类。

7RjAV37.png!mobile

我们先来查看BufferedReader的构造方法,发现传入的参数是顶层抽象的实例对象,所以可以判定BufferedReader是一个装饰类,并且它不是抽象的,所以这里直接忽略

了抽象装饰器,直接实现了具体的装饰器。当然Reader也有抽象的装饰器,比如FilterReader,感兴趣的同学可以自己去看API,但是它的具体的装饰器实现很少用,当然

如果需要自定义Reader装饰器,可以去继承这个抽象装饰器实现。

EZ32Mju.png!mobile

我们再来看InputStreamReader的构造方法,发现传入的参数不是顶层抽象的实例对象,所以可以判断InputStreamReader是一个被装饰的类。

ANfYjqA.png!mobile

这里只列举这两个常用的被装饰类和装饰的类,目的只是为了让大家理解装饰器模式在IO流中是怎么实现的,我们来看看具体的使用我们读取下面路径下的

名字为xiaoniu.txt文件里面的内容,然后打印出来,先来看不使用装饰类即用InputStreamReader的情况下怎么使用:

package com.liuyi.designmode.structure.decorator;

import java.io.FileInputStream;
import java.io.FileReader;
import java.io.InputStreamReader;

/**
 * @ClassName IoTest
 * @description:
 * @author:liuyi
 * @Date:2020/11/14 23:01
 */
public class IOTest {
    public static void main(String[] args) throws Exception {
        //读取当前项目下的xiaoniu.txt
        FileInputStream fileInputStream = new FileInputStream("xiaoniu.txt");
        InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
        //设置一次读取1024个字节
        char[] chars = new char[1024];
        int index;
        while ((index=inputStreamReader.read(chars))!=-1){
            System.out.println(String.valueOf(chars));
        }
        fileInputStream.close();
        inputStreamReader.close();
    }
}

先不着急使用装饰类,我们发现使用的时候还要传入一个InputStream的实例对象,通过查看JDK文档,我们发现InputStreamReader还有一个子类FileReader,

看看FileReader怎么使用:

package com.liuyi.designmode.structure.decorator;

import java.io.FileReader;

/**
 * @ClassName IoTest
 * @description:
 * @author:liuyi
 * @Date:2020/11/14 23:01
 */
public class IOTest {
    public static void main(String[] args) throws Exception {
        //读取当前项目下的xiaoniu.txt
        FileReader fileReader = new FileReader("xiaoniu.txt");
        //设置一次读取1024个字节
        char[] chars = new char[1024];
        int index;
        while ((index=fileReader.read(chars))!=-1){
            System.out.println(String.valueOf(chars));
        }
        fileReader.close();
    }
}

FileReader也起到增强的作用,只不过是通过子类的方式实现的,只是这种方式符合开闭原则,如果需要添加功能必须要修改子类,还要在接口添加抽象方法。

我们通常会读取文件里面的内容的时候,需要一行一行的读取,如果只用被装饰类的方法是很难实现的,这个时候我们的装饰器BufferedReader就闪亮登场了。

package com.liuyi.designmode.structure.decorator;

import java.io.BufferedReader;
import java.io.FileReader;

/**
 * @ClassName IoTest
 * @description:
 * @author:liuyi
 * @Date:2020/11/14 23:01
 */
public class IOTest {
    public static void main(String[] args) throws Exception {
        //读取当前项目下的xiaoniu.txt
        FileReader fileReader = new FileReader("xiaoniu.txt");
        BufferedReader bufferedReader = new BufferedReader(fileReader);
        //设置一次读取1024个字节
        String readLine = bufferedReader.readLine();
        System.out.println(readLine);
    }
}

装饰器模式与代理模式的区别:

从代理模式UML类图和通用代码实现来看,代理模式与装饰器模式几乎一模一样。从代码实现来看,代理模式确实与装饰器模式也是一样的,但是这两种设计模式

所面向的功能扩展面是不一样的。

装饰器模式强调自身的功能扩展,着重类功能的变化,比如添加方法。而代理模式强调对代理过程的控制,主要是对已有的方法进行功能增强,比如spring中通过

AOP实现客户端请求日志的记录。

装饰器模式的优点:

  • 装饰器是继承的有力补充,比继承灵活,在不改变原对象的情况下,动态地给一个对象扩展功能,即插即用。
  • 通过使用不同装饰类及这些装饰类的排列组合,可以实现不同效果。
  • 装饰器模式完全遵守开闭原则。

装饰器模式的缺点:

  • 会出现更多的代码、更多的类,增加程序的复杂性。
  • 动态装饰在多层装饰时会更复杂。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK