【设计模式】十八.行为型模式之中介者模式

中介者模式

一. 说明

中介者模式主要是用来降低多个类之间的通信复杂度,它通过一个中介者类来处理多个类之间的通信,从而降低类与类之间的耦合度,使代码易于维护。中介者模式是行为型模式的一种。

中介者模式简单来说就是介入类与类之间,由原来的类与类相互引用变为所有类引用中介者,从而让类与类相互解耦。在Spring中Bean与Bean相互引用会存在循环依赖的问题,而使用中介者模式就可以解决这个问题。中介者模式结构图示如下:
【设计模式】十八.行为型模式之中介者模式_第1张图片

二.应用场景

  1. 像聊天室服务器、游戏隧道服务器等都是中介者模式的思想,让服务器充当用户相互连接的媒介。
  2. 使用事件总线分发处理事件也是一种中介者模式的思想,系统将事件发布到事件总线而不用直接与其他组件通信,降低了系统耦合度。
  3. 工作流引擎作为所有流程节点的中介者,负责管理流程节点之间的通信,流程节点之间相互解耦。
  4. 在做支付功能对接聚合支付平台,聚合支付就是一种中介者思想,它聚合各个渠道的支付接口,而我们只用对接使用它的聚合接口即可。

三.代码示例

1. 例子一

先用简单的例子简单的表述一下中介者模式,以聊天室为例,用户加入聊天室,某个用户发送了一条消息,其他所有用户则会接收这条消息。

模型分析:这里聊天室就是中介者,用户发送消息到聊天室,所以用户需要依赖中介者对象

先创建中介者接口,它具有某个用户发送消息的功能和用户加入聊天室的功能

public interface Mediator {
    void sendMessage(String uid, String message);
    void addUser(User... user);
}

再创建聊天室(中介者的实现类)和用户实体,用户实体需要依赖聊天室对象并具有发送消息和接收消息的功能

public class ChatRoom implements Mediator{

    private List<User> users = new ArrayList<>();

    @Override
    public void sendMessage(String uid, String message) {
        //聊天室中,uid发送了消息,那么其他人则接收消息
        users.stream()
                .filter(user -> !user.getUid().equals(uid))
                .forEach(user -> user.receive(message));
    }

    @Override
    public void addUser(User... user) {
        users.addAll(Arrays.asList(user));
    }
}

@Data
public class User {
    private String uid;
    private String name;

    private Mediator mediator;
    public User(String uid, String name, Mediator mediator) {
        this.uid = uid;
        this.name = name;
        this.mediator = mediator;
    }

    public void send(String message) {
        System.out.println(name + "发送消息:" + message);
        mediator.sendMessage(uid, message);
    }

    public void receive(String message){
        System.out.println(name + "接收消息:" + message);
    }
}

编写测试代码

public static void main(String[] args) {
        //创建聊天室(中介者)
        ChatRoom chatRoom = new ChatRoom();

        //创建用户,用依赖中介者
        User user1 = new User("1", "张三", chatRoom);
        User user2 = new User("2", "李四", chatRoom);
        User user3 = new User("3", "王五", chatRoom);

        //用户加入聊天室
        chatRoom.addUser(user1, user2, user3);

        user1.send("我是张三,你们好");
        System.out.println("---------------");
        user2.send("我叫李四");
        System.out.println("---------------");
        user3.send("我是王五");
    }

【设计模式】十八.行为型模式之中介者模式_第2张图片

2. 例子二

以实际开发中实践例子,我们在做游戏中的天梯系统时,就用到了中介者模式。

我们使用SpringBoot开发天梯系统,天梯系统包含赛季信息,用户段位,排行榜等模块,模块之间存在相互调用,最原始的代码是这样的,创建了三个Service:

//天梯赛季信息模块,提供查询赛季信息的方法
@Service
public class LadderSeasonService {
    @Resource
    private LadderRankService ladderRankService;

    /**
     * 查询当前天梯赛季id
     */
    public SeasonVO queryCurrentSeason(){
        SeasonVO seasonVO =  new SeasonVO();
        seasonVO.setSeasonId(1L);
        seasonVO.setSeasonUserCount(ladderRankService.queryRankCount());
        return seasonVO;
    }

}
//天梯用户段位模块,提供单个/批量查询用户段位的方法
@Service
public class LadderLevelService {
    @Resource
    private LadderSeasonService ladderSeasonService;
    @Resource
    private LadderRankService ladderRankService;

