【設(shè)計(jì)模式(17)】行為型模式之中介者模式

個(gè)人學(xué)習(xí)筆記分享,當(dāng)前能力有限,請(qǐng)勿貶低,菜鳥互學(xué),大佬繞道

如有勘誤,歡迎指出和討論,本文后期也會(huì)進(jìn)行修正和補(bǔ)充


前言

相信很多人都對(duì)更換手機(jī)號(hào)有所顧慮,或者體驗(yàn)過了這種麻煩——通知每個(gè)人自己手機(jī)號(hào)變更了,親友朋友同事上司全都得通知一遍,萬一漏了,就失聯(lián)了,甚至還得確保他們收到且更換了通訊錄中你的號(hào)碼

那么,我們能不能將手機(jī)號(hào)存在一個(gè)中介處,我們只需要告知中介處我們的新手機(jī)號(hào)。當(dāng)有人想找我們的時(shí)候,他們也只需要詢問中介就行了。

比如公司、班級(jí)、家庭的通訊錄,自己手機(jī)號(hào)或者地址修改后,也同步修改一下就好了,找人的時(shí)候去上面查找即可。

同樣的例子,還有人才交流市場(chǎng)、聊天室、房屋中介等等,甚至是前臺(tái)小姐姐都算是中介者~

那么我們的網(wǎng)狀關(guān)系結(jié)構(gòu),就變成了星型關(guān)系結(jié)構(gòu)

簡(jiǎn)而言之,其目的就是為了==降低對(duì)象之間的耦合性,從而提高其靈活性==


1.介紹

適用目的:定義一個(gè)中介對(duì)象來封裝一系列對(duì)象之間的交互,使原有對(duì)象之間的耦合松散,且可以獨(dú)立地改變它們之間的交互。

主要解決:對(duì)象之間存在大量的關(guān)聯(lián)關(guān)系,會(huì)導(dǎo)致系統(tǒng)結(jié)構(gòu)復(fù)雜,若某一個(gè)對(duì)象發(fā)生改變,則需要跟蹤處理所有有關(guān)的對(duì)象。

何時(shí)使用:多個(gè)指責(zé)相似的類相互耦合,形成了網(wǎng)狀結(jié)構(gòu)。

如何解決:對(duì)象 Colleague 之間的通信封裝到一個(gè)類中統(tǒng)一處理。

關(guān)鍵代碼:定義中介者,用于存儲(chǔ)和管理Colleague;定義Colleague持有中介者,并通過中介者相互通訊;

應(yīng)用實(shí)例:聊天室;MVC框架;資源調(diào)度系統(tǒng);

優(yōu)點(diǎn):

  • 降低了類的復(fù)雜度,將一對(duì)多轉(zhuǎn)化成了一對(duì)一,提高了靈活性,易于擴(kuò)展和維護(hù)
  • 各個(gè)類之間的解耦,降低了系統(tǒng)的耦合度
  • 類之間各司其職,符合迪米特原則

缺點(diǎn):Colleague越多,則中介者越臃腫,越難以維護(hù)

使用場(chǎng)景

  • 系統(tǒng)中對(duì)象之間存在比較復(fù)雜的引用關(guān)系,導(dǎo)致它們之間的依賴關(guān)系結(jié)構(gòu)混亂而且難以復(fù)用該對(duì)象
  • 想通過一個(gè)中間類來封裝多個(gè)類中的行為,而又不想生成太多的子類。
  • 大量相似的類,相互之間若是網(wǎng)狀關(guān)系則十分混亂

注意事項(xiàng):不應(yīng)當(dāng)在職責(zé)混亂的時(shí)候使用。


2.結(jié)構(gòu)

