Java-设计模式-四-

66 阅读56分钟

Java 设计模式(四)

原文:Java Design Patterns

协议:CC BY-NC-SA 4.0

十八、迭代器模式

本章涵盖了迭代器模式。

GoF 定义

提供一种方法来顺序访问聚合对象的元素,而不暴露其底层表示。

概念

使用迭代器,客户机对象可以遍历容器(或对象集合)来访问其元素,而不需要知道这些数据是如何在内部存储的。当您需要以标准和统一的方式遍历不同种类的集合对象时,这个概念非常有用。以下是关于这种模式的一些要点。

img/395506_2_En_18_Fig1_HTML.jpg

图 18-1

迭代器模式的示例图

  • 它通常用于遍历树状结构的节点。因此,在许多场景中,您可能会注意到组合模式中迭代器模式的使用。

  • 迭代器的作用不仅限于遍历。这个角色可以改变以支持各种需求。

  • 客户端看不到实际的遍历机制。客户端程序只使用本质上是公共的迭代器方法。

  • 图 18-1 显示了一个迭代器模式的示例图。

与会者如下:

  • 迭代器:访问或遍历元素的接口。

  • 具体迭代器:实现迭代器接口方法。它还可以跟踪聚合遍历中的当前位置。

  • Aggregate :定义一个可以创建迭代器对象的接口。

  • ConcreteAggregate :实现聚合接口。它返回 ConcreteIterator 的一个实例。

真实世界的例子

假设有两家公司:A 公司和 b 公司。A 公司存储其员工记录(即姓名、地址、工资明细等。)在链表数据结构中。B 公司将其员工数据存储在一个数组数据结构中。一天,这两家公司决定合并成一个大组织。迭代器模式在这种情况下非常方便,因为开发人员不想从头开始编写代码。他们可以创建一个公共接口,这样他们就可以访问两家公司的数据,并以统一的方式调用这些方法。

考虑另一个例子。假设你的公司决定根据员工的表现提升他们。所以,所有的经理聚在一起,为晋升制定一个共同的标准。然后,他们遍历每个员工的过去记录,以标记潜在的晋升候选人。

最后,当您将歌曲存储在您喜欢的音频设备中时,例如 MP3 播放器或移动设备,您可以通过各种按钮按压或滑动动作来迭代它们。基本思想是为您提供一些机制来平滑地迭代您的列表。

计算机世界的例子

同样,让我们假设,一个大学艺术系正在使用数组数据结构来维护其学生的记录。科学系使用链表数据结构来保存学生的记录。行政部门不关心不同的数据结构,他们只是对从每个部门获取数据感兴趣,并且他们希望以通用的方式访问数据。

注意

Java 的集合框架中的迭代器类就是迭代器的例子。当您使用像 java.util.Iterator 或 java.util.Enumeration 这样的接口时,您基本上使用了这种模式。java.util.Scanner 类也遵循这种模式。如果你熟悉 C#,你可以使用 Visual Studio 2005 中引入的 C# 自己的迭代器。foreach 语句经常在此上下文中使用。

说明

在这一章中,迭代器模式有三种不同的实现。我将从一个遵循该模式核心理论的例子开始。在下一个例子中,我将使用 Java 对迭代器模式的内置支持来修改这个例子。在第三个也是最后一个例子中,您将这个模式用于不同的数据结构。在前两个例子中,我将简单地使用“字符串”数据类型,但在最后一个例子中,我将使用复杂的数据类型。

在开始之前,我建议您注意一下 Package Explorer 视图中的结构,以便立即参考。

在第一个实现中,让我们假设在一个特定的学院中,一个艺术系的学生需要学习四篇论文(或科目)——英语、历史、地理和心理学。这些论文的细节存储在一个数组数据结构中。你的工作是用迭代器打印课程表。

假设您的迭代器目前支持四种基本方法:first()next()currentItem()hasNext()

  • 在开始遍历数据结构之前,first()方法将指针重置为指向第一个元素。

  • next()方法返回容器中的下一个元素。

  • currentItem()方法返回迭代器在特定时间点指向的容器的当前元素。

  • hasNext()验证下一个元素是否可用于进一步处理。因此,它可以帮助您确定是否已经到达容器的末尾。

类图

图 18-2 显示了类图。

img/395506_2_En_18_Fig2_HTML.jpg

图 18-2

类图

注意

像本书中前面的许多例子一样,为了展示一个清晰的类图,我只展示了客户端代码依赖。对于 Eclipse 编辑器中显示的任何 ObjectAid 类图,您总是可以通过选择图中的一个元素,右键单击它,然后选择 Add ➤依赖项来查看其他依赖项。

包资源管理器视图

图 18-3 显示了程序的高层结构。

img/395506_2_En_18_Fig3_HTML.jpg

图 18-3

包资源管理器视图

首次实现

这是第一个实现。

package jdp2e.iterator.demo;

interface Subjects
{
    Iterator createIterator();
}
class Arts implements Subjects
{
    private String[] papers;

    public Arts()
    {
        papers = new String[] { "English","History", "Geography","Psychology" };
    }

    public Iterator createIterator()
    {
        return new ArtsIterator(papers);
    }
}
interface Iterator
{
    void first();//Reset to first element
    String next();//To get the next element
    String currentItem();//To retrieve the current element
    boolean hasNext();//To check whether there is any next element or not.
}
class ArtsIterator implements Iterator
{
    private String[] papers;
    private int position;
    public ArtsIterator(String[] papers)
    {
        this.papers = papers;
        position = 0;
    }
    @Override
    public void first()
    {
        position = 0;
    }
    @Override
    public String next()
    {
        //System.out.println("Currently pointing to: "+ this.currentItem())

;
        return papers[position++];
    }
    @Override
    public String currentItem()
    {
        return papers[position];
    }
    @Override
    public boolean hasNext()
    {
        if(position >= papers.length)
            return false;
        return true;
    }
}

public class IteratorPatternExample {

    public static void main(String[] args) {
        System.out.println("***Iterator Pattern Demo***");
        Subjects artsSubjects = new Arts();

        Iterator iteratorForArts = artsSubjects.createIterator();
        System.out.println("\n Arts subjects are as follows:");
        while (iteratorForArts.hasNext())
        {
            System.out.println(iteratorForArts.next());
        }
        //Moving back to first element
        iteratorForArts.first();
        System.out.println(" Currently pointing back to: "+ iteratorForArts.currentItem());
    }

}

输出

这是输出。

***Iterator Pattern Demo***

 Arts subjects are as follows:
English
History
Geography
Psychology
 Currently pointing back to: English

注意

如果想看到迭代器所指向的当前元素,可以在 next()方法:// System.out.println("当前指向:"+ this.currentItem())中取消对该行的注释;

现在让我们使用 Java 的内置迭代器接口来修改前面的实现。

第二个实现方案的主要特征

我使用了 Java 对迭代器模式的内置支持。请注意,在程序的开头包含了下面一行。

                        import java.util.Iterator;

如果你打开源代码,你会看到这个接口有三个方法:hasNext()next()remove()。但是remove()方法已经有了默认的实现。因此,在下面的例子中,我只需要重写hasNext()next()方法。

这里使用的是 Java 的迭代器接口,所以不需要定义自己的迭代器接口。

在这个修改的实现中,关键的变化以粗体显示。

第二次实现

下面是第二个实现。

package jdp2e.iterator.modified.demo;

import java.util.Iterator;

interface Subjects
{
    //Iterator CreateIterator();
    ArtsIterator createIterator();
}
class Arts implements Subjects
{
    private String[] papers;

    public Arts()
    {
        papers = new String[] { "English","History", "Geography","Psychology" };
    }

    //public Iterator CreateIterator()
    public ArtsIterator createIterator()
    {
        return new ArtsIterator(papers);
    }
}

class ArtsIterator implements Iterator<String>

{
    private String[] papers;
    private int position;
    public ArtsIterator(String[] papers)
    {
        this.papers = papers;
        position = 0;
    }
    public void first()
    {
        position = 0;
    }
    public String currentItem()
    {
        return papers[position];
    }
    @Override
    public boolean hasNext()
    {
        if(position >= papers.length)
            return false;
        return true;
    }
    @Override
    public String next()
    {
        return papers[position++];
    }
}

public class ModifiedIteratorPatternExample {

    public static void main(String[] args) {
        System.out.println("***Modified Iterator Pattern Demo.***");
        Subjects artsSubjects = new Arts();

        //Iterator IteratorForArts = artsSubjects.createIterator();
        ArtsIterator iteratorForArts = artsSubjects.createIterator();
        System.out.println("\nArts subjects are as follows:");
        while (iteratorForArts.hasNext())
        {
            System.out.println(iteratorForArts.next());
        }
        //Moving back to first element
        iteratorForArts.first();
        System.out.println("Currently pointing to: "+ iteratorForArts.currentItem());
    }
}

输出

这是修改后的输出。

***Modified Iterator Pattern Demo.***

Arts subjects are as follows:
English
History
Geography
Psychology
Currently pointing to: English

