揭秘中介者模式-如何优雅地管理对象间的沟通

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
可观测可视化 Grafana 版,10个用户账号 1个月
简介: 本文深入探讨了中介者模式在软件设计中的应用。中介者模式,作为一种行为型设计模式,通过引入中介者对象有效管理对象间的复杂交互,降低了系统的耦合度。文章详细分析了该模式的优点,如提高系统的灵活性和可维护性,同时也指出了其面临的挑战和局限,如中介者可能变得庞大难以维护、动态性处理复杂等。在使用中介者模式时,需要权衡利弊,合理设计中介者类,并持续维护系统的可扩展性和可维护性。总之,中介者模式为软件设计提供了一种有效的解耦和协调交互的机制,但需要根据具体场景和需求谨慎选择和应用。通过合理使用中介者模式,可构建更...

💪🏻 制定明确可量化的目标,并且坚持默默的做事。


一、案例场景🔍

image.png

1.1 经典的运用场景

    中介者模式在软件开发中的多种应用场景。以下是中介者模式在分布式系统、微服务架构和聊天室应用等场景中的经典应用及其优势:👇

  1. 分布式系统: 在分布式系统中,各个节点(或服务)之间需要进行通信和协作。中介者模式可以应用于此,作为中央协调器,负责管理和调度各个节点之间的通信。

    降低耦合度:通过中介者,各个节点可以只与中介者通信,而无需了解其他节点的具体实现和位置,从而降低了节点之间的耦合度。
    提高可扩展性:新节点加入系统时,只需与中介者进行交互,无需修改其他节点的代码,提高了系统的可扩展性。
    简化复杂性:中介者可以封装复杂的通信逻辑,使得各个节点只需关注自己的业务逻辑,简化了系统的复杂性。
  • 2. 微服务架:

    在微服务架构中,服务之间需要进行通信和协作。中介者模式可以作为服务网关或服务注册中心的角色出现,负责服务的发现、路由和负载均衡。

    服务解耦:通过中介者(如服务网关),服务之间可以实现解耦,各自独立演化,提高了系统的可维护性。
    统一入口:中介者可以作为系统的统一入口,对外部请求进行身份验证、限流、熔断等处理,提高了系统的安全性。
    动态扩展:通过服务注册中心,服务可以动态地注册和发现其他服务,实现了系统的动态扩展。
  • 3. 聊天室应用:

    在聊天室应用中,用户之间需要进行实时的消息传递。中介者模式可以作为消息服务器的角色出现,负责接收、存储和转发用户之间的消息。

    实时性:通过中介者(消息服务器),用户之间可以实现实时的消息传递,提高了用户的体验。
    解耦用户:用户只需与消息服务器通信,无需了解其他用户的具体实现和位置,降低了用户之间的耦合度。
    支持大规模并发:消息服务器可以设计为支持大规模并发连接和消息传递,满足了聊天室应用的高并发需求。

    下面我们来实现分布式系统场景 📄✏️。

1.2 一坨坨代码实现😻

image.png

    不使用中介者模式的情况下实现分布式系统中的节点通信,可以采用直接通信的方式,即每个节点都保存其他节点的引用或地址,并直接与其他节点进行通信。这种方法虽然简单,但随着节点数量的增加,节点之间的连接数将呈指数级增长,导致系统的复杂性和维护成本增加。
    这里提供一个简化的示例,其中只有两个节点(NodeA 和 NodeB)进行通信。在实际应用中,您可能需要使用更复杂的机制,如消息队列、RPC 框架或分布式事件总线来处理节点之间的通信。以下是简单的 Java 示例:👇

// 定义节点接口  
interface Node {
   
     
    void sendMessage(String message, Node recipient);  
    void receiveMessage(String message);  
}  

// 实现节点A  
class NodeA implements Node {
   
     
    private NodeB nodeB; // 直接持有NodeB的引用  

    public NodeA(NodeB nodeB) {
   
     
        this.nodeB = nodeB;  
    }  

    @Override  
    public void sendMessage(String message, Node recipient) {
   
     
        if (recipient instanceof NodeB) {
   
     
            nodeB.receiveMessage(message); // 直接发送给NodeB  
        } else {
   
     
            System.out.println("Unsupported recipient type");  
        }  
    }  

    @Override  
    public void receiveMessage(String message) {
   
     
        System.out.println("NodeA received: " + message);  
    }  
}  

// 实现节点B  
class NodeB implements Node {
   
     
    @Override  
    public void sendMessage(String message, Node recipient) {
   
     
        // 在这个简化的示例中,NodeB不发送消息给其他节点  
        System.out.println("NodeB is not configured to send messages");  
    }  

    @Override  
    public void receiveMessage(String message) {
   
     
        System.out.println("NodeB received: " + message);  
    }  
}  

