避免代码冗余,使用接口和泛型重构Java代码
引言
在经历了一段动态语言(如 Ruby)和 .NET 的开发工作后,我重新回到了 Java 开发领域。在 Ruby 中,消除代码冗余通常非常便捷,而在 Java 中,我们需要结合接口(Interface)和泛型(Generics)来实现类似的功能,以保持代码的整洁与可维护性。
原始代码与问题
以下是类中用于阐述的几个核心方法。为了使示例更简洁,我已移除了部分无关代码。
public V get(final K key) {
Session s;
try {
s = oGrid.getSession();
ObjectMap map = s.getMap(cacheName);
return (V) map.get(key);
} catch (ObjectGridException oge) {
throw new RuntimeException("Error performing cache operation", oge);
} finally {
if (s != null) {
s.close();
}
}
}
public void put(final K key, final V value) {
Session s;
try {
s = oGrid.getSession();
ObjectMap map = s.getMap(cacheName);
map.upsert(key, value);
} catch (ObjectGridException oge) {
throw new RuntimeException("Error performing cache operation", oge);
} finally {
if (s != null) {
s.close();
}
}
}
public Map<K, V> getAll(Set<? extends K> keys) {
final List<K> keyList = new ArrayList<K>();
keyList.addAll(keys);
final List<V> valueList = new ArrayList<V>();
Session s;
try {
s = oGrid.getSession();
ObjectMap map = s.getMap(cacheName);
valueList.addAll(map.getAll(keyList));
} catch (ObjectGridException oge) {
throw new RuntimeException("Error performing cache operation", oge);
} finally {
if (s != null) {
s.close();
}
}
Map<K, V> map = new HashMap<K, V>();
for (int i = 0; i < keyList.size(); i++) {
map.put(keyList.get(i), valueList.get(i));
}
return map;
}存在的问题
仔细观察可以发现,以下代码段几乎存在于类的每个方法中:
Session s;
try {
s = oGrid.getSession();
ObjectMap map = s.getMap(cacheName);
// 此处放置具体的业务逻辑
} catch (ObjectGridException oge) {
throw new RuntimeException("Error performing cache operation", oge);
} finally {
if (s != null) {
s.close();
}
}这种重复违反了 DRY 原则(Don't Repeat Yourself)。如果未来需要改变获取 Session 和 ObjectMap 实例的方式,或者发现这段代码存在缺陷,我们将不得不修改每一个包含该代码段的方法。因此,我们需要找到一种方式来复用这些基础设施代码。
重构方案:接口与泛型
为了传递包含原方法中业务逻辑的实例,我们创建一个带有抽象方法的 Executable 接口。execute() 方法的参数为我们欲操作的 ObjectMap 实例。
interface Executable<T> {
T execute(ObjectMap map) throws ObjectGridException;
}由于我们的目的仅仅是在每个方法中操作 ObjectMap 实例,可以创建 executeWithMap() 方法来封装前述的那一大段重复代码。这个方法的参数是 Executable 接口的实例,该实例包含着操作 Map 的必要逻辑(这样 Executable 接口的实例中就是纯粹的业务逻辑,实现了解耦合)。
private <T> T executeWithMap(Executable<T> ex) {
Session s;
try {
s = oGrid.getSession();
ObjectMap map = s.getMap(cacheName);
// 执行业务逻辑
return ex.execute(map);
} catch (ObjectGridException oge) {
throw new RuntimeException("Error performing cache operation", oge);
} finally {
if (s != null) {
s.close();
}
}
}现在,可以用如下形式的模板代码替换掉原始例子中的代码。这个模板创建了一个匿名内部类,实现了 Executable 接口和 execute() 方法。其中 execute() 方法执行业务逻辑,并以 getXXX() 的方式返回结果(若为 void 方法,返回 null)。
public V get(final K key) {
return executeWithMap(new Executable<V>() {
public V execute(ObjectMap map) throws ObjectGridException {
return (V) map.get(key);
}
});
}
public void put(final K key, final V value) {
executeWithMap(new Executable<Void>() {
public Void execute(ObjectMap map) throws ObjectGridException {
map.upsert(key, value);
return null;
}
});
}
public Map<K, V> getAll(Set<? extends K> keys) {
final List<K> keyList = new ArrayList<K>();
keyList.addAll(keys);
List<V> valueList = executeWithMap(new Executable<List<V>>() {
public List<V> execute(ObjectMap map) throws ObjectGridException {
return map.getAll(keyList);
}
});
Map<K, V> map = new HashMap<K, V>();
for (int i = 0; i < keyList.size(); i++) {
map.put(keyList.get(i), valueList.get(i));
}
return map;
}进一步优化:Java 8 函数式接口
Java 8 引入的 @FunctionalInterface 注解使这一切变得更加简单。若某接口带有一个抽象方法,这个接口便可以被称为函数式接口(Functional Interface),并可作为 Lambda 表达式的参数。
@FunctionalInterface
interface Executable<T> {
T execute(ObjectMap map) throws ObjectGridException;
}只要接口仅仅包含一个抽象方法,便可以使用这个注解。这样就能减少相当数量的模板代码,利用 Lambda 表达式可以让调用处更加简洁。
public V get(final K key) {
return executeWithMap((ObjectMap map) -> (V) map.get(key));
}
public void put(final K key, final V value) {
executeWithMap((ObjectMap map) -> {
map.upsert(key, value);
return null;
});
}
public Map<K, V> getAll(Set<? extends K> keys) {
final List<K> keyList = new ArrayList<K>();
keyList.addAll(keys);
List<V> valueList = executeWithMap((ObjectMap map) -> map.getAll(keyList));
Map<K, V> map = new HashMap<K, V>();
for (int i = 0; i < keyList.size(); i++) {
map.put(keyList.get(i), valueList.get(i));
}
return map;
}结论
实现这些重构令人满意。虽然它比原始的代码略复杂一点,但是更简明、更符合 DRY 原则,所以一切都是值得的。尽管还有提升的空间,但这是一个良好的开始。
原文链接:michaelbrameld
说明:本文涉及的重构方案基于 Java 泛型与接口机制。其中「Java 8 函数式接口」部分需要 Java 8 或更高版本 支持。原文发表于 2013 年,当时 Java 8 为新特性,现已成为主流开发版本。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。