问答环节

  1. 迭代器模式有什么用?

    • 你可以遍历一个对象结构而不知道它的内部细节。因此,如果您有一个不同的子集合的集合(例如,您的容器与数组、列表或链表等混合在一起。),您仍然可以遍历整个集合,并以通用的方式处理元素,而无需了解内部细节或它们之间的差异。

    • 您可以用不同的方式遍历集合。您还可以提供同时支持多个遍历的实现。

  2. 与此模式相关的主要挑战是什么?

    理想情况下,在遍历/迭代过程中,您不应该对核心架构进行任何意外的修改。

  3. 但是为了应对前面提到的挑战,你可以做一个备份,然后继续。这是正确的吗?

    制作备份并在以后重新检查是一项成本高昂的操作。

  4. 在整个讨论过程中,你一直在谈论收藏。什么是收藏?

    它是在一个单元中呈现的一组单独的对象。您可能经常会在 java 程序中看到 java.util.Collection、java.util.Map 等接口的使用。这些是 Java 集合类的一些通用接口,它们是在 JDK 1.2 中引入的。

    在集合之前,您可以选择数组、向量等来存储或操作一组对象。但是这些类没有一个公共接口;访问数组元素的方式与访问向量元素的方式截然不同。这就是为什么很难编写一个通用算法来访问这些不同实现中的不同元素。此外,这些方法中有许多是最终的,所以您不能扩展它们。

    收集框架的引入就是为了解决这些困难。同时,他们提供了高性能的实现,使程序员的生活更加轻松。

  5. 在修改后的实现中,为什么我看不到 first()和 currentItem()方法的@Override 注释?

    java.util.Iterator 接口中没有这两个方法。内置的迭代器接口有hasNext()next()方法。因此,我对这些方法使用了@Override 注释。在这个接口中还有另一个方法remove()。它有一个默认的实现。因为我没有使用过它,所以我不需要修改这个方法。

  6. 在这些实现中,我发现你只使用数组字符串来存储和操作数据。能否展示一个使用相对复杂的数据类型和不同数据结构的迭代器模式实现?

    To make these examples simple and straightforward, I only used strings and an array data structure. You can always choose your preferred data structure and apply the same process when you consider a complex data type. For example, consider the following illustration (third implementation) with these key characteristics.

    • 这里我使用了相对复杂的数据类型 Employee。每个 employee 对象有三样东西:一个名字、一个标识号(id)和薪水。

    • 在下面的实现中,我没有使用数组,而是使用了不同的数据结构 LinkedList。因此,我需要在这个实现中包含下面一行。

      import java.util.LinkedList;

    • 我采用了与前一个例子中相同的方法。

第三次实现

下面是第三个实现。

package jdp2e.iterator.questions_answers;
import java.util.Iterator;
import java.util.LinkedList;

class Employee
{
    private String name;
    private int id;
    private double salary;
    public Employee(String name, int id, double salary )
    {
        this.name=name;
        this.id=id;
        this.salary=salary;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    @Override
    public String toString(){
        return "Employee Name: "+this.getName()+", ID: "+this.getId()+ " and salary: "+this.getSalary()+"$";
    }
}
interface DataBase
{
    EmployeeIterator createIterator();
}
class EmployeeDatabase implements DataBase
{
    private LinkedList<Employee> employeeList;

    public EmployeeDatabase()
    {
        employeeList = new LinkedList<Employee>();
        employeeList.add(new Employee("Ron",1, 1000.25));
        employeeList.add(new Employee("Jack",2, 2000.5));
        employeeList.add(new Employee("Ambrose",3, 3000.75));
        employeeList.add(new Employee("Jian",4, 2550.0));
        employeeList.add(new Employee("Alex",5, 753.83));
    }
    public EmployeeIterator createIterator()
    {
        return new EmployeeIterator(employeeList);
    }
}

class EmployeeIterator implements Iterator<Employee>
{
    private LinkedList<Employee> employeeList;
    private int position;
    public EmployeeIterator(LinkedList<Employee> employeeList)
    {
        this.employeeList= employeeList;
        position = 0;
    }
    //@Override
    public void first()
    {
        position = 0;
    }

    //@Override
    public Employee currentItem()
    {
        return employeeList.get(position);
    }

    @Override
    public Employee next()
    {
        return employeeList.get(position++);
    }
    @Override
    public boolean hasNext() {
        if(position >= employeeList.size())
            return false;
        return true;
    }
}

public class ModifiedIteratorPatternExample2 {

    public static void main(String[] args) {
        System.out.println("***Modified Iterator Pattern Demo.Example-2.***");
        DataBase employeesList = new EmployeeDatabase();

        EmployeeIterator iteratorForEmployee = employeesList.createIterator();
        System.out.println("\n -----Employee details are as follows-----\n");

        while (iteratorForEmployee.hasNext())
        {
            System.out.println(iteratorForEmployee.next());
        }

    }
}

输出

这是第三个实现的输出。

***Modified Iterator Pattern Demo.Example-2.***

 -----Employee details are as follows-----

Employee Name: Ron, ID: 1 and salary: 1000.25$
Employee Name: Jack, ID: 2 and salary: 2000.5$
Employee Name: Ambrose, ID: 3 and salary: 3000.75$
Employee Name: Jian, ID: 4 and salary: 2550.0$
Employee Name: Alex, ID: 5 and salary: 753.83$

注意

您可以在一个实现中使用两种或多种不同的数据结构来展示这种模式的强大功能。您已经看到,在这些不同的实现中,我使用了 first()、next()、hasNext()和 currentItem()方法,它们的不同实现因其内部数据结构而有所不同。

十九、备忘录模式

这一章涵盖了备忘录模式。

GoF 定义

在不违反封装的情况下,捕获并具体化一个对象的内部状态,以便该对象可以在以后恢复到这个状态。

概念

在您的应用程序中,您可能需要支持“撤销”操作。为了实现这一点,你需要记录一个对象的内部状态。因此,您必须将此状态信息保存在一个可以再次引用的位置,以恢复对象的旧状态。但是一般来说,对象封装了它们的状态,这些状态对于外部世界是不可访问的。因此,如果您公开状态信息,那么您就违反了封装。

memento 的字典意思是(对过去事件的)提醒。因此,您可以猜测使用这种模式,您可以将对象恢复到它以前的状态,但是它确保您在不违反封装的情况下实现您的目标。

真实世界的例子

这方面的一个经典例子是有限状态机。这是一个数学模型,但它的一个简单应用可以在十字转门中找到。它有旋转臂,最初是锁定的。如果你被允许通过它(例如,当你插入硬币或当安检人员允许你通过安检时),锁就会被打开。一旦你通过,十字转门又回到锁定状态。

计算机世界的例子

在绘图应用程序中,您可能需要恢复到以前的状态。

注意

当您考虑 JTextField 类时,您会注意到类似的模式,该类扩展了 javax . swing . text . jtextcomponent 抽象类并提供了撤销支持机制。这里 javax.swing.undo.UndoManager 可以充当看管人,javax.swing.undo. UndoableEdit 的实现可以充当备忘录,javax.swing.text.Document 的实现可以充当发起人。你将很快了解到发起者、看管者和备忘录这些术语。此外,java.io.Serializable 通常被称为 memento 的一个例子,但是尽管您可以序列化 memento 对象,但它并不是 memento 设计模式的强制要求。

说明

仔细阅读代码,并按照注释进行操作,以便随时参考。在这个例子中,涉及三个对象:备忘录、发起者和看护者。(这些名称非常常见,所以我在我们的实现中保留了相同的命名约定。)

发起者对象有一个内部状态。客户端可以在其中设置状态。一个备忘录对象可以存储或多或少的发起者状态,由发起者决定。当看管人想要记录发起者的状态时,它向其请求当前状态。因此,它首先向发起者请求备忘录对象。

在下面的示例中,看守对象通过显示控制台消息来确认保存操作。假设客户端进行了一些更改,然后想要恢复到以前的状态。因为发起者对象的状态已经改变,所以回滚到以前的状态需要管理员对象的帮助,管理员对象先前保存了状态。看管人对象将备忘录对象(具有先前状态)返回给发起者。备忘录对象本身是不透明的对象(不允许管理员对其进行任何改变,并且理想地,只有创建该备忘录的发起者可以访问该备忘录的内部状态)。

因此,您可以得出结论,看守者对备忘录的视图/界面很窄,因为它只能将其传递给其他对象。相反,发起者看到的是宽接口,因为它可以访问返回到先前状态所需的数据。

类图

图 19-1 为类图。

img/395506_2_En_19_Fig1_HTML.jpg

图 19-1

类图

包资源管理器视图

图 19-2 显示了程序各部分的高层结构。

img/395506_2_En_19_Fig2_HTML.jpg

图 19-2

包资源管理器视图

履行

下面是实现。

package jdp2e.memento.demo;

class Memento
{
    private int stateId;
    public Memento(int stateId)
    {
        this.stateId = stateId;
    }
    public int getStateId() {
        return stateId;
    }
    /*This class does not have the
    setter method.We need to use this class
    to get the state of the object only.*/

    /*public void setState(String state) {
        this.state = state;
    }*/

}

/*
The 'Originator' class
WikiPedia notes( for your reference):
Make an object (originator) itself responsible for:
1.Saving its internal state to a(memento) object and
2.Restoring to a previous state from a(memento) object.
3.Only the originator that created a memento is allowed to access it

.
 */
class Originator
{
    private int stateId;
    public Originator()
    {
        this.stateId = 0;
        System.out.println(" Originator is created with state id : "+ stateId);
    }

    public int getStateId()
    {
        return stateId;
    }

    public void setStateId(int stateId)
    {
        System.out.println(" Setting the state id of the originator to : "+ stateId);
        this.stateId= stateId;
    }
    //Saving its internal state to a(memento) object
    public Memento saveMemento(int stateId)
    {
        System.out.println(" Saving originator's current state id. ");
        //Create memento with the current state and return it.
        return new Memento(stateId);
    }

    //Restoring to a previous state from a(memento) object

.
    public void revertMemento(Memento previousMemento)
    {
        System.out.println(" Restoring to state id..."+ previousMemento.getStateId());
        this.stateId = previousMemento.getStateId();
        System.out.println(" Current state id of originator : "+ stateId);
    }
}
/*
The 'Caretaker' class.
WikiPedia notes( for your reference):
1.A client (caretaker) can request a memento from the originator  to save the internal state of the originator and
2.Pass a memento back to the originator (to restore to a previous state)
This enables to save and restore the internal state of an originator without violating its encapsulation.
 */
public class MementoPatternExample {

