Java:让你的代码更整洁:函数式接口
许多开发人员认为,好代码的重要标志之一就是减少重复代码,甚至还给这个原则起了个特殊的名字:不要重复你自己(DRY)。但是当你写Java代码的时候,有时候做到这一点就不容易。许多情况下我们把长方法分解成更小的代码块(方法),然后重用这些代码,但是这样可能让代码更费解,因为你以一个包含许多小方法的类告终,而且他们之间并没有明确的关系。可以进一步地将提取的函数组成一个函数式接口对象,这样就保持结构,但是这种方式需要用到大量的样板代码。
让我们来看看,怎么使用函数式接口对象,来解决常见的代码重复问题。在下面的例子中,saveUser方法用于将user的信息保存到数据库,并且确保user的对象包含有效数据。
public class User {
private String ID = null;
private String name = null;
private String email = null;
private String nickname = null;
...
/*省略getter、setter方法*/
...
}
public class UserDao {
public void saveUser(User user) {
if (user.getID() == null || user.getID().trim().length() == 0) {
throw new IllegalArgumentException(String.format("Can't save user(ID=%s):%s", user.getID(), "empty ID"));
}
if (user.getName() == null || user.getName().trim().length() == 0) {
throw new IllegalArgumentException(String.format("Can't save user(ID=%s):%s", user.getID(), "empty Name"));
}
if (user.getEmail() == null || user.getEmail().trim().length() == 0) {
throw new IllegalArgumentException(String.format("Can't save user(ID=%s):%s", user.getID(), "empty Email"));
}
if (user.getNickname() == null || user.getNickname().trim().length() == 0) {
throw new IllegalArgumentException(String.format("Can't save user(ID=%s):%s", user.getID(), "empty Nickname"));
}
//保存到数据库
...
}
}
这里的重复代码是很少的,你可能不想要在类中的一个面面俱到的方法中,去验证用户字段的每一种特殊情况。但是,如果将验证代码放到函数式接口对象中,可以摆脱重复,并保持清晰的代码结构。可以这样做:
public class UserDao {
public void saveUser(User user) {
BiPredicate<String, String> emptyPredicate = (value, message) -> {
if (value == null || value.trim().length() == 0) {
throw new IllegalArgumentException(String.format("Can't save user(ID=%s):%s", user.getID(), message));
}
return true;
};
emptyPredicate.test(user.getID(),"empty ID");
emptyPredicate.test(user.getName(),"empty Name");
emptyPredicate.test(user.getEmail(),"empty Email");
emptyPredicate.test(user.getNickname(),"empty Nickname");
//保存到数据库中
...
}
}
备注:BiPredicate为Java8的一个函数式接口,其简单源码如下:
public interface BiPredicate<T, U> {
boolean test(T t, U u);
}
这样看起来好多了,不用重复验证逻辑,如果项目演进是需要向User添加其他字段,也可以轻松添加更多验证。不过我们再看看saveUser方法,发现验证逻辑与saveUser方法没有关系。我们再优化这段代码,使其看起来更整洁:
public class UserDao {
public void saveUser(User user) {
UserValicate.validateBeforeSave(user);
//保存到数据库中
...
}
}
将一段代码提取到工具类中,看起来相当有效。即使User属于当前代码库而不是类库的一部分,你也不会希望这段逻辑放到User的方法中,因为它和其他用到User的地方没有关系。如果你能遵循类的API只能包含必须的方法,那么就可以让类保持精炼的同时,也让你的思路更加清晰。