Java-设计模式-三-

79 阅读58分钟

Java 设计模式(三)

原文:Java Design Patterns

协议:CC BY-NC-SA 4.0

十二、桥接模式

本章涵盖了桥接模式。

GoF 定义

将抽象与其实现解耦,这样两者可以独立变化。

概念

这种模式也被称为句柄/主体模式,在这种模式中,您将实现从抽象中分离出来,并为它们构建独立的继承结构。最后,你通过一座桥把它们连接起来。

您必须注意,抽象和实现可以通过接口或抽象类来表示,但是抽象包含对其实现者的引用。通常,抽象的孩子被称为精炼抽象,实现的孩子被称为具体实现

这个桥接口使得具体类的功能独立于接口实现者类。您可以在结构上改变不同种类的类,而不会相互影响。

真实世界的例子

在软件产品开发公司中,开发团队和营销团队都扮演着至关重要的角色。营销团队进行市场调查并收集客户需求,这些需求可能会因客户的性质而异。开发团队在他们的产品中实现这些需求,以满足客户的需求。一个团队中的任何变化(如运营策略)都不应对另一个团队产生直接影响。此外,当新的需求来自客户端时,它不应该改变开发人员在他们的组织中工作的方式。在软件组织中,营销团队在客户和开发团队之间扮演着桥梁的角色。

计算机世界的例子

GUI 框架可以使用桥模式将抽象从特定于平台的实现中分离出来。例如,使用这种模式,它可以从 Linux 或 macOS 的窗口实现中分离出一个窗口抽象。

注意

在 Java 中,您可能会注意到 JDBC 的使用,它在您的应用程序和特定数据库之间提供了一座桥梁。例如,java.sql.DriverManager 类和 java.sql.Driver 接口可以形成一个桥接模式,其中第一个扮演抽象的角色,第二个扮演实现者的角色。具体的实现者是 com.mysql.jdbc.Driver 或者 Oracle . JDBC . driver . Oracle driver 等等。

说明

假设你是一个遥控器制造商,你需要为不同的电子产品制造遥控器。为简单起见,让我们假设您目前正在接受订单,生产电视机和 DVD 播放器的遥控器。我们还假设你的遥控器有两个主要功能:开和关。

您可能希望从图 12-1 所示的设计或图 12-2 所示的设计开始。

img/395506_2_En_12_Fig2_HTML.png

图 12-2

方法 2

img/395506_2_En_12_Fig1_HTML.png

图 12-1

方法 1

经过进一步分析,您会发现方法 1 确实很混乱,很难维护。

起初,方法 2 看起来更简洁,但是如果您想要包括新的状态,如睡眠、静音等,或者如果您想要包括新的电子项目,如 AC、DVD 等,您将面临新的挑战,因为这些元素在这种设计方法中紧密耦合。但是在真实的场景中,这种增强是经常需要的。

这就是为什么,为了将来的增强,您需要从一个松散耦合的系统开始,这样两个层次结构(电子产品及其状态)中的任何一个都可以独立增长。桥的模式完全符合这种情况。

先说最常见的桥模式类图(见图 12-3 )。

img/395506_2_En_12_Fig3_HTML.jpg

图 12-3

经典的桥梁模式

  • 抽象(一个抽象类)定义了抽象接口,它维护实现者引用。

  • RefinedAbstraction (具体类)扩展了抽象定义的接口。

  • 实现者(一个接口)定义实现类的接口。

  • 具体实现者(具体类)实现Implementor接口。

在下面的实现中,我遵循了类似的架构。为了便于您参考,我已经指出了以下实现中的所有参与者,并附上了注释。

类图

图 12-4 显示了类图。

img/395506_2_En_12_Fig4_HTML.jpg

图 12-4

类图

包资源管理器视图

图 12-5 显示了程序的高层结构。

img/395506_2_En_12_Fig5_HTML.jpg

图 12-5

包资源管理器视图

关键特征

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

  • ElectronicGoods 抽象类扮演了抽象的角色。状态接口扮演实现者的角色。

  • 具体的实现者是 OnState 类和 OffState 类。他们根据自己的需求实现了moveState()hardPressed()interface方法。

  • ElectronicGoods 抽象类持有状态实现者的引用。

  • 抽象方法将实现委托给实现者对象。例如,请注意,hardButtonPressed()实际上是state.hardPressed()的简写,其中 state 是实现者对象*。*

  • 有两种精炼的抽象:电视和 DVD。该类对从其父类继承的方法感到满意。但是 DVD 类想要提供一个额外的特性,所以它实现了一个特定于 DVD 的方法:doublePress()double press()方法 仅按照超类抽象进行编码

*### 履行

下面是实现。

package jdp2e.bridge.demo;

//Implementor
interface State
{
    void moveState();
    void hardPressed();
}
//A Concrete Implementor.
class OnState implements State
{
    @Override
    public void moveState()
    {
        System.out.print("On State\n");
    }

    @Override
    public void hardPressed()
    {
        System.out.print("\tThe device is already On.Do not press the button so hard.\n");

    }
}
//Another Concrete Implementor.
class OffState implements State
{
    @Override
    public void moveState()

    {
        System.out.print("Off State\n");
    }

    @Override
    public void hardPressed()
    {
        System.out.print("\tThe device is Offline now.Do not press the off button again.\n");

    }
}
//Abstraction
abstract class ElectronicGoods
{
    //Composition - implementor
    protected State state;
    /*Alternative approach:
      We can also pass an implementor (as input argument) inside a constructor.
     */
    /*public ElectronicGoods(State state)
    {
        this.state = state;
    }*/
    public State getState()
    {
        return state;
    }

    public void setState(State state)
    {
        this.state = state;
    }
    /*Implementation specific:
      We are delegating the implementation to the Implementor object

.
     */
    public void moveToCurrentState()
    {
        System.out.print("The electronic item is functioning at : ");
        state.moveState();
    }
    public void hardButtonPressed()
    {
        state.hardPressed();
    }

}
//Refined Abstraction
//Television does not want to modify any superclass method.
class Television extends ElectronicGoods
{

    /*public Television(State state)
    {
        super(state);
    }*/
}
/*DVD class also ok with the super class method.
In addition to this, it uses one additional method*/
class DVD extends ElectronicGoods
{

    /*public DVD(State state)
    {
        super(state);
    }*/
    /* Notice that following DVD specific method is coded with superclass methods but not with the implementor (State) method.So, this approach will allow to  vary the abstraction and implementation independently

.
     */
    public void doublePress() {
        hardButtonPressed();
        hardButtonPressed();
    }
}
public class BridgePatternDemo {

    public static void main(String[] args) {
        System.out.println("***Bridge Pattern Demo***");

        System.out.println("\n Dealing with a Television at present.");

        State presentState = new OnState();
        //ElectronicGoods eItem = new Television(presentState);
        ElectronicGoods eItem = new Television();
        eItem.setState(presentState);
        eItem.moveToCurrentState();
        //hard press
        eItem.hardButtonPressed();
        //Verifying Off state of the Television now
        presentState = new OffState();
        //eItem = new Television(presentState);
        eItem.setState(presentState);
        eItem.moveToCurrentState();

        System.out.println("\n Dealing with a DVD now.");
        presentState = new OnState();
        //eItem = new DVD(presentState);
        eItem = new DVD();
        eItem.setState(presentState);
        eItem.moveToCurrentState();

        presentState = new OffState();
        //eItem = new DVD(presentState);
        eItem = new DVD();
        eItem.setState(presentState);
        eItem.moveToCurrentState();

        //hard press-A DVD specific method
        //(new DVD(presentState)).doublePress();
        ((DVD)eItem).doublePress();

        /*The following line of code will cause error because a television object does not have this method.*/
        //(new Television(presentState)).doublePress();
    }
}

输出

这是输出。

***Bridge Pattern Demo***

 Dealing with a Television at present.
The electronic item is functioning at : On State
The device is already On.Do not press the button so hard.
The electronic item is functioning at : Off State

 Dealing with a DVD now.
The electronic item is functioning at : On State
The electronic item is functioning at : Off State
    The device is Offline now.Do not press the off button again.
    The device is Offline now.Do not press the off button again.

问答环节

  1. 这个模式看起来类似于一个状态模式。这是正确的吗?

    No. The state pattern falls into the behavioral pattern and its intent is different. In this chapter, you have seen an example where the electronic items can be in different states, but the key intent was to show that

    • 如何避免项目及其状态之间的紧密耦合。

    • 如何维护两个不同的层次结构,并且这两个层次结构都可以在不相互影响的情况下扩展。

除了这些要点之外,您正在处理多个对象,在这些对象中,实现在它们之间共享。

为了更好地理解,请仔细阅读该实现附带的注释。我还想让你注意 DVD 特有的doublePress()方法。注意,它是用超类方法构造的,超类方法又将实现委托给实现者对象(在本例中是一个状态对象)。这种方法允许您独立地改变抽象和实现,这是桥接模式的关键目标。

  1. 你可以用简单的子类化来代替这种设计。这是正确的吗?

    不。通过简单的子类化,你的实现不能动态变化。使用子类化技术时,实现的行为可能会有所不同,但实际上,这些变化在编译时就已经被绑定到抽象中了。

  2. 在这个例子中,我看到很多死代码。你为什么留着这些?

    与 Getter/Setter 方法相比,一些开发人员更喜欢构造函数。您可以看到不同实现中的差异。我把这些保存起来,供你随时参考。你可以自由使用其中任何一个。

  3. 使用桥设计模式的关键 优势 是什么?

    • 实现并不局限于抽象。

    • 抽象和实现都可以独立发展。

    • 具体类独立于接口实现类(即,其中一个类的变化不会影响另一个类)。您还可以用不同的方式改变接口和具体实现。

  4. 与此格局相关的 挑战 有哪些?

    • 整体结构可能变得复杂。

    • 有时它与适配器模式相混淆。(适配器模式的主要目的是只处理不兼容的接口。)

  5. 假设我只有一种状态;例如,在州内或州外。在这种情况下,我需要使用状态接口吗?

    不,这不是强制性的。GoF 将这种情况归类为桥模式的退化情况。

  6. 在这个例子中,抽象类用于表示抽象,接口用于实现。它是强制性的吗?

    不。你也可以用一个接口来抽象。基本上,您可以为任何抽象或实现使用抽象类或接口。我使用这种格式只是为了更好的可读性。*