    public UserLevelVO queryUserLevel(String uid){
        SeasonVO seasonVO = ladderSeasonService.queryCurrentSeason();
        UserLevelVO levelVO = new UserLevelVO();
        levelVO.setUid(uid);
        levelVO.setSeasonId(seasonVO.getSeasonId());
        levelVO.setLevel(1);
        levelVO.setLevelName("一等兵");
        levelVO.setRank(ladderRankService.queryUserRank(uid));
        return levelVO;
    }

    public List<UserLevelVO> queryUserLevel(List<String> uids){
        SeasonVO seasonVO = ladderSeasonService.queryCurrentSeason();
        int i = uids.size();
        List<UserLevelVO> list = new ArrayList<>();
        for(String uid : uids){
            UserLevelVO levelVO = new UserLevelVO();
            levelVO.setUid(uid);
            levelVO.setSeasonId(seasonVO.getSeasonId());
            levelVO.setLevel(1 + i);
            levelVO.setLevelName(i + "等兵");
            list.add(levelVO);
            i--;
        }
        return list;
    }
}
//天梯排行榜模块,提供查询全站排名和单个用户排名的方法
@Service
public class LadderRankService {
    //模拟数据库查询用户排名
    private static final List<String> DB_LIST = new ArrayList<>();
    static {
        DB_LIST.add("10000");
        DB_LIST.add("10001");
        DB_LIST.add("10002");
        DB_LIST.add("10003");
        DB_LIST.add("10004");
    }

    @Resource
    private LadderLevelService ladderLevelService;
    @Resource
    private LadderSeasonService ladderSeasonService;

    public List<UserLevelVO> queryRank() {
        SeasonVO seasonVO = ladderSeasonService.queryCurrentSeason();
        //模拟查询用户排名
        List<String> uids = queryUidsByRank();
        List<UserLevelVO> list = ladderLevelService.queryUserLevel(uids);
        return list;
    }

    public Integer queryUserRank(String uid){
        return DB_LIST.indexOf(uid) + 1;
    }

    public Integer queryRankCount(){
        return DB_LIST.size();
    }

    private List<String> queryUidsByRank(){
        return DB_LIST;
    }
}

这里我们看到,查询用户段位时又调用了赛季的查询赛季信息方法和排行榜的查询用户排名的方法,查询排行榜时又调用了段位模块批量查询用户段位的方法。即使只有三个模块,模块与模块之间混乱调用,不仅导致spring的循环依赖,还会让代码非常难以维护,虽然我们强调需要在规范上防止这种混乱发生,但那毕竟不是最佳实践。

我们用中介者模式改造上面的代码,新增一个中介类ILadderMediator,它装载天梯的所有子模块,在某个模块开发时需要用到其他模块,直接引用这个中介者即可。

首先声明所有子模块的枚举和所有子模块的公共接口

@Getter
public enum LadderModuleEnum {
    SEASON,
    LEVEL,
    RANK,
}

public interface ILadderModule {
    LadderModuleEnum getModule();
}

其次先新建所有子模块的Service的接口层,让Service实现自己的接口

//赛季子模块接口
public interface IILadderSeasonService extends ILadderModule {
    SeasonVO queryCurrentSeason();
}
//用户段位子模块接口
public interface ILadderLevelService extends ILadderModule{
    UserLevelVO queryUserLevel(String uid);
    List<UserLevelVO> queryUserLevel(List<String> uids);
}
//排行榜子模块接口
public interface ILadderRankService extends ILadderModule{
    List<UserLevelVO> queryRank();
    Integer queryUserRank(String uid);
    Integer queryRankCount();
}

再创建中介者接口和实现类,提供访问子模块的方法

//中介者接口,提供子模块访问入口
public interface ILadderMediator {
    Map<LadderModuleEnum, ILadderModule> moduleMap = new ConcurrentHashMap<>(8);

    //获取赛季子模块
    default IILadderSeasonService getSeasonModule() {
        return getModule(LadderModuleEnum.SEASON);
    }
    //获取段位子模块
    default ILadderLevelService getLevelModule() {
        return getModule(LadderModuleEnum.LEVEL);
    }
    //获取排名子模块
    default ILadderRankService getRankModule() {
        return getModule(LadderModuleEnum.RANK);
    }

