乐趣区

关于设计模式:百度工程师教你玩转设计模式装饰器模式

作者 | 北极星小组

想要写好代码,设计模式(Design Pattern)是必不可少的基本功,设计模式是对面向对象设计(Object Oriented Design)中重复呈现的一类问题的一种解决方案,本篇介绍装璜器模式(Decorator Pattern)。

在咱们日常的开发过程中,一个最常见的场景就是在已有的根底上新增性能,惯例的做法有以下几种:

  • 批改已有的类:违反开闭准则。
  • 减少新的子类:每次都得新增大量对应的类,随着性能的减少,子类越来越收缩。

在此场景下,装璜器模式就能够体现出它的劣势了,它容许在不批改原有对象的前提下,灵便的扩大已有类的性能。上面是装璜器模式的一个通用的类图:

△UML

其中的各个类的作用如下:

  • 形象组件 (Component): 能够是接口或者抽象类,它定义了具体类以及装璜器所领有的办法。
  • 具体组件 (ComponentA, ComponentB): 具体的组件,实现或者继承自形象组件。能够了解成上述场景中已存在的类。
  • 形象装璜器 (Decorator): 通常为抽象类,持有一个被装璜的对象,定义了具体装璜器的办法。此类非必须也能够没有,具体装璜器也可间接继承或者实现形象组件。
  • 具体装璜器 (DecoratorX, DecoratorY): 具体的装璜器,继承自形象装璜器 (也可间接继承自形象组件),扩大了形象组件的某些性能。

上面,将通过 3 个具体的案例的解说装璜器的应用形式,不便大家进一步的了解。

一、装璜器在工作解决场景的利用

在理论的开发中,咱们常常须要定义不同的类来解决各种不同的工作。假如一个这样的场景,咱们的零碎有多个具体的类,用来解决不同类型的工作。当初须要增加一个性能,就是在解决完工作后收回一条音讯。针对这个场景,应用装璜器模式的实现思路如下:

  • 形象组件 (TaskProcessor):解决工作的抽象类 (亦可通过接口实现),定义一个通用的工作解决办法 process()。
  • 具体组件 (TaskProcessorA, TaskProcessorB): 负责实现具体的工作解决逻辑
  • 形象装璜器 (TaskProcessDecorator): 持有一个工作解决对象实例
  • 具体装璜器 (AfterTaskProcessDecorator): 实现具体的工作解决实现后的音讯告诉扩大能力

具体的代码如下:

package com.baidu.demo;
public class Decorator {
    // 形象组件
    static abstract class TaskProcessor {abstract void process();
    }
    // 具体组件
    static class TaskProcessorA extends TaskProcessor {
        @Override
        void process() {System.out.println("TaskProcessorA 解决实现");
        }
    }
    // 具体组件
    static class TaskProcessorB extends TaskProcessor {
        @Override
        void process() {System.out.println("TaskProcessorB 解决实现");
        }
    }
    // 形象装璜器
    static abstract class TaskProcessDecorator extends TaskProcessor {
        protected TaskProcessor processor;
        public TaskProcessDecorator(TaskProcessor processor) {this.processor = processor;}
        abstract void process();}
    // 具体装璜器
    static class AfterTaskProcessDecorator extends TaskProcessDecorator {public AfterTaskProcessDecorator(TaskProcessor processor) {super(processor);
        }

        @Override
        void process() {processor.process();
            afterProcess();}

        void afterProcess() {System.out.println("工作处理完毕,发送音讯...");
        }
    }

    public static void main(String[] args) {
        // 扩大之前
        System.out.println("==========before==========");
        TaskProcessor processorA = new TaskProcessorA();
        processorA.process();
        TaskProcessor processorB = new TaskProcessorB();
        processorB.process();

        // 装璜器扩大之后:TaskProcessorA TaskProcessorB 并未做任何批改,即可实现性能的扩大
        System.out.println("==========after==========");
        TaskProcessor decoratorA = new AfterTaskProcessDecorator(processorA);
        decoratorA.process();
        TaskProcessor decoratorB = new AfterTaskProcessDecorator(processorB);
        decoratorB.process();}
}

// 输入后果如下
==========before==========
TaskProcessorA 解决实现
TaskProcessorB 解决实现
==========after==========
TaskProcessorA 解决实现
工作处理完毕,发送音讯...
TaskProcessorB 解决实现
工作处理完毕,发送音讯...

二、装璜器在文件 IO 场景的利用

装璜器模式,一个典型的利用就是文件 IO 操作,最根底的类实现字节流读取类,应用装璜器模式能够封装文件字节流读取类,而后能够持续封装可缓存的文件字节流读取类,在我的项目中按需应用。具体实现如下:

  • InputStream:具体组件,实现读取字节流。
  • FileInputStream:具体装璜器,作为 InputStream 的子类,扩大文件操作。
  • BufferedInputStream:具体装璜器,作为 FileInputStream 的子类,扩大缓存操作。

具体代码如下:

// 具体组件,实现读取字节流
public abstract class InputStream {public int read(byte b[], int off, int len) {}}

// 具体装璜器,作为 InputStream 的子类,扩大文件操作
public class FileInputStream extends InputStream {
    protected InputStream in;
    
    public FileInputStream(String name) {
        InputStream in = ... // 此处省略,通过文件名创建对象
        this.in = in;
    }
    
    public int read(byte b[], int off, int len) {return this.in.read(b, off, len);
    }
}

