过时的设计模式-命令模式

我敢肯定,我们大多数人都至少对本书中“四人帮”提出的一些模式很熟悉设计模式:可重用的面向对象软件的元素。 这些模式在OO语言中非常有用,因为它们为这些语言迫切需要的问题提供了答案。

面向对象语言是围绕类而不是函数的概念构建的,因此尽管对象通常被视为一等公民,但函数却并非如此。 这种无法充分发挥功能的能力在这些模式所填补的表现力上留下了空白。

但是,随着时间的流逝,语言之间正在相互学习,并且许多功能实践正在转向面向对象语言。 发生这种情况时,将不再需要解决这些表达差距的模式。

在本系列中,我将介绍一些已经过时或正在逐渐淘汰的模式。

Command Pattern

该模式的目标是封装有关如何在程序中执行方法的信息。命令。 一种命令是具有单个抽象方法的接口,通常称为执行类型() -> (). This method actually runs the method the 命令 is "wrapping".

命令的好处是:

  1. 可以作为第一类值传递的功能,例如将其存储在列表中,将其传递给其他函数以根据某些条件执行等。传入参数(具体是传入构造函数)和函数执行的分离。基于公共接口对命令进行分组的能力。关于执行功能以减少呼叫现场重复的信息的封装,以及业务逻辑与其他方面(如表示或数据存储)的分离。

但是,所有这些好处都可以通过其他方式实现。 不仅如此,而且这些其他方式通常在语义上更具表现力。

First, we'll go over an example of implementing the command pattern in Java 7. This example is mostly from GeeksforGeeks: Command Pattern. A similar example also exists on Wikipedia: Command Pattern. Afterwards, we'll refactor out the command pattern using Java 8 features.

Java 7 Code

这是我们的实体:

package Entities;

import Boundary.IO;

public class Light {
    private final IO io;

    public Light(IO io) {
        this.io = io;
    }

    public void on() {
        io.show("Light is on");
    }

    public void off() {
        io.show("Light is off");
    }
}
package Entities;

import Commands.Command;

public class RemoteControl {
    private Command button;

    public RemoteControl() { }

    public void setCommand(Command command) {
        button = command;
    }

    public void pressButton() {
        button.execute();
    }
}
package Entities;

import Boundary.IO;

public class Stereo {
    private final IO io;

    public Stereo(IO io) {
        this.io = io;
    }

    public void on() {
        io.show("Stereo is on");
    }

    public void off() {
        io.show("Stereo is off");
    }

    public void setCD() {
        io.show("Stereo is set for CD input");
    }

    public void setVolume(int volume) {
        io.show("Stereo volume set to " + volume);
    }
}

综上所述,您可能会好奇IO是。 好吧,这只是我在其中定义的简单依赖项边界以免我的代码与打印流或更具体地系统输出。

该代码实际上相对简单:

package Boundary;

public interface IO {
    void show(String str);
}

这个应用程式中唯一存在的实现就是这个:

package Presentation;

import Boundary.IO;

class ConsoleIO implements IO {
    @Override
    public void show(String str) {
        System.out.println(str);
    }
}

最后,我们的指令:

package Commands;

public interface Command {
    void execute();
}
package Commands;

import Entities.Light;

public class LightOnCommand implements Command {
    private final Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    public void execute() {
        light.on();
    }
}
package Commands;

import Entities.Light;

public class LightOffCommand implements Command {
    private final Light light;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    public void execute() {
        light.off();
    }
}
package Commands;

import Entities.Stereo;

public class StereoOnWithCDCommand implements Command {
    private final Stereo stereo;

    public StereoOnWithCDCommand(Stereo stereo) {
        this.stereo = stereo;
    }

    public void execute() {
        stereo.on();
        stereo.setCD();
        stereo.setVolume(10);
    }
}
package Commands;

import Entities.Stereo;

public class StereoOffCommand implements Command {
    private final Stereo stereo;

    public StereoOffCommand(Stereo stereo) {
        this.stereo = stereo;
    }

    public void execute() {
        stereo.off();
    }
}

这是我们可以用来查看上述功能的代码:

import Boundary.IO;
import Commands.*;
import Entities.*;
import Presentation.ConsoleIO;

class Main {
    public static void main(String[] args) {
        RemoteControl remote = new RemoteControl();
        IO io = new ConsoleIO();

        Light light = new Light(io);
        Stereo stereo = new Stereo(io);

        remote.setCommand(new LightOnCommand(light));
        remote.pressButton();
        remote.setCommand(new StereoOnWithCDCommand(stereo));
        remote.pressButton();
        remote.setCommand(new StereoOffCommand(stereo));
        remote.pressButton();
    }
}

Java 7 Explanation

到目前为止,一切都很好,但是这里肯定有很多开销。 存在明显的重复,并且重复阻碍了代码的表达性。

重复的主要形式是这种代码(在所有命令s):

public class SomeClass implements Command {
  private final SomeType someType;

  public SomeClass(SomeType someType) {
    this.someType = someType;
  }

  public void execute() {
    someType.doStuff();
    // sometimes more calls on `someType` are here
  }
}

我喜欢称这类为荣耀的功能。 该函数将键入为SomeType -> (),即它需要使用SomeType并且什么也不返回。

它在Java上下文中被“荣耀化”,因为它具有普通功能所没有的某些功能。 您可以在与执行不同的时间传入参数,并将此部分应用的函数作为第一类值传递。

在这种情况下,传递到任何Command的构造函数中的参数只是相应的实体(接收者)而已。 这实际上意味着在我们的重写中,我们不必担心高阶函数,因为该实体被隐式传递给所述实体的实例方法。