十三、访问者模式

本章介绍访问者模式。

GoF 定义

表示要在对象结构的元素上执行的操作。Visitor 允许您定义一个新的操作,而不改变它所操作的元素的类。

概念

这种模式帮助您在对象上添加新的操作,而无需修改相应的类,尤其是当您的操作经常改变时。理想情况下,访问者定义特定于类的方法,这些方法与该类的对象一起工作以支持新功能。在这里,您将算法从对象结构中分离出来,并使用新的层次结构添加新的操作。因此,这种模式可以支持开/关原则(允许扩展,但不允许修改类、函数、模块等实体)。).接下来的实现会让你更清楚这个概念。

注意

当您将这种设计模式与组合模式相结合时,您可以体验到这种设计模式的真正威力,如本章后面修改后的实现所示。

真实世界的例子

想象一个出租车预订的场景。当税到了,你上了车,出租车司机就控制了交通。出租车可能会通过一条你不熟悉的新路线把你带到目的地。所以,你可以在出租车司机的帮助下探索新的路线。但是你应该小心使用访问者模式,否则,你可能会遇到一些问题。(例如,考虑这样一种情况,你的出租车司机不知不觉地改变了目的地,你面临着麻烦)。

计算机世界的例子

当公共 API 需要支持插入操作时,这种模式非常有用。然后,客户端可以在不修改源代码的情况下对一个类(使用访问类)执行它们想要的操作。

注意

在 Java 中,当您使用抽象类 org.dom4j.VisitorSupport 时,您可能会注意到这种模式的使用,它扩展了 Object 并实现了 org.dom4j.Visitor 接口。此外,当您使用 javax . lang . model . element . element 接口或 javax . lang . model . element . element Visitor(其中 R 是 visitor 方法的返回类型,P 是 visitor 方法的附加参数类型)时,您可能会注意到 visitor 设计模式的使用。

说明

这里我们的讨论将从访问者设计模式的一个简单例子开始。让我们假设您有一个继承层次结构,其中 MyClass 具体类实现了 OriginalInterface 接口。MyClass 有一个整数 myInt。当创建 MyClass 的实例时,它用值 5 初始化。现在假设,您想要更新这个初始化的值并显示它。你可以用两种不同的方式来做这件事:你可以在我的类中添加一个方法来完成你的工作,或者使用一个访问者模式,我将要解释这一点。

在下面的实现中,我将现有值乘以 2,并使用访问者设计模式显示 myInt 的双精度值。如果我不使用这种模式,我需要在我的类中添加一个操作(或方法),它做同样的事情。

但是后一种方法有一个问题。如果您想要进一步更新逻辑(例如,您想要三重化 myInt 并显示值),您需要修改 MyClass 中的操作。这种方法的一个缺点是,如果涉及到许多类,那么在所有类中实现这种更新的逻辑将会很乏味。

但是在访问者模式中,您可以只更新访问者的方法。这样做的好处是你不需要改变原来的类。当您的操作经常变化时,这种方法会对您有所帮助。

那么,我们先来举个例子。让我们假设在这个例子中,您想要将 MyClass 中的初始整数值加倍并操纵它,但是您的约束是您不能更改 OriginalInterface 层次结构中的原始代码。因此,在这种情况下,您使用的是访问者模式。

为了实现这个目标,在下面的例子中,我将功能实现(即算法)从原始的类层次结构中分离出来。

类图

图 13-1 为类图。

img/395506_2_En_13_Fig1_HTML.jpg

图 13-1

类图

包资源管理器视图

图 13-2 显示了程序的高层结构。

img/395506_2_En_13_Fig2_HTML.jpg

图 13-2

包资源管理器视图

履行

下面是实现。

package jdp2e.visitor.demo;
interface OriginalInterface
{
    //The following method has a Visitor argument.
    void acceptVisitor(Visitor visitor);
}
class MyClass implements OriginalInterface
{
    //Here "myInt" is final.So, once initialized, it should not be changed.
    private final int myInt;
    public MyClass()
    {
        myInt=5;//Initial or default value
    }
    public int getMyInt()
    {
        return myInt;
    }

    @Override
    public void acceptVisitor(Visitor visitor)
    {
        visitor.visit(this);
    }
}

interface Visitor
{
    //The method to visit MyClass

    void visit(MyClass myClassObject);
}
class ConcreteVisitor implements Visitor
{
    @Override
    public void visit(MyClass myClassObject)
    {
        System.out.println("Current value of myInt="+ myClassObject.getMyInt());
        System.out.println("Visitor will make it double and display it.");
        System.out.println("Current value of myInt="+ 2*myClassObject.getMyInt());
        System.out.println("Exiting from Visitor.");
    }

}

public class VisitorPatternExample {

    public static void main(String[] args) {
        System.out.println("***Visitor Pattern Demo***\n");
        Visitor visitor = new ConcreteVisitor();
        MyClass myClass = new MyClass();
        myClass.acceptVisitor(visitor);
    }
}

输出

这是输出。

***Visitor Pattern Demo***

Current value of myInt=5
Visitor will make it double and display it.
Current value of myInt=10
Exiting from Visitor.

修改后的插图

您已经看到了访问者设计模式的一个非常简单的例子。但是当你将这种设计模式与组合模式结合起来时,你就可以发挥这种设计模式的真正威力了(参见第十一章)。因此,让我们来研究一个场景,在这个场景中,您需要将组合模式和访问者模式结合起来。

修改示例的关键特征

让我们回顾一下第十一章中的复合设计模式的例子。在这个例子中,有一个学院有两个不同的系。每个系都有一个系主任和多名教师(或教授/讲师)。每个团长向学院的校长报告。图 13-3 显示了我在那一章中讨论的树形结构。

img/395506_2_En_13_Fig3_HTML.jpg

图 13-3

复合设计示例的树结构

现在假设学院的校长想提升几个员工。让我们考虑教学经验是提升某人的唯一标准。理想情况下,高级教师和初级教师的标准应该不同。所以,我们假设初级教师的最低晋升标准是 12 年,高级教师的最低晋升标准是 15 年

要做到这一点,你需要引入一个新的领域,多年的经验。因此,当访问者从大学收集必要的信息时,它会显示出符合晋升条件的候选人。

访问者从原始学院结构中收集数据,而不对其进行任何修改,一旦收集过程结束,它将分析数据以显示预期的结果。为了直观地理解这一点,您可以按照接下来的图中的箭头进行操作。负责人在组织的最高层,所以你可以假设那个人不需要升职。

第一步

图 13-4 为步骤 1。

img/395506_2_En_13_Fig4_HTML.jpg

图 13-4

第一步

第二步

图 13-5 为步骤 2。

img/395506_2_En_13_Fig5_HTML.jpg

图 13-5

第二步

第三步

图 13-6 为步骤 3。

img/395506_2_En_13_Fig6_HTML.jpg

图 13-6

第三步

第四步

图 13-7 为步骤 4。

img/395506_2_En_13_Fig7_HTML.jpg

图 13-7

第四步

第五步

图 13-8 为步骤 5。

img/395506_2_En_13_Fig8_HTML.jpg

图 13-8

第五步

诸如此类…

在下面的实现中,有如下代码块。

@Override
public void acceptVisitor(Visitor visitor)
{
    visitor.visitTheElement(this);
}

从这个结构中,可以看出两个重要的东西。

  • 每当访问者访问一个特定的对象时,该对象就调用访问者的一个方法,并将自己作为一个参数传递。访问者拥有特定于特定类的方法。

  • 具体 employee 类(CompositeEmployee,SimpleEmployee)的对象只实现 acceptVisitor(Visitor visitor)方法。这些对象知道它应该调用的访问者的特定方法(在这里作为参数传递)。

那么,我们开始吧。

修改的类图

图 13-9 显示了修改后的类图。

img/395506_2_En_13_Fig9_HTML.jpg

图 13-9

修改的类图

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

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

img/395506_2_En_13_Fig10_HTML.jpg

图 13-10

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

修改的实现

下面是修改后的实现。

package jdp2e.visitor.modified.demo;
import java.util.ArrayList;
import java.util.List;

interface Employee
{
    void printStructures();
    //The following method has a Visitor argument.
    void acceptVisitor(Visitor visitor);
}
//Employees who have Subordinates
class CompositeEmployee implements Employee
{
    private String name;
    private String dept;
    //New field for this example.
    //It is tagged with "final", so visitor cannot modify it.
    private final int  yearsOfExperience;
    //The container for child objects
    private List<Employee> controls;
    // Constructor
    public CompositeEmployee(String name,String dept, int experience)
    {
        this.name = name;
        this.dept = dept;
        this.yearsOfExperience = experience;
        controls = new ArrayList<Employee>();
    }
    public void addEmployee(Employee e)
    {
        controls.add(e);
    }
    public void removeEmployee(Employee e)
    {
        controls.remove(e);
    }
    // Gets the name
    public String getName()
    {
        return name;
    }
    // Gets the department name

    public String getDept()
    {
        return dept;
    }

    // Gets the yrs. of experience

    public int getExperience()
    {
        return yearsOfExperience;

    }
    public List<Employee> getControls()
    {
        return this.controls;
    }
    @Override
    public void printStructures()
    {
        System.out.println("\t" + getName() + " works in  " + getDept() + " Experience :" + getExperience() + " years");
        for(Employee e: controls)
        {
            e.printStructures();
        }

    }
    @Override
    public void acceptVisitor(Visitor visitor)
    {
        visitor.visitTheElement(this);
    }
}
class SimpleEmployee implements Employee
{
    private String name;
    private String dept;
    //New field for this example
    private int yearsOfExperience;
    //Constructor
    public SimpleEmployee(String name, String dept, int experience)
    {
        this.name = name;
        this.dept = dept;
        this.yearsOfExperience = experience;
    }
    // Gets the name