    public static void main(String[] args) {
        System.out.println("***Memento Pattern Demo***\n");
        //Originator is initialized with a state
        Originator originatorObject = new Originator();
        Memento mementoObject;
        originatorObject.setStateId(1);
        // A client (caretaker) can request a memento from the originator
        //to save the internal state of the originator
        mementoObject=originatorObject.saveMemento(originatorObject.getStateId());
        System.out.println(" Snapshot #1: Originator's current state id is saved in caretaker.");
        //A client (or caretaker) cannot set/modify the memento's state
        //mementoObject.setState("arbitratyState");//error

        //Changing the state id of Originator
        originatorObject.setStateId(2);
        mementoObject=originatorObject.saveMemento(originatorObject.getStateId());
        System.out.println(" Snapshot #2: Originator's current state id is saved in caretaker.");

        //Changing the state id of Originator again

.
        originatorObject.setStateId(3);
        //Reverting back to previous state id.
        originatorObject.revertMemento(mementoObject);
    }

}

输出

这是输出。

***Memento Pattern Demo***

 Originator is created with state id : 0
 Setting the state id of the originator to : 1
 Saving originator's current state id.
 Snapshot #1: Originator's current state id is saved in caretaker.
 Setting the state id of the originator to : 2
 Saving originator's current state id.
 Snapshot #2: Originator's current state id is saved in caretaker.
 Setting the state id of the originator to : 3
 Restoring to state id...2
 Current state id of originator : 2

注意

如果您处理一个可变引用类型的状态,您可能需要做一个深度复制来将状态存储在 Memento 对象中。

问答环节

  1. 我可以恢复以前的快照/还原点。但是在现实生活中,我可能有多个恢复点。如何使用这种设计模式来实现呢?

    在这种情况下,可以使用数组列表。考虑下面的程序。

    发起者职业和备忘录职业和以前一样,所以我只展示修改后的看守者职业。我在即将到来的实现中使用了下面的代码行。

List<Memento> savedStateIds = new ArrayList<Memento>();

所以,你需要在开头包含这两行代码。

import java.util.ArrayList;
import java.util.List;

改良看守级

这是修改后的看守者类。

   /*
The modified 'Caretaker' class.
WikiPedia notes( for your reference):
1.A client (caretaker) can request a memento from the originator to save the internal state of the originator and
2.Pass a memento back to the originator (to restore to a previous state)
This enables to save and restore the internal state of an originator
without violating its encapsulation.
 */

public class ModifiedMementoPatternExample {

    public static void main(String[] args) {
        System.out.println("***Modified Memento Pattern Demo***\n");
        List<Memento> savedStateIds = new ArrayList<Memento>();
        //Originator is initialized with a state
        Originator originatorObject = new Originator();
        Memento mementoObject;
        originatorObject.setStateId(1);
        mementoObject=originatorObject.saveMemento(originatorObject.getStateId());
        savedStateIds.add( mementoObject);
        System.out.println(" Snapshot #1: Originator's current state id is saved in caretaker.");
        //A client or caretaker cannot set/modify the memento's state
        //mementoObject.setState("arbitratyState");//error

        //Changing the state id of Originator
        originatorObject.setStateId(2);
        mementoObject=originatorObject.saveMemento(originatorObject.getStateId());
        savedStateIds.add( mementoObject);
        System.out.println(" Snapshot #2: Originator's current state id is saved in caretaker.");

        //Changing the state id of Originator
        originatorObject.setStateId(3);
        mementoObject=originatorObject.saveMemento(originatorObject.getStateId());
        savedStateIds.add( mementoObject);
        System.out.println(" Snapshot #3: Originator's current state id is saved in caretaker (client).");

        //Reverting back to previous state id.
        //originatorObject.revertMemento(mementoObject);
        //Reverting back to specific id -say, Snapshot #1)
        //originatorObject.revertMemento(savedStateIds.get(0));

        //Roll back everything...
        System.out.println("Started restoring process...");
        for (int i = savedStateIds.size(); i > 0; i--)
        {
            originatorObject.revertMemento(savedStateIds.get(i-1));
        }
    }
}

修改输出

一旦您运行这个修改后的程序,您将获得以下输出。

***Modified Memento Pattern Demo***

 Originator is created with state id : 0
 Setting the state id of the originator to : 1
 Saving originator's current state id.
 Snapshot #1: Originator's current state id is saved in caretaker.
 Setting the state id of the originator to : 2
 Saving originator's current state id.
 Snapshot #2: Originator's current state id is saved in caretaker.
 Setting the state id of the originator to : 3
 Saving originator's current state id.
 Snapshot #3: Originator's current state id is saved in caretaker (client).
Started restoring process...
 Restoring to state id...3
 Current state id of originator : 3
 Restoring to state id...2
 Current state id of originator : 2
 Restoring to state id...1
 Current state id of originator : 1

分析

在这个修改过的程序中,你可以看到三种不同的“撤销”操作。

  • 您可以返回到上一个还原点。

  • 您可以返回到指定的还原点。

  • 您可以恢复到所有还原点。

要查看案例 1 和案例 2,请取消前面实现中的注释行。

  1. 在许多应用程序中,我注意到 memento 类是作为 Originator 的内部类出现的。你为什么不采用这种方法呢?

    memento 设计模式可以用许多不同的方式实现(例如,使用包私有可见性或使用对象序列化技术)。但是在每种情况下,如果您分析关键目标,您会发现一旦 memento 实例由发起者创建,除了它的创建者之外,其他任何人都不允许访问内部状态(这包括管理员/客户)。管理员的工作是存储备忘录实例(在我们的例子中是还原点),并在您需要时提供它们。所以,如果你的 memento 类是公开的,也没有什么坏处。您可以阻止 memento 的公共 setter 方法。我认为这已经足够了。

  2. 但是你还在使用 getter 方法 getStateId()。不违反封装吗?

    There is a lot of discussion and debate around this area—whether you should use getter/setter or not, particularly when you consider encapsulation. I believe that it depends on the amount of strictness that you want to impose. For example, if you just provide getter/setters for all fields without any reason, that is surely a bad design. The same thing applies when you use all the public fields inside the objects. But sometimes the accessor methods are required and useful. In this book, my aim is to encourage you learn design patterns with simple examples. If I need to consider each and every minute detail such as this, you may lose interest. So, in these examples, I show a simple way to promote encapsulation using the memento pattern. But, if you want to be stricter, you may prefer to implement the memento class as an inner class of the originator and modify the initial implementation, like in the following.

    package jdp2e.memento.questions_answers;
    
    /*
    The 'Originator' class
    WikiPedia notes( for your reference):
    Make an object (originator) itself responsible for:
    1.Saving its internal state to a(memento) object and
    2.Restoring to a previous state from a(memento) object.
    3.Only the originator that created a memento is allowed to access it.
     */
    class Originator
    {
        private int stateId;
        Memento myMemento;
        public Originator()
        {
            this.stateId = 0;
            System.out.println(" Originator is created with state id : "+ stateId);
        }
    
        public int getStateId()
        {
            return stateId;
        }
    
        public void setStateId(int stateId)
        {
            System.out.println(" Setting the state id of the originator to : "+ stateId);
            this.stateId= stateId;
        }
        //Saving its internal state to a(memento) object
        public Memento saveMemento()
        {
            System.out.println(" Saving originator's current state id. ");
            //Create memento with the current state and return it.
            return new Memento(this.stateId);
        }
    
        //Restoring to a previous state from a(memento) object.
        public void revertMemento(Memento previousMemento)
        {
            System.out.println(" Restoring to state id..."+ previousMemento.getStateId());
            this.stateId = previousMemento.getStateId();
            System.out.println(" Current state id of originator : "+ stateId);
        }
        //A memento class implemented as an inner class of Originator
        static class Memento
        {
            private int stateId;
            public Memento(int stateId)
            {
                this.stateId = stateId;
            }
            //Only outer class can access now
            public int getStateId() {
                return stateId;
            }
            /*This class does not have the
            setter method.We need to use this class
            to get the state of the object only.*/
    
            /*public void setState(String state) {
                this.state = state;
            }*/
    
        }
    }
    /*
    The 'Caretaker' class.
    WikiPedia notes( for your reference):
    1.A client (caretaker) can request a memento from the originator
    to save the internal state of the originator and
    2.Pass a memento back to the originator (to restore to a previous state)
    This enables to save and restore the internal state of an originator without violating its encapsulation.
     */
    public class MementoAsInnerClassExample {
    
        public static void main(String[] args) {
            System.out.println("***Memento Pattern Demo***\n");
            //Originator is initialized with a state
            Originator originatorObject = new Originator();
            Originator.Memento mementoObject;
            originatorObject.setStateId(1);
            // A client (caretaker) can request a memento from the originator
            //to save the internal state of the originator
            mementoObject=originatorObject.saveMemento();
            System.out.println(" Snapshot #1: Originator's current state id is saved in caretaker.");
            //A client (or caretaker) cannot set/modify the memento's state
    
            //Changing the state id of Originator
            originatorObject.setStateId(2);
            mementoObject=originatorObject.saveMemento();
            System.out.println(" Snapshot #2: Originator's current state id is saved in caretaker.");
    
            //Changing the state id of Originator again.
            originatorObject.setStateId(3);
            //Reverting back to previous state id.
            originatorObject.revertMemento(mementoObject);
        }
    
    }
    
    
  3. 使用备忘录设计模式的主要优势是什么?

    • 最大的优点是您可以随时丢弃不需要的更改,并将其恢复到预期的或稳定的状态。

    • 您不会损害与参与此模型的关键对象相关联的封装。

    • 保持高度凝聚力。

    • 提供了一种简单的恢复技术。

  4. 有哪些关键的 挑战 与备忘录设计模式相关联?

    • 大量的备忘录需要更多的存储空间。同时,它们给看护者增加了额外的负担。

    • 前面一点同时增加了维护成本。

    • 你不能忽略保存这些状态的时间。保存状态的额外时间降低了系统的整体性能。

注意在 C# 或 Java 这样的语言中,开发人员可能更喜欢序列化/反序列化技术,而不是直接实现 memento 设计模式。这两种技术各有利弊。但是您也可以在应用程序中结合使用这两种技术。