// 主程序入口  
public class DistributedSystemExample {
   
     
    public static void main(String[] args) {
   
     
        NodeB nodeB = new NodeB(); // 创建NodeB实例  
        NodeA nodeA = new NodeA(nodeB); // 创建NodeA实例,并将NodeB的引用传递给它  
        nodeA.sendMessage("Hello from NodeA", nodeB); // NodeA发送消息给NodeB  
    }  
}

    在这个示例中,我们定义了一个 Node 接口,其中包含 sendMessage 和 receiveMessage 方法。然后,我们实现了两个节点类 NodeA 和 NodeB,它们分别实现了 Node 接口。NodeA 持有一个对 NodeB 的引用,并可以直接向其发送消息。然而,这个示例非常简单且有限,因为它只支持两个节点之间的单向通信。
    在实际应用中,可能需要考虑使用更复杂的通信机制来处理多个节点之间的双向通信、错误处理、异步通信等问题。此外,随着节点数量的增加,可能需要引入中介者模式或其他设计模式来降低系统的复杂性并提高可维护性。

    虽然上述实现没有使用设计模式,但也体现出了如下优点:👇

  1. 简单性:
    🚀 代码实现非常直接和简单,容易理解。对于初学者或者小型项目来说,这种简单性可能是一个优点,因为它减少了复杂性并使得代码易于维护。
  2. 直接通信:
    🚀 由于 NodeA 直接持有 NodeB 的引用,因此在两个节点之间传输数据时没有任何中间层,这可以减少延迟和额外的网络开销(在分布式系统的上下文中,这通常不是优点,但在某些特定场景,如紧密集成的组件之间,这可能是可接受的)。
  3. 无需额外依赖:
    🚀 实现没有使用任何外部库或框架,这意味着它可以在没有这些依赖项的环境中运行,减少了部署和管理的复杂性。
  4. 明确的依赖关系:
    🚀 NodeA 对 NodeB 的依赖关系是明确的,这有助于在设计和分析系统时理解组件之间的交互。
  5. 适用于演示和教学:
    🚀 作为一个教学示例,它很好地展示了如何在没有使用设计模式的情况下实现基本的节点间通信。

1.3 痛点

image.png

    然而,没有复杂的设计下体现上述优点的同时也伴随着一些潜在的缺点,比如代码的耦合性、通用性和可扩展性可能会受到限制。对于更大或更复杂的项目,可能需要考虑使用设计模式和其他高级技术来改善代码的结构和质量。
    缺点(问题)👇逐一分析:

  1. 紧耦合: 😉 NodeA 直接依赖于 NodeB,这导致两者之间的紧密耦合。如果 NodeB 的实现发生变化,或者需要引入新的节点类型,NodeA 的代码可能也需要相应修改。
  2. 扩展性差: 😉 这个简单的实现不支持多于两个节点的系统。每增加一个节点,都需要在现有的节点类中增加对新节点的引用和处理逻辑,这会导致代码迅速变得复杂且难以维护。
  3. 单向通信: 😉 在这个实现中,只有 NodeA 能够发送消息给 NodeB,而 NodeB 没有办法回应或者发送消息给其他节点。这在真实的分布式系统中是不够用的。
  4. 缺乏通用性: 😉 sendMessage 方法中的类型检查限制了只能发送消息给特定类型的节点。这种方法不够灵活,也不支持动态地添加或移除节点。
  5. 缺乏错误处理: 😉 在发送消息时,没有考虑到可能的错误情况,比如网络故障、接收节点不可用等。
  6. 同步阻塞调用: 😉 sendMessage 方法是同步的,这意味着发送节点在消息被接收之前会一直等待。在实际的分布式系统中,通常更倾向于使用异步通信来避免阻塞。
  7. 单点故障: 😉 由于 NodeA 直接依赖于 NodeB,如果 NodeB 出现故障,NodeA 将无法正常工作。

    当考虑到分布式系统的复杂性和可扩展性需求时。以下是一些被违反的设计原则(问题)👇逐一分析:

  1. 开闭原则(Open/Closed Principle):
    💪 软件实体(类、模块、函数等)应当对扩展开放,对修改关闭。在上述实现中,如果我们需要增加新的节点类型或者改变节点之间的通信方式,我们可能需要修改现有的 NodeA 和 NodeB 类的代码,这违背了开闭原则。
  2. 依赖倒置原则(Dependency Inversion Principle):
    💪 高层模块不应该依赖于低层模块,它们都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。在上述实现中,NodeA 直接依赖于 NodeB 的具体实现,而不是依赖于一个更抽象的接口或基类,这违背了依赖倒置原则。
  3. 单一职责原则(Single Responsibility Principle):
    💪 一个类应该只有一个引起变化的原因。在上述实现中,NodeA 既负责自己的业务逻辑,又负责与 NodeB 的通信逻辑,这可能使得 NodeA 的职责过于复杂,违背了单一职责原则。
  4. 里氏替换原则(Liskov Substitution Principle):
    💪 在软件系统中,一个可以接受基类对象的地方,必然可以接受一个子类对象,而不会出现任何异常。虽然这个原则在上述实现中没有直接被违背(因为没有涉及到继承),但是如果我们尝试将 NodeA 和 NodeB 抽象为一个基类,并创建子类来扩展它们,可能会遇到问题,因为子类可能需要覆盖或修改基类的方法以实现特定的通信逻辑。
  5. 接口隔离原则(Interface Segregation Principle):
    💪 客户端不应该依赖它不需要的接口;一个类对另一个类的依赖性应当是最小的。在上述实现中,没有明确地定义接口,但是如果我们尝试引入接口来抽象节点的行为,可能会发现接口包含了太多不必要的方法,或者不同的节点类型需要实现不同的接口,这违背了接口隔离原则。
  6. 迪米特法则(Law of Demeter)或最少知识原则(Least Knowledge Principle):
    💪 一个对象应该对其他对象保持最少的了解。在上述实现中,NodeA 直接与 NodeB 交互,并且了解 NodeB 的具体实现细节(例如,它知道消息是发送给 NodeB 的),这违背了迪米特法则。

    二、解决方案🚀

    image.png

    为了解决上述代码中的缺点,我们可以使用中介者模式来重构代码。中介者模式引入了一个中介者对象,用于封装节点之间的通信逻辑,从而降低节点之间的耦合度并提高系统的可扩展性。

