zoukankan      html  css  js  c++  java
  • 【DDD】业务建模实践 —— 人关注人

      社区业务领域中,存在‘人关注人’的场景,在这个场景中,关系较为复杂,且均表现在‘人’同一个业务实体上,因此,这个case的建模过程值得思考。本文将就‘人关注人’这个业务case的领域建模进行探讨,欢迎拍砖。

    Round-I

      在做‘帖子’模块的建模过程中,遇到了‘查询帖子阅读者是否关注了帖子作者’的case,基于这个case,尝试对‘关注’这个业务领域进行建模。

    业务建模

      就‘人关注人’来讲,可以将人按照角色分为:关注者(FollowingUser,使用进行时标识行为主动发起者)、被关注者(FollowedUser,使用过去式标识行为被动接受者),FollowingUser可以‘关注’(follows)被关注者,FollowedUser可以‘被关注’(followed);FollowingUser持有一个‘被关注者‘集合(followedUsers),FollowedUser持有一个’关注者‘集合(followingUsers)。因此可以梳理出如下实体:FollowingUser、FollowedUser,且他们都应当是‘用户’(UserInfo)实体的子类。

      常见的需求中,人与人之间的关系可能有如下几种:单粉(FollowingUser follows FollowedUser)、互粉(FollowingUser follows FollowedUser, FollowedUser follows FollowingUser )、没有关系(不存在关注关系),通常需要判定制定的User和另外一个User之间的关注关系,所以,我们期望FollowingUser 能够判定自己是否关注过给定的User, 这个行为我们把他称为:hasFollowed(UserInfo);对应的,在FollowedUser 也需要判定自己是否被某个User关注,这个行为我们表示为:hasBeenFollowed(UserInfo)。

      我们来考虑一个比较特殊的场景:帖子详情查询场景,需要判定‘帖子当前阅读者是否关注了帖子作者’。在这个场景中,我们尝试把‘帖子阅读者’(PostReader)当做一个FollowingUser ,PostReader继承FollowingUser ;将‘帖子作者’(PostAuthor)当着一个FollowedUser ,PostAuthor继承FollowedUser 。

      为了完成判定PostReader和PostAuthor的关注关系,我们将PostAuthor作为一个User传入FollowingUser的hasFollowed(UserInfo)中,但是会发现无法识别出互粉的情况,因为,在这个场景中,我们并不认为PostAuthor是一个FollowingUser,它并不持有’被关注者‘集合(followedUsers),所以无法判定出PostAuthor是否关注过PostReader。

      那么我们是不是可以为PostAuthor加上FollowingUser这样一个角色呢? 重新梳理一遍,发现其实PostAuthor和PostReader也是我们给UserInfo的一对角色,一个用户在’帖子详情查询‘场景,不可能同时拥有PostAuthor和FollowingUser的角色。PostAuthor并不需要知道自己关注了那些人,因此为PostAuthor加上FollowingUser的角色并不合适。

      那么是不是可以撇开PostAuthor角色,再单独引入一个FollowingUser呢?按照这个思路,每个FollowingUser都可以将对方作为判定对象使用自己的hasFollowed(UserInfo)完成判定,这样我们为FollowingUser增加了一个行为:判定一个关注者和自己的关系,这个行为我们记为:getFollowRelation(FollowingUser)。

      先不论合理性,先尝试去实现之后再做评估。

    业务模型

    示例代码

    public class UserInfo {
           // 用户ID
        private long userId;
     
        /**
         * 判定给定用户是否是自己
         * @param UserInfo 给定的用户
         * @return true —— 是本人
         *         false —— 不是本人
         */
        public boolean isMyself(UserInfo userInfo) {
            if(userInfo == null) {
                return false;
            }
            if(userInfo.getUserId() == this.getUserId()) {
                return true;
            }
            return false;
        }
     
        ......
     
    }
    UserInfo.java
    public class FollowingUser extends UserInfo {
        /**
         * 我关注的用户 集
         */
        private Set<FollowedUser> followedUsers = new HashSet<FollowedUser>();
         
        public FollowingUser(long userId) {
            super(userId);
        }
         
        /**
         * 关注者 追随 被关注者
         * 如果此人已经关注过指定的用户,则不再重复关注
         * @param followedUserId 被关注者userId
         */
        public void follow(long followedUserId) {
            FollowedUser followedUser = new FollowedUser(followedUserId);
            this.follow(followedUser);
        }
         
        public void follow(FollowedUser followedUser) {
            if(!this.followedUsers.contains(followedUser)) {
                followedUser.followed(this);
                this.followedUsers.add(followedUser);
            }
        }
         
        /**
         * 检查本人是否关注过指定的User
         * @param userInfo 指定的用户
         * @return String
         * 1:自己
         * 2:单粉
         * 4:未关注
         */
        public String hasFollowed(UserInfo userInfo) {
            String followState =  FollowRelationConst.FOLLOW_SIGN_4;
            if(this.isMyself(userInfo)) {
                followState = FollowRelationConst.FOLLOW_SIGN_1;
            } else {
                if(this.followedUsers.contains(userInfo)) {
                    followState = FollowRelationConst.FOLLOW_SIGN_2;
                } else {
                    followState = FollowRelationConst.FOLLOW_SIGN_4;
                }
            }      
            return followState;
        }
         
        /**
         * 检查本人与指定的FollowingUser的关注关系,和hasFollowed不一样的地方在于,该方法可以识别出‘互粉’
         * @param userInfo 指定的用户
         * @return String
         * 1:自己
         * 2:单粉
         * 3:互粉
         * 4:未关注
         */
        public String getFollowRelation(FollowingUser followingUser) {
            String followState =  FollowRelationConst.FOLLOW_SIGN_4;
            if(this.isMyself(followingUser)) {
                followState = FollowRelationConst.FOLLOW_SIGN_1;
            } else {
                if(this.followedUsers.contains(followingUser)) {
                    followState = FollowRelationConst.FOLLOW_SIGN_2;
                    if(FollowRelationConst.FOLLOW_SIGN_2.equals(followingUser.hasFollowed(this))) {
                        followState = FollowRelationConst.FOLLOW_SIGN_3;
                    }
                } else {
                    followState = FollowRelationConst.FOLLOW_SIGN_4;
                }
            }      
            return followState;
        }
        //省略 getter/ setter方法
    }
    FollowingUser.java
    FollowdUser.java
    public class PostReader extends FollowingUser {
        ......
    }
    PostReader.java
    public class PostAuthor extends FollowedUser {
        ......
    }
    PostAuthor.java

       编写应用服务层代码,尝试判定PostReader和PostAuthor之间的关注关系,这是否发现现有的模型无法支持,我们需要新建一个临时的FollowingUser传递给PostReader.getFollowRelation()方法,这里看起来非常别扭,一个关注者(postReader这时是一个FollowingUser)怎么会去和另外一个关注者判定相互之间的关注关系呢?不符合业务场景;我们理不清FollowingUser和PostAuthor之间有什么区别,实际上,他们是标识同一个人,但是却被两个实体所表征,这会造成混乱。

    public BaseOutBean queryPostDetails(BaseInBean<QueryPostDetailsInBean> baseInBean) throws Exception {
            ......
            postReader.follow(followRepository.queryFollowedUser(post.getPostAuthorUserId(), postReader.getUserId()));
            FollowingUser followingUser = followRepository.queryFollowingUser(post.getPostAuthorUserId(), postReader.getUserId()); //临时的followingUser让人困惑
            String followSign = postReader.getFollowRelation(followingUser); //一个关注者判定自己和一个关注者之间的关注关系,这个在业务上讲不清楚的,很是别扭。
            ......
    }
    PostsServiceImpl.java

    Round-II

     业务建模

      鉴于第一次建模尝试中遇到的困扰,分析下来发现:PostAuthor这个人在‘互粉’的场景下持有了双重角色:FollowingUser和FollowedUser,因此导致模型的实现并不符合业务上的理解,哪个诡异的followingUser和postAuthor本身的关系让人不能一下子识别出来。

      既然PostAuthor在‘互粉’场景下即是FollowedUser又是FollowingUser,而FollowedUser和FollowingUser都是UserInfo,也就意味者UserInfo是可以将FollowedUser和FollowingUser的行为包含进去的,因此,我们退一步,在‘人关注人’的场景下,不去区分FollowedUser和FollowingUser,统一称之为UserInfo,并将之前的行为全部赋予UserInfo,这样得到的模型和业务场景完全一致,易于理解。

      因此,FollowedUser就没有存在的必要了,那么FollowingUser还要不要呢? 我们先保留,因为它在后面的‘人关注话题’场景中会有用武之地。

    业务模型

    代码示例

    public class UserInfo {
           // 用户ID
        private long userId;
      
        /**
         * 判定给定用户是否是自己
         * @param UserInfo 给定的用户
         * @return true —— 是本人
         *         false —— 不是本人
         */
        public boolean isMyself(UserInfo userInfo) {
            if(userInfo == null) {
                return false;
            }
            if(userInfo.getUserId() == this.getUserId()) {
                return true;
            }
            return false;
        }
        /**
         * 被关注者 被 关注者 追随
         * @param followingUserId 关注者userId
         */
        public void followed(long followingUserId) {
            UserInfo followingUser = new UserInfo(followingUserId);
            this.followed(followingUser);
            //NOTE:这里不再调用 followingUser.follow(followedUserId)。避免循环依赖。
        }
         
        /**
         * 被关注者 被 关注者 追随
         * 如果已经指定的FollowingUser, 则不必再关注
         * @param FollowingUser 关注者
         */
        public void followed(UserInfo followingUser) {
            if(!followingUsers.contains(followingUser)) {
                this.followingUsers.add(followingUser);
            };     
        }
         
        /**
         * 关注者 追随 被关注者
         * 如果此人已经关注过指定的用户,则不再重复关注
         * @param followedUserId 被关注者userId
         */
        public void follow(long followedUserId) {
            UserInfo followedUser = new UserInfo(followedUserId);
            this.follow(followedUser);
        }
         
        public void follow(UserInfo followedUser) {
            if(!this.followedUsers.contains(followedUser)) {
                followedUser.followed(this);
                this.followedUsers.add(followedUser);
            }
        }
         
        /**
         * 检查本人是否关注过指定的User
         * @param userInfo 指定的用户
         * @return boolean
         *  true —— 已经关注了指定的user
         *  false —— 还未关注指定的user,如果指定用户是自己,则也返回false
         */
        public boolean hasFollowed(UserInfo userInfo) {
            if(this.isMyself(userInfo)) {
                return false;
            } else {
                if(this.followedUsers.contains(userInfo)) {
                    return true;
                }
            }      
            return false;
        }
         
        /**
         * 检查本人与指定的FollowingUser的关注关系,和hasFollowed不一样的地方在于,该方法可以识别出‘互粉’
         * @param userInfo 指定的用户
         * @return String
         * 1:自己
         * 2:单粉
         * 3:互粉
         * 4:未关注
         */
        public String getFollowRelation(UserInfo followingUser) {
            String followState =  FollowRelationConst.FOLLOW_SIGN_4;
            if(this.isMyself(followingUser)) {
                followState = FollowRelationConst.FOLLOW_SIGN_1;
            } else {
                if(this.followedUsers.contains(followingUser)) {
                    followState = FollowRelationConst.FOLLOW_SIGN_2;
                    //NOTE:这里不能调用followingUser.getFollowRelation(this),否则进入死循环
                    if(followingUser.hasFollowed(this)) {
                        followState = FollowRelationConst.FOLLOW_SIGN_3;
                    }
                } else {
                    followState = FollowRelationConst.FOLLOW_SIGN_4;
                }
            }      
            return followState;
        }
      
        ......
      
    }
    UserInfo.java
    /**
     * 粉丝,关注者,具有‘关注者’角色的用户
     * ‘关注者’可以关注话题等,人关注人的逻辑放到了UserInfo中处理。
     * @author LENGFUPING610
     * @CreateDate 2017年9月6日
     *
     */
    public class FollowingUser extends UserInfo {
         
        public FollowingUser(long userId) {
            super(userId);
        }  
    }
    FollowingUser.java
    /**
     * @author LENGFUPING610
     * @CreateDate 2017年8月29日
     * 帖子读者
     * 读者通常也是追随者,通常会去关注作者,或者关注话题
     */
    public class PostReader extends FollowingUser {
        ......
    }
    PostReader.java

    Round-III

    业务建模

      上述两次建模过程没有考虑‘关注’场景的复杂业务规则,现在我们重头梳理下‘关注’场景的业务规则。从需求上看,需要满足如下业务规则:

      • 一个人不能关注自己
      • 不能重复关注同一个人

      为了第一条业务规则,我们模型中的follow行为需要调用UserInfo的isMyself判定是否本人,如果是本人则抛出异常。

      对于第二条业务规则,为了判定出FollowingUser是否已经关注过FollowedUser,理论上我们需要将FollowingUser关注过的FollowedUser都从存储中查询出来,装入到followedUsers,但是如果一个人关注了成千上万个,那么这种做法在性能上是不可取的。退一步我们可以只查询这次判定的两个人之间的关系,这样将结果集限定在1或者o个。同时我们需要在follow(UserInfo followedUser)方法中将此次关注的FollowedUser返回给调用方,这样调用方判定返回值是否为空,从而决定是否做存储操作。FollowedUser的followed行为和FollowingUser.follow()行为类似,不再赘述。

    业务模型

      同‘Round-II’中的业务模型

    示例代码

    public class UserInfo {
        ......
     
        /**
         * 关注者 追随 被关注者
         * 如果此人已经关注过指定的用户,则不再重复关注
         * @param followedUser
         * @return followedUser 被关注者
         * @throws BusinessException
         */
        public UserInfo follow(UserInfo followedUser) throws BusinessException{
            if(followedUser == null) {
                return null;
            }
            if(this.isMyself(followedUser)) {
                throw new BusinessException(MessageConst.CODE_1008);
            }
            if(!this.followedUsers.contains(followedUser)) {
                followedUser.followed(this);
                this.followedUsers.add(followedUser);
                return followedUser;
            }
            return null;
        }
     
        /**
         * 被关注者 被 关注者 追随
         * 如果已经指定的FollowingUser, 则不必再关注
         * @param FollowingUser 关注者
         * @param FollowingUser 关注者
         * @throws BusinessException
         */
        public UserInfo followed(UserInfo followingUser) throws BusinessException {
            if(followingUser == null) {
                return null;
            }
            if(this.isMyself(followingUser)) {
                throw new BusinessException(MessageConst.CODE_1008);
            }
            if(!followingUsers.contains(followingUser)) {
                this.followingUsers.add(followingUser);
                return followingUser;
            };     
            return null;
        }
        ......
    }
    UserInfo.java

    Round-IV

      经过上述三次建模迭代,我们得到了较完善的业务模型,但是不能沾沾自喜,当后续开发进入到‘关注’业务领域中的‘人关注人’和‘取消关注’的场景下时,发现上面的模型捉襟见肘了。

    业务建模

      考虑‘人关注人’的case,我们需要将模型存入到存储介质中,这里的存储介质使用的oracle,在数据模型中,‘人关注人’的关注场景需要包含如下信息项:关注者用户id(following_user_id)、被关注者用户id(followed_user_id)以及可能的其他信息项。

      现有业务模型中,在“关注”场景下,following_user_id是followingUser的userId,对于followed_user_id也能从followingUser.follow(UserInfo followedUser)的返回结果中获取(见上节描述)。那么再考虑一个更深层次的业务需求:“取消关注之后再次关注”,这里涉及到‘取消关注’这个场景的数据建模,对于‘取消关注’可以有两种做法:

      • 取消关注即将该条关注关系硬删除;
      • 取消关注不做硬删除,只是给改天关注关系打上删除标记,但是记录还被保留;

      对于与上述两种‘取消关注’,“取消关注之后再次关注”可以有如下几种做法:

      • 方案一、硬删除的情况下,再次关注直接插入一条新的关注关系;
      • 方案二、软删除的情况下,插入一条新的关注关系,同时保留旧的被标记为‘软删除’的关注关系;
      • 方案三、软删除的情况下,修改旧的被标记为‘软删除’的关注关系为正常的关注关系;

    现在我们来评估下三种方案的利弊:

      • 方案1、优点在于:简洁明了,且符合业务模型,在业务模型中我们可以将‘关注关系’作为一个值对象建模;缺点在于:历史的关注关系会丢失,因为做了硬删除。
      • 方案2、优点在于业务模型层处理简单;缺点在于:数据模型需要考虑软删除标记的影响,比如在删除标记上建立索引时需要做过滤;
      • 方案3、优点在于数据模型层较简单;缺点在于:业务模型需要区分出“第一次关注”和‘取消关注后再次关注’两种场景,同时数据模型丢失了关注的历史信息。

      我们再回过来看下,方案1、3丢失掉的关注的历史信息在方案2中被记录到了“关注关系”数据模型中,那么方案2合理吗? 其实不合理的,一个数据模型承担了两种角色:”关注关系“和”关注历史“。所以我们可以将”关注关系“和”关注历史“分开进行数据建模,那么方案1的缺点就没有了。最终我们得到的最优数据模型为:

         “关注关系” —— t_follow_relation(following_user_id, followed_user_id, create_time, last_update_time)
         “关注历史” —— t_follow_history(following_user_id, followed_user_id, action, create_time, last_update_time)

      回过头来我们再来看看业务模型,上面的业务模型,并没有将“关注关系”单独建模,而是表征在了UserInfo的两个set集合中;实际上,仅仅是表征‘人关注人’的话,业务模型是可以契合上面的最优数据模型的。再往前想一步假如“关注关系”中含有了其他属性呢?比如:关注渠道等,这时候就没法使用UserInfo的两个set属性来表征了。所以我们决定还是对
    “关注关系”(UserFollowRelation)单独建模,让UserInfo持有UserFollowRelation的集合。

    业务模型

     

    示例代码

    /**
     * 关注关系基类
     * @author DAOQIDELV
     *
     */
    public abstract class FollowRelation {
        /**
         * 用户ID
         */
        protected long followingUserId;
    
          // 省略setter/getter
    }
    FollowRelation.java
    public class FollowFactory extends Factory {
    
        public static UserFollowRelation getUserFollowRelationInstance(UserInfo followingUser, UserInfo followedUser) {
    
            if (followingUser == null || followedUser == null) {
                return null;
            }
    
            if (followedUser.getUserId() == followingUser.getUserId()) {
                return null;
            }
    
            return new UserFollowRelation(followingUser.getUserId(), followedUser.getUserId());
        }
    
    }
    FollowFactory.java
    public class UserFollowRelation extends FollowRelation {
        
        /**
         * 用户ID
         */
        private long followingUserId;
    
        /**
         * 关注用户ID
         */
        private long followedUserId;
    
        /**
         * 是否有效:1-有效,0-无效
         */
        private String enabled;
    
        /**
         * 来源
         */
        private String source;
    
        /**
         * 关注类型,0:系统默认:1:自主关注
         */
        private String followType;
    
        
    
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + (int) (followedUserId ^ (followedUserId >>> 32));
            result = prime * result + (int) (followingUserId ^ (followingUserId >>> 32));
            return result;
        }
    
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            UserFollowRelation other = (UserFollowRelation) obj;
            if (followedUserId != other.followedUserId)
                return false;
            if (followingUserId != other.followingUserId)
                return false;
            return true;
        }
    
        //省略setter/getter方法
    }
    UserFollowRelation.java
    public class UserInfo {
    
        // 用户ID
        private long userId;
    
        /**
         * user关注关系集合,关注侧和被关注侧维护的相同的、唯一的关系对象
         */
        private Map<UserFollowRelation, UserFollowRelation> userFollowRelationMap = new HashMap<UserFollowRelation, UserFollowRelation>();
    
        /**
         * 判定给定用户是否是自己
         * 
         * @param UserInfo
         *            给定的用户
         * @return true —— 是本人 false —— 不是本人
         */
        public boolean isMyself(UserInfo userInfo) {
            if (userInfo == null) {
                return false;
            }
            if (userInfo.getUserId() == this.getUserId()) {
                return true;
            }
            return false;
        }
    
        /**
         * 关注者 追随 被关注者 如果此人已经关注过指定的用户,则不再重复关注
         * 
         * @param followedUserId
         *            被关注者userId
         * @return followedUser 被关注者
         * @throws BusinessException
         */
    
        public UserFollowRelation follow(long followedUserId) throws BusinessException {
            UserInfo followedUser = new UserInfo(followedUserId);
            return this.follow(followedUser);
        }
    
        /**
         * 关注者 追随 被关注者 如果此人已经关注过指定的用户,则不再重复关注,单向
         * 
         * @param followedUser
         * @return followedUser 被关注者
         * @throws BusinessException
         */
        public UserFollowRelation follow(UserInfo followedUser) throws BusinessException {
    
            if (followedUser == null) {
                return null;
            }
    
            if (this.isMyself(followedUser)) {
                throw new BusinessException(MessageConst.CODE_1008);
            }
    
            UserFollowRelation followingRelationKeyOrInitial = FollowFactory.getUserFollowRelationInstance(this,
                    followedUser);
    
            // 获取当前对象和需要关注对象的关系 A关注B
            UserFollowRelation followingSide = this.userFollowRelationMap.get(followingRelationKeyOrInitial);
            // 获取当前对象和需要关注对象的关系 B被A关注了
            UserFollowRelation followedSide = followedUser.userFollowRelationMap.get(followingRelationKeyOrInitial);
    
            // 关注侧和被关注侧,只需要做一次判断即可(关系的原子性)
            if (followingSide == null) {
                followingSide = followingRelationKeyOrInitial;
                followingSide.setFollowType(FollowRelationConst.FOLLOW_TYPE_1);
                followingSide.setSource(FollowRelationConst.SOURCE_COMMUNITY);
                followingSide.setalreadyPersistentStatus(false);
                this.userFollowRelationMap.put(followingRelationKeyOrInitial, followingSide);
                // 同一份关系
                followedSide = followingSide;
                followedUser.userFollowRelationMap.put(followingRelationKeyOrInitial, followedSide);
            }else if (followingSide != null)) {
                // 如果有记录并且关注状态为未关注,则更改为关注
                followingSide.setFollowType(followingSide.getFollowType() != null ? followingSide.getFollowType()
                        : FollowRelationConst.FOLLOW_TYPE_1);
                followingSide.setSource(followingSide.getSource() != null ? followingSide.getSource()
                        : FollowRelationConst.SOURCE_COMMUNITY);
                followingSide.setalreadyPersistentStatus(true);
                this.userFollowRelationMap.put(followingRelationKeyOrInitial, followingSide);
    
                followedSide.setFollowType(followedSide.getFollowType() != null ? followedSide.getFollowType()
                        : FollowRelationConst.FOLLOW_TYPE_1);
                followedSide.setSource(
                        followedSide.getSource() != null ? followedSide.getSource() : FollowRelationConst.SOURCE_COMMUNITY);
                followedSide.setalreadyPersistentStatus(true);
                followedUser.userFollowRelationMap.put(followingRelationKeyOrInitial, followedSide);
            } else {
                throw new BusinessException(MessageConst.CODE_1025);
            }
    
            return followingSide;
        }
    
        public void followed(long followingUserId) throws BusinessException {
            UserInfo followedUser = new UserInfo(followingUserId);
            this.followed(followedUser);
        }
    
        public void followed(UserInfo followingUser) throws BusinessException {
    
            if (followingUser == null) {
                return;
            }
    
            if (this.isMyself(followingUser)) {
                throw new BusinessException(MessageConst.CODE_1008);
            }
    
            UserFollowRelation followedRelationKeyOrInitial = FollowFactory.getUserFollowRelationInstance(followingUser,
                    this);
    
            // 获取当前对象和需要关注对象的关系 B被A关注
            UserFollowRelation followedSide = followingUser.userFollowRelationMap.get(followedRelationKeyOrInitial);
            // 获取当前对象和需要关注对象的关系 A关注B
            UserFollowRelation followingSide = this.userFollowRelationMap.get(followedRelationKeyOrInitial);
    
            if (followedSide == null) {
                followedSide = followedRelationKeyOrInitial;
                followedSide.setFollowType(FollowRelationConst.FOLLOW_TYPE_1);
                followedSide.setSource(FollowRelationConst.SOURCE_COMMUNITY);
                this.userFollowRelationMap.put(followedRelationKeyOrInitial, followedSide);
                // 同一份关系
                followingSide = followedSide;
                followingUser.userFollowRelationMap.put(followedRelationKeyOrInitial, followingSide);
            }else if (followedSide != null) {
                // 如果有记录并且关注状态为未关注,则更改为关注
                followedSide.setFollowType(followedSide.getFollowType() != null ? followedSide.getFollowType()
                        : FollowRelationConst.FOLLOW_TYPE_1);
                followedSide.setSource(
                        followedSide.getSource() != null ? followedSide.getSource() : FollowRelationConst.SOURCE_COMMUNITY);
                this.userFollowRelationMap.put(followedRelationKeyOrInitial, followedSide);
    
                followingSide.setFollowType(followingSide.getFollowType() != null ? followingSide.getFollowType()
                        : FollowRelationConst.FOLLOW_TYPE_1);
                followingSide.setSource(followingSide.getSource() != null ? followingSide.getSource()
                        : FollowRelationConst.SOURCE_COMMUNITY);
                followingUser.userFollowRelationMap.put(followedRelationKeyOrInitial, followingSide);
            }
    
        }
    
        /**
         * 取消关注 返回 非null,则需改变数据库enable状态
         * 
         * @param followedUser
         * @return
         * @throws BusinessException
         */
        public UserFollowRelation cancelFollow(UserInfo followedUser) throws BusinessException {
    
            if (followedUser == null) {
                return null;
            }
    
            if (this.isMyself(followedUser)) {
                throw new BusinessException(MessageConst.CODE_1008);
            }
    
            UserFollowRelation cancelRelationKey = FollowFactory.getUserFollowRelationInstance(this, followedUser);
    
            // 1.关注端取消
            UserFollowRelation followingSide = this.userFollowRelationMap.get(cancelRelationKey);
    
            if (followingSide == null) {
                throw new BusinessException(MessageConst.CODE_1023);
            }
            // 2.被关注端取消
            UserFollowRelation followedSide = followedUser.userFollowRelationMap.get(cancelRelationKey);
    
            // 如果不为空,且已经关注,且状态有效
            if (followingSide != null) {
                followingSide.setEnabled(FollowRelationConst.FOLLOW_ENABLED_0);
                followingSide.setSource(FollowRelationConst.SOURCE_COMMUNITY);// 社区场景
                followingSide.setFollowType(FollowRelationConst.FOLLOW_TYPE_1);
                this.userFollowRelationMap.put(followingSide, followingSide);
    
                // 如果不为空,且已经关注,且状态有效
                followedSide.setEnabled(FollowRelationConst.FOLLOW_ENABLED_0);
                followedSide.setSource(FollowRelationConst.SOURCE_COMMUNITY);// 社区场景
                followedSide.setFollowType(FollowRelationConst.FOLLOW_TYPE_1);
                followedUser.userFollowRelationMap.put(followedSide, followedSide);
            } else {
                throw new BusinessException(MessageConst.CODE_1024);
            }
            return followingSide;
        }
    
        /**
         * 检查本人是否关注过指定的User,单向关系
         * 
         * @param userInfo
         *            指定的用户
         * @return boolean true —— 已经关注了指定的user false —— 还未关注指定的user,如果指定用户是自己,则也返回false
         * @throws BusinessException
         */
        public boolean hasFollowed(UserInfo userInfo) {
            if (this.isMyself(userInfo)) {
                return false;
            } else {
                UserFollowRelation FollowingRelation = this.userFollowRelationMap
                        .get(FollowFactory.getUserFollowRelationInstance(this, userInfo));
                if (FollowingRelation != null) {
                    return true;
                }
            }
            return false;
        }
    
        /**
         * 检查本人与指定的FollowingUser的关注关系,和hasFollowed不一样的地方在于,该方法可以识别出‘互粉’,双向关系
         * 
         * @param userInfo
         *            指定的用户
         * @return String 1:自己 2:单粉 3:互粉 4:未关注
         * @throws BusinessException
         */
        public String getFollowRelation(UserInfo followingUser) {
            String followState = FollowRelationConst.FOLLOW_SIGN_4;
            if (this.isMyself(followingUser)) {
                followState = FollowRelationConst.FOLLOW_SIGN_1;
            } else {
                boolean AFollowB = this.hasFollowed(followingUser);
                boolean BFollowA = followingUser.hasFollowed(this);
                if (AFollowB == true && BFollowA == true) {
                    // A关注 B,B也关注A
                    followState = FollowRelationConst.FOLLOW_SIGN_3;
                } else if (AFollowB == true || BFollowA == true) {
                    followState = FollowRelationConst.FOLLOW_SIGN_2;
                } else {
                    followState = FollowRelationConst.FOLLOW_SIGN_4;
                }
            }
            return followState;
        }
        //ignore setter/getter
    }
    UserInfo.java

      UserFollowRelation是一个值对象。

      NOTE:UserInfo持有UserFollowRelation的表现形式为一个Map,且map的key和value均为同一个UserFollowRelation对象,这样做的目的是为了方便在hasFollowed等场景下,快速地根据(followingUserId和followedUserId)查找到一个UserFollowRelation,如果使用Set也可以实现,但是需要遍历整个Set,性能上有损耗。

    Summarize

      从上述建模过程中可以发现,我们最开始从‘查询关注关系’入手建模,得到的简易模型无法满足后续‘人关注人’、‘取消关注’两个场景,导致推翻重来。故,最好从模型的最复杂场景开始建模,而不是最简单场景。

  • 相关阅读:
    [转]WM_COMMAND消息
    [转]DELPHI之关于String的内存分配
    [转]我们永远优雅,但绝不炫耀:合并BPL包图文教程!
    [转]AS400
    [转]Delphi中变体记录及存储方式
    [转]WaitForMultipleObject与MsgWaitForMultipleObjects用法
    [转]delphi中的HWnd,THandle,HDC有什么区别
    [转]Delphi使用FireBird嵌入式版本发布方法
    [转]如何使用Delphi设计强大的服务器程序
    Oracle递归查询
  • 原文地址:https://www.cnblogs.com/daoqidelv/p/7657785.html
Copyright © 2011-2022 走看看