中介者模式包含以下主要角色

  • 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事對(duì)象注冊(cè)與轉(zhuǎn)發(fā)同事對(duì)象信息的抽象方法。
  • 具體中介者(Concrete Mediator)角色:實(shí)現(xiàn)中介者接口,定義一個(gè) List 來管理同事對(duì)象,協(xié)調(diào)各個(gè)同事角色之間的交互關(guān)系,因此它依賴于同事角色。
  • 抽象同事類(Colleague)角色:定義同事類的接口,保存中介者對(duì)象,提供同事對(duì)象交互的抽象方法,實(shí)現(xiàn)所有相互影響的同事類的公共功能。
  • 具體同事類(Concrete Colleague)角色:是抽象同事類的實(shí)現(xiàn)者,當(dāng)需要與其他同事對(duì)象交互時(shí),由中介者對(duì)象負(fù)責(zé)后續(xù)的交互。
image-20210224181126223


3.實(shí)現(xiàn)步驟

3.1.簡(jiǎn)單實(shí)例:模擬聊天室

業(yè)務(wù)流程如下

  • 用戶進(jìn)入聊天室時(shí)進(jìn)行注冊(cè)
  • 用戶發(fā)送消息時(shí),會(huì)將消息轉(zhuǎn)發(fā)給聊天室的其他用戶
  • 用戶收到消息時(shí),顯示消息內(nèi)容和發(fā)送者

代碼如下

  1. 定義抽象中介者

    //抽象中介者
    abstract class Mediator {
        public abstract void register(Colleague colleague);
    
        public abstract void relay(Colleague colleague,String msg);
    }
    
  2. 定義具體中介者,實(shí)現(xiàn)抽象中介者的功能

    class ConcreteMediator extends Mediator {
    
        private Set<Colleague> colleagues = new HashSet<>();
    
        @Override
        public void register(Colleague colleague) {
            if (!colleagues.contains(colleague)) {
                colleagues.add(colleague);
            }
        }
    
        @Override
        public void relay(Colleague colleague,String msg) {
            for (Colleague item : colleagues) {
                if (!Objects.equals(item, colleague)) {
                    item.receive(colleague,msg);
                }
            }
        }
    }
    

    此處使用Set作為容器,也可以使用List、Map、Queue等容器,沒啥區(qū)別

    轉(zhuǎn)發(fā)的行為也可以是存入消息隊(duì)列、命令池等,視情況而定

    請(qǐng)注意此處用對(duì)象比較,不要使用其內(nèi)部的參數(shù),避免兩個(gè)不同的人使用相同標(biāo)識(shí)符的情況,也給同事類留出更大的自定義擴(kuò)展空間

  3. 定義抽象同事類

    abstract class Colleague {
    
        protected Mediator mediator;
        public String name;
    
        public Colleague(Mediator mediator, String name) {
            this.mediator = mediator;
            this.name = name;
        }
    
        public abstract void receive(Colleague colleague, String msg);
    
        public abstract void send(String msg);
    }
    

    此處的mediator即中介者,被同事持有,以便于向中介者傳遞消息,當(dāng)然也可以使用單例模式或者向接口發(fā)送消息等解決方案

    此處的name為同事類的標(biāo)識(shí)符,即用于分辨同事是誰,也可以使用uid等作為標(biāo)識(shí)符

    請(qǐng)注意此處的標(biāo)識(shí)符并不唯一,相當(dāng)于生活中的姓名,是可能也允許出現(xiàn)重復(fù)的

  4. 定義具體同事類,實(shí)現(xiàn)抽象同事類

    class ConcreteColleague extends Colleague {
    
        public ConcreteColleague(Mediator mediator, String name) {
            super(mediator, name);
        }
    
        @Override
        public void receive(Colleague colleague,String msg) {
            System.out.println(name + " receive message from "+colleague.name+": " + msg);
        }
    
        @Override
        public void send(String msg) {
            System.out.println(name + " send message: " + msg);
            mediator.relay(this,msg);
        }
    }
    

    具體同事類可以定義多個(gè),每個(gè)可以自行定義收發(fā)消息的行為,也可以存儲(chǔ)自定義的信息

    例如:

    • 定義老師類,存儲(chǔ)老師的聯(lián)系方式、教師編號(hào)等信息

    • 定義學(xué)生類,存儲(chǔ)學(xué)生的學(xué)號(hào)、所在班級(jí)等信息

    • 定義管理員類,存儲(chǔ)聯(lián)系方式即可

    但存在多種同事類的情況下,建議在抽象同事類中留有類型標(biāo)記(如int type),以便于中介者識(shí)別

