中介模式

81 阅读7分钟

中介

中介是在事物之间传播信息的中间媒介。中介模式(Mediator)为对象构架出一个互动平台,通过减少对象间的依赖程度以达到解耦的目的。我们的生活中有各种各样的媒介,如婚介所、房产中介、门户网站、电子商务、交换机组网、通信基站、即时通软件等,这些都与人类的生活息息相关,离开它们我们将举步维艰。

对媒体来说,虽然它们的作用都一样,但在传递信息的方式上还是有差别的。以传统媒体为例,书刊杂志、报纸、电视、广播等,都是把信息传递给读者,有些是实时的(如电视),有些是延迟的(如报纸),但它们都是以单向的传递方式来传递信息的。而作为新媒体的互联网,不但可以更高效地把信息传递给用户,而且可以反向地获取用户的反馈信息。除此之外,互联网还能作为一个平台,让用户相互进行沟通,这种全终端、多点互通的结构特点更类似于中介模式。

构建交互平台

通过中介我们可以更轻松、高效地完成信息交互。读者可能会提出这样的疑问:如果排除空间的限制,沟通人可以直接进行交互,根本不需要任何第三方的介入。对于面对面的二人沟通,中介显得有些多余。

双方在沟通前必须先建立连接,互相持有对方对象的引用,这样才能知道对方的存在。但如此便造成你中有我、我中有你,谁也离不开谁的状况,双方对象的耦合性太强。虽然在两人沟通的情况下,强耦合也不会造成太大问题,但是倘若我们要进行一场多方讨论的会议,那么在这种沟通模式下,每个参会人就不止是持有沟通对方这么简单了,而是必须持有其他所有人对象的引用列表(如使用ArrayList),以建立每个对象之间的两两连接。我们以对象间的引用关系图来表示这种模式。

对象间这种千丝万缕的耦合关系会带来很大的麻烦,当我们要加入或减少一个参会人时,都要将其同步更新给所有人,每个人发送消息时都要先查找一遍消息接收方,从而产生很多重复工作。我们陷入了一种多对多的对象关联陷阱,这让复杂的对象关系难以维护,所以必须重新考虑更合理的设计模式。

要解决对象间复杂的耦合问题,我们就必须借助第三方平台来把它们拆分开。首先要做的是把每个人持有的重复引用抽离出来,将所有人的引用列表放入一个中介类,这样就可以在同一个地方将它们统一维护起来,对引用的操作只需要进行一次。我们来看引入中介平台后的对象关系图

用户类User

package intermediary;

import java.util.Objects;

public class User {
   private String name;//名字
   private ChatRoom chatRoom;//聊天室

   public User(String name){
      this.name = name;//初始化必须有名字
   }

   public String getName(){
      return this.name;
   }
   public void login(ChatRoom chatRoom){
      //用户登录
      this.chatRoom = chatRoom;//注入聊天室引用
      this.chatRoom.register(this);
   }
   public void logout(){
      //用户注销
      chatRoom.deregister(this);
      this.chatRoom = null;
   }
   public void talk(User to,String msg){//用户发言
      if (Objects.isNull(chatRoom)){
         System.out.println("["+name+"的对话框]你还没有登录");
         return;
      }
      chatRoom.sendMsg(this,to,msg);//给聊天室发消息
   }
   public void listen(User fromWhom,User to,String msg){
      //用户聆听
      System.out.println("{"+this.name+"的对话框}");
      System.out.println(chatRoom.processMsg(fromWhom,to,msg));
   }

   @Override
   public boolean equals(Object obj) {
      if (obj==null||getClass() != obj.getClass())return false;
      User user = (User)obj;
      return Objects.equals(name,user.name);
   }
}

聊天室抽象类ChatRoom

package intermediary;

import java.util.ArrayList;
import java.util.List;

public abstract class ChatRoom {
   protected String name;//聊天室命名
   List<User> users = new ArrayList<>();//加入聊天室的用户们
   public ChatRoom(String name){
      this.name = name; //初始化必须命名聊天室
   }