    default <T extends ILadderModule> T getModule(LadderModuleEnum module) {
        return (T) moduleMap.get(module);
    }
}

//中介者实现类,获取所有子模块装在到moduleMap
@Component
public class LadderMediator implements ILadderMediator {
    @Resource
    private Set<ILadderModule> moduleSet;

    @PostConstruct
    private void init() {
        moduleSet.forEach(module -> {
            moduleMap.put(module.getModule(), module);
        });
    }
}

最后就是改造Service层的引用逻辑

//天梯赛季信息模块,提供查询赛季信息的方法
@Service
public class LadderSeasonService implements IILadderSeasonService {

    @Resource
    private ILadderMediator ladderMediator;

    @Override
    public LadderModuleEnum getModule() {
        return LadderModuleEnum.SEASON;
    }

    /**
     * 查询当前天梯赛季id
     */
    public SeasonVO queryCurrentSeason(){
        SeasonVO seasonVO =  new SeasonVO();
        seasonVO.setSeasonId(1L);
        seasonVO.setSeasonUserCount(ladderMediator.getRankModule().queryRankCount());
        return seasonVO;
    }
}
//天梯用户段位模块,提供单个/批量查询用户段位的方法
@Service
public class LadderLevelService implements ILadderLevelService{
    @Resource
    private ILadderMediator ladderMediator;
  
    @Override
    public LadderModuleEnum getModule() {
        return LadderModuleEnum.LEVEL;
    }

    public UserLevelVO queryUserLevel(String uid){
        SeasonVO seasonVO = ladderMediator.getSeasonModule().queryCurrentSeason();
        UserLevelVO levelVO = new UserLevelVO();
        levelVO.setUid(uid);
        levelVO.setSeasonId(seasonVO.getSeasonId());
        levelVO.setLevel(1);
        levelVO.setLevelName("一等兵");
        levelVO.setRank(ladderMediator.getRankModule().queryUserRank(uid));
        return levelVO;
    }

    public List<UserLevelVO> queryUserLevel(List<String> uids){
        SeasonVO seasonVO = ladderMediator.getSeasonModule().queryCurrentSeason();
        int i = uids.size();
        List<UserLevelVO> list = new ArrayList<>();
        for(String uid : uids){
            UserLevelVO levelVO = new UserLevelVO();
            levelVO.setUid(uid);
            levelVO.setSeasonId(seasonVO.getSeasonId());
            levelVO.setLevel(1 + i);
            levelVO.setLevelName(i + "等兵");
            list.add(levelVO);
            i--;
        }
        return list;
    }
}
//天梯排行榜模块,提供查询全站排名和单个用户排名的方法
@Service
public class LadderRankService implements ILadderRankService {
    //模拟数据库查询用户排名
    private static final List<String> DB_LIST = new ArrayList<>();
    static {
        DB_LIST.add("10000");
        DB_LIST.add("10001");
        DB_LIST.add("10002");
        DB_LIST.add("10003");
        DB_LIST.add("10004");
    }

    @Resource
    private ILadderMediator ladderMediator;

    @Override
    public LadderModuleEnum getModule() {
        return LadderModuleEnum.RANK;
    }

    public List<UserLevelVO> queryRank() {
        SeasonVO seasonVO = ladderMediator.getSeasonModule().queryCurrentSeason();
        //模拟查询用户排名
        List<String> uids = queryUidsByRank();
        List<UserLevelVO> list = ladderMediator.getLevelModule().queryUserLevel(uids);
        return list;
    }

    public Integer queryUserRank(String uid){
        return DB_LIST.indexOf(uid) + 1;
    }

    public Integer queryRankCount(){
        return DB_LIST.size();
    }

    private List<String> queryUidsByRank(){
        return DB_LIST;
    }
}

这样,所有的子模块调用其他子模块,都是注入中介者ILadderMediator 来获取其他子模块的实例对象,未来扩展更多子模块,只需要扩展ILadderMediator 的获取子模块的方法即可,实现了业务模块之间的完全解耦。

四. 总结

中介者模式满足开闭原则和最少知道原则,类与类的解耦可以使我们非常方便的添加扩展别的类功能。中介者思想在生活中、编程中我们会经常遇到,学会用中介者思想解决我们的实际问题是一个非常好的方法。

你可能感兴趣的:(java设计模式,设计模式,中介者模式,java)