2.1 定义

简化对象间交互,通过中央协调者管理通信,降低耦合度。

2.2 案例分析🧐

image.png

2.3 中介者模式结构图及说明

image.png

  1. Mediator:
    抽象中介者定义了一个接口,用于同事对象之间进行通信。它通常声明了一些方法,这些方法由具体中介者实现,用于接收和发送消息。

    在下面👇的重构代码中,并没有直接定义一个抽象的Mediator接口,而是直接实现了一个具体的中介者ConcreteMediator。但在更一般的实现中,可能会有一个抽象的Mediator接口,用于定义中介者的基本行为。
  1. ConcreteMediator:
    具体中介者是抽象中介者的子类,通过协调各个同事对象来实现协作行为。它维持了对各个同事对象的引用,并在需要时将消息从一个同事对象转发给另一个。

    在下面👇的重构代码中,ConcreteMediator类扮演了这个角色。它实现了消息的分发逻辑,通过sendMessage方法将消息从发送者传递给其他接收者。

  2. Colleague:
    抽象同事类定义了各个同事对象共有的方法,并声明了一些抽象方法供子类实现。它通常维持了一个对抽象中介者的引用,以便子类可以通过这个引用来与中介者通信。

    在下面👇的重构代码中,并没有明确定义一个抽象的Colleague类,但Node接口扮演了类似的角色。它定义了同事对象的基本行为,如sendMessage和receiveMessage。

  3. ConcreteColleague:
    具体同事类是抽象同事类的子类(或实现了同事接口的具体类)。每一个同事对象在需要和其他同事对象进行通信时,先与中介者通信,通过中介者间接完成与其他同事的通信。

    在下面👇的重构代码中,NodeA和NodeB类就是具体同事类的例子。它们实现了sendMessage方法,但在这个方法中并不是直接与其他节点通信,而是将消息发送给中介者(mediator.sendMessage(message, this)),由中介者负责消息的进一步分发。

  结构总结
    在中介者模式中,各个组件通过中介者进行间接通信,降低了它们之间的直接依赖。这种模式有助于减少类间的耦合度,提高系统的可扩展性和可维护性。同时,它也需要仔细设计和规划,以确保中介者不会变得过于庞大和复杂。

    需要注意的是,下面👇的重构代码示例并没有完全遵循中介者模式的经典结构(如没有明确定义抽象中介者和抽象同事类),但它仍然体现了中介者模式的核心思想:通过一个中介对象来封装和协调一系列对象之间的交互。在实际应用中,可以根据系统的具体需求和设计考虑来选择适当的实现方式。

2.4 使用中介者模式重构示例

    重构步骤
    为解决传统方式带来的缺点,如高耦合度、低可扩展性、代码难以维护等,我们可以使用中介者模式来重构代码。以下是使用中介者模式重构的核心步骤:👇

  1. 定义中介者接口:
    首先,定义一个中介者的接口,这个接口将声明用于注册同事对象、转发请求等方法。中介者将负责协调各个同事对象之间的交互。

  2. 实现具体中介者:
    接下来,实现中介者的具体类,这个类将实现中介者接口中声明的方法。具体中介者需要维护对各个同事对象的引用,并协调它们之间的交互。

  3. 定义同事接口(可选):
    如果同事对象之间有共同的行为或状态,可以定义一个同事接口。这个接口将声明同事对象需要实现的方法,以便中介者与之交互。
  4. 实现具体同事类:
    实现同事接口的具体类。这些类将代表系统中的各个对象,它们将通过中介者来进行交互。在每个同事类中,将中介者作为成员变量,以便在需要时与中介者通信。
  5. 在客户端代码中使用中介者和同事对象:
    在客户端代码中,创建中介者和同事对象的实例,并将同事对象注册到中介者中。然后,通过调用中介者的方法来触发同事对象之间的交互。
  6. 测试与调试:
    重构完成后,进行充分的测试以确保系统的功能没有受到影响,并且新的结构确实解决了之前存在的问题。

    重构示例
    以下是使用中介者模式重构后的代码示例:👇

  1. 定义节点接口
public interface Node {
    
      
    void sendMessage(String message);  
    void receiveMessage(String message);  
}
  1. 实现节点
// 实现节点A
public class NodeA implements Node {
    
      
    private Mediator mediator;  

    public NodeA(Mediator mediator) {
    
      
        this.mediator = mediator;  
    }  