測(cè)試代碼

public class Test {
    public static void main(String[] args) {
        Mediator mediator=new ConcreteMediator();
        Colleague colleagueA=new ConcreteColleague(mediator,"A");
        Colleague colleagueB=new ConcreteColleague(mediator,"B");
        Colleague colleagueC=new ConcreteColleague(mediator,"C");
        mediator.register(colleagueA);
        mediator.register(colleagueB);
        mediator.register(colleagueC);


        colleagueA.send("Hello! I'm A");
        System.out.println("-------------");
        colleagueB.send("Hello! I'm B");
        System.out.println("-------------");
        colleagueC.send("Hello! I'm C");
    }
}

運(yùn)行結(jié)果

image-20210225101254234

3.2.進(jìn)階實(shí)例:模擬聊天室

模擬業(yè)務(wù)

  • 建立一個(gè)聊天室,有三種類型的人員:學(xué)生、老師、GM
  • 每種身份均允許多個(gè)人參與
  • 每個(gè)人擁有一個(gè)獨(dú)立的聊天框,顯示所有人發(fā)送的消息

全部代碼

package com.company.designPattern.mediator.test2;

import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;

/**
 * @Description: 聊天室demo代碼
 * @Author: Echo
 * @Time: 2021/2/25 10:22
 * @Email: 347110596@qq.com
 */

public class Test {
    public static void main(String[] args) {
        //初始化聊天室
        Medium md = new ChatRoom();
        //初始化參與者
        Colleague member1 = new Student("小明");
        Colleague member2 = new Student("小紅");
        Colleague member3 = new Teacher("張老師");
        Colleague member4 = new Manager("GM");
        //注冊(cè)
        md.register(member1);
        md.register(member2);
        md.register(member3);
        md.register(member4);
    }
}

enum Types {
    Teacher("老師"),
    Student("學(xué)生"),
    Manager("管理員"),
    ;

    private final String TypeName;

    public String getTypeName() {
        return TypeName;
    }

    Types(String typeName) {
        TypeName = typeName;
    }
}

//抽象中介者
interface Medium {
    void register(Colleague member); //注冊(cè)

    void relay(Colleague from, String ad); //轉(zhuǎn)發(fā)
}

//具體中介者:房地產(chǎn)中介
class ChatRoom implements Medium {
    private List<Colleague> members = new ArrayList<>();

    public void register(Colleague member) {
        if (!members.contains(member)) {
            members.add(member);
            member.setMedium(this);
        }
    }

    public void relay(Colleague from, String msg) {
        for (Colleague to : members) {
            if (!Objects.equals(from, to)) {
                to.receive(from, msg);
            }
        }
    }
}

//抽象同事類
abstract class Colleague extends JFrame implements ActionListener {
    private static final long serialVersionUID = -7219939540794786080L;
    protected Medium medium;
    protected String name;
    protected Types identify;
    JTextField SentText;
    JTextArea ReceiveArea;

    public Colleague(String name, Types identify) {
        //初始化彈窗標(biāo)題
        super(name);
        //初始化同事類信息
        this.name = name;
        this.identify = identify;
        //初始化彈窗
        ClientWindow();
    }

    //定義客戶端
    void ClientWindow() {
        //初始化容器
        Container cp = this.getContentPane();
        ReceiveArea = new JTextArea(10, 18);
        ReceiveArea.setEditable(false);
        SentText = new JTextField(18);

        // 接收消息模塊
        JPanel p1 = new JPanel();
        p1.setBorder(BorderFactory.createTitledBorder("接收內(nèi)容:"));
        p1.add(ReceiveArea);
        // 初始化消息內(nèi)容
        JScrollPane sp = new JScrollPane(p1);
        // 置頂
        cp.add(sp, BorderLayout.NORTH);

        // 發(fā)送消息模塊
        JPanel p2 = new JPanel();
        p2.setBorder(BorderFactory.createTitledBorder("發(fā)送內(nèi)容:"));
        p2.add(SentText);
        // 置底
        cp.add(p2, BorderLayout.SOUTH);
        // 輸入框設(shè)置監(jiān)聽
        SentText.addActionListener(this);

        // 設(shè)置客戶端窗口屬性
        this.setLocation(50, 100); // 窗口位置
        this.setSize(250, 330);// 窗口大小
        this.setResizable(false); // 窗口大小不可調(diào)整
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 窗口關(guān)閉操作-退出
        this.setVisible(true); // 窗口可見
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        // 發(fā)送消息
        String tempInfo = SentText.getText().trim();
        this.send(tempInfo);
        // 輸入框重置為空
        SentText.setText("");
    }