    public String getName()
    {
        return name;
    }
    // Gets the department name
    public String getDept()
    {
        return this.dept;

    }
    // Gets the yrs. of experience
    public int getExperience()
    {
        return yearsOfExperience;
    }
    @Override
    public void printStructures()
    {
        System.out.println("\t\t" + getName() + " works in  " + getDept() + " Experience :" + getExperience() + " years");
    }
    @Override
    public void acceptVisitor(Visitor visitor)
    {
        visitor.visitTheElement(this);
    }
}

interface Visitor
{
    void visitTheElement(CompositeEmployee employees);
    void visitTheElement(SimpleEmployee employee);
}
class ConcreteVisitor implements Visitor
{
    @Override
    public void visitTheElement(CompositeEmployee employee)
    {
        //We'll promote them if experience is greater than 15 years

        boolean eligibleForPromotion = employee.getExperience() > 15 ? true : false;
        System.out.println("\t\t" + employee.getName() + " from  " + employee.getDept() + " is eligible for promotion? " + eligibleForPromotion);
    }
    @Override
    public void visitTheElement(SimpleEmployee employee)
    {
        //We'll promote them if experience is greater than 12 years
        boolean eligibleForPromotion = employee.getExperience() > 12 ? true : false;
        System.out.println("\t\t" + employee.getName() + " from  " + employee.getDept() + " is eligible for promotion? " + eligibleForPromotion);
    }

}

public class ModifiedVisitorPatternExample {

    public static void main(String[] args) {
        System.out.println("***Visitor Pattern combined with Composite Pattern Demo***\n");
        /*2 teachers other than HOD works in
         Mathematics department*/
        SimpleEmployee mathTeacher1 = new SimpleEmployee("Math Teacher-1","Maths",13);
        SimpleEmployee mathTeacher2 = new SimpleEmployee("Math Teacher-2","Maths",6);

        /* 3 teachers other than HOD works in
          Computer Sc. department*/
        SimpleEmployee cseTeacher1 = new SimpleEmployee("CSE Teacher-1","Computer Sc.",10);
        SimpleEmployee cseTeacher2 = new SimpleEmployee("CSE Teacher-2", "Computer Sc.",13);
        SimpleEmployee cseTeacher3 = new SimpleEmployee("CSE Teacher-3", "Computer Sc.",7);

        //The College has 2 Head of Departments-One from Mathematics, One from Computer Sc.
        CompositeEmployee hodMaths = new CompositeEmployee("Mrs.S.Das(HOD-Maths)","Maths",14);
        CompositeEmployee hodCompSc = new CompositeEmployee("Mr. V.Sarcar(HOD-CSE)", "Computer Sc.",16);

        //Principal of the college

        CompositeEmployee principal = new CompositeEmployee("Dr.S.Som(Principal)","Planning-Supervising-Managing",20);

        //Teachers of Mathematics directly reports to HOD-Maths
        hodMaths.addEmployee(mathTeacher1);
        hodMaths.addEmployee(mathTeacher2);

        //Teachers of Computer Sc. directly reports to HOD-CSE

        hodCompSc.addEmployee(cseTeacher1);
        hodCompSc.addEmployee(cseTeacher2);
        hodCompSc.addEmployee(cseTeacher3);

        /*Principal is on top of college.HOD -Maths and Comp. Sc directly reports to him */
        principal.addEmployee(hodMaths);
        principal.addEmployee(hodCompSc);

        System.out.println("\n Testing the overall structure");
        //Prints the complete structure
        principal.printStructures();

        System.out.println("\n***Visitor starts visiting our composite structure***\n");
        System.out.println("---The minimum criteria for promotion is as follows ---");
        System.out.println("--Junior Teachers-12 years and Senior teachers-15 years.--\n");
        Visitor myVisitor = new ConcreteVisitor();
        /*
         * At first, we are building a container for employees who will be considered for promotion

.
         Principal is holding the highest position.So, he is not considered for promotion.
         */
        List<Employee> employeeContainer= new ArrayList<Employee>();
        //For employees who directly reports to Principal
        for (Employee e : principal.getControls())
        {
            employeeContainer.add(e);
        }
        //For employees who directly reports to HOD-Maths
        for (Employee e : hodMaths.getControls())
        {
            employeeContainer.add(e);
        }
        //For employees who directly reports to HOD-Comp.Sc
        for (Employee e : hodCompSc.getControls())
        {
            employeeContainer.add(e);
        }
        //Now visitor can traverse through the container.
        for (Employee e :employeeContainer)
        {
            e.acceptVisitor(myVisitor);
        }
    }

}

修改输出

这是修改后的输出。

***Visitor Pattern combined with Composite Pattern Demo***

 Testing the overall structure
 Dr.S.Som(Principal) works in  Planning-Supervising-Managing Experience :20 years
   Mrs.S.Das(HOD-Maths) works in  Maths Experience :14 years
     Math Teacher-1 works in  Maths Experience :13 years
     Math Teacher-2 works in  Maths Experience :6 years
   Mr. V.Sarcar(HOD-CSE) works in  Computer Sc. Experience :16 years
     CSE Teacher-1 works in  Computer Sc. Experience :10 years
     CSE Teacher-2 works in  Computer Sc. Experience :13 years
     CSE Teacher-3 works in  Computer Sc. Experience :7 years

***Visitor starts visiting our composite structure***

---The minimum criteria for promotion is as follows ---

--Junior Teachers-12 years and Senior teachers-15 years.--

   Mrs.S.Das(HOD-Maths) from  Maths is eligible for promotion? false
  Mr. V.Sarcar(HOD-CSE) from  Computer Sc. is eligible for promotion? true
   Math Teacher-1 from  Maths is eligible for promotion? true
   Math Teacher-2 from  Maths is eligible for promotion? false
  CSE Teacher-1 from  Computer Sc. is eligible for promotion? false
  CSE Teacher-2 from  Computer Sc. is eligible for promotion? true
  CSE Teacher-3 from  Computer Sc. is eligible for promotion? false

问答环节

  1. 什么时候应该考虑实现访问者设计模式?

    您需要向一组对象添加新的操作,而不改变它们对应的类。这是实现访问者模式的主要目标之一。当运营经常变化时,这种方法可以成为你的救星。在这种模式中,封装不是主要考虑的问题。

    如果需要改变各种操作的逻辑,只需通过 visitor 实现即可。

  2. 这种模式有什么缺点吗?

    • 封装不是它的主要关注点。所以,你可以通过使用访问者来打破封装的力量。

    • 如果您需要频繁地向现有架构添加新的具体类,那么访问者层次结构将变得难以维护。例如,假设您现在想在原来的层次结构中添加另一个具体的类。在这种情况下,您需要相应地修改 visitor 类的层次结构来实现这个目的。

  3. 你为什么说访问者类会违反封装?

    在我们的示例中,我测试了一个非常简单的 visitor 设计模式,在这个模式中,我通过 visitor 类显示了 myInt 的一个更新的整数值。此外,在许多情况下,您可能会看到访问者需要在一个复合结构周围移动,以从其中收集信息,然后它可以修改这些信息。所以,当你提供这种支持时,你违背了封装的核心目标。

  4. 为什么这种模式会损害封装?

    在这里,您对一组不同种类的对象执行一些操作。但是您的约束是您不能改变它们对应的类。因此,您的访问者需要一种方法来访问这些对象的成员。因此,您需要向访问者公开这些信息。

  5. 在修改后的实现的访问者接口中,您使用了方法重载的概念(即方法名相同)。这是强制性的吗?

    不。在我的《C# 中的设计模式》一书中,我在类似的上下文中使用了VisitCompositeElement()VisitLeafNode()这样的方法名。请记住,这些接口方法应该针对特定的类,比如 SimpleEmployee 或 CompositeEmployee。

  6. 假设在修改后的实现中,我添加了 Employee 的一个具体子类 UndefinedEmployee。我应该如何进行?我应该在访问者接口中有另一个特定的方法吗?

    Exactly. You need to define a new method that is specific to this new class. So, your interface may look like the following.

    interface Visitor
    {
        void visitTheElement(CompositeEmployee employees);
        void visitTheElement(SimpleEmployee employee);
        void visitTheElement(UndefinedEmployee employee);
    }
    
    

    稍后,您需要在具体的 visitor 类中实现这个新方法。

  7. 假设我需要在现有架构中支持新的操作。我应该如何处理访问者模式?

    对于每个新操作,创建一个新的 visitor 子类,并在其中实现操作。然后,按照前面示例中所示的方式访问您现有的结构。

  8. 在客户端代码中,你首先创建了一个雇员容器,然后它开始访问。创建这样的结构是强制性的吗?

    不。它只是帮助客户一次顺利访问。如果您没有创建任何这样的结构,您总是可以单独调用它。

十四、观察者模式

本章涵盖了观察者模式。

GoF 定义

定义对象之间的一对多依赖关系,这样当一个对象改变状态时,它的所有依赖对象都会得到通知并自动更新。

概念

在这个模式中,有许多观察者(对象)在观察一个特定的主体(也是一个对象)。观察者将自己注册到一个主题,以便在该主题内部发生变化时获得通知。当他们对该主题失去兴趣时,他们只是从该主题中注销。它也被称为发布-订阅模式。整个想法可以总结如下:使用这个模式,一个对象(subject)可以同时向多个观察者(一组对象)发送通知。

您可以在下面的图表中可视化这些场景。

**第一步。**观察者正在请求一个主题获得通知(见图 14-1 )。

img/395506_2_En_14_Fig1_HTML.jpg

图 14-1

第一步

**第二步。**主体同意请求,连接建立(见图 14-2 )。

img/395506_2_En_14_Fig2_HTML.jpg

图 14-2

第二步

第三步。主体向注册用户发送通知(在主体发生典型事件并希望通知其他人的情况下)(见图 14-3 )。