    @Override  
    public void sendMessage(String message) {
    
      
        mediator.sendMessage(message, this);  
    }  

    @Override  
    public void receiveMessage(String message) {
    
      
        System.out.println("NodeA received: " + message);  
    }  
}  
// 实现节点B
public class NodeB implements Node {
    
      
    private Mediator mediator;  

    public NodeB(Mediator mediator) {
    
      
        this.mediator = mediator;  
    }  

    @Override  
    public void sendMessage(String message) {
    
      
        mediator.sendMessage(message, this);  
    }  

    @Override  
    public void receiveMessage(String message) {
    
      
        System.out.println("NodeB received: " + message);  
    }  
}
  1. 定义中介者接口
public interface Mediator {
    
      
    void sendMessage(String message, Node sender);  
    void registerNode(Node node);  
}
  1. 实现中介者
public class ConcreteMediator implements Mediator {
    
      
    private List<Node> nodes = new ArrayList<>();  

    @Override  
    public void sendMessage(String message, Node sender) {
    
      
        for (Node node : nodes) {
    
      
            if (!node.equals(sender)) {
    
     // 不将消息发送给自己  
                node.receiveMessage(message);  
            }  
        }  
    }  

    @Override  
    public void registerNode(Node node) {
    
      
        nodes.add(node);  
    }  
}
  1. 主程序入口
public class DistributedSystemExample {
    
      
    public static void main(String[] args) {
    
      
        // 创建中介者  
        Mediator mediator = new ConcreteMediator();  

        // 创建节点并注册到中介者  
        NodeA nodeA = new NodeA(mediator);  
        NodeB nodeB = new NodeB(mediator);  
        mediator.registerNode(nodeA);  
        mediator.registerNode(nodeB);  

        // 节点A发送消息,中介者负责将消息传递给其他节点  
        nodeA.sendMessage("Hello from NodeA");  
    }  
}

    在这个重构后的代码中,我们引入了一个 Mediator 接口和一个具体的实现类 ConcreteMediator。ConcreteMediator 维护了一个节点列表,并提供了注册节点和发送消息的方法。当节点需要发送消息时,它调用中介者的 sendMessage 方法,并传入消息内容和发送者自身。中介者负责将消息传递给其他节点。
    这样,节点之间不再直接通信,而是通过中介者进行间接通信。这种设计降低了节点之间的耦合度,提高了系统的可扩展性和可维护性。同时,由于中介者负责消息的分发,我们可以很容易地添加新的节点类型或修改节点之间的通信逻辑,而无需修改现有节点的代码。

2.5 重构后解决的问题👍

image.png

    优点
    上述使用中介者模式重构后的代码解决了以下已知缺点:👇

  1. 降低了耦合度:
    ✨ 原始实现中,节点之间直接相互引用并通信,导致高度耦合。重构后,节点不再直接通信,而是通过中介者进行间接通信,降低了节点之间的耦合度。这使得节点可以更加独立地演变和扩展,而不会对其他节点产生不必要的影响。
  2. 提高了可扩展性:
    ✨ 在原始实现中,添加新的节点类型或修改节点之间的通信逻辑可能需要修改多个节点的代码。然而,在重构后的代码中,由于引入了中介者,这些变化可以通过修改中介者或添加新的中介者实现来集中处理,从而提高了系统的可扩展性。
  3. 遵循了开闭原则:
    ✨ 重构后的代码更加符合开闭原则。我们可以通过扩展中介者或节点的实现来引入新的功能,而不需要修改已有的代码。这有助于保持系统的稳定性和可维护性。
  4. 简化了节点类的职责:
    ✨ 原始实现中,节点类既负责自己的业务逻辑,又负责与其他节点的通信逻辑。这违反了单一职责原则。重构后,节点的通信逻辑被封装在中介者中,节点类只需要关注自己的业务逻辑,从而简化了节点类的职责。
  5. 提高了代码的可读性和可维护性:
    ✨ 通过引入中介者模式,代码的结构变得更加清晰和易于理解。中介者封装了节点之间的通信逻辑,使得节点之间的交互更加明确和可预测。这有助于提高代码的可读性和可维护性

    遵循的设计原则
    上述使用中介者模式重构后的代码解决了以下已知缺点:👇

  1. 迪米特法则(Law of Demeter)或最少知识原则(Least Knowledge Principle):
    ✈️ 在重构后的代码中,各个节点(如NodeA和NodeB)只与中介者(Mediator)通信,而不直接与其他节点交互。这减少了类之间的直接依赖,使得系统更加模块化,每个部分只关心自己的直接交互对象,即中介者,而不需要了解系统的其他部分。
  2. 单一职责原则(Single Responsibility Principle):
    ✈️ 通过引入中介者,节点的职责被分离。节点现在只负责它们自己的特定功能或行为,而通信的逻辑被移交给中介者来处理。这使得代码更加清晰,每个类或模块只做一件事情。
  3. 开闭原则(Open/Closed Principle):
    ✈️ 虽然在这个特定的例子中可能不太明显,但中介者模式通常有助于实现开闭原则。这意味着系统应该对扩展开放,对修改关闭。通过修改中介者或添加新的中介者实现,可以引入新的节点类型或修改节点之间的通信方式,而不需要修改已有的节点代码。
  4. 依赖倒置原则(Dependency Inversion Principle):
    ✈️ 在重构后的代码中,高层模块(节点)依赖于抽象(中介者接口),而不是具体实现(具体的中介者类)。这有助于减少类之间的耦合,使得系统更加灵活和可扩展。需要注意的是,这个原则在这个例子中的体现可能不是特别明显,因为只有一个具体的中介者实现被使用。但在更大的系统中,可能会有多个不同的中介者实现,节点将依赖于中介者的抽象接口而不是具体实现。

    缺点
    上述使用中介者模式重构后的实现虽然解决了许多设计上的问题,并带来了诸多好处,但仍然可能存在一些潜在的缺点或局限性:👇

  1. 中介者可能变得庞大且复杂:
    💡 随着系统的发展,中介者可能需要处理越来越多的交互逻辑。这可能导致中介者类变得庞大且难以维护。如果中介者的职责过重,它本身可能成为一个单点故障或瓶颈。
  2. 减少了直接交互的明确性:
    💡 由于节点之间不再直接通信,而是通过中介者间接通信,这可能会减少代码的直接交互明确性。开发者需要查看中介者的实现来理解节点之间的通信方式,这可能会增加理解系统的难度。
  3. 可能的性能开销:
    💡 中介者模式的引入可能会带来一定的性能开销,因为所有的消息传递都需要通过中介者进行。在高性能要求的系统中,这可能成为一个考虑因素。
  4. 动态行为的限制:
    💡 如果系统中的节点需要在运行时动态地改变它们之间的交互方式,中介者模式可能会引入一些限制。因为所有的交互逻辑都被封装在中介者中,所以动态行为的改变可能需要修改中介者的实现。
  5. 过多的中介者:
    💡 在复杂的系统中,可能需要多个中介者来处理不同类型的交互。这可能会导致系统中存在大量的中介者,增加了系统的复杂性。

