中介
中介是在事物之间传播信息的中间媒介。中介模式(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()
}