img/395506_2_En_14_Fig3_HTML.jpg

图 14-3

第三步

**第四步(可选)。**观察器 2 不想得到进一步的通知,所以它注销自己(见图 14-4 )。

img/395506_2_En_14_Fig4_HTML.jpg

图 14-4

第四步

**第五步。**从现在开始,只有观察者 1 和观察者 3 会收到受试者的通知(见图 14-5 )。

img/395506_2_En_14_Fig5_HTML.jpg

图 14-5

第五步

真实世界的例子

想想一个在社交媒体上有很多粉丝的名人。这些追随者中的每一个都想要他们最喜爱的名人的所有最新消息。所以,他们追随名人直到兴趣减退。当他们失去兴趣时,他们就不再关注那个名人了。你可以把这些粉丝或追随者看作观察者,把名人看作主体。

计算机世界的例子

在计算机科学领域,考虑一个简单的基于 UI 的例子。让我们假设这个 UI 连接到一个数据库。用户可以通过该 UI 执行查询,在搜索数据库后,结果将在 UI 中返回。在这里,您将 UI 与数据库隔离开来,如果数据库发生变化,UI 会得到通知,并根据变化更新其显示。

为了简化这个场景,假设您是组织中负责维护特定数据库的人。每当数据库内部发生变化时,您都需要一个通知,以便在必要时采取措施。

注意

通常,您会在事件驱动软件中看到这种模式的存在。像 C#、Java 等现代语言都内置了对遵循这种模式处理事件的支持。这些结构让你的生活变得简单。

在 Java 中,你可以看到事件监听器的使用。这些听众只是观察者。在 Java 中,你有一个现成的类叫做 Observable,它可以有多个观察者。这些观察者需要实现观察者接口。观察者接口有一个“更新”方法:void update(Observable o,Object arg)。每当被观察对象发生变化时,就会调用此方法。您的应用程序需要调用可观察对象的 notifyObservers 方法来通知观察者的变化。addObserver(Observer o)和 deleteObserver(Observer o)方法添加或删除观察器,类似于前面讨论的 register 和 unregister 方法。可以从 docs 了解更多。甲骨文。com/javase/8/docs/API/Java/util/Observer。html docs。甲骨文。com/ javase/ 8/ docs/ api/ index。html?java/ util/ Observable。html

如果你熟悉。NET 框架,你可以看到在 C# 中,你有通用系统。可观察的和系统。IObserver 接口,其中泛型类型参数提供通知。

说明

让我们考虑下面的例子,并对输出进行事后分析。我创建了三个观察者和一个主题。该主题为其所有注册用户维护一个列表。我们的观察者希望在主题中的标志值发生变化时收到通知。在输出中,您发现当标志值分别更改为 5、50 和 100 时,观察器会收到通知。但是其中一个人在标志值更改为 50 时没有收到任何通知,因为此时他不是 subject 中的注册用户。但最终,他会收到通知,因为他再次注册了自己。

在这个实现中,register()unregister()notifyRegisteredUsers()方法有它们典型的含义。register()方法在主题的通知列表中注册一个观察者,unregister()方法从主题的通知列表中删除一个观察者,notifyRegisteredUsers()在主题中发生典型事件时通知所有注册的用户。

类图

图 14-6 显示了类图。

img/395506_2_En_14_Fig6_HTML.jpg

图 14-6

类图

包资源管理器视图

图 14-7 显示了程序的高层结构。

img/395506_2_En_14_Fig7_HTML.jpg

图 14-7

包资源管理器视图

履行

下面是实现。

package jdp2e.observer.demo;

import java.util.*;

interface Observer
{
    void update(int updatedValue);
}
class ObserverType1 implements Observer
{
    String nameOfObserver;
    public ObserverType1(String name)
    {
        this.nameOfObserver = name;
    }
    @Override
    public void update(int updatedValue)
    {
        System.out.println( nameOfObserver+" has received an alert: Updated myValue in Subject is: "+ updatedValue);
    }
}
class ObserverType2 implements Observer
{
    String nameOfObserver;
    public ObserverType2(String name)
    {
        this.nameOfObserver = name;
    }
    @Override
    public void update(int updatedValue)
    {
        System.out.println( nameOfObserver+" has received an alert: The current value of myValue in Subject is: "+ updatedValue);
    }
}

interface SubjectInterface
{
    void register(Observer anObserver);
    void unregister(Observer anObserver);
    void notifyRegisteredUsers(int notifiedValue);
}
class Subject implements SubjectInterface
{
    private int flag;
    public int getFlag()
    {
        return flag;
    }
    public void setFlag(int flag)
    {
        this.flag = flag;
        //Flag value changed. So notify registered users/observers.
        notifyRegisteredUsers(flag);
    }
    List<Observer> observerList = new ArrayList<Observer>();
    @Override
    public void register(Observer anObserver) {
        observerList.add(anObserver);

    }
    @Override
    public void unregister(Observer anObserver) {
        observerList.remove(anObserver);
    }
    @Override
    public void notifyRegisteredUsers(int updatedValue)
    {
        for (Observer observer : observerList)
            observer.update(updatedValue); 

    }
}
public class ObserverPatternExample {

    public static void main(String[] args) {
        System.out.println(" ***Observer Pattern Demo***\n");
        //We have 3 observers- 2 of them are ObserverType1, 1 of them is of //ObserverType2
        Observer myObserver1 = new ObserverType1("Roy");
        Observer myObserver2 = new ObserverType1("Kevin");
        Observer myObserver3 = new ObserverType2("Bose");
        Subject subject = new Subject();
        //Registering the observers-Roy,Kevin,Bose
        subject.register(myObserver1);
        subject.register(myObserver2);
        subject.register(myObserver3);
        System.out.println(" Setting Flag = 5 ");
        subject.setFlag(5);
        //Unregistering an observer(Roy))
        subject.unregister(myObserver1);
        //No notification this time Roy. Since it is unregistered.
        System.out.println("\n Setting Flag = 50 ");
        subject.setFlag(50);
        //Roy is registering himself again
        subject.register(myObserver1);
        System.out.println("\n Setting Flag = 100 ");
        subject.setFlag(100);
    }
}

输出

这是输出。

***Observer Pattern Demo***

 Setting Flag = 5
Roy has received an alert: Updated myValue in Subject is: 5
Kevin has received an alert: Updated myValue in Subject is: 5
Bose has received an alert: The current value of myValue in Subject is: 5

 Setting Flag = 50
Kevin has received an alert: Updated myValue in Subject is: 50
Bose has received an alert: The current value of myValue in Subject is: 50

 Setting Flag = 100
Kevin has received an alert: Updated myValue in Subject is: 100
Bose has received an alert: The current value of myValue in Subject is: 100

Roy has received an alert: Updated myValue in Subject is: 100

分析

最初,所有三名观察者——罗伊、凯文和博斯——都注册了来自该对象的通知。所以,在最初阶段,他们都收到了通知。在某种程度上,罗伊变得对通知不感兴趣,所以他取消了自己的注册。因此,从这个时候开始,只有 Kevin 和 Bose 收到通知(注意,当我将标志值设置为 50 时)。但罗伊改变了主意,他重新注册了自己,以获得来自该主题的通知。所以,在最后一个案例中,所有人都收到了受试者的通知。

问答环节

  1. 如果我只有一个观察者,那么我可能不需要设置界面。这是正确的吗?

    是的。但是如果你想遵循纯面向对象的编程准则,编程到一个接口/抽象类总是被认为是一个更好的实践。所以,比起具体的类,你应该更喜欢接口(或者抽象类)。此外,通常,您有多个观察者,并且您希望他们按照约定以系统的方式实现方法。你从这种设计中获益。

  2. 同一个应用程序中可以有不同类型的观察者吗?

    是的。这就是为什么我和来自两个不同班级的三个观察者一起玩。但是你不应该对每个观察者都有这种感觉;您需要创建一个不同的类。

    考虑一个真实的场景。当公司发布或更新新软件时,公司业务合作伙伴和购买该软件的客户会收到通知。在这种情况下,业务伙伴和客户是两种不同类型的观察者。

  3. 我可以在运行时添加或删除观察者吗?

    是的。在我们的程序开始时,罗伊注册获得通知;然后他注销了,后来又重新注册了。

  4. 观察者模式和责任链模式之间似乎有相似之处。这是正确的吗?

    In an observer pattern, all registered users get notifications at the same time, but in a chain of responsibility pattern, objects in the chain are notified one by one, and this process continues until the object fully handles the notification. Figure 14-8 and Figure 14-9 summarize the differences.

    img/395506_2_En_14_Fig9_HTML.jpg

    图 14-9

    责任链模式的基本工作流程

    img/395506_2_En_14_Fig8_HTML.jpg

    图 14-8

    观察者模式的基本工作流程

  5. 此模型支持一对多关系。这是正确的吗?

    是的。由于一个主题可以向多个观察者发送通知,这种依赖关系显然是一对多的关系。

  6. 如果你已经有了这些现成的结构,为什么还要编写自己的代码呢?

    根据您的偏好更改现成的构造并不总是容易的。在许多情况下,您根本无法更改内置功能。当您尝试自己实现这个概念时,您可能会更好地理解如何使用那些现成的构造。

    Consider some typical scenarios.

    • 在 Java 中,Observable 是一个具体的类。它不实现接口。因此,您不能创建自己的实现来使用 Java 的内置 Observer API。

    • Java 不允许多重继承。所以,当你必须扩展 Observable 类时,你必须记住这个限制。这可能会限制重用潜力。

    • 可观察对象中setChanged方法的签名如下:protected void setChanged()。这意味着要使用它,你需要子类化 Observable 类。这违反了一个关键的设计原则,基本上就是说要优先组合而不是继承。

  7. 观察者模式的主要优势 是什么?

    • 主体和它的注册用户(观察者)正在构建一个松散耦合的系统。他们不需要明确地相互了解。

    • 在通知列表中添加或删除观察者时,不需要修改主题。

    • 此外,您可以随时单独添加或删除观察者。

  8. 与观察者模式相关的主要挑战是什么?

    • 毫无疑问,当您处理任何基于事件的机制时,内存泄漏是最大的问题。在这种情况下,自动垃圾收集器可能并不总是对您有所帮助。您可以考虑这样一种情况,即取消注册/取消注册操作没有正确执行。

    • 通知的顺序不可靠。

    • Java 对观察者模式的内置支持有一些关键的限制,我在前面已经讨论过了。(重温问题 6 的答案。)其中一个强迫你更喜欢继承而不是组合,所以它显然违反了一个更喜欢相反的关键设计原则。

