需求背景
原来有代码中有一个搜索接口,现在要对搜索接口功能进行扩充.例如需要记录搜索的词的搜索次数,还需要增加搜索词日志记录.同时计数功能和日志功能最好是能让使用者自己配置组合配置.
简单实现
下面我使用装饰模式来实现上述需求,代码如下:
/**
* 搜索接口
* Created by zengchao on 2020/7/15.
*/
public interface ISearch {
/**
* 通过关键字搜索
* @param key 关键字
* @return
*/
String search(String key);
}
/**
* 具体的搜索逻辑实现
* Created by zengchao on 2020/7/15.
*/
public class ConcreteSearch implements ISearch{
private static final Map<String,String> CONTENT = new HashMap<>();
static {
CONTENT.put("中国","中国是世界上最大的国家");
CONTENT.put("天气","明天可能是晴天也可能是雨天");
CONTENT.put("JAVA","一种编程语言");
}
@Override
public String search(String key) {
return CONTENT.getOrDefault(key,"未找到相关内容");
}
}
/**
* 装饰类
* Created by zengchao on 2020/7/15.
*/
public class DecoratorSearch implements ISearch {
private ISearch iSearch;
public DecoratorSearch(ISearch iSearch) {
this.iSearch = iSearch;
}
@Override
public String search(String key) {
return iSearch.search(key);
}
}
/**
* 具体装饰类-打印搜索日志
* Created by zengchao on 2020/7/15.
*/
public class LogSearch extends DecoratorSearch {
public LogSearch(ISearch iSearch) {
super(iSearch);
}
@Override
public String search(String key) {
long start = System.currentTimeMillis();
String info = super.search(key);
long end = System.currentTimeMillis();
System.out.println(String.format("log-info:搜索耗时:[%d]ms", end - start));
return info;
}
}
/**
* 具体装饰类-记录搜索词的搜索次数
* Created by zengchao on 2020/7/15.
*/
public class CountSearch extends DecoratorSearch{
private Map<String,Integer> count = new HashMap<>();
public CountSearch(ISearch iSearch) {
super(iSearch);
}
@Override
public String search(String key) {
Integer searchCount = count.getOrDefault(key, 0);
System.out.println(String.format("当前为第[%d]次搜索",searchCount+1));
count.put(key,searchCount+1);
return super.search(key);
}
/**
* 获取当前的搜索次数
* @param key
*/
public Integer getSearchCount(String key){
return count.getOrDefault(key,0);
}
}
客户端调用代码如下:
public class App {
public static void main(String[] args) {
ISearch search = new ConcreteSearch();
ISearch logSearch = new LogSearch(search);
ISearch countSearch = new CountSearch(logSearch);
//第一次搜索 中国
String info = countSearch.search("中国");
System.out.println("info = " + info);
//第二次搜索 中国
countSearch.search("中国");
}
}
最后的打印结果如下:
当前为第[1]次搜索
log-info:搜索耗时:[0]ms
info = 中国是世界上最大的国家
当前为第[2]次搜索
log-info:搜索耗时:[0]ms
分析
上面客户端通过装饰类的组合,对搜索接口进行了各种增强实现了记录搜索次数和日志的功能.同时这个增强是通过组合方式产生的,也就是说你可以很简单的增加或者减少装饰.例如我们现在不需要记录日志了,那么要去掉这个也很简单,只需要将客户端代码稍微修改即可,修改如下:
public class App {
public static void main(String[] args) {
ISearch search = new ConcreteSearch();
ISearch countSearch = new CountSearch(search);
//第一次搜索 中国
String info = countSearch.search("中国");
System.out.println("info = " + info);
//第二次搜索 中国
countSearch.search("中国");
}
}
最后的打印结果如下:
当前为第[1]次搜索
info = 中国是世界上最大的国家
当前为第[2]次搜索
从结果可以看出日志功能被我们去除了,而且我们并没有修改实现类的逻辑,我们只在客户端调用的时候去掉了LogSearch
这个装饰类.
下图是装饰模式的UML图:

这个就是装饰模式的UML,但是实际上可能会因为情况不同而有稍许的减少或者不同.不过总体思想上差别不大.一般在装饰模式中有四个角色分别如下:
-
抽象构件(Component)
:给出一个抽象接口,用来规范准备接收附加责任的对象.在我们的例子中即ISearch
接口. -
具体构件(ConcreteComponent)
:定义一个将要附加责任的类.在我们例子中即ConcreteSearch
即真正实现了搜索功能的类. -
装饰(Decorator)
:持有一个构件Component
对象的实例,并定义一个与抽象构件接口一致的接口.我们的例子中即DecoratorSearch
,重要的点就在于它内部持有ISearch
实例. -
具体装饰(ConcreteDecorator)
:负责给构件加上装饰.在我们的例子中指的就是LogSearch
和CountSearch
,它们分别给构件加上了日志功能和计数功能.
在我们的例子中LogSearch
和CountSearch
提供的功能是单一的,同时它们在对ConcreteSearch
功能做添加的时候并没有与之耦合起来.
透明和半透明
在我们上面的例子中,CountSearch
提供了getSearchCount
方法.
public class App {
public static void main(String[] args) {
ISearch search = new ConcreteSearch();
ISearch logSearch = new LogSearch(search);
CountSearch countSearch = new CountSearch(logSearch);
//第一次搜索 中国
String info = countSearch.search("中国");
System.out.println("info = " + info);
//第二次搜索 中国
countSearch.search("中国");
//获取 中国 的搜索次数
Integer count = countSearch.getSearchCount("中国");
System.out.println(count);
}
}
我们将countSearch
声明为CountSearch
,这样我们就可以调用getSearchCount
方法.在透明性装饰模式中,要求程序不要将对象声明为具体构件(ConcreteSearch)类型或者具体装饰(LogSearch或CountSearch)类型
.因为这样对于客户端来说具体构件对象和具体装饰构件在外部看来是没有区别的,像这种我们就叫他为透明性装饰模式
,而我们示例中的只能称为半透明装饰模式
.
在我们的实际开发中,使用透明性装饰模式的机会还是比较少的,多数的还是使用了半透明装饰模式.
装饰模式在JAVA-I/O库中的应用
看完上面的示例是不是有一种似曾相识的感觉.在我们常用的JAVA-I/O库中大量的应用了装饰设计模式.

根据上图可以看出:
-
抽象构件
:即InputStream. -
具体构件
:即ByteArrayInputStream,FileInputStream等等 -
抽象装饰
:即FilterInputStream -
具体装饰
:即BufferedInputStream等等
优缺点
优点
- 装饰模式和继承都可以用来扩展对象的功能,但是装饰模式相比于继承具有更多的灵活性.
- 通过不同的装饰类排列组合可以实现多种不同的功能.
缺点
- 使用装饰模式会产生比继承更多的类,这些类看上去很相似在出现异常时排除错误比较麻烦.
网友评论