零基础设计模式——行为型模式 - 中介者模式

第四部分:行为型模式 - 中介者模式 (Mediator Pattern)

接下来,我们学习中介者模式。这个模式用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

  • 核心思想:用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

中介者模式 (Mediator Pattern)

"用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。" (Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.)

想象一个机场的控制塔:

  • 飞机 (Colleague Objects):多架飞机在机场附近飞行,准备降落或起飞。
  • 控制塔 (Mediator):控制塔负责协调所有飞机的行动。飞机不直接相互通信来决定谁先降落、谁使用哪个跑道。它们都只与控制塔通信。
  • 通信 (Interaction):飞机向控制塔报告自己的状态和意图(如请求降落),控制塔根据整体情况向飞机发出指令(如允许降落、指定跑道、等待)。

如果没有控制塔,飞机之间需要复杂的直接通信来避免碰撞和混乱,这将形成一个复杂的网状通信结构。控制塔将这种网状结构简化为星型结构,所有通信都通过中心节点(控制塔)进行。

1. 目的 (Intent)

中介者模式的主要目的:

  1. 减少对象间的直接依赖:将对象间复杂的网状依赖关系转变为星型依赖关系。各个同事对象不再直接相互引用,而是都只引用中介者。
  2. 集中控制交互逻辑:对象间的交互逻辑被封装在中介者对象中,使得交互逻辑更易于理解和维护。
  3. 促进松耦合:同事对象之间松散耦合,它们可以独立地变化和复用。
  4. 提高系统的可扩展性:当需要修改或增加交互行为时,通常只需要修改中介者类,或者增加新的中介者类,而不需要修改大量的同事类。

2. 生活中的例子 (Real-world Analogy)

  • 聊天室 (Chat Room)

    • 用户 (Colleague Objects):聊天室中的多个用户。
    • 聊天室服务器 (Mediator):用户发送消息给服务器,服务器再将消息广播给聊天室中的其他用户(或特定用户)。用户之间不直接点对点发送消息。
  • 联合国安理会 (UN Security Council)

    • 国家 (Colleague Objects):各个成员国。
    • 安理会 (Mediator):国家之间通过安理会这个平台进行沟通、协调和决策,而不是所有国家都两两直接谈判所有事务。
  • GUI应用程序中的对话框 (Dialog Box)

    • 各种控件 (Colleague Objects):如按钮、文本框、列表框、复选框等。
    • 对话框本身 (Mediator):对话框负责协调其内部控件之间的交互。例如,当一个列表框的选择改变时,对话框可能会启用或禁用某个按钮,或者更新某个文本框的内容。控件之间不直接相互操作。

3. 结构 (Structure)

中介者模式通常包含以下角色:

  1. Mediator (中介者接口/抽象类):定义一个接口用于与各个同事对象通信。它通常包含一个方法,供同事对象在自身状态改变时通知中介者。
  2. ConcreteMediator (具体中介者):实现 Mediator 接口。它了解并维护所有的具体同事类,并负责协调它们之间的交互。它封装了同事之间的复杂交互逻辑。
  3. Colleague (同事接口/抽象类):定义一个接口,包含一个指向中介者对象的引用。每个同事类都知道其中介者对象。
  4. ConcreteColleague (具体同事类) :实现 Colleague 接口。每个具体同事类只知道自己的行为,当需要与其他同事交互时,它会通知中介者,由中介者去协调。

    工作流程
  • 客户端创建具体中介者对象和所有具体同事对象。
  • 客户端将中介者对象设置给每个同事对象,并将每个同事对象注册到中介者中。
  • 当一个同事对象发生变化或需要与其他同事交互时(例如,用户在GUI中点击了一个按钮 ConcreteColleagueA):
    • 该同事对象会调用其 changed() 方法,通知中介者 (ConcreteMediator)。
    • 中介者接收到通知后,根据预定义的交互逻辑,可能会调用其他同事对象的方法(例如,中介者调用 ConcreteColleagueB.someOperationB())。
  • 同事对象之间不直接通信,所有交互都通过中介者进行。

4. 适用场景 (When to Use)

  • 当一组对象以定义良好但复杂的方式进行通信,导致了对象之间存在大量相互依赖和直接引用时(网状结构)。
  • 当一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象时。
  • 当你想自定义一个分布在多个类中的行为,而又不想生成太多子类时。可以将这些行为提取到中介者中。
  • 在GUI设计中,组件之间的复杂交互(例如,一个组件的状态变化影响其他多个组件)是中介者模式的经典应用场景。

5. 优缺点 (Pros and Cons)