   public void register(User user){
      this.users.add(user);//用户进入聊天室加入列表
   }
   public void deregister(User user){
      users.remove(user);//用户注销,从列表中删除用户
   }
// public void sendMsg(User fromWhom,String msg){
//    //循环users列表,将消息发送给所有用户
//    users.stream().forEach(toWhom->toWhom.listen(fromWhom,msg));
// }
   protected abstract void sendMsg(User from,User to,String msg);
   protected abstract String processMsg(User from,User to,String msg);
}

公共聊天室类PublicChatRoom

package intermediary;

import java.util.Objects;

public class PublicChatRoom extends ChatRoom{
   public PublicChatRoom(String name) {
      super(name);
   }

   @Override
   public void register(User user) {
      super.register(user);
      System.out.print("系统消息:欢迎【" + user.getName() + "】");
      System.out.println("】加入公共聊天室【" + name + "】,当前人数:" + users.size());
   }

   @Override
   public void deregister(User user) {
      super.deregister(user);
      System.out.print("系统消息:" + user.getName());
      System.out.println("离开公共聊天室,当前人数:" + users.size());
   }

   @Override
   protected void sendMsg(User from, User to, String msg) {
      if (Objects.isNull(to)) {//如果接收者为空,则将消息发送给所有人
         users.forEach(user -> user.listen(from, null, msg));
         return;
      }
      //否则发送消息给特定的人
      users.stream().filter(
            user -> user.equals(to)||user.equals(from)
      ).forEach(
            user -> user.listen(from,to,msg)
      );
   }

   @Override
   protected String processMsg(User from, User to, String msg) {
      String toName = "所有人";
      if (!Objects.isNull(to)) {
         toName = to.getName();
      }
      return from.getName() + "对" + toName + "说: " + msg;
   }
}

私密聊天室类PrivateChatRoom

package intermediary;


public class PrivateChatRoom extends ChatRoom {
   public PrivateChatRoom(String name) {
      super(name);
   }

   @Override
   public synchronized void register(User user) {
      if (users.size()==2){
         System.out.println("系统消息:聊天室已满");
         return;
      }
      super.register(user);
      System.out.println("系统消息:欢迎{"+user.getName()+"}加入两人聊天室【"+name+"】");
   }

   @Override
   public void deregister(User user) {
      super.deregister(user);
   }

   @Override
   protected void sendMsg(User from, User to, String msg) {
      users.forEach(user -> user.listen(from,null,msg));
   }

   @Override
   protected String processMsg(User from, User to, String msg) {
      return from.getName()+"说:"+msg;
   }
}

超级用户类AdminUser

package intermediary;

public class AdminUser extends User {
   public AdminUser(String name) {
      super(name);
   }
   public void kick(User user){
      user.logout();//调用被踢用户的注销方法
   }
}

总结:星形拓扑

中介模式不仅在生活中应用广泛,还大量存在于软硬件架构中,例如微服务架构中的注册发现中心、数据库中的外键关系表,再如网络设备中的路由器等,中介的角色均发挥了使对象解耦的关键作用。不管是对象引用维护还是消息的转发,都由处于中心节点的中介全权负责,最终架构出一套类似于星形拓扑的网络结构,如图所示,极大地简化了各对象间多对多的复杂关联,最终解决了对象间过度耦合、频繁交互的问题,请参看中介模式的类结构

中介模式的各角色定义如下。

Mediator(中介):共事者之间通信的中介平台接口,定义与共事者的通信标准,如连接注册方法与发送消息方法等。对应本章例程中的聊天室类ChatRoom(本例以抽象类的形式定义中介接口)。

ConcreteMediator(中介实现):可以有多种实现,持有所有共事者对象的列表,并实现中介定义的通信方法。对应本章例程中的公共聊天室类PublicChatRoom、私密聊天室类PrivateChatRoom。

Colleague(共事者)、ConcreteColleague(共事实现):共事者可以有多种共事者实现。共事者持有中介对象的引用,以使其在发送消息时可以调用中介,并由它转发给其他共事者对象。对应本章例程中的用户类User。

众所周知,对象间显式的互相引用越多,意味着依赖性越强,同时独立性越弱,不利于代码的维护与扩展。中介模式很好地解决了这些问题,它能将多方互动的工作交由中间平台去完成,解除了你中有我、我中有你的相互依赖,让各个模块之间的关系变得更加松散、独立,最终增强系统的可复用性与可扩展性,同时也使系统运行效率得到提升。

