引言

在经历了一段动态语言(如 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)。如果未来需要改变获取 SessionObjectMap 实例的方式,或者发现这段代码存在缺陷,我们将不得不修改每一个包含该代码段的方法。因此,我们需要找到一种方式来复用这些基础设施代码。

重构方案:接口与泛型

为了传递包含原方法中业务逻辑的实例,我们创建一个带有抽象方法的 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 为新特性,现已成为主流开发版本。