    @Override
    public String getName() {
        return name;
    }

    public void setMedium(Medium medium) {
        this.medium = medium;
    }

    public abstract void send(String ad);

    public abstract void receive(Colleague from, String ad);
}

//具體同事類
class CommonColleague extends Colleague {
    private static final long serialVersionUID = -1443076716629516027L;

    public CommonColleague(String name, Types identify) {
        super(name, identify);
    }

    public void send(String msg) {
        // 顯示消息
        ReceiveArea.append("我: " + msg + "\n");
        // 使?jié)L動(dòng)條滾動(dòng)到最底端
        ReceiveArea.setCaretPosition(ReceiveArea.getText().length());
        // 轉(zhuǎn)發(fā)消息
        medium.relay(this, msg);
    }

    public void receive(Colleague from, String msg) {
        // 獲取發(fā)送者身份
        String sender = from.name + "(" + from.identify.getTypeName() + ")";
        // 顯示消息
        ReceiveArea.append(sender + ": " + msg + "\n");
        // 使?jié)L動(dòng)條滾動(dòng)到最底端
        ReceiveArea.setCaretPosition(ReceiveArea.getText().length());
    }
}

//具體同事類:學(xué)生
class Student extends CommonColleague {
    private String stuNum;// 學(xué)號(hào)
    private String classId;// 班級(jí)編號(hào)

    //身份
    private final static Types identify = Types.Student;

    public Student(String name) {
        super(name, identify);
    }
}

//具體同事類:老師
class Teacher extends CommonColleague {
    private String phone;// 手機(jī)號(hào)

    //身份
    private final static Types identify = Types.Teacher;

    public Teacher(String name) {
        super(name, identify);
    }
}

//具體同事類:管理員
class Manager extends CommonColleague {
    //身份
    private final static Types identify = Types.Manager;

    public Manager(String name) {
        super(name, identify);
    }
}

運(yùn)行結(jié)果:

image-20210225115154936


相關(guān)demo地址https://gitee.com/echo_ye/practice/tree/master/src/main/java/com/company/designPattern/mediator


4.實(shí)際使用

在實(shí)際開發(fā)中,通常并不嚴(yán)格按照上述模式開發(fā),而通過簡(jiǎn)化來使快速開發(fā),并縮小項(xiàng)目體積

  1. 不定義中介者接口,直接使用中介者(不太建議):效果顯而易見,但即便只有一個(gè)中介者也不太建議,使項(xiàng)目更簡(jiǎn)單很重要,但易于擴(kuò)展同樣重要

  2. 同事類不持有中介者,使用時(shí)直接獲取并調(diào)用(建議):相互持有是一個(gè)很有風(fēng)險(xiǎn)的事情,避開風(fēng)險(xiǎn)顯然有利無害

  3. 單例化中介者(建議):若只有一個(gè)中介者,將其單例化可以避免誤操作再次實(shí)例化;當(dāng)然多個(gè)中介者的話就不能這樣了


后記

中介者模式的核心目的是將網(wǎng)狀結(jié)構(gòu)轉(zhuǎn)換為星型結(jié)構(gòu),即從N-N改變?yōu)镹-1-N,從而降低系統(tǒng)的復(fù)雜度


作者:Echo_Ye

WX:Echo_YeZ

Email :echo_yezi@qq.com

個(gè)人站點(diǎn):在搭了在搭了。。。(右鍵 - 新建文件夾)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容