  1. 在这些实现中,如果你公开发起者的状态,那么我们的客户也可以直接访问这些状态。这是正确的吗?

    是的。但你不应该试图打破封装。注意 GoF 定义的开头是“不违反封装 …”

  2. 在这些实现中,memento 类没有公共的 setter 方法。这背后的原因是什么?

    再过一遍问题 2 的答案。并阅读代码中的注释,“只有创建备忘录的发起人才能访问它。”因此,如果您没有为 memento 类提供一个公共 setter 方法,那么管理员或客户就不能修改由发起者创建的 memento 实例。

  3. In these implementations, you could ignore the getter method of the memento by using package-private visibility for stateId. For example, you could code memento class like the following.

    class Memento
    {
        //private int stateId;
        int stateId;//←-Change is here
        public Memento(int stateId)
        {
            this.stateId = stateId;
        }
        public int getStateId() {
            return stateId;
        }
        /*This class does not have the
        setter method.We need to use this class
        to get the state of the object only.*/
    
        /*public void setState(String state) {
            this.state = state;
        }*/
    
    }
    
    

    And then you can use the following line.

            //System.out.println(" Restoring to state id..."+ previousMemento.getStateId());
            System.out.println(" Restoring to state id..."+ previousMemento.stateId);//←The change is shown in bold
    
    

    这是正确的吗?

    是的。在许多应用程序中,其他类(除了发起者)甚至不能读取备忘录的状态。当您使用包私有可见性时,您不需要任何访问器方法。换句话说,在这种情况下,您只是使用默认修饰符。

    因此,这种可见性比私有可见性稍微开放一些,同一个包中的其他类可以访问类成员。因此,在这种情况下,预期的类需要放在同一个包中。同时,您需要接受同一包中的所有其他类都可以直接访问这个状态。因此,当您将类放入您的特殊包中时,您需要足够小心。

  4. 我很困惑。为了支持撤销操作,我应该选择哪种模式——memento 还是 command?

    GoF 告诉我们这些是相关的模式。这主要取决于你想如何处理这种情况。例如,假设您正在将 10 加到一个整数上。添加之后,您希望通过执行相反的操作来撤消操作(例如,50 + 10 = 60,所以要返回,您需要执行 60–10 = 50)。在这种类型的操作中,我们不需要存储以前的状态。

    但是考虑一种情况,您需要在操作之前存储对象的状态。在这种情况下,备忘录是你的救星。因此,在绘画应用程序中,您可以避免撤销绘画操作的成本。您可以在执行命令之前存储对象列表。在这种情况下,这个存储的列表可以被视为备忘录。您可以将此列表与相关命令一起保存。我建议你去 www.developer.com/design/article.php/3720566/Working-With-Design-Patterns-Memento.htm 看看网上的好文章。

    因此,应用程序可以使用这两种模式来支持撤销操作。

    最后,您必须记住,在 memento 模式中存储 memento 对象是强制性的,这样您就可以回滚到以前的状态;但是在命令模式中,没有必要存储命令。一旦你执行一个命令,它的工作就完成了。如果您不支持“撤销”操作,您可能根本不会对存储这些命令感兴趣。

  5. 您谈到了首次实现后的深度复制。我为什么需要那个?

    在第二章(原型模式),我讨论了浅层复制和深层复制。可以参考这个讨论,供大家参考。为了回答你的问题,我们用一个简单的例子来分析一下 deep copy 有什么特别之处。考虑下面的例子。

Java 中的浅层拷贝与深层拷贝

你用 Java 中的clone()方法进行克隆,但同时你需要实现 Cloneable 接口(这是一个标记接口),因为实现这个 Cloneable 接口的 Java 对象只有资格进行克隆。默认版本的clone()创建一个浅层拷贝。要创建深层副本,您需要覆盖clone()方法。

以下计划的主要特征

在下面的示例中,您有两个类:Employee 和 EmpAddress。

Employee 类有三个字段:id、name 和 EmpAddress。因此,您可能会注意到,要形成 Employee 对象,您还需要传递一个 EmpAddress 对象。因此,在下面的示例中,您会注意到这一行代码:

Employee emp=new Employee(1,"John",initialAddress);

EmpAddress 只有一个名为 Address 的字段,它是一个字符串数据类型。

在客户机代码中,创建一个 Employee 对象 emp,然后通过克隆创建另一个对象 empClone。因此,您会注意到如下代码行:

Employee empClone=(Employee)emp.clone();

然后,更改 emp 对象的字段值。但是作为这种改变的副作用,empClone 对象的地址也改变了,但是这不是所希望的。

履行

下面是实现。

package jdp2e.memento.questions_answers;
class EmpAddress implements Cloneable
{
    String address;
    public EmpAddress(String address)
    {
        this.address=address;
    }
    public String getAddress()
    {
        return address;
    }
    public void setAddress(String address)
    {
        this.address = address;
    }
    @Override
    public String toString()
    {
        return  this.address

;
    }
    @Override
    public Object clone() throws CloneNotSupportedException
    {
        //Shallow Copy
        return super.clone();
    }
}
class Employee implements Cloneable
{
    int id;
    String name;
    EmpAddress empAddress;
    public Employee(int id,String name,EmpAddress empAddress)
    {
        this.id=id;
        this.name=name;
        this.empAddress=empAddress;
    }
    public int getId()
    {
        return id;
    }
    public void setId(int id)
    {
        this.id = id;
    }
    public String getName()
    {
        return name;
    }
    public void setName(String name)
    {
        this.name = name;
    }
    public EmpAddress getAddress()
    {
        return this.empAddress;
    }
    public void setAddress(EmpAddress newAddress) 

    {
        this.empAddress=newAddress;
    }
    @Override
    public String toString()
    {
        return "EmpId=" +this.id+ " EmpName="+ this.name+ " EmpAddressName="+ this.empAddress;
    }
    @Override
    public Object clone() throws CloneNotSupportedException
    {
        //Shallow Copy
        return super.clone();
    }
}

public class ShallowVsDeepCopy {

    public static void main(String[] args) throws CloneNotSupportedException  {
        System.out.println("***Shallow vs Deep Copy Demo***\n");
        EmpAddress initialAddress=new EmpAddress("21, abc Road, USA");
        Employee emp=new Employee(1,"John",initialAddress);
        System.out.println("emp1 object is as follows:");
        System.out.println(emp);
        Employee empClone=(Employee)emp.clone();
        System.out.println("empClone object is as follows:");
        System.out.println(empClone);
        System.out.println("\n Now changing the name, id and address of the emp object ");
        emp.setId(10);
        emp.setName("Sam");
        emp.empAddress.setAddress("221, xyz Road, Canada");
        System.out.println("Now emp1 object is as follows:");
        System.out.println(emp);
        System.out.println("And emp1Clone object is as follows:");
        System.out.println(empClone);
    }

}

输出

这是输出。

***Shallow vs Deep Copy Demo***

emp1 object is as follows:
EmpId=1 EmpName=John EmpAddressName=21, abc Road, USA
empClone object is as follows:
EmpId=1 EmpName=John EmpAddressName=21, abc Road, USA

 Now changing the name and id of emp object
Now emp1 object is as follows:
EmpId=10 EmpName=Sam EmpAddressName=221, xyz Road, Canada
And emp1Clone object is as follows:
EmpId=1 EmpName=John EmpAddressName=221, xyz Road, Canada

分析

注意输出的最后一行。你会看到不必要的副作用。由于对 emp 对象的修改,克隆对象的地址被修改。这是因为原始对象和克隆对象都指向同一个地址,并且它们不是 100%分离的。图 19-3 描述了该场景。

img/395506_2_En_19_Fig3_HTML.jpg

图 19-3

浅拷贝

所以,现在让我们来试验一个深度拷贝实现。让我们修改 Employee 类的克隆方法,如下所示。

@Override
    public Object clone() throws CloneNotSupportedException
    {
        //Shallow Copy
        //return super.clone();

        //For deep copy
        Employee employee = (Employee)  super.clone();
        employee.empAddress = (EmpAddress) empAddress.clone();
        return employee;
    }

修改输出

下面是修改后的输出。

***Shallow vs Deep Copy Demo***

emp1 object is as follows:
EmpId=1 EmpName=John EmpAddressName=21, abc Road, USA
empClone object is as follows:
EmpId=1 EmpName=John EmpAddressName=21, abc Road, USA