Go实现版本

package intermediary

import (
    "fmt"
    "strconv"
    "sync"
)

type ChatRoom struct {
    name  string
    users []*User
}

func (c *ChatRoom) register(user *User) {
    c.users = append(c.users, user)
}

func (c *ChatRoom) deregister(user *User) {
    var index int = -1
    for i, v := range c.users {
        if v.name == user.name {
            index = i
            break
        }
    }
    if index < 0 {
        fmt.Println("ERROR,没有这个用户")
    }
    //删除一个用户在聊天室的注册
    c.users = append(c.users[:index], c.users[index+1:]...)
}

type Chat interface {
    register(user *User)
    deregister(user *User)
    sendMsg(from, to *User, msg string)
    processMsg(from, to *User, msg string) string
}

type PublicChatRoom struct {
    ChatRoom
}

func NewPublicChatRoom(name string) *PublicChatRoom {
    return &PublicChatRoom{
        ChatRoom: ChatRoom{
            name: name,
        },
    }
}
func (p *PublicChatRoom) register(user *User) {
    p.ChatRoom.register(user)
    fmt.Println("系统消息:欢迎【" + user.name + "】" + "加入公共聊天室【" + p.name + "】,当前人数:" + strconv.Itoa(len(p.users)))
}
func (p *PublicChatRoom) deregister(user *User) {
    p.ChatRoom.deregister(user)
}
func (p *PublicChatRoom) sendMsg(from, to *User, msg string) {
    if to == nil {
        for _, user := range p.users {
            user.Listen(from, nil, msg)
        }
        return
    }
    for _, user := range p.users {
        if user.name == from.name || user.name == to.name {
            user.Listen(from, to, msg)
        }
    }

}

func (p *PublicChatRoom) processMsg(from, to *User, msg string) string {
    toName := "所有人"
    if to != nil {
        toName = to.name
    }
    return from.name + "对" + toName + "说:" + msg
}

type PrivateChatRoom struct {
    ChatRoom
    lock sync.Mutex
}

func NewPrivateChatRoom(name string) *PrivateChatRoom {
    return &PrivateChatRoom{
        ChatRoom: ChatRoom{
            name: name,
        },
    }
}

func (p *PrivateChatRoom) register(user *User) {
    p.lock.Lock()
    defer p.lock.Unlock()
    if len(p.users) == 2 {
        fmt.Println("系统消息:聊天室已满")
        return
    }
    p.ChatRoom.register(user)
    fmt.Println("系统消息:欢迎{" + user.name + "}加入两人聊天室{" + p.name + "}")
}
func (p *PrivateChatRoom) deregister(user *User) {
    p.ChatRoom.deregister(user)
}
func (p *PrivateChatRoom) sendMsg(from, to *User, msg string) {
    for _, v := range p.users {
        v.Listen(from, nil, msg)
    }
}
func (p *PrivateChatRoom) processMsg(from, to *User, msg string) string {
    return from.name + "说:" + msg
}
package intermediary

import "fmt"

type User struct {
    name     string
    chatroom Chat
}

func NewUser(name string) *User {
    return &User{
        name: name,
    }
}

func (u User) GetName() string {
    return u.name
}
func (u *User) Login(chatroom Chat) {
    u.chatroom = chatroom
    u.chatroom.register(u)
}
func (u *User) logout() {
    u.chatroom.deregister(u)
}
func (u *User) Talk(to *User, msg string) {
    if u.chatroom == nil {
        fmt.Println("还没有登录")
        return
    }
    u.chatroom.sendMsg(u, to, msg)
}

func (u *User) Listen(from, to *User, msg string) {
    fmt.Println(u.name + "的对话框")
    fmt.Println(u.chatroom.processMsg(from, to, msg))
}

type AdminUser struct {
    User
}

func NewAdminUser(name string) *AdminUser {
    return &AdminUser{
        User: User{
            name: name,
        },
    }
}
func (a *AdminUser) Kick(user *User) {
    user.logout()
}

命令