十五、策略模式

本章涵盖了策略模式。

GoF 定义

定义一系列算法,封装每一个算法,并使它们可以互换。策略让算法独立于使用它的客户端而变化。

概念

假设有一个应用程序,其中有多个算法,每个算法都可以执行特定的任务。客户端可以动态地选择这些算法中的任何一个来满足其当前的需求。

策略模式建议您在单独的类中实现这些算法。当你将一个算法封装在一个单独的类中时,你称之为策略。使用策略对象的对象通常被称为上下文对象。这些“算法”在一些应用中也被称为行为

真实世界的例子

通常在足球比赛结束时,如果 A 队以 1 比 0 领先 B 队,他们不会进攻,而是采取防守来保持领先。另一方面,B 队全力以赴去扳平比分。

计算机世界示例

假设你有一个整数列表,你想对它们进行排序。你通过使用各种算法来做到这一点;例如,冒泡排序、合并排序、快速排序、插入排序等等。所以,你可以有很多不同的排序算法。现在,您可以在单独的类中实现这些变体(算法),并在客户端代码中传递这些类的对象来对整数列表进行排序。

注意

在这种情况下,可以考虑 java.util.Comparator 接口。您可以实现这个接口,并提供具有不同算法的比较器的多个实现,以便使用 compare() 方法进行各种比较。该比较结果可以进一步用于各种分类技术。在这种情况下,比较器接口扮演策略接口的角色。

说明

在您继续之前,让我们记住以下几点。

  • 策略模式鼓励你使用对象组合而不是子类化。所以,它建议你不要在不同的子类中覆盖父类行为。相反,您将这些行为放在共享一个公共接口的单独的类中(称为策略)。

  • 客户端类只决定使用哪种算法;上下文类并不决定这一点。

  • 上下文对象包含策略对象接口类型的引用变量。所以,你可以通过改变情境中的策略来获得不同的行为。

在下面的实现中,Vehicle 类是一个扮演上下文角色的抽象类。船和飞机是 Vehicle 类的两个具体实现。你知道它们与不同的行为相关联:一个通过水传播,另一个通过空气传播。

这些行为被分为两个具体的类:空运和水运。这些类共享一个公共接口 TransportMedium。因此,这些具体的类扮演着策略类的角色,不同的行为通过transport()方法实现来反映。

在 Vehicle 类中,有一个方法叫做showTransportMedium()。使用这种方法,我将任务委托给相应的行为类。因此,一旦选择了策略,就可以调用相应的行为。请注意,在 Vehicle 类中,有一个名为commonJob()的方法,它不应该在将来发生变化,因此它的行为不会被视为易变行为。

类图

图 15-1 为类图。

img/395506_2_En_15_Fig1_HTML.jpg

图 15-1

类图

包资源管理器视图

图 15-2 显示了程序的高层结构。

img/395506_2_En_15_Fig2_HTML.jpg

图 15-2

包资源管理器视图

履行

下面是实现。

// Vehicle.java

package jdp2e.strategy.demo;

//Context class
public abstract class Vehicle
{
    /*A context object contains reference variable/s for the strategy object/s interface type.*/
    TransportMedium transportMedium;
    public Vehicle()
    {

    }
    public void showTransportMedium()
    {
        //Delegate the task to the //corresponding behavior class.
        transportMedium.transport();
    }
    //The code that does not vary.
    public void commonJob()
    {
        System.out.println("We all can be used to transport");
    }
    public abstract void showMe();

}

// Boat.java

package jdp2e.strategy.demo;

public class Boat extends Vehicle
{
    public Boat()
    {
        transportMedium= new WaterTransport();
    }
    @Override
    public void showMe() {
        System.out.println("I am a boat.");

    }
}
// Aeroplane.java

package jdp2e.strategy.demo;

public class Aeroplane extends Vehicle
{
    public Aeroplane()
    {
        transportMedium= new AirTransport();
    }

    @Override
    public void showMe() {
        System.out.println("I am an aeroplane.");

    }
}
// TransportMedium.java
package jdp2e.strategy.demo;

public interface TransportMedium
{
    public void transport();
}
//WaterTransport.java
package jdp2e.strategy.demo;
//This class represents an algorithm/behavior

.
public class WaterTransport implements TransportMedium
{
    @Override
    public void transport()
    {
        System.out.println("I am transporting in water.");
    }
}
//AirTransport.java
package jdp2e.strategy.demo;
//This class represents an algorithm/behavior.
public class AirTransport implements TransportMedium
{
    @Override
    public void transport()
    {
        System.out.println("I am transporting in air.");
    }
}

// StrategyPatternExample.java

package jdp2e.strategy.demo;

//Client code

public class StrategyPatternExample {

    public static void main(String[] args) {
        System.out.println("***Strategy Pattern Demo***");
        Vehicle vehicleContext=new Boat();
        vehicleContext.showMe();
        vehicleContext.showTransportMedium();
        System.out.println("________");

        vehicleContext=new Aeroplane();
        vehicleContext.showMe();
        vehicleContext.showTransportMedium();

    }

}

输出

这是输出。

***Strategy Pattern Demo***
I am a boat.
I am transporting in water.
________
I am an aeroplane.
I am transporting in air.

问答环节

  1. 你为什么要通过避免这些行为的简单子类化来使这个例子复杂化呢?

    在面向对象的编程中,您可能更喜欢使用多态性的概念,这样您的代码就可以在运行时挑选预期的对象(在不同的对象类型中),而不改变您的代码。

    当你熟悉设计模式时,大多数情况下,你更喜欢组合而不是继承。

    策略模式帮助您将组合与多态性结合起来。我们来考察一下这背后的原因。

    It is assumed that you try to use the following guidelines in any application you write:

    • 将变化很大的代码与没有变化的代码部分分开。

    • 尽量保持不同部件独立(便于维护)。

    • 尽可能地重复使用它们。

遵循这些指导方针,我使用了组合来提取和封装代码的易变/可变部分,以便整个任务可以轻松处理,并且您可以重用它们。

但是当你使用继承时,你的父类可以提供一个默认的实现,然后派生类修改它(Java 称之为覆盖它)。下一个派生类可以进一步修改实现,所以您基本上是将任务分散到不同的级别,这可能会在将来导致严重的维护和可扩展性问题。我们来考察这样一个案例。

让我们假设您的 vehicle 类具有以下结构。

abstract class Vehicle

{
    //Default implementation
    public void showTransportMedium()
    {
        System.out.println("I am transporting in air.");
    }
    //The code that does not vary.
    public void commonJob()
    {
        System.out.println("We all can be used to transport");
    }
    public abstract void showMe();
}

所以,做一个具体的车辆实现,像这样:

class Aeroplane extends Vehicle
{
    @Override
    public void showMe() {
        System.out.println("I am an aeroplane.");

    }
}

并在客户端类中使用以下代码行。

Aeroplane aeroplane=new Aeroplane();
aeroplane.showMe();
aeroplane.showTransportMedium();

您将收到以下输出:

I am an aeroplane.
I am transporting in air.

到目前为止,看起来不错。现在假设您已经引入了另一个类 Boat,如下所示。

class Boat extends Vehicle

{
    @Override
    public void showMe() {
        System.out.println("I am a boat.");

    }
}

在客户端类中使用以下代码行(新行以粗体显示)。

Aeroplane aeroplane=new Aeroplane();
aeroplane.showMe();
aeroplane.showTransportMedium();

Boat boat=new Boat();

boat.showMe();

boat.showTransportMedium();

您会收到以下输出。

I am an aeroplane.
I am transporting in air.

I am a boat.

I am transporting in air.

你可以看到你的船正在向空中移动。为了防止这种糟糕的情况,您需要适当地覆盖它。

现在进一步假设你需要引入另一个类,快艇,它也可以在水中高速运输。你需要警惕这样的情况:

class Boat extends Vehicle

{
    @Override
    public void showMe()
    {
        System.out.println("I am a boat.");

    }
    @Override
    public void showTransportMedium() {
        System.out.println("I am transporting in water.");

    }
}
class SpeedBoat extends Vehicle
{
    @Override
    public void showMe() {
        System.out.println("I am a speedboat.");

    }
    @Override
    public void showTransportMedium() {
        System.out.println("I am transporting in water with high speed.");
    }
}