 Now changing the name, id and address of the emp object
Now emp1 object is as follows:

EmpId=10 EmpName=Sam EmpAddressName=221, xyz Road, Canada

And emp1Clone object is as follows:

EmpId=1 EmpName=John EmpAddressName=21, abc Road, USA

分析

注意输出的最后一行。现在,您不会看到由于修改 emp 对象而产生的不必要的副作用。这是因为原始对象和克隆对象完全不同,彼此独立。图 19-4 描述了该场景。

img/395506_2_En_19_Fig4_HTML.jpg

图 19-4

深层拷贝

注意

你在第二章的“问答环节”看到了浅抄和深抄的理论部分。

二十、状态模式

本章介绍了状态模式。

GoF 定义

允许对象在其内部状态改变时改变其行为。该对象看起来会改变它的类。

概念

假设您正在处理一个代码库快速增长的大规模应用程序。结果,情况变得复杂,您可能需要引入大量的if-else blocks/switch 语句来保护各种情况。状态模式适合这样的环境。它允许您的对象基于当前状态有不同的行为,并且您可以用不同的类定义特定于状态的行为。

因此,在这种模式中,您开始考虑应用程序的可能状态,并相应地分离代码。理想情况下,每个状态都独立于其他状态。您跟踪这些状态,并且您的代码根据当前状态的行为做出响应。例如,假设你正在观看一个电视节目。如果您按下电视遥控器上的静音按钮,您会发现电视的状态发生了变化。但是如果电视已经关闭,你就不会注意到任何变化。

因此,基本思想是,如果您的代码可以跟踪应用程序的当前状态,您就可以集中任务,分离您的代码,并相应地做出响应。

真实世界的例子

考虑网络连接的场景 TCP 连接。一个对象可以处于各种状态;例如,连接可能已经建立,连接可能已经关闭,或者对象已经开始通过连接进行侦听。当此连接收到来自其他对象的请求时,它会根据其当前状态做出响应。

交通信号灯或电视(TV)的功能也可以被认为属于这一类。例如,如果电视已经处于打开模式,您可以更换频道。如果它处于关闭模式,它将不响应频道改变请求。

计算机世界的例子

假设您有一个作业处理系统,可以一次处理一定数量的作业。当一个新的作业出现时,要么系统处理该作业,要么它发出信号表明系统正忙于它一次可以处理的最大数量的作业。换句话说,当达到作业处理能力的总数时,系统发送一个繁忙信号。

注意

在 javax.faces.lifecycle 包中,有一个名为 lifecycle 的类。这个类有一个名为 *execute(FacesContext context),*的方法,在这个方法中,您可能会注意到状态设计模式的一个实现。FacesServlet 可以调用生命周期的 execute 方法,生命周期对象与不同的阶段(状态)进行通信。

说明

下面的实现模拟了电视及其遥控器的功能。假设您有一个支持电视操作的遥控器。您可以简单地假设,在任何给定的时间,电视处于这三种状态之一:开、关或静音。最初,电视处于关闭状态。当您按下遥控器上的 on 按钮时,电视将进入 On 状态。如果您按下静音按钮,它将进入静音状态。

您可以假设,如果您在电视已经处于关闭状态时按下关闭按钮,或者如果您在电视已经处于打开状态时按下打开按钮,或者如果您在电视已经处于静音模式时按下静音按钮,电视的状态不会改变。

如果您按下关闭按钮,电视可以从打开状态进入关闭状态,如果您按下静音按钮,电视将进入静音状态。图 20-1 显示了反映所有这些可能情况的状态图。

img/395506_2_En_20_Fig1_HTML.jpg

图 20-1

电视的不同状态

注意

在此图中,我没有将任何状态标记为最终状态,尽管在此图中,在最后,我关闭了电视。为了使设计简单,我假设如果您在电视已经处于关闭状态时按下关闭按钮,或者如果您在电视已经处于打开状态时按下打开按钮,或者如果您在电视已经处于静音模式时按下静音按钮,则电视的状态不会改变。但在现实世界中,遥控器的工作方式可能会有所不同。例如,如果电视当前处于打开状态,并且您按下了静音按钮,电视可以进入静音模式,然后如果再次按下静音按钮,电视可能会再次回到打开状态。因此,您可能需要添加额外的逻辑来支持这种行为。

关键特征

以下实现的主要特征如下。

  • 对于特定于状态的行为,有单独的类。例如,这里有像开、关和静音这样的类。

  • TV 类是这里的主类(单词 main 并不意味着它包括main()方法),客户端代码只与它对话。用设计模式的术语来说,电视是这里的上下文类。

  • TV 类中定义的操作将行为委托给当前状态的对象实现。

  • PossibleState 是定义当您拥有一个对象时调用的方法/操作的接口。开、关和静音是实现该接口的具体状态。

  • 状态本身触发状态转换(从一个状态到另一个状态)。

类图

图 20-2 为类图。

img/395506_2_En_20_Fig2_HTML.jpg

图 20-2

类图

包资源管理器视图

图 20-3 显示了程序的高层结构。

img/395506_2_En_20_Fig3_HTML.jpg

图 20-3

包资源管理器视图

履行

下面是实现。

package jdp2e.state.demo;
interface PossibleState
{
    void pressOnButton(TV context);
    void pressOffButton(TV context);
    void pressMuteButton(TV context);
}
//Off state
class Off implements  PossibleState
{
    //User is pressing Off button when the TV is in Off state
    @Override
    public void pressOnButton(TV context)
    {
        System.out.println("You pressed On button. Going from Off to On state.");
        context.setCurrentState(new On());
        System.out.println(context.getCurrentState().toString());
    }
    //TV is Off already, user is pressing Off button again
    @Override
    public void pressOffButton(TV context)
    {
        System.out.println("You pressed Off button. TV is already in Off state.");
    }
    //User is pressing Mute button when the TV is in Off state
    @Override
    public void pressMuteButton(TV context)
    {
        System.out.println("You pressed Mute button.TV is already in Off state, so Mute operation will not work.");
    }
    public String toString()
    {
        return "\t**TV is switched off now.**";
    }
}
//On state
class On implements PossibleState
{
    //TV is On already, user is pressing On button again
    @Override
    public void pressOnButton(TV context)
    {
        System.out.println("You pressed On button. TV is already in On state.");
    }
    //User is pressing Off button when the TV is in On state
    @Override
    public void pressOffButton(TV context)
    {
        System.out.println("You pressed Off button.Going from On to Off state.");
        context.setCurrentState(new Off());
        System.out.println(context.getCurrentState().toString());
    }
    //User is pressing Mute button when the TV is in On state

    @Override
    public void pressMuteButton(TV context)
    {
        System.out.println("You pressed Mute button.Going from On to Mute mode.");
        context.setCurrentState(new Mute());
        System.out.println(context.getCurrentState().toString());
    }
    public String toString()
    {
        return "\t**TV is switched on now.**";
    }
}
//Mute state
class Mute implements PossibleState
{
    //User is pressing On button when the TV is in Mute mode
    @Override
    public void pressOnButton(TV context)
    {
        System.out.println("You pressed On button.Going from Mute mode to On state.");
        context.setCurrentState(new On());
        System.out.println(context.getCurrentState().toString());
    }
    //User is pressing Off button when the TV is in Mute mode
    @Override
    public void pressOffButton(TV context)
    {
        System.out.println("You pressed Off button. Going from Mute mode to Off state.");
        context.setCurrentState(new Off());
        System.out.println(context.getCurrentState().toString());
    }
    //TV is in mute mode already, user is pressing mute button again

    @Override
    public void pressMuteButton(TV context)
    {
        System.out.println("You pressed Mute button.TV is already in Mute mode.");
    }
    public String toString()
    {
        return "\t**TV is silent(mute) now**";
    }
}
class TV
{
    private PossibleState currentState;
    public TV()
    {
        //Initially TV is initialized with Off state
        this.setCurrentState(new Off());
    }
    public PossibleState getCurrentState()
    {
        return currentState;
    }
    public void setCurrentState(PossibleState currentState)
    {
        this.currentState = currentState;
    }
    public void pressOffButton()
    {
        currentState.pressOffButton(this);//Delegating the state
    }
    public void pressOnButton()
    {
        currentState.pressOnButton(this);//Delegating the state

    }
    public void pressMuteButton()
    {
        currentState.pressMuteButton(this);//Delegating the state
    }
}

//Client
public class StatePatternExample {

    public static void main(String[] args) {
        System.out.println("***State Pattern Demo***\n");
        //Initially TV is Off.
        TV tv = new TV();
        System.out.println("User is pressing buttons in the following sequence:");
        System.out.println("Off->Mute->On->On->Mute->Mute->Off\n");
        //TV is already in Off state.Again Off button is pressed.
        tv.pressOffButton();
        //TV is already in Off state.Again Mute button is pressed.
        tv.pressMuteButton();
        //Making the TV on
        tv.pressOnButton();
        //TV is already in On state.Again On button is pressed.
        tv.pressOnButton();
        //Putting the TV in Mute mode
        tv.pressMuteButton();
        //TV is already in Mute mode.Again Mute button is pressed.
        tv.pressMuteButton();
        //Making the TV off
        tv.pressOffButton();
    }

}

输出

这是输出。

***State Pattern Demo***

User is pressing buttons in the following sequence:

Off->Mute->On->On->Mute->Mute->Off

You pressed Off button. TV is already in Off state.
You pressed Mute button.TV is already in Off state, so Mute operation will not work.
You pressed On button. Going from Off to On state.
    **TV is switched on now.**
You pressed On button. TV is already in On state.
You pressed Mute button.Going from On to Mute mode.
    **TV is silent(mute) now**
You pressed Mute button.TV is already in Mute mode.
You pressed Off button. Going from Mute mode to Off state.
    **TV is switched off now.**

问答环节

  1. 你能详细说明这种模式在另一个真实场景中是如何有用的吗?

    心理学家反复证明了这样一个事实:当人们处于放松状态并且没有紧张感时,他们可以表现得最好,但是在相反的情况下,当他们的头脑充满紧张感时,他们就不能产生好的结果。这就是为什么心理学家总是建议你应该在放松的心情下工作。你可以把这个简单的哲学与电视插图联系起来。如果电视开着,它可以娱乐你;如果关闭,就不能。正确所以,如果你想设计一个对象内部状态变化时类似的行为变化,这种模式是有用的。

    除了这个例子之外,您还可以考虑一个场景,客户购买了一张在线机票,然后在某个阶段取消了它。退款金额可能因不同情况而异;例如,您可以取消机票的天数。

  2. 在本例中,您只考虑了电视的三种状态:开、关或静音。还有许多其他状态,例如,可能有一个处理连接问题或显示条件的状态。为什么你忽略了这些?

    直截了当的回答是代表简单。如果系统中状态的数量显著增加,那么维护系统就变得很困难(这也是与这种设计模式相关的关键挑战之一)。但是如果你理解这个实现,你可以很容易地添加任何你想要的状态。

  3. 我注意到在他们的名著中,GoF 代表了国家模式和策略模式的相似结构。我对此感到困惑。

    是的,结构是相似的,但是你需要注意意图是不同的。除了这个关键的区别,你可以简单地这样想:用一个策略模式提供了一个子类化的更好的选择。另一方面,在状态设计模式中,不同类型的行为可以封装在一个状态对象中,并且上下文被委托给这些状态中的任何一个。当上下文的内部状态改变时,它的行为也会改变。

