一、前言
最近打算在自己的项目中加上一个加密算法工具类,用来调用不同的加密算法,并且希望能够符合开闭原则,本来打算用《大话设计模式》中讲解的策略模式,但是对书中的实例不太满意,但是书中介绍了想要实现一个更好的策略模式需要用到java的反射机制,所以开始学习如何用策略模式实现一种好用的加密算法工具类。
二、解决思路与实现
先来看一下策略模式的模型图:
在上图的策略模式原型中,可以看出来,我们如果想要用ConcreteStrategyA的算法的话就需要在客户端代码中实例化一个ConcreteStrategyA对象并将其注入到Context对象中,代码如下:
public static void main(String[] arg){
Context context = new Context(new ConcreteStrategyA());
context.algorithm();
}
其中Context类代码如下:
public class Context{
private Strategy strategy;
public Context(Strategy strategy){
this.strategy = strategy;
}
public String algorithm(String content){
return strategy.algorithm(content);
}
}
A.策略模式变种 --- 通过反射实现
上面的原型策略模式抛去客户端代码的话会是一个完美的模式,但是恰恰因为客户端代码这一块,我很嫌弃自己每次用的时候都要实例化对象的这种方式,于是我就像能不能换一种方式不用我进行实例化同时代码又能符合开闭原则,于是自然而然的就想到了java的反射机制,改变后的Context类代码如下:
public class Context {
public static String algorithm(Class<? extends Strategy> c, String content) {
Strategy strategy = null;
try {
strategy = c.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return strategy.algorithm(content);
}
}
客户端代码:
public static void main(String[] args) {
String content = Context.algorithm(ConcreteStrategyA.class, "haha");
System.out.println(content);
}
github项目地址:https://github.com/codeHaoHao/strategyTest,项目中test1包中为这个实例代码
B、策略模式变种 -- 加入Map
加入map这种方式相当于加入了一个缓存,把实例化后的对象缓存起来不用再次被实例化,因为我的业务上算法类会被经常调用,因此我将实例化的算法对象缓存起来,避免重新实例化产生过多的瞬时内存垃圾,实现的Context代码如下:
public class Context {
private static Map<String, Strategy> map = new ConcurrentHashMap<String, Strategy>();
public static String algorithm(Class<? extends Strategy> c, String content) {
Strategy strategy = null;
try {
if (map.get(c.getName()) != null) {
System.out.println("from map");
strategy = map.get(c.getName());
} else {
System.out.println("from class.newInstance");
strategy = c.newInstance();
map.put(c.getName(), strategy);
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return strategy.algorithm(content);
}
}
之所以使用ConcurrentHashMap而不使用HashMap是为了避免使用HashMap扩容时会产生的线程不安全问题。
客户端代码:
public static void main(String[] args) {
String content = Context.algorithm(ConcreteStrategyA.class, "haha");
System.out.println(content);
}
github代码:https://github.com/codeHaoHao/strategyTest/tree/master/src/test2
C、策略模式变种 -- 最终版
其实通过上面的两次策略模式变种这个算法工具类Context变得好用了很多,当我创建一个新的算法实现Strategy接口时不再需要向Context类中添加什么东西,符合开闭原则,同时客户端代码也不需要做出改变,只需要传入不同的Class对象即可。
但是我还是嫌弃每次用的使用需要传入一个Class对象,并不是很喜欢这种方式,我希望能让我每次使用的时候传入一个字符串,然后根据不同的字符串执行不同的算法,而且我还希望这个算法工具类能够符合开闭原则,即Context类中需要一个algorithm方法能够根据传入字符串的不同执行不同的算法,同时我新加算法ConcreteStrateC类的时候不需要改变这个Context类中的任何代码,如此便能实现一个完美的策略模式。
思路:
既然只传字符串,该字符串还不能用类常量,因为用了类常量就意味着每次新加一个算法就得重新新加一个类常量,因此想到可以用类名称,只要我能获取到这个类的Class对象,那么我就能获取到类类名称。但是新的问题我怎么获取到这个类的Class对象,我还不能传递Class对象,因此只能另辟蹊径,从这些类的接口开始,因为这些类都实现了一个抽象接口Strategy,那么我只要能获取到这个接口的所有实现就可以了,我写了一个ClassUtils工具类用来获取指定接口的实现,请看文章 java找到指定接口的实现类
只要能根据指定接口找到其实现类,那一切就都简单了,看具体Context实现:
public class Context {
// 存储类实例,key为类名,value为实例
private static Map<String, Strategy> map;
static {
List<Object> strategys = null;
try {
strategys = ClassUtils.getAllObjectByInterface(Strategy.class);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
map = new HashMap<String, Strategy>();
for (int i = 0; i < strategys.size(); i++) {
Strategy strategy = (Strategy) strategys.get(i);
String name = strategy.getClass().getSimpleName(); // 获取类名
map.put(name, strategy);
}
}
public static String algorithm(String className, String content) {
return map.get(className).algorithm(content);
}
}
客户端代码:
public static void main(String[] args) {
String content = Context.algorithm("ConcreteStrategyA", "haha");
System.out.println(content);
}
GitHub代码:https://github.com/codeHaoHao/strategyTest/tree/master/src/test3
从客户端代码可以看出我们不再需要传递一个Class对象了,我们现在只需要传递一个类名就可以了,并且当我们新加一个ConcreteStrategyC类的时候,我们不需要对Context类进行任何修改,完美。
三、总结
经过这次的对策略模式的深入思考与学习,对设计模式有了更深的认识,很多设计模式还有可以优化的地方,需要自己去摸索学习,加油。