您可以看到,如果您将任务分散到不同的类(及其子类)中,从长远来看,维护会变得非常昂贵。如果您想经常适应类似的变化,您会经历很多痛苦,因为您需要在每种情况下不断更新showTransportMedium()方法。

  1. 如果是这种情况,您可以创建一个单独的接口 TransportInterface,并将 showTransportMedium()方法放在该接口中。现在,任何想要获取该方法的类也可以实现该接口。这种理解正确吗?

    Yes, you can do that. But this is what the code looks like:

    abstract class Vehicle
    {
        //The code that does not vary.
        public void commonJob()
        {
            System.out.println("We all can be used to transport");
        }
        public abstract void showMe();
    }
    interface TransportInterface
    {
        void showTransportMedium();
    }
    class Aeroplane extends Vehicle implements TransportInterface
    {
        @Override
        public void showMe() {
            System.out.println("I am an aeroplane.");
    
        }
        @Override
        public void showTransportMedium() {
            System.out.println("I am transporting in air.");
        }
    }
    class Boat extends Vehicle implements TransportInterface
    {
        @Override
        public void showMe()
        {
            System.out.println("I am a boat.");
    
        }
    
        @Override
        public void showTransportMedium() {
            System.out.println("I am transporting in water.");
    
        }
    }
    
    

    您可以看到每个类及其子类可能需要为showTransportMedium()方法提供自己的实现。所以,你不能重用你的代码,这和继承一样糟糕。

  2. 你能在你的实现中修改运行时的默认行为吗?

    Yes, you can. Let’s introduce a special vehicle that can transport in both water and air, as follows.

    public class SpecialVehicle extends Vehicle
    {
        public SpecialVehicle()
        {
            //Initialized with AirTransport
            transportMedium= new AirTransport();
        }
    
        @Override
        public void showMe()
        {
            System.out.println("I am a special vehicle who can transport both in air and water.");
        }
    }
    
    

    And add a setter method in the Vehicle class(changes are shown in bold).

    //Context class
    public abstract class Vehicle
    {
        //A context object contains reference variable/s
        //for the strategy object/s interface type
        TransportMedium transportMedium;
        public Vehicle()
        {
    
        }
        public void showTransportMedium()
        {
            //Delegate the task to the corresponding behavior class.
            transportMedium.transport();
        }
        //The code that does not vary.
        public void commonJob()
        {
            System.out.println("We all can be used to transport");
        }
        public abstract void showMe();
    
        //Additional code to explain the answer of question no 3 in
        //the "Q&A session"
    
        public void setTransportMedium(TransportMedium transportMedium)
        {
            this.transportMedium=transportMedium;
        }
    
    }
    
    

    To test this, add a few lines of code in the client class, as well.

    //Client code
    public class StrategyPatternExample {
    
        public static void main(String[] args) {
            System.out.println("***Strategy Pattern Demo***");
            Vehicle vehicleContext=new Boat();
            vehicleContext.showMe();
            vehicleContext.showTransportMedium();
            System.out.println("________");
    
            vehicleContext=new Aeroplane();
            vehicleContext.showMe();
            vehicleContext.showTransportMedium();
            System.out.println("________");
    
            //Additional code to explain the answer of question no
            //3 in the "Q&A session"
            vehicleContext=new SpecialVehicle();
            vehicleContext.showMe();
            vehicleContext.showTransportMedium();
            System.out.println("- - - - -");
            //Changing the behavior of Special vehicle
            vehicleContext.setTransportMedium(new WaterTransport());
            vehicleContext.showTransportMedium();
    
        }
    }
    
    

    Now if you execute this modified program, you get the following output.

    ***Strategy Pattern Demo***
    ***Strategy Pattern Demo***
    I am a boat.
    I am transporting in water.
    ________
    I am an aeroplane.
    I am transporting in air.
    ________
    
    I am a special vehicle who can transport both in air and water.
    
    I am transporting in air.
    
    - - - - -
    
    I am transporting in water.
    
    

    初始行为在稍后阶段被动态修改。

  3. 可以用抽象类代替接口吗?

    是的。在某些情况下,您可能希望将常见行为放在抽象类中,这是合适的。我在关于构建器模式的“问答环节”中详细讨论了它。

  4. 使用策略设计模式的关键 优势 是什么?

    • 这种模式使你的类独立于算法。这里,一个类在运行时动态地将算法委托给策略对象(封装了算法)。所以,你可以简单地说,算法的选择在编译时是不受约束的。

    • 更容易维护您的代码库。

    • 它很容易扩展。(在这种情况下,请参考对问题 2 和 3 的答复。)

  5. 与策略设计模式相关的主要挑战是什么?

    • 上下文类的添加导致我们的应用程序中有更多的对象。

    • 应用程序的用户必须了解不同的策略;否则,输出可能会让他们大吃一惊。因此,在客户端代码和不同策略的实现之间存在着紧密的耦合。

    • 当您引入一个新的行为/算法时,您可能还需要更改客户端代码。

十六、模板方法模式

本章涵盖了模板方法模式。

GoF 定义

在操作中定义算法的框架,将一些步骤推迟到子类。模板方法允许子类在不改变算法结构的情况下重新定义算法的某些步骤。

概念

在模板方法中,定义算法的最小或基本结构。然后你将一些责任委托给子类。关键的意图是你可以重新定义算法的某些步骤,但是这些改变不应该影响算法的基本流程。

因此,当您实现一个多步算法并且希望允许通过子类进行定制时,这种设计模式非常有用。

真实世界的例子

假设你正在一家餐馆订购比萨饼。对于厨师来说,披萨的基本准备是一样的;他根据顾客的选择加入了一些最终的配料。例如,你可以选择素食比萨饼或非素食比萨饼。你也可以选择培根、洋葱、额外的奶酪、蘑菇等配料。厨师根据你的喜好准备最后的成品。

计算机世界的例子

假设你正在做一个设计工程课程的程序。让我们假设第一学期对所有流都是通用的。在随后的学期中,你需要根据课程在申请中添加新的论文/科目。在接下来的插图中,您会看到类似的情况。请记住,当您希望在应用程序中避免重复代码时,这种模式是有意义的。同时,您可能希望允许子类更改基类工作流程的某些特定细节,以在应用程序中提供不同的行为。

注意

java.util.AbstractSet 的 removeAll()方法是模板方法模式的一个示例。除此之外,java.util.AbstractMap 和 java.util.AbstractSet 类中还有许多非抽象方法,也可以看作是模板方法模式的例子。

说明

在下面的实现中,我假设每个工科学生需要完成数学课程,然后是软技能(该科目可能涉及沟通技能、性格特征、人员管理技能等)。)来获得学位。稍后你将在这些课程(计算机科学或电子学)中加入特殊的论文。

为此,在抽象类 BasicEngineering 中定义了 completeCourse()方法。我还将方法标记为 final,这样 BasicEngineering 的子类就不能覆盖 completeCourse()方法来改变课程完成的顺序。

另外两个具体的类——计算机科学和电子学是基础工程类的子类,它们根据自己的需要完成抽象方法 completeSpecialPaper()。

类图

图 16-1 为类图。

img/395506_2_En_16_Fig1_HTML.jpg

图 16-1

类图

包资源管理器视图

图 16-2 显示了程序的高层结构。

img/395506_2_En_16_Fig2_HTML.jpg

图 16-2

包资源管理器视图

履行

实现如下:

package jdp2e.templatemethod.demo;

abstract class BasicEngineering
{
    //Making the method final to prevent overriding.
    public final void completeCourse()
    {
        //The course needs to be completed in the following sequence
        //1.Math-2.SoftSkills-3.Special Paper
        //Common Papers:
        completeMath();
        completeSoftSkills();
        //Specialization Paper:
        completeSpecialPaper();
    }
    private void completeMath()
    {
        System.out.println("1.Mathematics");
    }
    private void completeSoftSkills()
    {
        System.out.println("2.SoftSkills");
    }
    public abstract void completeSpecialPaper();
}
class ComputerScience extends BasicEngineering

{
    @Override
    public void completeSpecialPaper() {
        System.out.println("3.Object-Oriented Programming");
    }
}
class Electronics extends BasicEngineering
{
    @Override
    public void completeSpecialPaper()
    {
        System.out.println("3.Digital Logic and Circuit Theory");
    }
}
public class TemplateMethodPatternExample {

    public static void main(String[] args) {
        System.out.println("***Template Method Pattern Demo***\n");
        BasicEngineering preferrredCourse = new ComputerScience();
        System.out.println("Computer Sc. course will be completed in following order:");
        preferrredCourse.completeCourse();
        System.out.println();
        preferrredCourse = new Electronics();
        System.out.println("Electronics course will be completed in following order:");
        preferrredCourse.completeCourse();
    }
}

输出

以下是输出结果:

***Template Method Pattern Demo***

Computer Sc. course will be completed in following order:
1.Mathematics
2.SoftSkills
3.Object-Oriented Programming

Electronics course will be completed in following order:
1.Mathematics
2.SoftSkills
3.Digital Logic and Circuit Theory

问答环节

  1. 在这个模式中,我看到子类可以根据他们的需要简单地重新定义方法。理解正确吗?

    是的。

  2. 在抽象类 BasicEngineering 中,只有一个方法是抽象的,其他两个方法是具体的方法。背后的原因是什么?

    这是一个简单的例子,只有 3 个方法,我希望子类只覆盖completeSpecialPaper()方法。其他方法是两个流共有的,它们不需要被子类覆盖。

  3. 考虑这样一种情况:假设你想在 BasicEngineering 类中添加更多的方法,但是当且仅当子类需要这些方法时,你才想使用它们,否则你将忽略它们。这种情况在一些博士课程中很常见,有些课程并不是对所有考生都是必修的。例如,如果学生具有某些资格,他/她可能不需要参加那些科目的讲座。可以用模板方法模式设计这种情况吗?

    是的,我们可以。基本上,你可能需要放一个钩子,它只是一个方法,可以帮助我们控制算法中的流程。

    为了展示这种设计的一个例子,我在BasicEngineering中增加了一个叫做AdditionalPapersNeeded()的方法。让我们假设计算机科学的学生需要完成这门课程,但是电子学的学生可以选择不要这篇论文。让我们检查程序和输出。

修改的实现

下面是修改后的实现。关键变化以粗体显示。

package jdp2e.templatemethod.questions_answers;

abstract class BasicEngineering
{
    //Making the method final to prevent overriding.
    public final void completeCourse()
    {
        //The course needs to be completed in the following sequence
        //1.Math-2.SoftSkills-3.Special Paper-4.Additional Papers(if any)
        //Common Papers:
        completeMath();
        completeSoftSkills();
        //Specialization Paper:
        completeSpecialPaper();
        if (isAdditionalPapersNeeded())
        {
            completeAdditionalPapers();
        }
    }