    State patterns can also help us avoid lots of if conditions in some contexts. (Consider our example once again. If the TV is in the Off state, it cannot go to the Mute state. From this state, it can move to the On state only.) So, if you do not like state design pattern, you may need to code like this for a On button press.

    class TV
    {
    //Some code before
    public void pressOnButton()
    {
    if(currentState==Off )
    {
    System.out.println (" You pressed Onbutton. Going from Off to OnState");
    //Do some operations
    }
    if(currentState==On )
     {
      System.out.println (" You pressed On button. TV is already  in On state");
     }
    //TV presently is in mute mode
    else
     {
      System.out.println (" You pressed On button . Going from Mute mode to On State");
     }
    //Do some operations
    }
    
    

    请注意,您需要对不同种类的按钮按压重复这些检查。(例如,对于pressOffButton()pressMuteButton()方法,您需要重复这些检查并相应地执行。)

    如果你不考虑状态,如果你的代码库增长,维护会变得困难。

  4. 在我们的实现中,您是如何支持开闭原则的?

    这些 TV 状态中的每一个都被关闭进行修改,但是您可以向 TV 类添加全新的状态。

  5. 策略模式和状态模式有什么共同特征?

    两者都可以促进构图和授权。

  6. 在我看来,这些状态对象就像单态对象一样。这是正确的吗?

    是的。大多数时候他们都是这样做的。

  7. Can you avoid the use of “contexts” in the method parameters. For example, can you avoid them in the following statements?

    void pressOnButton(TV context);
    
    ....
    
    

    如果您不想这样使用上下文参数,您可能需要修改实现。为了给出一个快速的概述,我将展示修改后的 Package Explorer 视图,其中只包含修改后的实现。

    下面实现中的一个关键变化可以在 TV 类中看到。用所有可能的状态对象初始化TV()构造函数,这些对象用于后面阶段的状态改变。为此调用 getter 方法。考虑下面的实现。

已修改的包资源管理器视图

在这种情况下,所有三种可能的状态都有相似的组成部分。因此,为了保持图表简短,我在下面的 Package Explorer 视图中只显示了其中的一个。

图 20-4 显示了修改后的程序高层结构。

img/395506_2_En_20_Fig4_HTML.jpg

图 20-4

已修改的包资源管理器视图

修改的实现

下面是修改后的实现。

package jdp2e.state.modified.demo;

interface PossibleStates
{
    void pressOnButton();
    void pressOffButton();
    void pressMuteButton();
}

class Off implements  PossibleStates
{
    TV tvContext;
    //Initially we'll start from Off state
    public Off(TV context)
    {
        //System.out.println(" TV is Off now.");
        this.tvContext = context;
    }
    //Users can press any of these buttons at this state-On, Off or Mute
    //TV is Off now, user is pressing On button
    @Override
    public void pressOnButton()
    {
        System.out.println(" You pressed On button. Going from Off to On state");
        tvContext.setCurrentState(tvContext.getOnState());
        System.out.println(tvContext.getCurrentState().toString());
    }
    //TV is Off already, user is pressing Off button again
    @Override
    public void pressOffButton()
    {
        System.out.println(" You pressed Off button. TV is already in Off state");
    }
    //TV is Off now, user is pressing Mute button
    @Override
    public void pressMuteButton()
    {
        System.out.println(" You pressed Mute button.TV is already in Off state, so Mute operation will not work.");
    }
    public String toString()
    {
        return "\t**TV is switched off now.**";
    }

}
class On implements PossibleStates
{
    TV tvContext;
    public On(TV context)
    {
        //System.out.println(" TV is On now.");
        this.tvContext = context;
    }
    //Users can press any of these buttons at this state-On, Off or Mute
    //TV is On already, user is pressing On button again
    @Override
    public void pressOnButton()
    {
        System.out.println("You pressed On button. TV is already in On state.");
    }
    //TV is On now, user is pressing Off button
    @Override
    public void pressOffButton()
    {
        System.out.println(" You pressed Off button.Going from On to Off state.");
        tvContext.setCurrentState(tvContext.getOffState());
        System.out.println(tvContext.getCurrentState().toString());
    }
    //TV is On now, user is pressing Mute button
    @Override
    public void pressMuteButton()
    {
        System.out.println("You pressed Mute button.Going from On to Mute mode.");
        tvContext.setCurrentState(tvContext.getMuteState());
        System.out.println(tvContext.getCurrentState().toString());
    }
    public String toString()
    {
        return "\t**TV is switched on now.**";
    }
}
class Mute implements PossibleStates
{
    TV tvContext;
    public Mute(TV context)
    {
        this.tvContext = context;
    }
    //Users can press any of these buttons at this state-On, Off or Mute
    //TV is in mute, user is pressing On button
    @Override
    public void pressOnButton()
    {
        System.out.println("You pressed On button.Going from Mute mode to On state.");
        tvContext.setCurrentState(tvContext.getOnState());
        System.out.println(tvContext.getCurrentState().toString());
    }
    //TV is in mute, user is pressing Off button
    @Override
    public void pressOffButton()
    {
        System.out.println("You pressed Off button. Going from Mute mode to Off state.");
        tvContext.setCurrentState(tvContext.getOffState());
        System.out.println(tvContext.getCurrentState().toString());
    }
    //TV is in mute already, user is pressing mute button again
    @Override
    public void pressMuteButton()
    {
        System.out.println(" You pressed Mute button.TV is already in Mute mode.");
    }
    public String toString()
    {
        return "\t**TV is silent(mute) now**";
    }
}
class TV
{
    private PossibleStates currentState;
    private PossibleStates onState;
    private PossibleStates offState;
    private PossibleStates muteState;
    public TV()
    {
        onState=new On(this);
        offState=new Off(this);
        muteState=new Mute(this);
        setCurrentState(offState);
    }
    public PossibleStates getCurrentState()
    {
        return currentState;
    }
    public void setCurrentState(PossibleStates currentState)
    {
        this.currentState = currentState;
    }
    public void pressOffButton()
    {
        currentState.pressOffButton();
    }
    public void pressOnButton()
    {
        currentState.pressOnButton();
    }
    public void pressMuteButton()
    {
        currentState.pressMuteButton();
    }
    public PossibleStates getOnState()
    {
        return onState;
    }
    public PossibleStates getOffState()
    {
        return offState;
    }
    public PossibleStates getMuteState()
    {
        return muteState;
    }
}

//Client
public class StatePatternAlternativeImplementation {

    public static void main(String[] args) {
        System.out.println("***State Pattern Alternative Implementation Demo***\n");
        //Initially TV is Off.
        TV tv = new TV();
        System.out.println("User is pressing buttons in the following sequence:");
        System.out.println("Off->Mute->On->On->Mute->Mute->Off\n");
        //TV is already in Off state.Again Off button is pressed.
        tv.pressOffButton();
        //TV is already in Off state.Again Mute button is pressed.
        tv.pressMuteButton();
        //Making the TV on
        tv.pressOnButton();
        //TV is already in On state.Again On button is pressed.
        tv.pressOnButton();
        //Putting the TV in Mute mode
        tv.pressMuteButton();
        //TV is already in Mute mode.Again Mute button is pressed.
        tv.pressMuteButton();
        //Making the TV off
        tv.pressOffButton();

    }
}

修改输出

下面是修改后的实现的输出。

  1. 在这些实现中,TV 是一个具体的类。在这种情况下,你为什么不编程接口?

    我假设 TV 类不会改变,所以我忽略了这一部分以减少程序的代码量。但是是的,你总是可以从一个你可以定义契约的界面开始。

  2. 状态设计模式有哪些优点和缺点?

    Pros

    • 您已经看到,遵循打开/关闭原则,您可以轻松地添加新状态和新行为。此外,状态行为可以毫无争议地扩展。例如,在这个实现中,您可以为 TV 类添加新的状态和新的行为,而无需更改 TV 类本身。

    • 减少了if-else语句的使用(即,条件复杂度降低。(参考问题 3 的回答)。

      Cons

    • 状态模式也被称为状态的对象。所以,你可以假设更多的状态需要更多的代码,而明显的副作用是你很难维护。

  3. 在 TV 类构造函数中,你初始化了一个关闭状态的 TV。那么,状态和上下文类都能触发状态转换吗?

    是的。

 ***State Pattern Alternative Implementation Demo***

User is pressing buttons in the following sequence:
Off->Mute->On->On->Mute->Mute->Off

 You pressed Off button. TV is already in Off state
 You pressed Mute button.TV is already in Off state, so Mute operation will not work.
 You pressed On button. Going from Off to On state
    **TV is switched on now.**
You pressed On button. TV is already in On state.
You pressed Mute button.Going from On to Mute mode.
    **TV is silent(mute) now**
 You pressed Mute button.TV is already in Mute mode.
You pressed Off button. Going from Mute mode to Off state.
    **TV is switched off now.**

二十一、中介模式

本章涵盖了中介模式。

GoF 定义

定义一个封装一组对象如何交互的对象。Mediator 通过防止对象显式地相互引用来促进松散耦合,并允许您独立地改变它们的交互。

概念

中介负责控制和协调无法显式引用彼此的特定对象组的交互。所以,你可以把一个中介者想象成一个媒介,通过它这些物体彼此交流。这种实现有助于减少不同对象之间的互连数量。因此,您可以促进系统中的松散耦合。

因此,在这种设计中,对象通信是用一个中介对象封装的,这样它们就不能直接相互通信,从而减少了它们之间的依赖性。

真实世界的例子

当航班需要起飞时,会进行一系列验证。这些类型的验证确认所有组件/零件(相互依赖)都处于完美状态。

还要考虑飞机飞行员(接近或离开航站区)何时与塔台通信。他们不明确地与来自不同航空公司的其他飞行员交流。他们只向塔台发送他们的状态。这些塔也发送信号来确认哪架飞机可以起飞或降落。你必须注意,这些塔并不控制整个飞行。它们仅在端子区域中实现约束。

计算机世界的例子

当客户端处理业务应用程序时,开发人员可能需要对其施加一些约束。例如,在一个表单中,客户需要提供用户 ID 和密码来访问他们的帐户。在同一个表单上,客户必须提供其他信息,如电子邮件、地址、年龄等等。让我们假设开发人员应用了如下约束。

最初,应用程序检查用户提供的 ID 是否有效。如果是有效的用户 id,则仅启用密码字段。在提供这两个字段之后,应用程序表单需要检查电子邮件地址是否是用户提供的。让我们进一步假设在提供了所有这些信息(有效的用户 id、密码、格式正确的电子邮件等)之后。),提交按钮被启用。因此,基本上,如果客户端以正确的顺序提供了有效的用户 id、密码、电子邮件和其他必需的详细信息,那么 Submit 按钮就会被启用。开发人员可能还会强制要求用户 ID 必须是一个整数,因此如果用户错误地在该字段中放置了任何字符,提交按钮将保持禁用状态。在这种情况下,中介模式变得非常方便。

因此,当一个程序由许多类组成,并且逻辑分布在它们之间时,代码变得更难阅读和维护。在这些场景中,如果您想在系统行为中引入新的变化,这可能会很困难,除非您使用中介模式。

注意

java.util.concurrent.Executor 接口中的 execute()方法遵循这种模式。

javax.swing.ButtonGroup 类是支持这种模式的另一个例子。这个类有一个方法 setSelected(),确保用户提供一个新的选择。

java.util.Timer 类的各种 schedule()方法的不同重载版本也可以视为遵循这种模式。

说明

mediator 模式(基本上采用了 GoF 的设计模式:可重用面向对象软件的元素)的一种常见结构,通常用图 21-1 所示的图表来描述。

img/395506_2_En_21_Fig1_HTML.jpg

图 21-1

中介模式示例

参与者描述如下。