// 具体装璜器,作为 FileInputStream 的子类,扩大缓存操作
public class BufferedInputStream extends FileInputStream {
    protected FileInputStream in;
    protected byte[] buffer;
    
    public BufferedInputStream(FileInputStream in) {this.in = in;}
    
    public int read(byte b[], int off, int len) {if (this.buffer == null || this.buffer.length == 0) {this.in.read(this.buffer, 0, in.lenght());
        }
        
        System.arraycopy(this.buffer, off, b, 0, len);
        ...
    }
}

public static void main(String[] args) {FileInputStream fs = new FileInputStream('./test.log');
    BufferedInputStream bs = new BufferedInputStream(fs);
    
    byte[] b;
    bs.read(b, 0, 1);
}

三、装璜器在日志零碎场景的利用

在日志零碎中,个别罕用日志的级别别离为 DEBUG(调试)、INFO(运行信息)、WARN(正告)、ERROR(谬误),一旦产生谬误级别的日志后,则须要触发报警告诉相干人员及时进行跟进,报警形式个别有:邮件、短信、如流等,通常咱们会依据业务场景以组合的形式进行报警告诉,应用装璜器模式则能很好实现组合报警这一性能。

  • 形象组件:Log 接口形象
  • 具体组件:Slf4j 具体日志类的实现
  • 形象装璜器:LogDecorator 日志装璜器的基类
  • 具体装璜器:MailLogDecorator、SMSLogDecorator、InfoFlowLogDecorator 具体装璜类
/**
 * 日志接口
 */
public interface Log {void debug(String message);
    void info(String message);
    void warn(String message);
    void error(String message);
}

/**
 * Slf4j 日志
 */
public class Slf4jLog implements Log {

    // 日志记录对象
    private final Logger log = LoggerFactory.getLogger("system_log");

    @Override
    public void debug(String message) {if (log.isDebugEnabled()) {log.debug(message);
        }
    }

    @Override
    public void info(String message) {if (log.isInfoEnabled()) {log.info(message);
        }
    }

    @Override
    public void warn(String message) {if (log.isWarnEnabled()) {log.warn(message);
        }
    }

    @Override
    public void error(String message) {if (log.isErrorEnabled()) {log.error(message);
        }
    }
}

/**
 * 日志装璜器
 */
public class LogDecorator implements Log {
    protected Log log;

    public LogDecorator(Log log) {this.log = log;}

    @Override
    public void debug(String message) {log.debug(message);
    }

    @Override
    public void info(String message) {log.info(message);
    }

    @Override
    public void warn(String message) {log.warn(message);
    }

    @Override
    public void error(String message) {log.error(message);
    }
}

/**
 * 邮件日志装璜器
 */
public class MailLogDecorator extends LogDecorator {public MailLogDecorator(Log log) {super(log);
    }

    @Override
    public void warn(String message) {log.warn(message);
        mail(message);
    }

    @Override
    public void error(String message) {log.error(message);
        mail(message);
    }
    
    public void mail(String message) {
        // 模仿邮件发送
        log.info("邮件已发送,信息:" + message);
    }
}

/**
 * 短信日志装璜器
 */
public class SMSLogDecorator extends LogDecorator {public SMSLogDecorator(Log log) {super(log);
    }
    
    @Override
    public void error(String message) {log.error(message);
        send(message);
    }

    public void send(String message) {
        // 模仿短信发送
        log.info("短信已发送,信息:" + message);
    }
}

/**
 * 如流日志装璜器
 */
public class InfoflowLogDecorator extends LogDecorator {public InfoflowLogDecorator(Log log) {super(log);
    }

    @Override
    public void warn(String message) {log.warn(message);
        send(message);
    }
    
    @Override
    public void error(String message) {log.error(message);
        send(message);
    }

    
    public void send(String message) {
        // 模仿如流发送
        log.info("如流音讯已发送,信息:" + message);
    }
}

/**
 * 日志测试类
 */
public class LogTest {

    /**
     * 测试日志装璜器
     */
    @Test
    public void testLogDecorator() {Log log = new SMSLogDecorator(new InfoFlowLogDecorator(new MailLogDecorator(new Slf4jLog())));
        log.debug("零碎调试开启");
        log.info("零碎失常运行");
        log.warn("数据为空正告");
        log.error("db 连贯谬误");
    }
}
===========output=========
15:16:56.564 [main] DEBUG system_log - 零碎调试开启
15:16:56.566 [main] INFO  system_log - 零碎失常运行
15:16:56.566 [main] WARN  system_log - 数据为空正告
15:16:56.566 [main] INFO  system_log - 邮件已发送,信息:数据为空正告
15:16:56.566 [main] INFO  system_log - 如流音讯已发送,信息:数据为空正告
15:16:56.566 [main] ERROR system_log - db 连贯谬误
15:16:56.566 [main] INFO  system_log - 邮件已发送,信息:db 连贯谬误
15:16:56.566 [main] INFO  system_log - 如流音讯已发送,信息:db 连贯谬误
15:16:56.566 [main] INFO  system_log - 短信已发送,信息:db 连贯谬误

Process finished with exit code 0

四、总结

如上几个案例,装璜器的最大作用就是在不批改原有类的根底上扩大已有的性能,它合乎开闭准则,而且实现也比拟灵便。

———- END ———-

举荐浏览【技术加油站】系列:

百度工程师教你玩转设计模式(工厂模式)

百度工程师教你玩转设计模式(适配器模式)

百度工程师教你玩转设计模式(单例模式)

退出移动版