    private void completeMath()
    {
        System.out.println("1.Mathematics");
    }
    private void completeSoftSkills()
    {
        System.out.println("2.SoftSkills");
    }
    public abstract void completeSpecialPaper();
    private void completeAdditionalPapers()
    {
        System.out.println("4.Additional Papers are needed for this course.");
    }
    //By default, AdditionalPapers are needed for a course

.
    boolean isAdditionalPapersNeeded()
    {
        return true;
    }
}
class ComputerScience extends BasicEngineering
{
    @Override
    public void completeSpecialPaper()
    {
        System.out.println("3.Object-Oriented Programming");
    }
    //Additional papers are needed for ComputerScience
    //So, there is no change for the hook method.
}
class Electronics extends BasicEngineering
{
    @Override
    public void completeSpecialPaper()
    {
        System.out.println("3.Digital Logic and Circuit Theory");
    }
    //Overriding the hook method:
    //Indicating that AdditionalPapers are not needed for Electronics.
    @Override
    public  boolean isAdditionalPapersNeeded()
    {
        return false;
    }
}

public class TemplateMethodPatternModifiedExample {

    public static void main(String[] args) {
        System.out.println("***Template Method Pattern Modified Demo***\n");
        BasicEngineering preferrredCourse = new ComputerScience();
        System.out.println("Computer Sc. course will be completed in following order:");
        preferrredCourse.completeCourse();
        System.out.println();
        preferrredCourse = new Electronics();

        System.out.println("Electronics course will be completed in following order:");
        preferrredCourse.completeCourse();
    }
}

修改输出

下面是修改后的输出:

***Template Method Pattern Modified Demo***

Computer Sc. course will be completed in following order:
1.Mathematics
2.SoftSkills
3.Object-Oriented Programming

4.Additional Papers are needed for this course.

Electronics course will be completed in following order:
1.Mathematics
2.SoftSkills
3.Digital Logic and Circuit Theory

注意

你可能更喜欢另一种方法。例如,您可以在 BasicEngineering 中创建一个默认方法 isAdditionalPapersNeeded()。然后你可以覆盖电子类中的方法,然后你可以让方法体为空。但是,如果与前面的方法相比,这种方法看起来并没有更好。

  1. 看起来这个模式类似于 Builder 模式。理解正确吗?

    不。你不应该忘记核心意图;模板方法是一种行为设计模式,而构建器是一种创造设计模式。在构建模式中,客户/顾客是老板——他们可以控制算法的顺序。另一方面,在模板方法模式中,您是老板——您将代码放在一个中心位置,并且您只提供相应的行为(例如,注意 BasicEngineering 中的 completeCourse()方法,并查看课程完成顺序是如何在那里定义的)。所以,你对执行的流程有绝对的控制权。你也可以根据自己的需要修改你的模板,然后其他参与者需要跟随你。

  2. 使用模板设计模式的关键 优势 是什么?

    • 你可以控制算法的流程。客户端无法更改它们。(注意 completeCourse()在抽象类 BasicEngineering 中用 final 关键字标记)

    • 公共操作被放在一个集中的位置,例如,在一个抽象类中。子类可以只重新定义变化的部分,这样,你可以避免多余的代码。

  3. 模板设计模式的主要挑战是什么?

    • 客户端代码不能指导步骤的顺序(如果您需要这种方法,您可以遵循构建器模式)。

    • 子类可以覆盖父类中定义的方法(即在父类中隐藏原始定义),这可能违反 Liskov 替换原则,即:如果 S 是 T 的子类型,则 T 类型的对象可以被 S 类型的对象替换。您可以从以下链接了解详细信息: https://en.wikipedia.org/wiki/Liskov_substitution_principle

    • 子类越多,意味着代码越分散,维护越困难。

  4. 看起来子类可以覆盖基础工程中的其他父方法。理解正确吗?

    你可以这样做,但理想情况下,这不应该是你的意图。在这种模式中,您可能不希望完全覆盖所有的父方法来对子类进行彻底的修改。这样,它不同于简单的多态性。

十七、命令模式

本章介绍了命令模式。

GoF 定义

将请求封装为一个对象,从而允许您用不同的请求、队列或日志请求来参数化客户端,并支持可撤销的操作。

概念

这里您封装了一个方法调用过程。一般来说,有四个术语是相关联的:调用方客户端命令方接收方。命令对象可以以特定于接收方的类的方式调用接收方的方法。然后,接收方开始处理作业。命令对象被单独传递给调用程序对象来调用命令。客户端对象持有调用者对象和命令对象。客户端只决定执行哪些命令,然后将命令传递给调用程序对象(用于执行)。

真实世界的例子

当你用铅笔画东西的时候,你可能需要撤销(擦除和重画)一些部分来使它变得更好。

计算机世界的例子

真实世界的绘画场景适用于 Microsoft Paint。您可以使用菜单或快捷键在这些上下文中执行撤消/重做操作。

通常,您可以在编辑器或 IDE(集成开发环境)的菜单系统中观察到这种模式。所以,如果你想做一个需要支持撤销、多次撤销或者类似操作的应用,那么 command 模式可以成为你的救星。

微软在 Windows 演示基础(WPF)中使用了这种模式。位于 https://visualstudiomagazine.com/articles/2012/04/10/command-pattern-in-net.aspx 的在线资源详细描述道:“命令模式非常适合处理 GUI 交互。它工作得非常好,微软已经将其紧密集成到 Windows 演示基础(WPF)堆栈中。最重要的部分是系统的 ICommand 接口。窗户。输入命名空间。任何实现 ICommand 接口的类都可以用于通过通用 WPF 控件处理键盘或鼠标事件。这种链接可以在 XAML 中完成,也可以在代码隐藏中完成。

注意

当您实现 java.lang.Runnable 接口的 run()方法时,您基本上是在使用命令设计模式。另一个接口 java.swing.Action 也代表了命令设计模式。重要的是要注意,undos 的实现各不相同,可能很复杂。memento 设计模式也支持撤销操作。您可能需要在应用程序中使用这两种设计模式来实现复杂的撤消操作。

说明

考虑下面的例子。为了便于理解,我使用了与前面描述的概念相似的类名。为了更好的理解,你可以参考相关的评论。

类图

图 17-1 为类图。

img/395506_2_En_17_Fig1_HTML.jpg

图 17-1

类图

包资源管理器视图

图 17-2 显示了程序的高层结构。

img/395506_2_En_17_Fig2_HTML.jpg

图 17-2

包资源管理器视图

履行

下面是实现。

package jdp2e.command.demo;

interface Command
{
    //Typically this method does not take any argument.
    //Some of the reasons are:
    //1.We supply all the information when it is created.
    //2.Invoker may reside in different address space.etc.
    void executeCommand();
}

class MyUndoCommand implements Command
{
    private Receiver receiver;
    public MyUndoCommand(Receiver receiver)
    {
        this.receiver=receiver;
    }
    @Override
    public void executeCommand()
    {
        //Perform any optional task prior to UnDo
        receiver.doOptionalTaskPriorToUndo();
        //Call UnDo in receiver now
        receiver.performUndo();
    }
}
class MyRedoCommand implements Command
{
    private Receiver receiver;
    public MyRedoCommand(Receiver receiver)
    {
        this.receiver=receiver;
    }
    @Override
    public void executeCommand()
    {
        //Perform any optional task prior to ReDo

        receiver.doOptionalTaskPriorToRedo();
        //Call ReDo in receiver now
        receiver.performRedo();
    }
}
//Receiver Class
class Receiver
{
    public void performUndo()
    {
        System.out.println("Performing an undo command in Receiver.");
    }
    public void performRedo()
    {
        System.out.println("Performing an redo command in Receiver.");
    }
    /*Optional method-If you want to perform

     any prior tasks before undo operations.*/
    public void doOptionalTaskPriorToUndo()
    {
        System.out.println("Executing -Optional Task/s prior to    execute undo command.");
    }
    /*Optional method-If you want to perform
     any prior tasks before redo operations*/
    public void doOptionalTaskPriorToRedo()
    {
        System.out.println("Executing -Optional Task/s prior to    execute redo command.");
    }

}
//Invoker class
class Invoker
{
    Command commandToBePerformed;
    //Alternative approach:
    //You can also initialize the invoker with a command object
    /*public Invoker(Command command)
    {
        this.commandToBePerformed = command;
    }*/

    //Set the command
    public void setCommand(Command command)
    {
        this.commandToBePerformed = command;
    }
    //Invoke the command
    public void invokeCommand()
    {
        commandToBePerformed.executeCommand();
    }
}

//Client
public class CommandPatternExample {

    public static void main(String[] args) {
        System.out.println("***Command Pattern Demo***\n");
        /*Client holds both the Invoker and Command Objects*/
        Receiver intendedReceiver = new Receiver();
        MyUndoCommand undoCmd = new MyUndoCommand(intendedReceiver);
        //If you use parameterized constructor of Invoker

        //use the following line of code.
        //Invoker invoker = new Invoker(undoCmd);
        Invoker invoker = new Invoker();
        invoker.setCommand(undoCmd);
        invoker.invokeCommand();

        MyRedoCommand redoCmd = new MyRedoCommand(intendedReceiver);
        invoker.setCommand(redoCmd);
        invoker.invokeCommand();
    }
}

输出

这是输出。

***Command Pattern Demo***

Executing -Optional Task/s prior to    execute undo command.
Performing an undo command in Receiver.
Executing -Optional Task/s prior to    execute redo command.
Performing an redo command in Receiver.

问答环节

  1. 我有两个问题。在本例中,您只处理一个接收者。你如何处理多个接收者?GoF 定义说这种模式支持可撤销的操作。您能展示一个使用这种模式的真正撤销操作的例子吗?