  • Mediator :定义接口,提供同事对象之间的通信。

  • ConcreteMediator :维护同事对象列表。它实现中介接口并协调同事对象之间的通信。

  • 同事:定义与其他同事交流的接口。

  • ConcreteColleague1 和 ConcreteColleague2 :实现同事接口。这些对象通过中介相互通信。

在这一章中,我提供了这种模式的两个实现。在第一个实现中,我将单词同事替换为员工。此外,ConcreteColleague1 和 ConcreteColleague2 分别替换为 JuniorEmployee 和 SeniorEmployee。假设您有三名员工:Amit、Sohel 和 Raghu,其中 Amit 和 Sohel 是初级员工,向他们的老板 Raghu 报告,rag Hu 是高级员工。拉古想顺利协调事情。让我们进一步假设他们可以通过聊天服务器相互通信。

在下面的实现中,Mediator 是一个有两个方法的接口:register()sendMessage()register()方法向中介注册一个雇员,sendMessage()向服务器发送消息。ConcreteMediator 类是 Mediator 接口的具体实现。

Employee 是一个抽象类,JuniorEmployee 和 SeniorEmployee 类是它的具体实现。Employee 类的sendMessage()方法描述如下。

public void sendMessage(String msg) throws InterruptedException
{
    mediator.sendMessage(this, msg);
}

您可以看到,当一个雇员调用sendMessage()方法时,它正在调用 mediator 的sendMessage()方法。因此,实际的沟通过程是通过中介进行的。

在客户端代码中,我引入了另一个人,Jack。但是他没有向中介对象注册自己。因此,中介不允许他向该服务器发布任何消息。

现在检查代码和相应的输出。

类图

图 21-2 为类图。

img/395506_2_En_21_Fig2_HTML.jpg

图 21-2

类图

包资源管理器视图

图 21-3 显示了程序的高层结构。

img/395506_2_En_21_Fig3_HTML.jpg

图 21-3

包资源管理器视图

履行

这是第一个实现。

package jdp2e.mediator.demo;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

interface Mediator
{
    void register(Employee employee);
    void sendMessage(Employee employee, String msg) throws InterruptedException;
}
// ConcreteMediator
class ConcreteMediator implements Mediator
{
    List<Employee> participants = new ArrayList<Employee>();
    @Override
    public void register(Employee employee)
    {
        participants.add(employee);
    }
    public void displayRegisteredEmployees()
    {
        System.out.println("At present,registered employees are:");
        for (Employee employee: participants)
        {

            System.out.println(employee.getName());
        }
    }
    @Override
    public void sendMessage(Employee employee, String msg) throws InterruptedException
    {
        if (participants.contains(employee))
        {
            System.out.println(employee.getName() +" posts:"+ msg+"Last message posted at "+LocalDateTime.now());
            Thread.sleep(1000);
        }
        else
        {
            System.out.println("An outsider named "+ employee.getName()+" is trying to send some messages.");
        }
    }
}

// The abstract class-Employee
abstract class Employee
{
    protected Mediator mediator;
    protected String name;
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    // Constructor
    public Employee(Mediator mediator)
    {
        this.mediator = mediator;
    }
    public void sendMessage(String msg) throws InterruptedException
    {
        mediator.sendMessage(this, msg);
    }
    public abstract String employeeType();
}
// Junior Employee
class JuniorEmployee extends Employee
{
    public JuniorEmployee(Mediator mediator, String name)
    {
        super(mediator);
        this.name = name;
    }

    @Override
    public String employeeType()
    {
        return "JuniorEmployee";
    }
}

//Senior Employee
class SeniorEmployee extends Employee
{
    // Constructor
    public SeniorEmployee(Mediator mediator, String name)
    {
        super(mediator);
        this.name = name;
    }
    @Override
    public String employeeType()
    {
        return "SeniorEmployee";
    }
}
// Unknown participant.
class Unknown extends Employee
{
    // Constructor
    public Unknown(Mediator mediator, String name)
    {
        super(mediator);
        this.name = name;
    }
    @Override
    public String employeeType()
    {
        return "Outsider";
    }
}

public class MediatorPatternExample {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("***Mediator Pattern Demo***\n");

        ConcreteMediator mediator = new ConcreteMediator();

        JuniorEmployee amit = new JuniorEmployee(mediator, "Amit");
        JuniorEmployee sohel = new JuniorEmployee(mediator, "Sohel");
        SeniorEmployee raghu = new SeniorEmployee(mediator, "Raghu");

        //Registering participants
        mediator.register(amit);
        mediator.register(sohel);
        mediator.register(raghu);
        //Displaying the participant's list
        mediator.displayRegisteredEmployees();

        System.out.println("Communication starts among participants...");
        amit.sendMessage("Hi Sohel,can we discuss the mediator pattern?");
        sohel.sendMessage("Hi Amit,yup, we can discuss now.");
        raghu.sendMessage("Please get back to work quickly.");

        //An outsider/unknown person tries to participate
        Unknown unknown = new Unknown(mediator, "Jack");
        unknown.sendMessage("Hello Guys..");
    }
}

输出

这是输出。

***Mediator Pattern Demo***

At present,registered employees are:
Amit
Sohel
Raghu
Communication starts among participants...
Amit posts:Hi Sohel,can we discuss the mediator pattern?Last message posted at 2018-09-09T17:41:21.868
Sohel posts:Hi Amit,yup, we can discuss now.Last message posted at 2018-09-09T17:41:23.369
Raghu posts:Please get back to work quickly.Last message posted at 2018-09-09T17:41:24.870

An outsider named Jack is trying to send some messages

.

分析

请注意,只有注册用户才能相互通信,并在聊天服务器上成功发布消息。调解人不允许任何外人进入系统。(注意输出的最后一行。)

修改后的插图

您已经看到了中介模式的一个简单例子。但是你可以做得更好。你确定了以下几点。

  • 这些信息只是单向传递的。

  • 当一个参与者发布消息时,每个人都可以看到该消息。所以,没有隐私可言。

  • 如果员工忘记自己注册,则不允许他发送消息。这很好,但他不应该被当作外人。在正常情况下,组织外部人员应该与忘记在服务器上注册的组织员工区别对待。

  • 向中介注册参与者所需的客户机代码。尽管你可能认为这不是缺点,但你可以选择更好的方法。例如,当您在客户端代码中创建一个 Employee 对象时,您可以将参与者自动注册到一个中介。

  • 您没有在客户端代码中使用employeeType()方法。

因此,记住这几点,让我们修改前面的例子。下面是修改后的实现的一些关键特征。

  • JuniorEmployee 和 SeniorEmployee 类被替换为一个 ConcreteEmployee 类。它帮助我们容易地识别谁属于组织,谁不属于组织(换句话说,是局外人)。

  • 在修改后的实现中,这些参与者中的每一个都可以看到谁在发布消息,但是不会公开消息的目标是谁或者实际的消息是什么。因此,两个参与者之间有隐私,但这种方法可以帮助像 Raghu 这样的人协调事情,因为如果他看到员工聊得太多,他可能会干涉。

  • 在客户端代码中,您可以像下面这样创建参与者。

Employee Amit = new ConcreteEmployee(mediator, "Amit", true);

第三个参数(对/错)用于确定参与者是否希望向中介注册自己。当他试图发布消息时,他会得到相应的处理。