三、模式讲解🎭

image.png

    核心思想

中介者模式的核心思想是:通过一个中介对象来封装和协调一系列对象之间的交互。

    中介者模式通过引入一个中介者对象来简化和集中管理多个对象之间的交互关系,从而降低系统的复杂性、提高可维护性和可扩展性。这种设计模式在处理复杂的对象交互场景时非常有用,可以帮助开发人员更好地组织和管理代码。

    目地
    中介者模式的主要目的是解除对象间的紧耦合关系,提高系统的可维护性和可扩展性。通过将对象间的交互逻辑集中在中介者中,可以更容易地理解和管理系统的行为,同时也方便了对系统行为的修改和扩展。

3.1 认识中介者模式⏰

    本质

中介者模式的本质是:封装交互。

    只要是实现封装对象之间的交互功能,就可以应用中介者模式,而不必过于拘泥于中介者模式本身的结构。标准的中介者模式限制很多,导致能完全按照标准使用中介者模式的地方并不是很多,而且多集中在界面实现上。只要本质不变,稍稍变形一下,简化一下,或许能更好地使用中介者模式。

    功能
    中介者模式主要用于解耦多个对象之间的复杂交互关系,将这些交互通过一个中介者对象进行统一管理和协调。以下是中介者模式的主要功能:👇

  1. 集中管理交互:
    🚀 中介者模式将原本散落在多个对象之间的交互逻辑集中到一个中介者对象中,由中介者来负责协调和管理这些交互。这样,原本复杂的对象间关系得以简化,系统的结构也变得更加清晰和易于理解。
  2. 降低对象间耦合度:
    🚀 通过引入中介者对象,各个对象之间不再需要直接相互引用和交互,而是通过中介者来进行间接的通信。这使得对象间的耦合度得以降低,对象的独立性和可重用性得以提高。
  3. 提高系统的可维护性和可扩展性:
    🚀 由于交互逻辑被集中到了中介者对象中,当需要修改或扩展系统的交互行为时,只需要修改或扩展中介者对象即可,而不需要修改多个相互依赖的对象。这大大降低了系统的维护成本和扩展难度。
  4. 封装交互细节:
    🚀 中介者模式可以将对象间的交互细节封装在中介者对象中,使得外部使用者只需要关注中介者提供的接口,而不需要了解对象间具体的交互过程。这有助于保护对象的内部状态和实现细节,提高了系统的安全性和稳定性。

    Mediator接口是否有必要
    在实现中介者模式时,是否需要定义Mediator接口取决于具体的设计需求和预期的扩展性。

  1. 不扩展(中介者的实现只有一个,预计未来无扩展需求)
    不定义Mediator接口,直接让各个同事对象使用具体的中介者实现对象进行交互。这种情况下,系统结构相对简单,但缺点是当需要添加新的中介者实现时,可能需要修改现有的代码,违反了开闭原则。
  2. 需要扩展(预计未来有扩展需求)
    定义Mediator接口。这样做的好处是,各个同事对象可以面向Mediator接口编程,而无需关心具体的中介者实现。这样当需要添加新的中介者实现时,只需要实现Mediator接口,并注册到系统中即可,无需修改现有的同事对象代码。这符合开闭原则,提高了系统的可扩展性和可维护性。

    同事关系
    同事关系是指那些通过中介者对象进行交互的对象之间的关系。这些对象被称为同事对象(Colleague Objects),它们之间不直接进行交互,而是通过中介者(Mediator)来协调和管理它们的交互行为。

  1. ⌛ 中介者模式通过引入一个中介者对象来管理同事对象之间的交互,避免了它们之间复杂的直接通信关系,使系统更加灵活和易于维护。同事对象通过中介者进行间接通信,降低了耦合度,提高了系统的可扩展性。

  2. ⌛ 这种同事关系的好处是降低了对象之间的耦合度,提高了系统的可维护性和可扩展性。由于同事对象之间不再直接相互依赖,它们可以更加独立地进行修改和扩展,而不会对其他对象造成太大的影响。同时,中介者对象集中管理了同事对象之间的交互逻辑,使得这些逻辑更加清晰和易于理解。

  3. ⌛ 注意的是,在中介者模式中,同事对象之间的交互必须通过中介者对象来进行。这意味着同事对象需要知道中介者对象的接口和通信协议,以便正确地发送和接收消息。同时,中介者对象也需要知道各个同事对象的存在和它们之间的交互关系,以便正确地转发消息和更新状态。因此,在设计中介者模式时,需要仔细考虑同事对象和中介者对象之间的接口设计和交互协议。

    同事与中介者的关系
    同事(Colleague)与中介者(Mediator)的关系是模式的核心组成部分。下面我将详细解释这两者之间的关系:

  1. 依赖关系:
    同事对象依赖于中介者对象来进行交互。它们通常持有一个对中介者对象的引用,并通过这个引用来发送和接收消息。这种依赖关系使得同事对象可以专注于自己的功能实现,而无需关心与其他对象的复杂交互逻辑。
  2. 解耦:
    中介者模式的主要目标之一是解耦同事对象之间的直接交互。通过将交互逻辑封装在中介者对象中,同事对象之间的耦合度得以降低。这意味着同事对象可以更加独立地进行修改和扩展,而不会对其他对象造成太大的影响。
  3. 通信枢纽:
    中介者对象充当了同事对象之间的通信枢纽。它负责接收来自同事对象的消息,并根据需要将这些消息转发给其他同事对象。这种集中式的管理方式使得系统的交互行为更加清晰和可预测。
  4. 简化交互:
    中介者模式简化了同事对象之间的交互过程。同事对象只需要与中介者对象进行交互,而无需了解其他同事对象的具体实现和细节。这有助于减少代码的复杂性和提高系统的可维护性。