考虑下面的程序。该计划的主要特点如下:

  • 这里有两个不同的接收器(接收器 1 和接收器 2)。它们中的每一个都实现了接收器接口方法。因为我要处理多个接收器,所以我引入了一个公共接口 Receiver。

  • 在撤消操作中,您通常希望撤销上一个操作。典型的撤消操作可能涉及复杂的逻辑。但是在即将到来的实现中,我将给出一个简单的例子,它支持撤销操作,假设如下。

    • Receiver1 对象用值 10 初始化(myNumber 实例变量用于此目的),Receiver2 对象用“关机”状态初始化(status 实例变量用于此目的)。任何 Receiver1 对象都可以在现有的整数上加 2。

    • 我在值 10 上打了一个勾号,这样当您处理一个撤销操作时,如果您注意到一个 Receiver1 对象的 myNumber 是 10,您将不会超出(因为您从 10 开始)。

    • Receiver2 对象做不同的事情。它可以打开或关闭机器。如果机器已经通电,则通过请求撤销操作,您可以关闭机器,反之亦然。但是,如果您的机器已经处于开机模式,那么进一步的“开机”请求将被忽略。

修改的类图

图 17-3 所示修改后的类图中有很多参与者和依赖关系。为了说明主要设计并保持图表整洁,我没有显示客户端代码依赖关系。

img/395506_2_En_17_Fig3_HTML.jpg

图 17-3

修改的类图

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

图 17-4 显示了修改后的包浏览器视图。

img/395506_2_En_17_Fig4_HTML.jpg

图 17-4

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

修改的实现

下面是修改后的实现

package jdp2e.command.modified.demo;

/**
 *In general, an undo operation involves complex logic.
 But for simplicity, in this example,I assume that executeDo() can either add 2 with a given integer or it can switch on a machine.
 Similarly, executeUnDo() can either subtract 2 from a given number() or,
 it will switch off a machine.But you cannot go beyond the initialized value(i.e.10 in this case)*/

interface Command
{
    void executeDo();
    void executeUnDo();
}
class AdditionCommand implements Command
{
    private Receiver receiver;
    public AdditionCommand(Receiver receiver)
    {
        this.receiver = receiver;
    }
    @Override
    public void executeDo()
    {
        receiver.performDo();
    }
    @Override
    public void executeUnDo()
    {
        receiver.performUnDo();
    }
}
class PowerCommand implements Command
{
    private Receiver receiver;
    public PowerCommand(Receiver receiver)
    {
        this.receiver = receiver;
    }
    @Override
    public void executeDo()
    {
        receiver.performDo();
    }
    @Override
    public void executeUnDo()
    {
        receiver.performUnDo();
    }
}

//To deal with multiple receivers , we are using interfaces here
interface Receiver
{
    //It will add 2 with a number or switch on the m/c
    void performDo();
    //It will subtract 2 from a number or switch off the m/c
    void performUnDo();
}
//Receiver Class
class Receiver1 implements Receiver
{
    private int myNumber;

    public int getMyNumber()
    {
        return myNumber;
    }
    public void setMyNumber(int myNumber)
    {
        this.myNumber = myNumber;
    }
    public Receiver1()
    {
        myNumber = 10;
        System.out.println("Receiver1 initialized with " + myNumber);
        System.out.println("The objects of receiver1 cannot set beyond "+ myNumber);
    }
    @Override
    public void performDo()
    {
        System.out.println("Received an addition request.");
        int presentNumber = getMyNumber();
        setMyNumber(presentNumber + 2);
        System.out.println(presentNumber +" + 2 ="+ this.myNumber);
    }
    @Override
    public void performUnDo()
    {
        System.out.println("Received an undo addition request.");
        int presentNumber = this.myNumber;
        //We started with number 10.We'll not decrease further.
        if (presentNumber > 10)
        {
            setMyNumber(this.myNumber - 2);
            System.out.println(presentNumber +" - 2 ="+ this.myNumber);
            System.out.println("\t Undo request processed.");
        }
        else
        {
            System.out.println("Nothing more to undo...");
        }
    }
}
//Receiver2 Class

class Receiver2 implements Receiver
{
    boolean status;

    public Receiver2()
    {
        System.out.println("Receiver2 initialized ");
        status=false;
    }
    @Override
    public void performDo()
    {
        System.out.println("Received a system power on request.");
        if( status==false)
        {
            System.out.println("System is starting up.");
            status=true;
        }
        else
        {
            System.out.println("System is already running.So, power on request is ignored.");

        }

    }
    @Override
    public void performUnDo()
    {
        System.out.println("Received a undo request.");
        if( status==true)
        {
            System.out.println("System is currently powered on.");
            status=false;
            System.out.println("\t Undo request processed.System is switched off now.");
        }
        else
        {
            System.out.println("System is switched off at present.");
            status=true;
            System.out.println("\t Undo request processed.System is powered on now.");

        }
    }
}

//Invoker class

class Invoker
{
    Command commandToBePerformed;
    public void setCommand(Command command)
    {
        this.commandToBePerformed = command;
    }
    public void executeCommand()
    {
        commandToBePerformed.executeDo();
    }
    public void undoCommand()
    {
        commandToBePerformed.executeUnDo();
    }
}

//Client
public class ModifiedCommandPatternExample {
    public static void main(String[] args) {

        System.out.println("***Command Pattern Q&As***");
        System.out.println("***A simple demo with undo supported operations***\n");
        //Client holds  both the Invoker and Command Objects

        //Testing receiver -Receiver1
        System.out.println("-----Testing operations in Receiver1-----");
        Receiver intendedreceiver = new Receiver1();
        Command currentCmd = new AdditionCommand(intendedreceiver);

        Invoker invoker = new Invoker();
        invoker.setCommand(currentCmd);
        System.out.println("*Testing single do/undo operation*");
        invoker.executeCommand();
        invoker.undoCommand();
        System.out.println("_______");
        System.out.println("**Testing a series of do/undo operations**");
        //Executed the command 2 times
        invoker.executeCommand();
        //invoker.undoCommand();
        invoker.executeCommand();
        //Trying to undo 3 times
        invoker.undoCommand();
        invoker.undoCommand();
        invoker.undoCommand();

        System.out.println("\n-----Testing operations in Receiver2-----");
        intendedreceiver = new Receiver2();
        currentCmd = new PowerCommand(intendedreceiver);
        invoker.setCommand(currentCmd);

        System.out.println("*Testing single do/undo operation*");
        invoker.executeCommand();
        invoker.undoCommand();
        System.out.println("_______");
        System.out.println("**Testing a series of do/undo operations**");
        //Executing the command 2 times
        invoker.executeCommand();
        invoker.executeCommand();
        //Trying to undo 3 times
        invoker.undoCommand();
        invoker.undoCommand();
        invoker.undoCommand();

    }

}

修改输出

这是修改后的输出。

  1. 在这个修改过的程序中,两个接收者在做不同的事情。这是故意的吗?

    是的。它展示了命令设计模式提供的强大功能和灵活性。你可以看到这些接收器中的performDo()实际上执行不同的动作。对于 Receiver1,它将 2 与现有整数相加,对于 Receiver2,它将打开一台机器。所以,你可能会认为其他一些名字像addNumber()powerOn()会更适合他们。

    但是在这种情况下,我需要同时使用接收器和它们对应的方法。因此,我需要使用一个通用的接口和通用的名称,以便两个接收者都可以使用。

    因此,如果您需要使用具有不同方法名称的两个不同的接收器,您可以用一个公共名称替换它们,使用一个公共接口,并且通过多态性,您可以轻松地调用这些方法。

  2. 你为什么需要发票员?

    很多时候,程序员在面向对象的编程中尝试封装数据和相应的方法。但是如果你仔细观察,你会发现在这个模式中,你是在试图封装命令对象。换句话说,您正在从不同的角度实现封装。

    当您处理一组复杂的命令时,这种方法是有意义的。

    现在让我们再看一遍这些条款。您创建命令对象将它们发送给接收者并调用一些方法。但是您通过调用程序来执行这些命令,调用程序调用命令对象的方法(例如,executeCommand)。但是对于一个简单的例子,这个 invoker 类不是强制的;例如,考虑这样一种情况,其中一个命令对象只有一个方法要执行,而您正试图免除调用程序来调用该方法。但是,当您希望跟踪日志文件(或队列)中的多个命令时,调用程序可能会发挥重要作用。

  3. 你为什么对跟踪这些日志感兴趣?

    如果您想要执行撤消或重做操作,它们会很有用。

  4. 指挥模式的主要优势是什么?

    • 创建请求和最终执行是分离的。客户端可能不知道调用者如何执行操作。

    • 您可以创建宏(命令序列)。

    • 可以在不影响现有系统的情况下添加新命令。

    • 最重要的是,您可以支持撤销/重做操作。

  5. 与指挥模式相关的挑战是什么?

    • 为了支持更多的命令,您需要创建更多的类。因此,随着时间的推移,维护可能会很困难。

    • 当出现错误情况时,如何处理错误或决定如何处理返回值变得很棘手。客户可能想知道这些。但是这里您将命令与客户端代码解耦,所以这些情况很难处理。在多线程环境中,调用者也在不同的线程中运行,这一挑战变得很大。

***Command Pattern Q&As***
***A simple demo with undo supported operations***

-----Testing operations in Receiver1-----
Receiver1 initialized with 10
The objects of receiver1 cannot set beyond 10
*Testing single do/undo operation*
Received an addition request.
10 + 2 =12
Received an undo addition request.
12 - 2 =10
     Undo request processed.
_______
**Testing a series of do/undo operations**
Received an addition request.
10 + 2 =12
Received an addition request.
12 + 2 =14
Received an undo addition request.
14 - 2 =12
     Undo request processed.
Received an undo addition request.
12 - 2 =10
     Undo request processed.
Received an undo addition request.
Nothing more to undo...

-----Testing operations in Receiver2-----
Receiver2 initialized
*Testing single do/undo operation*
Received a system power on request.
System is starting up.
Received a undo request.
System is currently powered on.
     Undo request processed.System is switched off now.
_______
**Testing a series of do/undo operations**
Received a system power on request.
System is starting up.
Received a system power on request.
System is already running.So, power on request is ignored.
Received a undo request.
System is currently powered on.
     Undo request processed.System is switched off now.
Received a undo request.
System is switched off at present.
     Undo request processed.System is powered on now.
Received a undo request.
System is currently powered on.
     Undo request processed.System is switched off now

.