  • employeeType()方法确定参与者是来自组织内部还是来自外部。在这种情况下,您可能还会注意到,没有使用下面的代码行
if( fromEmployee.employeeType()=="UnauthorizedUser")

您可以直接使用这行代码:

if( fromEmployee.getClass().getSimpleName().equals("UnauthorizedUser"))

为了更好的可读性,我使用了前者。

修改的类图

图 21-4 显示了修改后的类图。为了显示关键的变化和简洁的图表,我没有在下面的图表中显示客户端代码依赖关系。

img/395506_2_En_21_Fig4_HTML.jpg

图 21-4

类图

已修改的包资源管理器视图

图 21-5 显示了修改后的包浏览器视图。

img/395506_2_En_21_Fig5_HTML.jpg

图 21-5

已修改的包资源管理器视图

修改的实现

下面是修改后的实现。

package jdp2e.mediator.modified.demo;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

interface Mediator
{
    void register(Employee employee);
    void sendMessage(Employee fromEmployee, Employee toEmployee,String msg) throws InterruptedException;
}
// ConcreteMediator
class ConcreteMediator implements Mediator
{
    List<Employee> participants = new ArrayList<Employee>();
    @Override
    public void register(Employee employee)
    {
        participants.add(employee);
    }
    public void displayRegisteredEmployees()
    {
        System.out.println("At present ,registered participants are:");
        for (Employee employee: participants)
        {

            System.out.println(employee.getName());
        }
    }
    @Override
    public void sendMessage(Employee fromEmployee,Employee toEmployee,String msg) throws InterruptedException
    {
        /*if( fromEmployee.getClass().getSimpleName().equals("UnauthorizedUser"))*/
        if( fromEmployee.employeeType()=="UnauthorizedUser")
        {
            System.out.println("[ALERT Everyone] An outsider named "+ fromEmployee.getName()+" trying to send some messages to "+ toEmployee.getName());
            fromEmployee.receive(fromEmployee, ",you are not allowed to enter here.");
        }
        else if (participants.contains(fromEmployee))
        {
            System.out.println("-----"+fromEmployee.getName() +" posts some message at: "+LocalDateTime.now()+"-----");
            Thread.sleep(1000);
            //No need to inform everyone or himself
            //Only let the target receiver know
            if(participants.contains(toEmployee))
            {
                toEmployee.receive(fromEmployee,msg);
            }
            //If target receipient does not exist
            else
            {
                System.out.println(fromEmployee.getName() +" , your target recipient does not exist");
            }
        }
        //An outsider tries to send message.
        else
        {
            System.out.println("[ALERT] An unregistered employee named "+ fromEmployee.getName()+" trying to send some messages to "+ toEmployee.getName());
            System.out.println(fromEmployee.getName()+", you need to register yourself first.");
        }

    }
}
// Employee
abstract class Employee
{
    private Mediator mediator;
    protected String name;
    private boolean authorizedUser;
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    // Constructor
    public Employee(Mediator mediator, String name, boolean authorizedUser)
    {
        this.mediator = mediator;
        this.name=name;
        this.authorizedUser=authorizedUser;
        if(authorizedUser)
        {
            mediator.register(this);
        }

    }
    //The following method name need not be same as the Mediator's method name
    public void send(Employee toFriend,String msg) throws InterruptedException
    {
        mediator.sendMessage(this,toFriend, msg);
    }
    //public abstract void receive(Friend fromFriend,String message);

    public void receive(Employee fromFriend,String message)
    {
        System.out.println(this.name+" received a message : " + message +" from an employee "+ fromFriend.getName() +".");

    }
    public abstract String employeeType();
}
//A concrete friend
class ConcreteEmployee extends Employee
{

    public ConcreteEmployee(Mediator mediator, String name,boolean authorizedUser)
    {
        super(mediator,name, authorizedUser);
    }
    @Override
    public String employeeType()
    {
        return "ConcreteEmployee";
    }
}
//Unauthorized user
class UnauthorizedUser extends Employee
{
    public UnauthorizedUser(Mediator mediator, String name)
    {
        //The user is always treated an unauthorized user.So, the flag is
        //false always.
        super(mediator,name, false);
    }

    @Override
    public void receive(Employee fromEmployee,String message)
    {
        System.out.println(this.name + message);

    }

    @Override
    public String employeeType()
    {
        return "UnauthorizedUser";
    }
}

public class ModifiedMediatorPatternExample {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("***Mediator Pattern Demo***\n");

        ConcreteMediator mediator = new ConcreteMediator();

        Employee Amit = new ConcreteEmployee(mediator, "Amit", true);
        Employee Sohel = new ConcreteEmployee(mediator, "Sohel",true);
        Employee Raghu = new ConcreteEmployee(mediator, "Raghu",true);
        //Unauthorized user
        Employee Jack = new ConcreteEmployee(mediator, "Jack",false);
        //Only two parameter needed to pass in the following case.
        Employee Divya = new UnauthorizedUser(mediator, "Divya");

        //Displaying the participant's list
        mediator.displayRegisteredEmployees();

        System.out.println("Communication starts among participants...");
        Amit.send(Sohel,"Hi Sohel,can we discuss the mediator pattern?");
        Sohel.send(Amit,"Hi Amit,Yup, we can discuss now.");
        //Boss is sending messages to each of them individually
        Raghu.send(Amit,"Please get back to work quickly.");
        Raghu.send(Sohel,"Please get back to work quickly.");

        //An unregistered employee(Jack) and an outsider(Divya) are also
        //trying to participate.
        Jack.send(Amit,"Hello Guys..");
        Divya.send(Raghu, "Hi Raghu");

    }
}

修改输出

下面是修改后的输出。

***Mediator Pattern Demo***

At present ,registered participants are:
Amit
Sohel
Raghu
Communication starts among participants...
-----Amit posts some message at: 2018-09-04T20:37:00.999-----
Sohel received a message : Hi Sohel,can we discuss the mediator pattern? from an employee Amit.
-----Sohel posts some message at: 2018-09-04T20:37:01.999-----
Amit received a message : Hi Amit,Yup, we can discuss now. from an employee Sohel.
-----Raghu posts some message at: 2018-09-04T20:37:03.002-----
Amit received a message : Please get back to work quickly. from an employee Raghu.
-----Raghu posts some message at: 2018-09-04T20:37:04.016-----
Sohel received a message : Please get back to work quickly. from an employee Raghu.
[ALERT] An unregistered employee named Jack trying to send some messages to Amit
Jack, you need to register yourself first.
[ALERT Everyone] An outsider named Divya trying to send some messages to Raghu
Divya,you are not allowed to enter here

.

分析

请注意,当名为 Jack 的员工(属于该组织)在没有注册的情况下发送消息时,系统会阻止他发布消息,但会给他一个建议。但 Divya,谁是一个组织的局外人,被告知,她是不允许进入该系统。它也警告其他人。

问答环节

img/395506_2_En_21_Fig7_HTML.jpg

图 21-7

使用调解人的沟通

img/395506_2_En_21_Fig6_HTML.jpg

图 21-6

不使用调解人的沟通

  1. 你为什么要把事情复杂化?在第一个例子中,每个参与者都可以彼此直接对话,您可以绕过中介的使用。这是正确的吗?

    在这个例子中,您只有三个注册的参与者,所以看起来他们可以直接相互交流。但是你可能需要考虑一个相对复杂的场景。例如,只有当目标参与者处于在线模式(这是聊天服务器的常见情况)时,参与者才能向目标参与者发送消息。因此,根据您提出的体系结构,如果它们试图相互通信,那么在发送消息之前,它们中的每一个都需要维护所有其他参与者的状态。而如果参与人数不断增长,你能想象系统的复杂程度吗?

    因此,调解人肯定可以帮助你处理这种情况。图 21-6 和图 21-7 描绘了该场景。

    案例一。没有中间人的交流。

案例二。与调解人沟通。

此外,您可以在这个上下文中考虑修改后的实现。在修改后的实现中,您可以看到中介在维护逻辑——应该允许谁在服务器上发布消息以及应该如何对待他/她。

  1. 使用中介模式的 优势 是什么?

    • 您可以降低系统中对象通信的复杂性。

    • 该模式促进了松散耦合。

    • 它减少了系统中子类的数量。

    • 您可以将“多对多”关系替换为“一对多”关系,这样更容易阅读和理解。(考虑我们在这方面的第一个例子)。作为一个明显的效果,维护变得容易。

    • 您可以使用这种模式通过中介提供集中控制。

    • 简而言之,从我们的代码中移除紧密耦合(对象之间)一直是我们的目标,这种模式在这种情况下得分很高。

  2. 使用中介模式的缺点是什么?

    • 在某些情况下,实现适当的封装是很棘手的。

    • 如果在中介对象中放入过多的逻辑,那么它的架构可能会变得复杂。不恰当地使用中介模式可能会导致“上帝级”反模式。(你将在第二十八章中学习反模式)。

    • 有时维护中介会成为一个大问题。

  3. 如果您需要添加一个新的规则或逻辑,您可以直接将其添加到中介器中。这是正确的吗?

    是的。

  4. 我在门面模式和中介模式中发现了一些相似之处。这是正确的吗?

    是的。Steve Holzner 在他的书中提到了相似性,他把中介模式描述为一个复用的门面模式。在 mediator 中,您不是使用单个对象的接口,而是在多个对象之间创建一个多路复用的接口,以提供平滑的过渡。

  5. 在这个模式中,你减少了不同对象之间的相互联系。通过这一缩减,您获得了哪些关键优势?

    对象之间更多的互连可以形成一个整体系统,其中系统的行为很难改变(系统的行为分布在许多对象中)。作为副作用,您可能需要创建许多子类来在系统中带来这些变化。

  6. 在修改后的实现中,您使用的是线程。睡眠(1000)。这是什么原因呢?

    你可以忽略它。我用它来模拟现实生活。我假设参与者在正确阅读消息后发布消息,此活动至少需要 1 秒钟。

  7. 在一些应用中,我只看到了具体中介物的使用。这种做法可以吗?

    中介模式并不限制您只能使用一个具体的中介。但是我喜欢遵循专家的建议,“对超类型(抽象类/接口)编程是一种更好的方法”,从长远来看,它可以提供更多的灵活性。

  8. 我是否可以简单地说,如果一个类简单地从多个对象调用方法,它就是一个中介体?

    一点也不。中介的主要目的是简化系统中对象之间的复杂通信。我建议您始终牢记 GoF 定义和相应的概念。

  9. 在第一个实现中,两个发送方法(中介和雇员)都被命名为 sendMessage(),但是在修改后的实现中,它们是不同的——一个是 send(),另一个是 sendMessage()。我需要遵循任何特定的命名约定吗?

    不。两个都很好。这是你的选择。