3.2 实现步骤

    中介者模式的实现步骤可以概括为以下几个部分:

  1. 定义中介者接口:
    首先,定义一个中介者接口(通常命名为Mediator),该接口声明了与各个同事对象交互所需的方法。这些方法通常包括注册同事对象、发送消息给同事对象等。
  2. 创建具体中介者类:
    实现中介者接口,创建一个或多个具体的中介者类(如ConcreteMediator)。这些类将维护对同事对象的引用,并协调它们之间的交互。具体中介者类实现了与同事对象交互的逻辑。
  3. 定义同事接口:
    定义同事对象的接口(通常命名为Colleague),该接口声明了同事对象与中介者交互所需的方法,比如发送消息给中介者、接收来自中介者的消息等。
  4. 创建具体同事类:
    实现同事接口,创建具体的同事类(如ConcreteColleagueA、ConcreteColleagueB等)。这些类在需要与其他同事对象通信时,会通过调用中介者的方法来间接完成通信。
  5. 在同事类中引用中介者:
    在具体同事类中,通常会有一个对中介者对象的引用。同事对象通过这个引用来与中介者进行交互,而不是直接与其他同事对象交互。
  6. 实现交互逻辑:
    在具体中介者类中实现交互逻辑。当同事对象需要与另一个同事对象通信时,它会调用中介者的方法。中介者根据交互逻辑决定如何转发消息或协调同事对象的行为。
  7. 客户端代码使用中介者和同事对象:
    在客户端代码中,创建中介者和同事对象的实例,并将同事对象注册到中介者中。然后,客户端可以调用同事对象的方法来触发交互,而这些交互实际上是由中介者来协调和管理的。