但是,在我们进一步研究重写的细节之前,让我们实际看一下Java 8代码。

Java 8 Code

实体:

// Identical to Java 7 version
package Entities;

import Boundary.IO;

public class Light {
    private final IO io;

    public Light(IO io) {
        this.io = io;
    }

    public void on() {
        io.show("Light is on");
    }

    public void off() {
        io.show("Light is off");
    }
}
// This uses Runnable instead of Command now
package Entities;

public class RemoteControl {
    private Runnable button;

    public RemoteControl() { }

    public void setCommand(Runnable command) {
        button = command;
    }

    public void pressButton() {
        button.execute();
    }
}

/* 
  We added a new method here (onWithCD) to replace one of the commands 
  that used multiple methods on this entity.

  Then we made `off` public because that was just a 1:1 call 
  from a command previously.

  And lastly we made everything else private. So although we 
  added code to this class, we actually reduced the public API.
*/
package Entities

import Boundary.IO;

public class Stereo {
    private final IO io;

    public Stereo(IO io) {
        this.io = io;
    }

    public void onWithCD() {
        on();
        setCD();
        setVolume(10);
    }


    public void off() {
        io.show("Stereo is off");
    }

    private void on() {
        io.show("Stereo is on");
    }

    private void setCD() {
        io.show("Stereo is set for CD input");
    }

    private void setVolume(int volume) {
        io.show("Stereo volume set to " + volume);
    }

    private void setDVD() {
        io.show("Stereo is set for DVD input");
    }

    private void setRadio() {
        io.show("Stereo is set for Radio");
    }
}

IO:

// Identical to Java 7 version
package Boundary;

public interface IO {
    void show(String str);
}
// Identical to Java 7 version
package Presentation;

import Boundary.IO;

class ConsoleIO implements IO {
    @Override
    public void show(String str) {
        System.out.println(str);
    }
}

主要:

// The method references used here instead of commands are talked about
// in the explanation below
import Boundary.IO;
import Entities.*;
import Presentation.ConsoleIO;

public class Main {
    public static void main(String[] args) {
        RemoteControl remote = new RemoteControl();
        IO io = new ConsoleIO();

        Light light = new Light(io);
        Stereo stereo = new Stereo(io);

        remote.setCommand(light::on);
        remote.pressButton();
        remote.setCommand(stereo::onWithCD);
        remote.pressButton();
        remote.setCommand(stereo::off);
        remote.pressButton();
    }
}

Java 8 Explanation

所以当立体声再过一会儿,您可能已经注意到整个相对较大的部分丢失了!指令 are entirely gone, yet we still have code that hits all the original benefits enumerated at the top of this post. I'll go over those one by one, and explain how we're still achieving the benefits of the Command pattern without using 指令 at all.

1个。 可以作为第一类值传递的功能,例如将其存储在列表中,将其传递给其他函数以根据某些条件执行等。

因此,尽管Java缺少一流的功能,但确实有一个功能 class, as well as other similar 功能al Interfaces that can be implemented by lambdas or method references. And this 功能al Interface is first class. When we construct a 可运行与点亮,我们正在将该方法的引用传递给set命令,就像我们如何传递各种参考命令Java 7中的。

2。 传入参数(具体是传入构造函数)和函数执行的分离。

实际上,这是我之前提到的,我们没有专门这样做,因为我们不需要在这里。 我们传递给命令构造函数是他们正在访问的实体(对于任何给定实体命令)。

不过,这很容易做到。 如果我们需要在以下时间传递参数setCommand,我们可以通过获取需要参数的函数来做到这一点(即从类似方法的方法返回可能嵌套的函数接口CD上要么关)。

作为一个超级简单的currying示例,这里有一些简短的说明,请注意类型很复杂,但是实现很简单,因此,将来类型推断可以简化更多的事情:

Function<Integer, Function<Integer, Integer>> add = x -> y -> x + y;
3。 根据通用接口对命令进行分组的能力,通常称为命令。

Java 8中的通用接口是那些功能接口s(可运行,函数,双功能,谓词等)。 相同类型的接口是可运行,这就是我们在Java 8版本中使用它的原因。

在具有一流功能的语言中,它只是功能。

4。 关于执行功能以减少呼叫现场重复的信息的封装,以及业务逻辑与其他方面(如表示或数据存储)的分离。

在调用站点,重复次数仍然减少,因为我们仍然拥有方法内部存在的该命令的逻辑。 由于业务逻辑仍位于实体中而不在表示层内部,因此也实现了分离。

另外,在我们的表示层中,调用者不会尝试使用以前在实体上公开的方法,因为这些方法现在是私有的。 他们之前公开的唯一原因是命令需要访问它们以将它们公开给表示层。

Conclusion

这样一来,您便可以在较新的语言中获得命令的所有好处,而不会被命令模式的语义限制所拖累。

需要注意的重要一点是,这种基于函数的方法在Java的第一个版本中就比较简单(Java 8)。 Java首先是OO语言。 它以类作为所有交互的基础进行设计。 这种语言和其他类似语言是为什么首先需要这种类型的模式的原因,并且由于这些语言采用功能语言的某些方面,因此这些OO模式由于功能而将变得过时。

在从头开始设计为使用一流函数的语言中,无论是像Haskell这样的功能语言,还是JVM中像Kotlin这样的另一种语言,该代码都是极大地更简单,尤其是具有更高级的概念(例如currying,高阶函数等)时。

请继续关注本系列的更多内容! 感谢您的阅读,如果您有任何想让我讨论的模式,请告诉我!

from: https://dev.to//tylerwbrown/obsolete-design-patterns-command-pattern-4ijf

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值