优点:

  1. 减少了类间的依赖:将对象之间复杂的网状通信结构简化为星型结构,降低了同事类之间的耦合度。
  2. 集中控制交互:将对象间的交互行为封装在中介者对象中,使得交互逻辑更易于理解、维护和修改。
  3. 符合迪米特法则:同事类只需要与中介者通信,不需要了解其他同事类。
  4. 提高了同事类的可复用性:由于同事类之间的耦合降低,它们更容易被复用。
  5. 提高了系统的灵活性和可扩展性:修改交互行为通常只需要修改中介者,而不需要修改各个同事类。

缺点:

  1. 中介者可能变得庞大复杂:如果系统中同事对象过多,或者交互逻辑过于复杂,中介者类可能会变得非常庞大,承担过多的责任,难以维护(演变成上帝类 God Object)。
  2. 增加了中介者类:引入了额外的中介者类,增加了系统的类数量。

6. 实现方式 (Implementations)

让我们以一个简单的聊天室为例。用户 (User) 是同事,聊天室 (ChatRoom) 是中介者。

同事接口 (ChatUser - Colleague)
go 复制代码
// chat_user.go (Colleague interface - can be an abstract struct or interface)
package colleague

// Mediator 定义了中介者需要暴露给同事的方法
type Mediator interface {
	SendMessage(message string, sender User)
}

// User 定义了同事需要暴露给中介者的方法,以及同事自身的方法
type User interface {
	GetName() string
	ReceiveMessage(message string, senderName string)
	Send(message string) // 同事通过此方法经由中介者发送消息
	SetMediator(mediator Mediator)
}
java 复制代码
// User.java (Colleague abstract class or interface)
package com.example.colleague;

import com.example.mediator.ChatMediator;

public abstract class User {
    protected ChatMediator mediator;
    protected String name;

    public User(ChatMediator mediator, String name) {
        this.mediator = mediator;
        this.name = name;
    }

    public String getName() {
        return name;
    }

    // 同事发送消息,通过中介者
    public abstract void send(String message);

    // 同事接收消息
    public abstract void receive(String message, String senderName);
}
具体同事 (ConcreteUser - ConcreteColleague)
go 复制代码
// concrete_user.go
package colleague

import "fmt"

// ConcreteUser 实现了 User 接口
type ConcreteUser struct {
	name     string
	mediator Mediator
}

func NewConcreteUser(name string) *ConcreteUser {
	return &ConcreteUser{name: name}
}

func (u *ConcreteUser) SetMediator(mediator Mediator) {
	u.mediator = mediator
}

func (u *ConcreteUser) GetName() string {
	return u.name
}

func (u *ConcreteUser) Send(message string) {
	fmt.Printf("%s sends: %s\n", u.name, message)
	if u.mediator != nil {
		u.mediator.SendMessage(message, u) // 通过中介者发送
	}
}

func (u *ConcreteUser) ReceiveMessage(message string, senderName string) {
	fmt.Printf("%s receives from %s: %s\n", u.name, senderName, message)
}
java 复制代码
// ConcreteUser.java (ConcreteColleague)
package com.example.colleague;

import com.example.mediator.ChatMediator;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

public class ConcreteUser extends User {

    public ConcreteUser(ChatMediator mediator, String name) {
        super(mediator, name);
    }

    @Override
    public void send(String message) {
        String time = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
        System.out.println(time + " [" + this.name + "] sends: " + message);
        mediator.sendMessage(message, this);
    }

    @Override
    public void receive(String message, String senderName) {
        String time = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
        System.out.println(time + " [" + this.name + "] received from [" + senderName + "]: " + message);
    }
}
中介者接口 (ChatMediator - Mediator)
go 复制代码
// chat_mediator.go (Mediator interface - already defined in colleague/chat_user.go for Go)
// For clarity, we can define it in its own package if preferred.
// package mediator
// type ChatMediator interface {
//    SendMessage(message string, sender colleague.User)
//    AddUser(user colleague.User)
// }
java 复制代码
// ChatMediator.java (Mediator interface)
package com.example.mediator;

import com.example.colleague.User;

public interface ChatMediator {
    void sendMessage(String message, User sender);
    void addUser(User user);
}
具体中介者 (ConcreteChatRoom - ConcreteMediator)
go 复制代码
// concrete_chat_room.go
package mediator // Assuming this is in a 'mediator' package

import (
	"../colleague" // Path to colleague package
	"fmt"
)

// ConcreteChatRoom 实现了 colleague.Mediator 接口
type ConcreteChatRoom struct {
	users []colleague.User
}

func NewConcreteChatRoom() *ConcreteChatRoom {
	return &ConcreteChatRoom{
		users: make([]colleague.User, 0),
	}
}

func (cr *ConcreteChatRoom) AddUser(user colleague.User) {
	fmt.Printf("ChatRoom: %s joined.\n", user.GetName())
	cr.users = append(cr.users, user)
	user.SetMediator(cr) // 关键:将中介者设置给同事
}