3.3 思考中介者模式

    中介者模式与外观者模式
    相似点

  1. 简化复杂性:
    中介者模式和外观模式都致力于简化系统的复杂性。中介者通过封装对象间的交互逻辑来降低耦合度,而外观模式则通过提供一个简化的接口来隐藏子系统的复杂性。
  2. 结构型模式:
    两者都属于结构型设计模式,关注于如何将类或对象组合成更大的结构,以提供新的功能或简化现有的功能。
  3. 封装:
    两种模式都涉及封装的概念。中介者模式封装了同事对象之间的交互,而外观模式封装了子系统的接口和实现细节。

    异同点

  1. 目的不同:
    中介者模式主要关注对象之间的交互逻辑,旨在降低对象间的耦合度;而外观模式主要关注子系统的接口简化,旨在提供一个统一且简单的接口给客户端使用。
  2. 实现方式不同:
    中介者模式通过引入一个中介者对象来协调和管理同事对象之间的交互;而外观模式则通过定义一个高层接口来封装子系统的复杂性和实现细节。
  3. 关注点不同:
    中介者模式关注的是对象间的交互行为和通信过程;而外观模式关注的是子系统的接口简化和易用性。
  4. 应用场景不同:
    中介者模式通常应用于需要管理复杂交互场景的系统,如GUI组件之间的交互、游戏中的角色交互等;而外观模式通常应用于需要简化子系统接口的场景,如大型软件系统的模块划分、API设计等。

    易混淆点

  1. 都是封装:
    虽然中介者模式和外观模式都涉及到封装的概念,但它们的封装目的和层次不同。中介者模式封装的是对象之间的交互逻辑,而外观模式封装的是子系统的接口和实现细节。
  2. 都是结构型设计模式:
    这两种模式都属于结构型设计模式,用于描述如何将类或对象组合成更大的结构,但它们在结构上的作用不同。
  3. 都可能涉及多个对象/接口:
    在中介者模式中,中介者对象需要与多个同事对象进行交互;而在外观模式中,外观对象可能需要封装多个子系统的接口。这使得这两种模式在实现上可能涉及多个对象或接口,但它们的处理方式和目的不同。

四、总结🌟

image.png

4.1 优点💖

    中介者模式是一种行为型设计模式,它定义了一个中介对象来封装一系列对象之间的交互。通过引入中介者,可以减少对象之间的直接依赖,降低系统的耦合度,并使得系统更加灵活和易于维护。以下是对中介者模式优点的详细分析:👇

  1. 降低耦合度:
    中介者模式通过引入一个中介者对象来管理对象之间的交互,使得对象之间不需要直接相互引用和通信。这样,对象之间的依赖关系变得简单清晰,降低了系统的耦合度。当需要修改对象间的交互逻辑时,只需要修改中介者对象即可,而不需要修改大量相互依赖的对象。
  2. 集中管理交互逻辑:
    中介者模式将对象之间的交互逻辑集中管理在中介者对象中。这使得交互逻辑更加清晰和易于维护。当系统变得复杂时,通过中介者可以更容易地理解和跟踪对象之间的交互关系。
  3. 提高系统的可扩展性和灵活性:
    由于对象之间的交互由中介者来协调,当需要添加新的交互逻辑或修改现有逻辑时,只需要修改中介者对象即可,而不需要修改其他对象。这使得系统更加灵活和可扩展。同时,中介者模式也支持动态地添加或移除同事对象,进一步增强了系统的灵活性。
  4. 减少不必要的依赖和通信:
    在没有中介者的情况下,对象之间可能需要建立复杂的依赖关系和通信机制。这不仅增加了系统的复杂性,还可能导致不必要的性能开销。中介者模式通过集中管理交互逻辑,减少了对象之间的直接依赖和通信次数,提高了系统的效率。
  5. 促进代码重用:
    在某些情况下,不同的对象之间可能存在相似的交互逻辑。通过将这些逻辑封装在中介者对象中,可以实现代码的重用和共享。这有助于减少代码冗余和提高系统的可维护性。
  6. 增强系统的可测试性:
    中介者模式使得对象之间的交互逻辑变得清晰和集中,这有助于编写针对交互逻辑的单元测试。同时,由于对象之间的依赖关系减少,可以更容易地模拟和替换依赖对象进行测试。这提高了系统的可测试性和测试覆盖率。

4.2 缺点

    虽然中介者模式在减少对象间的耦合、简化系统结构等方面具有显著优点,但同时也存在一些固有的缺点和不足,不要包括以下几个方面:👇

  1. 中介者可能变得过于复杂和庞大:
    当中介者负责协调和管理大量对象之间的交互时,中介者类本身可能会变得非常复杂和庞大。这可能会导致中介者类难以理解和维护,违反了单一职责原则。此外,当系统需要添加新的交互逻辑时,可能需要修改中介者类,这可能会引入新的错误或影响现有的功能。
  2. 过度集中化可能导致性能瓶颈:
    由于中介者模式将对象之间的交互逻辑集中管理在中介者对象中,因此当交互频繁发生时,中介者可能会成为性能瓶颈。所有的交互请求都需要经过中介者处理,这可能会导致系统响应变慢或吞吐量下降。
  3. 破坏了对象的封装性:
    在中介者模式中,同事对象通常需要将自身的状态和行为暴露给中介者,以便中介者能够协调它们之间的交互。这可能会破坏对象的封装性,使得对象的内部状态和行为可以被外部直接访问和修改。这增加了系统的脆弱性,可能会导致意外的副作用和错误。
  4. 引入额外的抽象层:
    中介者模式引入了一个额外的抽象层(即中介者)来处理对象之间的交互。这可能会增加系统的复杂性和开发成本。同时,对于简单的交互场景,使用中介者模式可能过于麻烦和冗余。
  5. 难以处理动态变化:
    中介者模式通常假设同事对象和中介者之间的关系是静态的。然而,在某些场景下,对象之间的关系可能会动态变化。例如,同事对象可能需要在运行时动态地添加或移除。在这种情况下,中介者模式可能需要额外的逻辑来处理这些动态变化,增加了系统的复杂性。
  6. 可能导致过度设计:
    在某些情况下,过度使用中介者模式可能会导致过度设计。如果系统中的对象之间的交互并不复杂或频繁,那么引入中介者可能并不是必要的。过度使用中介者模式可能会增加系统的复杂性和开发成本,而带来的好处却相对有限。

