作者 | 北极星小组
想要写好代码,规划形式(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 ———-
推荐阅览【技能加油站】系列:
百度工程师教你玩转规划形式(工厂形式)
百度工程师教你玩转规划形式(适配器形式)
百度工程师教你玩转规划形式(单例形式)