// SendMessage 是 colleague.Mediator 接口的实现
func (cr *ConcreteChatRoom) SendMessage(message string, sender colleague.User) {
	for _, u := range cr.users {
		// 不把消息发回给发送者自己
		if u.GetName() != sender.GetName() {
			u.ReceiveMessage(message, sender.GetName())
		}
	}
}
java 复制代码
// ConcreteChatMediator.java (ConcreteMediator)
package com.example.mediator;

import com.example.colleague.User;
import java.util.ArrayList;
import java.util.List;

public class ConcreteChatMediator implements ChatMediator {
    private List<User> users;

    public ConcreteChatMediator() {
        this.users = new ArrayList<>();
    }

    @Override
    public void addUser(User user) {
        System.out.println("ChatRoom: " + user.getName() + " joined the chat.");
        this.users.add(user);
        // In Java, the mediator is typically passed to User's constructor,
        // so no need for user.setMediator(this) here if done in constructor.
    }

    @Override
    public void sendMessage(String message, User sender) {
        for (User user : users) {
            // Don't send the message back to the sender
            if (user != sender) {
                user.receive(message, sender.getName());
            }
        }
    }
}
客户端使用
go 复制代码
// main.go (示例用法)
/*
package main

import (
	"./colleague"
	"./mediator"
)

func main() {
	chatRoom := mediator.NewConcreteChatRoom()

	user1 := colleague.NewConcreteUser("Alice")
	user2 := colleague.NewConcreteUser("Bob")
	user3 := colleague.NewConcreteUser("Charlie")

	// 用户加入聊天室,聊天室会自动设置自己为用户的中介者
	chatRoom.AddUser(user1)
	chatRoom.AddUser(user2)
	chatRoom.AddUser(user3)

	fmt.Println("\n--- Chatting Starts ---")
	user1.Send("Hi everyone!")
	// Expected:
	// Alice sends: Hi everyone!
	// Bob receives from Alice: Hi everyone!
	// Charlie receives from Alice: Hi everyone!

	fmt.Println("\n")
	user2.Send("Hello Alice!")
	// Expected:
	// Bob sends: Hello Alice!
	// Alice receives from Bob: Hello Alice!
	// Charlie receives from Bob: Hello Alice!
}
*/
java 复制代码
// Main.java (示例用法)
/*
package com.example;

import com.example.colleague.ConcreteUser;
import com.example.colleague.User;
import com.example.mediator.ChatMediator;
import com.example.mediator.ConcreteChatMediator;

public class Main {
    public static void main(String[] args) {
        ChatMediator chatRoom = new ConcreteChatMediator();

        User user1 = new ConcreteUser(chatRoom, "Alice");
        User user2 = new ConcreteUser(chatRoom, "Bob");
        User user3 = new ConcreteUser(chatRoom, "Charlie");

        chatRoom.addUser(user1);
        chatRoom.addUser(user2);
        chatRoom.addUser(user3);

        System.out.println("\n--- Chatting Starts ---");
        user1.send("Hi everyone!");
        // Expected output will show timestamps and formatted messages

        System.out.println(""); // For spacing
        user2.send("Hello Alice! How are you?");

        System.out.println("");
        user3.send("I'm good, thanks for asking!");
    }
}
*/

7. 总结

中介者模式通过引入一个中心协调对象(中介者),将系统中原本复杂的对象间网状交互结构转变为星型结构。这有效地降低了对象之间的耦合度,使得各个对象(同事)可以独立变化,同时也使得交互逻辑集中在中介者中,更易于管理和维护。虽然它可能导致中介者自身变得复杂,但在处理多对多对象交互的场景下,中介者模式提供了一种优雅且有效的解决方案,尤其在GUI开发和需要解耦多个协作组件的系统中非常有用。

相关推荐
JH30739 分钟前
Java Stream API 在企业开发中的实战心得:高效、优雅的数据处理
java·开发语言·oracle
九月十九2 小时前
java使用aspose读取word里的图片
java·word
一 乐4 小时前
民宿|基于java的民宿推荐系统(源码+数据库+文档)
java·前端·数据库·vue.js·论文·源码
爱记录的小磊4 小时前
java-selenium自动化快速入门
java·selenium·自动化
鹏码纵横4 小时前
已解决:java.lang.ClassNotFoundException: com.mysql.jdbc.Driver 异常的正确解决方法,亲测有效!!!
java·python·mysql
weixin_985432114 小时前
Spring Boot 中的 @ConditionalOnBean 注解详解
java·spring boot·后端
Mr Aokey4 小时前
Java UDP套接字编程:高效实时通信的实战应用与核心类解析
java·java-ee
冬天vs不冷4 小时前
Java分层开发必知:PO、BO、DTO、VO、POJO概念详解
java·开发语言
hong_zc4 小时前
Java 文件操作与IO流
java·文件操作·io 流