4.3 挑战和局限

    中介者模式是一种在软件设计中广泛使用的行为型设计模式,它通过引入一个中介者对象来封装一系列对象之间的交互,从而降低对象之间的耦合度。然而,与所有设计模式一样,中介者模式也有其自身的挑战和局限。以下是对这些挑战和局限的详细分析:👇
    挑战

  1. 设计和维护的复杂性:
    中介者膨胀:随着系统的发展,中介者可能需要处理越来越多的交互逻辑,导致其变得庞大且难以维护。这违背了设计模式中的单一职责原则,使得中介者类成为了一个“上帝类”,负责处理所有交互细节。
    更新和维护成本:当系统需要添加新的交互逻辑或修改现有逻辑时,可能需要修改中介者类。由于中介者封装了许多交互逻辑,这样的修改可能会变得复杂且易出错。
  2. 动态性的挑战:
    静态关系:中介者模式通常假设同事对象和中介者之间的关系是静态的。然而,在动态系统中,对象之间的关系可能会在运行时发生变化。处理这种动态性可能需要额外的逻辑和复杂性。
    灵活性限制:由于中介者集中管理交互,它可能限制了系统的灵活性。例如,难以在运行时动态地添加或移除同事对象,而不影响系统的其他部分。
  3. 测试和调试的困难:
    测试复杂性:由于中介者封装了许多交互逻辑,测试这些逻辑可能变得复杂。需要设计大量的测试用例来覆盖所有可能的交互场景。
    调试困难:当中介者类中的逻辑出现错误时,定位和修复这些错误可能具有挑战性。需要仔细分析中介者与同事对象之间的交互以找到问题的根源。

    局限

  1. 适用场景的限制:
    简单交互场景:对于简单的交互场景,使用中介者模式可能过于麻烦和冗余。在这些情况下,直接的对象间通信可能更加简单和高效。
    特定领域问题:中介者模式更适合解决具有复杂交互和通信需求的通用问题。对于特定领域的问题,可能存在更适合的设计模式或解决方案。
  2. 封装性的破坏:
    暴露内部状态:为了协调交互,同事对象可能需要将其内部状态和行为暴露给中介者。这破坏了对象的封装性,增加了系统的脆弱性和出错的可能性。
  3. 性能考虑:
    性能开销:由于所有的交互都通过中介者进行,这可能会引入额外的性能开销。特别是在交互频繁或数据量大的情况下,这种开销可能变得显著。
  4. 可扩展性的限制:
    子系统划分:中介者模式可能不适用于需要将系统划分为多个独立子系统的场景。在这种情况下,每个子系统可能需要自己的中介者,这增加了系统的复杂性和管理难度。
相关实践学习
RocketMQ一站式入门使用
从源码编译、部署broker、部署namesrv,使用java客户端首发消息等一站式入门RocketMQ。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
5月前
|
设计模式 前端开发
前端设计模式之【中介模式】
前端设计模式之【中介模式】
25 1
|
7月前
|
设计模式 前端开发
设计模式~调停者(中介)模式-06
调停者(中介)模式 优点 缺点 使用场景 用一个中介对
29 0
|
5月前
|
设计模式
二十三种设计模式全面解析-组合模式与享元模式的结合应用:实现对象的共享和高效管理
二十三种设计模式全面解析-组合模式与享元模式的结合应用:实现对象的共享和高效管理
|
2月前
|
设计模式
【设计模式】中介模式
【设计模式】中介模式
|
5月前
|
设计模式
二十三种设计模式-解密状态模式:优雅地管理对象状态
二十三种设计模式-解密状态模式:优雅地管理对象状态
|
10月前
|
存储 设计模式 缓存
享元设计模式解读
享元设计模式解读
|
10月前
|
设计模式 缓存 算法
设计模式——组件协作模式之策略模式
现代软件专业分工之后的第一个结果是 “框架与应用程序的划分”,“组件协作” 模式通过晚期绑定,来实现框架与应用程序之间的松耦合,是二者之间协作时常用的模式。
97 0
|
10月前
|
设计模式 算法
设计模式——组件协作模式之模板方法模式
现代软件专业分工之后的第一个结果是 “框架与应用程序的划分”,“组件协作” 模式通过晚期绑定,来实现框架与应用程序之间的松耦合,是二者之间协作时常用的模式。
56 0
设计模式——组件协作模式之模板方法模式
|
10月前
|
设计模式 前端开发
设计模式——组件协作模式之观察者模式
现代软件专业分工之后的第一个结果是 “框架与应用程序的划分”,“组件协作” 模式通过晚期绑定,来实现框架与应用程序之间的松耦合,是二者之间协作时常用的模式。
43 0
|
12月前
|
存储 设计模式
【设计模式】【第四章】【订单状态流转】【状态模式 + 享元模式+模板方法模式】
【设计模式】【第四章】【订单状态流转】【状态模式 + 享元模式+模板方法模式】