设计模式之 ==> 装饰器设计模式
一、什么是装饰器设计模式
装饰器模式(Decorator Pattern),是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。 使用装饰器模式的时候需要注意一下几点内容:
- 装饰对象和真实对象有相同的接口。这样客户端对象就可以以和真实对象相同的方式和装饰对象交互。
- 装饰对象包含一个真实对象的引用。
- 装饰对象接受所有的来自客户端的请求,它把这些请求转发给真实的对象。
- 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。然而,装饰者模式,可以在应用程序运行时,动态扩展功能,更加方便、灵活。 适用装饰者模式场。
- 在我们想给一个类扩展多种功能时,可以使用装饰者设计模式进行多层装饰,这样我们可以把具体的扩展功能分开,如过再需要扩展功能时,只需要再增加装饰类即可,只需要在客户端调用时使用就好,减少了代码的耦合性,增加了可扩展性。
二、装饰器设计模式结构图
- Component定义的是一个接口,可以给这些对象动态的添加功能
- ConcreteComponent是一个具体的类,是 Component 接口的实现类,我们主要是为了给这些具体的实现类来扩展一些功能
- Decorator是装饰抽象类,继承 Component 接口,扩展 Component 的功能,但是对于 Component 来说,无需知道 Decorator 的存在
- ConcteteDecorator是具体的装饰类,起到给ConcreteComponent类扩展功能的的作用
上图是装饰器模式的完整结构,但实际运用当中有很多可以变化的地方,如:
- Component 可以是接口也可以是抽象类,甚至是一个普通的父类(这个强烈不推荐,普通的类作为继承体系的超级父类不易于维护)
- 装饰器的抽象父类 Decorator 并不是必须的,这时,装饰器模式和静态代理模式非常的相似,区别就在于装饰器模式在装饰类中将被装饰的类当参数传入,需要装饰哪个类由客户端调用来决定,而静态代理则是在代理类中就已经把被代理的类明确了。
三、装饰器模式写法举例
下面,我们装饰器设计模式来实现一个功能:从数据库查询出一段内容,然后替换两处指定内容
首先,是待装饰的抽象类,相当于 Component
public abstract class AbstractProcessor<T, R> { public abstract RetMsg<R> doProcessor(T t); }
然后是它的一个子类,相当于实现类 ConcreteComponent
public class MockProcessor extends AbstractProcessor<MockRequestDTO, String> { @Override public RetMsg<String> doProcessor(MockRequestDTO mockRequestDTO) { String mockData = getMockDataFromDB(mockRequestDTO).getData(); return RetMsg.buildSuccessMsg(mockData); } private RetMsg<String> getMockDataFromDB(MockRequestDTO mockRequestDTO){ // 从数据库中去查数据 Set<MappingConfDO> mappingConfs = DaoFacade.of().mapping(MappingConfMapper.class, mapper -> mapper.queryByUrlAndHost(mockRequestDTO.getMockUrl(), mockRequestDTO.getRemoutHost())); if (mappingConfs.isEmpty()) { return RetMsg.buildFailedMsg("没有找到匹配数据"); } if (mappingConfs.size() > 1) { return RetMsg.buildFailedMsg("找到的匹配数据太多"); } // 这是找到了我们所要的那个匹配,并将MappingConfDO转化为MappingConfDTO MappingConfDTO mappingConfDTO = mappingConfs.stream().map(MappingConfDO::toMappingConfDTO).findFirst().get(); // 从 response_data 表查出数据,返回的是ResponseDataDO对象 ResponseDataDO responseDataDO = DaoFacade.of().mapping(ResponseDataMapper.class, mapper -> mapper.quaryByMappingId(mappingConfDTO.getMappingId())); return RetMsg.buildSuccessMsg(responseDataDO.getData()); } }
以上 MockProcessor 类的 doProcessor() 方法是从数据库中查询匹配的数据。假设我们查出来的数据的格式:AAA &{date:yyyy-MM-dd HH:mm:ss} BBB &{id:16},我们需要对这段数据中的 date 和 id 后面的字符串根据一定规则进行替换。
接下来抽象装饰父类,相当于 Decorator
public class AbstractDecorator extends AbstractProcessor<MockRequestDTO,String> { protected AbstractProcessor<MockRequestDTO,String> processor; public AbstractDecorator(AbstractProcessor<MockRequestDTO, String> processor) { this.processor = processor; } @Override public RetMsg<String> doProcessor(MockRequestDTO mockRequestDTO) { return processor.doProcessor(mockRequestDTO); } }
再来是具体的装饰类 DateTimeResponseDecorator 和 IdResponseDecorator,分别用于替换 AAA &{date:yyyy-MM-dd HH:mm:ss} BBB &{id:16} 中的 date 和 id 之后的内容。相当于 ConcteteDecorator
public class DateTimeResponseDecorator extends AbstractDecorator { private static Pattern DATETIME_PATTERN = Pattern.compile("&\\{date:(.*?)}"); public DateTimeResponseDecorator(AbstractProcessor<MockRequestDTO, String> processor) { super(processor); } @Override public RetMsg<String> doProcessor(MockRequestDTO mockRequestDTO) { RetMsg<String> retMsg = this.processor.doProcessor(mockRequestDTO); if (retMsg.isSuccess()) { String data = retMsg.getData(); // 替换代码 Matcher matcher = DATETIME_PATTERN.matcher(data); while (matcher.find()) { String format = matcher.group(1); String currentTime = genCurrentTime(format); data = StringUtils.replace(data, genSearchElement(format), currentTime); } retMsg.setData(data); return retMsg; } return super.doProcessor(mockRequestDTO); } private String genSearchElement(String format) { return new Formatter().format("&{date:%s}", format).toString(); } private String genCurrentTime(String format) { return LocalDateTime.now().format(DateTimeFormatter.ofPattern(format)); } }
public class IdResponseDecorator extends AbstractDecorator { private static final Pattern ID_PATTERN = Pattern.compile("&\\{id:(.*?)}"); public IdResponseDecorator(AbstractProcessor<MockRequestDTO, String> processor) { super(processor); } @Override public RetMsg<String> doProcessor(MockRequestDTO mockRequestDTO) { RetMsg<String> retMsg = this.processor.doProcessor(mockRequestDTO); if (retMsg.isSuccess()) { String data = retMsg.getData(); // 替换代码 Matcher matcher = ID_PATTERN.matcher(data); while (matcher.find()) { String format = matcher.group(1); String id = IdUtils.getId(Integer.parseInt(format)); data = StringUtils.replace(data, genSearchElement(format), id); } retMsg.setData(data); return retMsg; } return super.doProcessor(mockRequestDTO); } private String genSearchElement(String format) { return new Formatter().format("&{id:%s}", format).toString(); } }
最后来看一下客户端调用
public final class CoreFacade { private CoreFacade() { // } public static String doMock(MockRequestDTO mockRequestDTO){ // 这部分功能实现使用了装饰器设计模式,一层一层包下去 RetMsg<String> retMsg = new IdResponseDecorator(new DateTimeResponseDecorator(new MockProcessor())).doProcessor(mockRequestDTO); return retMsg.isSuccess() ? retMsg.getData() : retMsg.getErrMsg(); } }
从客户端调用可以看出,装饰器设计模式有多个装饰类的时候,可以一层一层包下去,每一层的功能都是相对独立的,甚至包装的顺序都是可以变化的,我们只需要保证的是最里层的是我们的被装饰类,所以我们可以把客户端的调用写成这样:
RetMsg<String> retMsg = new DateTimeResponseDecorator(new IdResponseDecorator(new MockProcessor())).doProcessor(mockRequestDTO);
最后,我们来看下最终的运行结果:
无论装饰类 DateTimeResponseDecorator 在前还是 IdResponseDecorator 在前,结果都是正常的。