中介者模式(学习笔记)
1. 意图
用一个中介对象来封装一系列的对象交互。中介者是各个对象不需要显示的相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
2. 动机
面向对象设计鼓励将行为分布到各个对象中。这种分布可能导致对象间有很多连接。在最坏的情况,每一个对象都知道其他对象的存在。
虽然将一个系统分割成许多对象通常可以增强可复用性,但是对象间相互连接的激增又会降低其可复用性。大量的相互连接使得一个对象似乎不太可能在没有其他对象的支持下工作——系统表现为一个不可分割的整体。这样,对系统的任何较大改动都十分困难,因为行为被分布在许多对象中。结果是,你可能不得不定义很多子类以定制系统的行为。
例如,考虑一个图形用户界面中对话框的实现。对话框使用一个窗口来展现一系列的窗口组件,如按钮、菜单和输入域等,如下图所示。
通常对话框中的组件间存在依赖关系。例如,当一个特定的输入域为空时,某个按钮不能使用;在称为列表框的一列选项中一个表目可能会改变输入域的内容;一旦文本出现在输入域中,其他按钮可能就变得能够使用了,这些按钮允许用户做一些操作,比如改变或删除文本所指的东西。不同的对话框会有不同的窗口组件间依赖关系。因此,即使对话框显示相同类型的窗口组件,也没办法简单的直接复用已有的窗口组件类,而必须定制以反映特定对话框的依赖关系。由于涉及多个类,用逐个生成子类的办法来定制会很冗长
· 通过将集体行为封装在一个单独的中介者对象中来避免这个问题。中介者充当一个中介以使组中的对象不在相互的显式引用。这些对象仅知道中介者的存在,从而减少了连接的数目
例如,FontDialogDirector可作为一个对话框中的窗口组件间的中介者,充当窗口组件间通信的中转中心
下面的交互图说明了各对象如何协作处理一个列表框中选项的变化:
下面一系列事件使得一个列表框的选择被传送给一个输入域:
1) ListBox通知中介者它被改变了
2) 中介者从ListBox中得到选中的选项
3) 中介者将该选项传递给EntryField
4) 现在EnrryField已有文本,导控者使得用于发起一个动作(如”黑体“,”斜体“)的某个按钮可用
3. 适用性
- 一组对象以定义良好但复杂的方式进行通信,产生的相互依赖关系结构混乱且难以理解
- 一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象
- 如果为了能在不同情景下复用一些基本行为,导致你需要被迫创建大量组件子类时,可使用中介者模式(所有组件间关系都被包含在中介者中,因此你无需修改组件就能方便地新建中介者类以定义新的组件合作方式)
4. 结构
5. 效果
1) 减少子类的生成 Mediator将原本分布于多个对象间的行为集中在一起。改变这些行为只需生成Mediator的子类即可,这样各个colleague可以复用
2)将各个Colleague解耦 Mediator有利于各Colleague间的松耦合,可以独立的改变和复用各Colleague类和Mediator类(开闭原则)
3)简化了对象协议 用Mediator和各Colleague间的一对多交互来代替多对多交互。一对多更便于理解、维护和扩展
4)使控制集中化 中介者模式将交互的复杂性变为中介者的复杂性。中介者封装了协议,使得它变得比任一个Colleague都复杂。这使得中介者本身变成一个难于维护的庞然大物
6. 代码实现
本例展示了如何将许多 GUI 元素组织起来, 使其在中介者的帮助下无需相互依赖就能合作
components/Component.java
package mediator.components; import mediator.mediator.Mediator; /** * @author GaoMing * @date 2021/7/25 - 20:23 * Common component interface. */ public interface Component { void setMediator(Mediator mediator); String getName(); }
components/AddButton.java
package mediator.components; import mediator.mediator.Mediator; import mediator.mediator.Note; import javax.swing.*; import java.awt.event.ActionEvent; /** * @author GaoMing * @date 2021/7/25 - 20:20 * Concrete components don't talk with each other. They have only one * communication channel–sending requests to the mediator. */ public class AddButton extends JButton implements Component { private Mediator mediator; public AddButton() { super("Add"); } @Override public void setMediator(Mediator mediator) { this.mediator = mediator; } @Override protected void fireActionPerformed(ActionEvent actionEvent) { mediator.addNewNote(new Note()); } @Override public String getName() { return "AddButton"; } }
components/DeleteButton.java
package mediator.components; import mediator.mediator.Mediator; import javax.swing.*; import java.awt.event.ActionEvent; /** * @author GaoMing * @date 2021/7/25 - 20:21 * Concrete components don't talk with each other. They have only one * communication channel–sending requests to the mediator. */ public class DeleteButton extends JButton implements Component { private Mediator mediator; public DeleteButton() { super("Del"); } @Override public void setMediator(Mediator mediator) { this.mediator = mediator; } @Override protected void fireActionPerformed(ActionEvent actionEvent) { mediator.deleteNote(); } @Override public String getName() { return "DelButton"; } }
components/Filter.java
package mediator.components; import mediator.mediator.Mediator; import mediator.mediator.Note; import javax.swing.*; import java.awt.event.KeyEvent; import java.util.ArrayList; /** * @author GaoMing * @date 2021/7/25 - 20:21 * Concrete components don't talk with each other. They have only one * communication channel–sending requests to the mediator. */ public class Filter extends JTextField implements Component{ private Mediator mediator; private ListModel listModel; public Filter() {} @Override public void setMediator(Mediator mediator) { this.mediator = mediator; } @Override protected void processComponentKeyEvent(KeyEvent keyEvent) { String start = getText(); searchElements(start); } public void setList(ListModel listModel) { this.listModel = listModel; } private void searchElements(String s) { if (listModel == null) { return; } if (s.equals("")) { mediator.setElementsList(listModel); return; } ArrayList<Note> notes = new ArrayList<>(); for (int i = 0; i < listModel.getSize(); i++) { notes.add((Note) listModel.getElementAt(i)); } DefaultListModel<Note> listModel = new DefaultListModel<>(); for (Note note : notes) { if (note.getName().contains(s)) { listModel.addElement(note); } } mediator.setElementsList(listModel); } @Override public String getName() { return "Filter"; } }
components/List.java
package mediator.components; import mediator.mediator.Mediator; import mediator.mediator.Note; import javax.swing.*; /** * @author GaoMing * @date 2021/7/25 - 20:21 * Concrete components don't talk with each other. They have only one * communication channel–sending requests to the mediator. */ @SuppressWarnings("unchecked") public class List extends JList implements Component { private Mediator mediator; private final DefaultListModel LIST_MODEL; public List(DefaultListModel listModel) { super(listModel); this.LIST_MODEL = listModel; setModel(listModel); this.setLayoutOrientation(JList.VERTICAL); Thread thread = new Thread(new Hide(this)); thread.start(); } @Override public void setMediator(Mediator mediator) { this.mediator = mediator; } public void addElement(Note note) { LIST_MODEL.addElement(note); int index = LIST_MODEL.size() - 1; setSelectedIndex(index); ensureIndexIsVisible(index); mediator.sendToFilter(LIST_MODEL); } public void deleteElement() { int index = this.getSelectedIndex(); try { LIST_MODEL.remove(index); mediator.sendToFilter(LIST_MODEL); } catch (ArrayIndexOutOfBoundsException ignored) {} } public Note getCurrentElement() { return (Note)getSelectedValue(); } @Override public String getName() { return "List"; } private class Hide implements Runnable { private List list; Hide(List list) { this.list = list; } @Override public void run() { while (true) { try { Thread.sleep(300); } catch (InterruptedException ex) { ex.printStackTrace(); } if (list.isSelectionEmpty()) { mediator.hideElements(true); } else { mediator.hideElements(false); } } } } }
components/SaveButton.java
package mediator.components; import mediator.mediator.Mediator; import javax.swing.*; import java.awt.event.ActionEvent; /** * @author GaoMing * @date 2021/7/25 - 20:21 * Concrete components don't talk with each other. They have only one * communication channel–sending requests to the mediator. */ public class SaveButton extends JButton implements Component { private Mediator mediator; public SaveButton() { super("Save"); } @Override public void setMediator(Mediator mediator) { this.mediator = mediator; } @Override protected void fireActionPerformed(ActionEvent actionEvent) { mediator.saveChanges(); } @Override public String getName() { return "SaveButton"; } }
components/TextBox.java
package mediator.components; import mediator.mediator.Mediator; import javax.swing.*; import java.awt.event.KeyEvent; /** * @author GaoMing * @date 2021/7/25 - 20:21 * Concrete components don't talk with each other. They have only one * communication channel–sending requests to the mediator. */ public class TextBox extends JTextArea implements Component { private Mediator mediator; @Override public void setMediator(Mediator mediator) { this.mediator = mediator; } @Override protected void processComponentKeyEvent(KeyEvent keyEvent) { mediator.markNote(); } @Override public String getName() { return "TextBox"; } }
components/Title.java
package mediator.components; import mediator.mediator.Mediator; import javax.swing.*; import java.awt.event.KeyEvent; /** * @author GaoMing * @date 2021/7/25 - 20:21 * Concrete components don't talk with each other. They have only one * communication channel–sending requests to the mediator. */ public class Title extends JTextField implements Component { private Mediator mediator; @Override public void setMediator(Mediator mediator) { this.mediator = mediator; } @Override protected void processComponentKeyEvent(KeyEvent keyEvent) { mediator.markNote(); } @Override public String getName() { return "Title"; } }
mediator/Mediator.java: 定义通用的中介者接口
package mediator.mediator; import javax.swing.*; import mediator.components.Component; /** * @author GaoMing * @date 2021/7/25 - 20:31 * Common mediator interface. */ public interface Mediator { void addNewNote(Note note); void deleteNote(); void getInfoFromList(Note note); void saveChanges(); void markNote(); void clear(); void sendToFilter(ListModel listModel); void setElementsList(ListModel list); void registerComponent(Component component); void hideElements(boolean flag); void createGUI(); }
mediator/Editor.java: 具体中介者
package mediator.mediator; import mediator.components.*; import mediator.components.Component; import mediator.components.List; import javax.swing.*; import javax.swing.border.LineBorder; import java.awt.*; /** * @author GaoMing * @date 2021/7/25 - 20:22 * Concrete mediator. All chaotic communications between concrete components * have been extracted to the mediator. Now components only talk with the * mediator, which knows who has to handle a request. */ public class Editor implements Mediator{ private Title title; private TextBox textBox; private AddButton add; private DeleteButton del; private SaveButton save; private List list; private Filter filter; private JLabel titleLabel = new JLabel("Title:"); private JLabel textLabel = new JLabel("Text:"); private JLabel label = new JLabel("Add or select existing note to proceed..."); /** * Here the registration of components by the mediator. */ @Override public void registerComponent(Component component) { component.setMediator(this); switch (component.getName()) { case "AddButton": add = (AddButton)component; break; case "DelButton": del = (DeleteButton)component; break; case "Filter": filter = (Filter)component; break; case "List": list = (List)component; this.list.addListSelectionListener(listSelectionEvent -> { Note note = (Note)list.getSelectedValue(); if (note != null) { getInfoFromList(note); } else { clear(); } }); break; case "SaveButton": save = (SaveButton)component; break; case "TextBox": textBox = (TextBox)component; break; case "Title": title = (Title)component; break; } } /** * Various methods to handle requests from particular components. */ @Override public void addNewNote(Note note) { title.setText(""); textBox.setText(""); list.addElement(note); } @Override public void deleteNote() { list.deleteElement(); } @Override public void getInfoFromList(Note note) { title.setText(note.getName().replace('*', ' ')); textBox.setText(note.getText()); } @Override public void saveChanges() { try { Note note = (Note) list.getSelectedValue(); note.setName(title.getText()); note.setText(textBox.getText()); list.repaint(); } catch (NullPointerException ignored) {} } @Override public void markNote() { try { Note note = list.getCurrentElement(); String name = note.getName(); if (!name.endsWith("*")) { note.setName(note.getName() + "*"); } list.repaint(); } catch (NullPointerException ignored) {} } @Override public void clear() { title.setText(""); textBox.setText(""); } @Override public void sendToFilter(ListModel listModel) { filter.setList(listModel); } @SuppressWarnings("unchecked") @Override public void setElementsList(ListModel list) { this.list.setModel(list); this.list.repaint(); } @Override public void hideElements(boolean flag) { titleLabel.setVisible(!flag); textLabel.setVisible(!flag); title.setVisible(!flag); textBox.setVisible(!flag); save.setVisible(!flag); label.setVisible(flag); } @Override public void createGUI() { JFrame notes = new JFrame("Notes"); notes.setSize(960, 600); notes.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); JPanel left = new JPanel(); left.setBorder(new LineBorder(Color.BLACK)); left.setSize(320, 600); left.setLayout(new BoxLayout(left, BoxLayout.Y_AXIS)); JPanel filterPanel = new JPanel(); filterPanel.add(new JLabel("Filter:")); filter.setColumns(20); filterPanel.add(filter); filterPanel.setPreferredSize(new Dimension(280, 40)); JPanel listPanel = new JPanel(); list.setFixedCellWidth(260); listPanel.setSize(320, 470); JScrollPane scrollPane = new JScrollPane(list); scrollPane.setPreferredSize(new Dimension(275, 410)); listPanel.add(scrollPane); JPanel buttonPanel = new JPanel(); add.setPreferredSize(new Dimension(85, 25)); buttonPanel.add(add); del.setPreferredSize(new Dimension(85, 25)); buttonPanel.add(del); buttonPanel.setLayout(new FlowLayout()); left.add(filterPanel); left.add(listPanel); left.add(buttonPanel); JPanel right = new JPanel(); right.setLayout(null); right.setSize(640, 600); right.setLocation(320, 0); right.setBorder(new LineBorder(Color.BLACK)); titleLabel.setBounds(20, 4, 50, 20); title.setBounds(60, 5, 555, 20); textLabel.setBounds(20, 4, 50, 130); textBox.setBorder(new LineBorder(Color.DARK_GRAY)); textBox.setBounds(20, 80, 595, 410); save.setBounds(270, 535, 80, 25); label.setFont(new Font("Verdana", Font.PLAIN, 22)); label.setBounds(100, 240, 500, 100); right.add(label); right.add(titleLabel); right.add(title); right.add(textLabel); right.add(textBox); right.add(save); notes.setLayout(null); notes.getContentPane().add(left); notes.getContentPane().add(right); notes.setResizable(false); notes.setLocationRelativeTo(null); notes.setVisible(true); } }
mediator/Note.java: 笔记类
package mediator.mediator; /** * @author GaoMing * @date 2021/7/25 - 20:22 */ public class Note { private String name; private String text; public Note() { name = "New note"; } public void setName(String name) { this.name = name; } public void setText(String text) { this.text = text; } public String getName() { return name; } public String getText() { return text; } @Override public String toString() { return name; } }
Demo.java: 初始化代码
package mediator; import mediator.components.*; import mediator.mediator.Mediator; import mediator.mediator.Editor; import javax.swing.*; /** * @author GaoMing * @date 2021/7/25 - 20:20 */ public class Demo { public static void main(String[] args) { Mediator mediator = new Editor(); mediator.registerComponent(new Title()); mediator.registerComponent(new TextBox()); mediator.registerComponent(new AddButton()); mediator.registerComponent(new DeleteButton()); mediator.registerComponent(new SaveButton()); mediator.registerComponent(new List(new DefaultListModel())); mediator.registerComponent(new Filter()); mediator.createGUI(); } }
运行结果
7. 与其他模式的关系
-
责任链模式、命令模式、中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式:
- 责任链按照顺序将请求动态传递给一系列的潜在接收者,直至其中一名接收者对请求进行处理
- 命令在发送者和请求者之间建立单向连接
- 中介者清除了发送者和请求者之间的直接连接,强制它们通过一个中介对象进行间接沟通
- 观察者允许接收者动态地订阅或取消接收请求 -
外观模式和中介者的职责类似:它们都尝试在大量紧密耦合的类中组织起合作
- 外观为子系统中的所有对象定义了一个简单接口,但是它不提供任何新功能。子系统本身不会意识到外观的存在。子系统中的对象可以直接进行交流
- 中介者将系统中组件的沟通行为中心化。各组件只知道中介者对象,无法直接相互交流 -
有一种流行的中介者模式实现方式依赖于观察者。中介者对象担当发布者的角色,其他组件则作为订阅者,可以订阅中介者的事件或取消订阅。当中介者以这种方式实现时,它可能看上去与观察者非常相似
8. 已知应用
使用示例:中介者模式在Java 代码中最常用于帮助程序GUI组件之间的通信。在MVC模式中,控制器是中介者的同义词
下面是核心Java程序库中该模式的一些示例:
java.util.Timer (所有 scheduleXXX()方法)
java.util.concurrent.Executor#execute()
java.util.concurrent.ExecutorService ( invokeXXX()和 submit()方法)
java.util.concurrent.ScheduledExecutorService (所有 scheduleXXX()方法)
java.lang.reflect.Method#invoke()