public class User {
// 用户 id
private Long uid;
// 用户的部门,为了保持示例简单,这里就用普通的字符串
// 需要远程调用 通讯录系统 获得
private String department;
// 用户的主管,为了保持示例简单,这里就用一个 id 表示
// 需要远程调用 通讯录系统 获得
private Long supervisor;
// 用户所持有的权限
// 需要远程调用 权限系统 获得
private Set<String> permission;
}
这看起来非常棒,“用户“常用的属性全部集中到了一个实体里,只要将这个 User 作为方法的参数,这个方法基本就不再需要查询其他用户信息了。但是一旦实施起来就会发现问题,部门和主管信息需要远程调用通讯录系统获得,权限需要远程调用权限系统获得,每次构造 User 都必须付出这两次远程调用的代价,即使有的信息没有用到。比如下面的方法就展示了这种情况(判断一个用户是否是另一个用户的主管):
public boolean isSupervisor(User u1, User u2) {
return Objects.equals(u1.getSupervisor(), u2.getUid());
}
Supplier<Integer> a = () -> 10 + 1;
int b = a.get() + 1;
三 Supplier 的进一步优化:Lazy
Supplier 还存在一个问题,就是每次通过 get 获取值时都会重新进行计算,真正的惰性计算应该在第一次 get 后把值缓存下来。只要对 Supplier 稍作包装即可:
/**
* 为了方便与标准的 Java 函数式接口交互,Lazy 也实现了 Supplier
*/
public class Lazy<T> implements Supplier<T> {
private final Supplier<? extends T> supplier;
// 利用 value 属性缓存 supplier 计算后的值
private T value;
private Lazy(Supplier<? extends T> supplier) {
this.supplier = supplier;
}
public static <T> Lazy<T> of(Supplier<? extends T> supplier) {
return new Lazy<>(supplier);
}
public T get() {
if (value == null) {
T newValue = supplier.get();
if (newValue == null) {
throw new IllegalStateException("Lazy value can not be null!");
}
value = newValue;
}
return value;
}
}
通过 Lazy 来写之前的惰性计算代码:
Lazy<Integer> a = Lazy.of(() -> 10 + 1);
int b = a.get() + 1;
// get 不会再重新计算, 直接用缓存的值
int c = a.get();
通过这个惰性加载工具类来优化我们之前的通用用户实体:
public class User {
// 用户 id
private Long uid;
// 用户的部门,为了保持示例简单,这里就用普通的字符串
// 需要远程调用 通讯录系统 获得
private Lazy<String> department;
// 用户的主管,为了保持示例简单,这里就用一个 id 表示
// 需要远程调用 通讯录系统 获得
private Lazy<Long> supervisor;
// 用户所含有的权限
// 需要远程调用 权限系统 获得
private Lazy<Set<String>> permission;
public Long getUid() {
return uid;
}
public void setUid(Long uid) {
this.uid = uid;
}
public String getDepartment() {
return department.get();
}
/**
* 因为 department 是一个惰性加载的属性,所以 set 方法必须传入计算函数,而不是具体值
*/
public void setDepartment(Lazy<String> department) {
this.department = department;
}
// ... 后面类似的省略
}
一个简单的构造 User 实体的例子如下:
Long uid = 1L;
User user = new User();
user.setUid(uid);
// departmentService 是一个rpc调用
user.setDepartment(Lazy.of(() -> departmentService.getDepartment(uid)));
// ....
public class Lazy<T> implements Supplier<T> {
private final Supplier<? extends T> supplier;
private T value;
private Lazy(Supplier<? extends T> supplier) {
this.supplier = supplier;
}
public static <T> Lazy<T> of(Supplier<? extends T> supplier) {
return new Lazy<>(supplier);
}
public T get() {
if (value == null) {
T newValue = supplier.get();
if (newValue == null) {
throw new IllegalStateException("Lazy value can not be null!");
}
value = newValue;
}
return value;
}
public <S> Lazy<S> map(Function<? super T, ? extends S> function) {
return Lazy.of(() -> function.apply(get()));
}
public <S> Lazy<S> flatMap(Function<? super T, Lazy<? extends S>> function) {
return Lazy.of(() -> function.apply(get()).get());
}
}
七 构造一个能够自动优化性能的实体
利用 Lazy 我们写一个构造通用 User 实体的工厂:
@Component
public class UserFactory {
// 部门服务, rpc 接口
@Resource
private DepartmentService departmentService;
// 主管服务, rpc 接口
@Resource
private SupervisorService supervisorService;
// 权限服务, rpc 接口
@Resource
private PermissionService permissionService;
public User buildUser(long uid) {
Lazy<String> departmentLazy = Lazy.of(() -> departmentService.getDepartment(uid));
// 通过部门获得主管
// department -> supervisor
Lazy<Long> supervisorLazy = departmentLazy.map(
department -> SupervisorService.getSupervisor(department)
);
// 通过部门和主管获得权限
// department, supervisor -> permission
Lazy<Set<String>> permissionsLazy = departmentLazy.flatMap(department ->
supervisorLazy.map(
supervisor -> permissionService.getPermissions(department, supervisor)
)
);
User user = new User();
user.setUid(uid);
user.setDepartment(departmentLazy);
user.setSupervisor(supervisorLazy);
user.setPermissions(permissionsLazy);
}
}
工厂类就是在构造一颗求值树,通过工厂类可以清晰地看出 User 各个属性间的求值依赖关系,同时 User 对象能够在运行时自动地优化性能,一旦某个节点被求值,路径上的所有属性的值都会被缓存。