(学习极客时间-如何落地业务建模 的笔记, 展示代码皆使用文中例子, 仅做学习记录)
富知识类 User
我们有三个功能模块, 落地在领域建模时, 分为三个上下文.
接着对它们的交集点 User 建模, 得到的代码如下:
public class User {
private long id;
// 社交上下文
private List<Friendship> friends;
private List<Moments> moments;
// 订阅上下文
private List<Subscription> subscriptions;
// 订单上下文
private List<Order> orders;
private List<Payment> payments;
// 社交上下文
public void make(Friendship friend) {
...
}
public void break(Friendship friend) {
...
}
// 订单上下文
public void placeOrder(Column column) {
...
}
// 订阅上下文
public boolean canView(Content content) {
...
}
}
它的问题是, 一个类中包含太多信息, 让维护者难以理解.
角色对象 (Role Object)
在不同的上下文中, User 用户以不同的角色与其他对象发生交互.
角色对象作为实际类, 来承载各自上下文, 具体代码如下:
public class Reader {
private User user;
private List<Subscription> subscriptions;
public Reader(User user) {
...
}
// 订阅上下文
public boolean canView(Content content) {
...
}
}
public class Buyer {
private User user;
private List<Order> orders;
private List<Payment> payments;
public Buyer(User user) {
...
}
// 订单上下文
public void placeOrder(Column column) {
...
}
}
public class Contact {
private User user;
private List<Friendship> friends;
private List<Moments> moments;
public Contact(User user) {
...
}
// 社交上下文
public void make(Friendship friend) {
...
}
public void break(Friendship friend) {
...
}
Repository 对象中, 做具体的角色转换.
public interface UserRepository {
User findById(long id);
Buyer asBuyer(User user);
Reader asReader(User user);
Contact asContact(User user);
}
public class UserRepositoryDB implements UserRepository {
public User findById(long id) {
return db.executeQuery(...)
}
public Buyer asBuyer(User user) {
return new Buyer(user, db.executeQuery(...));
}
public Reader asReader(User user) {
return new Reader(user, db.executeQuery(...));
}
public Contact asContact(User user) {
return new Contact(user, db.executeQuery(...));
}
}
实际调用的代码就变成:
User user = repository.findById(....);
Buyer buyer = repository.asBuyer(user);
Reader reader = repository.asReader(user);
Contact Contact = repository.asContact(user);
上下文对象 (Context Object)
使用角色对象的模式, 虽然做到了将 User 与各个上下文信息解耦, 但还是存在一个问题.
在 UserRepositoryDB 中我们对角色对象的转换, 是依据数据库查询的方式, 找到其角色对应的上下文信息.
也就是说, User 用户的仓储实现, 与角色对象的上下文实例化没有解耦, 如果角色对象的上下文信息来自 RestFul API 亦或是其他途径, 没有办法灵活变通.
解决这类问题的通用手段就是中间层… 亦如角色对象一样, 我们创建出上下文对象, 用来解耦上下文信息.
interface SubscriptionContext {
interface Reader {
boolean canView(Content content);
}
Reader asReader(User user);
}
interface SocialContext {
interface Contact {
void make(Friendship friend);
void break(Friendship friend);
}
Contact asContact(User user);
}
interface OrderContext {
interface Buyer {
void placeOrder(Column column);
}
Buyer asBuyer(User user);
此时, UserRepository 的关联信息就不再是角色对象, 而是上下文对象, 借由上下文对象的具体实现, 实例化角色对象.
interface UserRepository {
User findUserById(long id);
SubscriptionContext inSubscriptionContext();
SocialContext inSocialContext();
OrderContext inOrderContext();
}
public class UserRepositoryDB {
//通过依赖注入获取不同的上下文对象
@Inject private SubscriptionContext subscriptionContext;
@Inject private SocialContext socialContext;
@Inject private OrderContext orderContext;
...
}
最后, 使用方式变为:
UserRepository users = ....;
User user = users.findUserById(...);
Buyer buyer = users.inOrderContext().asBuyer(user);
Reader reader = users.inSubscriptionContext().asReader(user);
Contact contact = users.inSocialContext().asContact(user);
另外, 上下文对象还有个好处, 当我们需要跨上下文协作, 各种其他上下文的依赖可以直接放在上下文对象中, 那么实际的业务代码就能获取到这些上下文对象信息, 参与逻辑.