Behavioral Patterns in Core Java – 核心Java中的行为模式

最后修改: 2020年 12月 22日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

Recently we looked at Creational Design Patterns and where to find them within the JVM and other core libraries. Now we’re going to look at Behavioral Design Patterns. These focus on how our objects interact with each other or how we interact with them.

最近,我们研究了娱乐设计模式以及在JVM和其他核心库中找到它们的地方。现在我们来看看行为设计模式这些模式侧重于我们的对象如何相互作用或我们如何与它们相互作用。

2. Chain of Responsibility

2.责任链

The Chain of Responsibility pattern allows for objects to implement a common interface and for each implementation to delegate on to the next one if appropriate. This then allows us to build a chain of implementations, where each one performs some actions before or after the call to the next element in the chain:

责任链模式允许对象实现一个共同的接口,并且每个实现都可以酌情委托给下一个对象。这样我们就可以建立一个实现链,其中每个实现在调用链中的下一个元素之前或之后执行一些动作

interface ChainOfResponsibility {
    void perform();
}
class LoggingChain {
    private ChainOfResponsibility delegate;

    public void perform() {
        System.out.println("Starting chain");
        delegate.perform();
        System.out.println("Ending chain");
    }
}

Here we can see an example where our implementation prints out before and after the delegate call.

这里我们可以看到一个例子,我们的实现在调用委托之前和之后都会打印出来。

We aren’t required to call on to the delegate. We could decide that we shouldn’t do so and instead terminate the chain early. For example, if there were some input parameters, we could have validated them and terminated early if they were invalid.

我们并不是必须要调用委托人。我们可以决定不这样做,而是提前终止该链。例如,如果有一些输入参数,我们可以对它们进行验证,如果它们是无效的,就提前终止。

2.1. Examples in the JVM

2.1.JVM中的例子

Servlet Filters are an example from the JEE ecosystem that works in this way. A single instance receives the servlet request and response, and a FilterChain instance represents the entire chain of filters. Each one should then perform its work and then either terminate the chain or else call chain.doFilter() to pass control on to the next filter:

Servlet 过滤器是 JEE 生态系统中以这种方式工作的一个例子。一个实例接收Servlet请求和响应,而FilterChain实例代表整个过滤器链。每个过滤器应执行其工作,然后终止该链,或者调用chain.doFilter()以将控制权传递给下一个过滤器

public class AuthenticatingFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
      throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        if (!"MyAuthToken".equals(httpRequest.getHeader("X-Auth-Token")) {
             return;
        }
        chain.doFilter(request, response);
    }
}

3. Command

3.指挥部

The Command pattern allows us to encapsulate some concrete behaviors – or commands – behind a common interface, such that they can be correctly triggered at runtime.

Command模式允许我们将一些具体的行为–或命令–封装在一个通用接口后面,这样就可以在运行时正确地触发它们。

Typically we’ll have a Command interface, a Receiver instance that receives the command instance, and an Invoker that is responsible for calling the correct command instance. We can then define different instances of our Command interface to perform different actions on the receiver:

通常,我们会有一个命令接口,一个接收命令实例的接收器,以及一个负责调用正确命令实例的调用者。然后我们可以定义不同的命令接口实例来对接收器执行不同的操作

interface DoorCommand {
    perform(Door door);
}
class OpenDoorCommand implements DoorCommand {
    public void perform(Door door) {
        door.setState("open");
    }
}

Here, we have a command implementation that will take a Door as the receiver and will cause the door to become “open”. Our invoker can then call this command when it wishes to open a given door, and the command encapsulates how to do this.

在这里,我们有一个命令的实现,它将把一个Door作为接收器,并使门变得 “打开”。当我们的调用者希望打开一扇给定的门时,可以调用这个命令,而这个命令封装了如何做到这一点。

In the future, we might need to change our OpenDoorCommand to check that the door isn’t locked first. This change will be entirely within the command, and the receiver and invoker classes don’t need to have any changes.

在未来,我们可能需要改变我们的OpenDoorCommand,以首先检查门是否被锁住。这一改变将完全在命令中进行,接收器和调用器类不需要有任何改变。

3.1. Examples in the JVM

3.1.JVM中的例子

A very common example of this pattern is the Action class within Swing:

这种模式的一个非常常见的例子是Swing中的Action类。

Action saveAction = new SaveAction();
button = new JButton(saveAction)

Here, SaveAction is the command, the Swing JButton component that uses this class is the invoker, and the Action implementation is called with an ActionEvent as the receiver.

这里,SaveAction是命令,使用该类的SwingJButton组件是调用者,而Action的实现是以ActionEvent作为接收器来调用。

4. Iterator

4.迭代器

The Iterator pattern allows us to work across the elements in a collection and interact with each in turn. We use this to write functions taking an arbitrary iterator over some elements without regard to where they are coming from. The source could be an ordered list, an unordered set, or an infinite stream:

迭代器模式允许我们在一个集合中的元素上工作,并依次与每个元素互动。我们用它来编写函数,在一些元素上取一个任意的迭代器,而不考虑它们来自哪里。来源可以是一个有序的列表、一个无序的集合或一个无限的流。

void printAll<T>(Iterator<T> iter) {
    while (iter.hasNext()) {
        System.out.println(iter.next());
    }
}

4.1. Examples in the JVM

4.1.JVM中的例子

All of the JVM standard collections implement the Iterator pattern by exposing an iterator() method that returns an Iterator<T> over the elements in the collection. Streams also implement the same method, except in this case, it might be an infinite stream, so the iterator might never terminate.

所有的JVM标准集合都实现了迭代器模式,暴露了一个iterator()方法,该方法在集合中的元素上返回一个Iterator<T>。流也实现了同样的方法,只是在这种情况下,它可能是一个无限的流,所以迭代器可能永远不会终止。

5. Memento

5.纪念品

The Memento pattern allows us to write objects that are able to change state, and then revert back to their previous state. Essentially an “undo” function for object state.

Memento模式允许我们编写能够改变状态的对象,然后恢复到之前的状态。本质上是对象状态的 “撤销 “功能。

This can be implemented relatively easily by storing the previous state any time a setter is called:

这可以通过在任何时候调用setter时存储之前的状态来相对容易地实现。

class Undoable {
    private String value;
    private String previous;

    public void setValue(String newValue) {
        this.previous = this.value;
        this.value = newValue;
    }

    public void restoreState() {
        if (this.previous != null) {
            this.value = this.previous;
            this.previous = null;
        }
    }
}

This gives the ability to undo the last change that was made to the object.

这就提供了撤销对该对象所做的最后一次修改的能力。

This is often implemented by wrapping the entire object state in a single object, known as the Memento. This allows the entire state to be saved and restored in a single action, instead of having to save every field individually.

这通常是通过将整个对象的状态包裹在一个对象中来实现的,这个对象被称为Memento。这使得整个状态可以在一个动作中被保存和恢复,而不是单独保存每个字段。

5.1. Examples in the JVM

5.1.JVM中的例子

JavaServer Faces provides an interface called StateHolder that allows implementers to save and restore their state. There are several standard components that implement this, consisting of individual components – for example, HtmlInputFile, HtmlInputText, or HtmlSelectManyCheckbox – as well as composite components such as HtmlForm.

JavaServer Faces提供了一个名为StateHolder的接口,允许实现者保存和恢复其状态。有几个标准组件实现了这一点,由单个组件组成–例如,HtmlInputFileHtmlInputTextHtmlSelectManyCheckbox–以及诸如HtmlForm等复合组件。

6. Observer

6. 观察员

The Observer pattern allows for an object to indicate to others that changes have happened. Typically we’ll have a Subject – the object emitting events, and a series of Observers – the objects receiving these events. The observers will register with the subject that they want to be informed about changes. Once this has happened, any changes that happen in the subject will cause the observers to be informed:

Observer模式允许一个对象向其他人表明发生了变化。通常,我们会有一个主体–发出事件的对象,以及一系列的观察者–接收这些事件的对象。观察者将向主体注册,表示他们希望被告知变化。一旦这样做了,在主体中发生的任何变化都会导致观察者被告知

class Observable {
    private String state;
    private Set<Consumer<String>> listeners = new HashSet<>;

    public void addListener(Consumer<String> listener) {
        this.listeners.add(listener);
    }

    public void setState(String newState) {
        this.state = state;
        for (Consumer<String> listener : listeners) {
            listener.accept(newState);
        }
    }
}

This takes a set of event listeners and calls each one every time the state changes with the new state value.

这需要一组事件监听器,并在每次状态改变时调用每一个新的状态值。

6.1. Examples in the JVM

6.1.JVM中的例子

Java has a standard pair of classes that allow us to do exactly this – java.beans.PropertyChangeSupport and java.beans.PropertyChangeListener.

Java有一对标准的类允许我们这样做 – java.beans.PropertyChangeSupportjava.beans.PropertyChangeListener

PropertyChangeSupport acts as a class that can have observers added and removed from it and can notify them all of any state changes. PropertyChangeListener is then an interface that our code can implement to receive any changes that have happened:

PropertyChangeSupport作为一个类,可以在其中添加和删除观察者,并可以将任何状态变化通知他们。PropertyChangeListener是一个接口,我们的代码可以实现它来接收任何已经发生的变化。

PropertyChangeSupport observable = new PropertyChangeSupport();

// Add some observers to be notified when the value changes
observable.addPropertyChangeListener(evt -> System.out.println("Value changed: " + evt));

// Indicate that the value has changed and notify observers of the new value
observable.firePropertyChange("field", "old value", "new value");

Note that there are another pair of classes that seem a better fit – java.util.Observer and java.util.Observable. These are deprecated in Java 9 though, due to being inflexible and unreliable.

请注意,还有一对类似乎更合适–java.util.Observerjava.util.Observable。不过,由于不灵活、不可靠,这些类在Java 9中已经被废弃了。

7. Strategy

7.战略

The Strategy pattern allows us to write generic code and then plug specific strategies into it to give us the specific behavior needed for our exact cases.

策略模式允许我们编写通用代码,然后将特定的策略插入其中,以便为我们的确切案例提供所需的特定行为。

This will typically be implemented by having an interface representing the strategy. The client code is then able to write concrete classes implementing this interface as needed for the exact cases. For example, we might have a system where we need to notify end-users and implement the notification mechanisms as pluggable strategies:

这通常会通过一个代表策略的接口来实现。然后客户代码就可以根据具体的情况编写实现这个接口的具体类。例如,我们可能有一个需要通知最终用户的系统,并将通知机制实现为可插拔的策略。

interface NotificationStrategy {
    void notify(User user, Message message);
}
class EmailNotificationStrategy implements NotificationStrategy {
    ....
}
class SMSNotificationStrategy implements NotificationStrategy {
    ....
}

We can then decide at runtime exactly which of these strategies to actually use to send this message to this user. We can also write new strategies to use with minimal impact on the rest of the system.

然后我们可以在运行时决定究竟使用哪种策略来向这个用户发送这个消息。我们还可以编写新的策略来使用,对系统的其他部分影响最小。

7.1. Examples in the JVM

7.1.JVM中的例子

The standard Java libraries use this pattern extensively, often in ways that may not seem obvious at first. For example, the Streams API introduced in Java 8 makes extensive use of this pattern. The lambdas provided to map(), filter(), and other methods are all pluggable strategies that are provided to the generic method.

标准的 Java 库广泛地使用了这种模式,其方式往往在一开始可能并不明显。例如,Java 8 中引入的 Streams API 就广泛使用了这种模式。提供给map()filter()和其他方法的lambdas都是提供给通用方法的可插拔策略。

Examples go back even further, though. The Comparator interface introduced in Java 1.2 is a strategy that can be provided to sort elements within a collection as required. We can provide different instances of the Comparator to sort the same list in different ways as desired:

不过,例子可以追溯到更早。Java 1.2中引入的Comparator接口是一种策略,可以根据需要提供对集合中的元素进行排序。我们可以根据需要提供不同的Comparator实例,以不同的方式对同一个列表进行排序。

// Sort by name
Collections.sort(users, new UsersNameComparator());

// Sort by ID
Collections.sort(users, new UsersIdComparator());

8. Template Method

模板方法

The Template Method pattern is used when we want to orchestrate several different methods working together. We’ll define a base class with the template method and a set of one or more abstract methods – either unimplemented or else implemented with some default behavior. The template method then calls these abstract methods in a fixed pattern. Our code then implements a subclass of this class and implements these abstract methods as needed:

模板方法模式是在我们想要协调几个不同的方法一起工作时使用的。我们将定义一个带有模板方法的基类,以及一组或多个抽象方法–要么不实现,要么以某种默认行为实现。然后模板方法以固定的模式调用这些抽象方法。然后我们的代码实现这个类的子类,并根据需要实现这些抽象方法。

class Component {
    public void render() {
        doRender();
        addEventListeners();
        syncData();
    }

    protected abstract void doRender();

    protected void addEventListeners() {}

    protected void syncData() {}
}

Here, we have some arbitrary UI components. Our subclasses will implement the doRender() method to actually render the component. We can also optionally implement the addEventListeners() and syncData() methods. When our UI framework renders this component it will guarantee that all three get called in the correct order.

在这里,我们有一些任意的UI组件。我们的子类将实现doRender()方法来实际渲染该组件。我们还可以选择性地实现addEventListeners()syncData()方法。当我们的UI框架渲染这个组件时,它将保证这三个方法以正确的顺序被调用。

8.1. Examples in the JVM

8.1.JVM中的例子

The AbstractList, AbstractSet, and AbstractMap used by Java Collections have many examples of this pattern. For example, the indexOf() and lastIndexOf() methods both work in terms of the listIterator() method, which has a default implementation but which gets overridden in some subclasses. Equally, the add(T) and addAll(int, T) methods both work in terms of the add(int, T) method which doesn’t have a default implementation and needs to be implemented by the subclass.

Java集合使用的AbstractListAbstractSetAbstractMap有许多这种模式的例子。例如,indexOf()lastIndexOf()方法都以listIterator()方法工作,该方法有一个默认实现,但在一些子类中被重写。同样,add(T) addAll(int, T) 方法都是以add(int, T)方法来工作的,该方法没有默认实现,需要由子类来实现。

Java IO also makes use of this pattern within InputStream, OutputStream, Reader, and Writer. For example, the InputStream class has several methods that work in terms of read(byte[], int, int), which needs the subclass to implement.

Java IO也在InputStreamOutputStreamReader,Writer中使用这种模式。例如,InputStream类有几个方法以read(byte[], int, int)的方式工作,这需要子类来实现。

9. Visitor

9.访客

The Visitor pattern allows our code to handle various subclasses in a typesafe way, without needing to resort to instanceof checks. We’ll have a visitor interface with one method for each concrete subclass that we need to support. Our base class will then have an accept(Visitor) method. The subclasses will each call the appropriate method on this visitor, passing itself in. This then allows us to implement concrete behavior in each of these methods, each knowing that it will be working with the concrete type:

visitor模式允许我们的代码以一种类型安全的方式处理各种子类,而不需要借助instanceof检查。我们将有一个visitor接口,为我们需要支持的每个具体子类提供一个方法。我们的基类将有一个accept(Visitor)方法。子类将各自调用这个访问者的适当方法,并将自己传入。这样我们就可以在每个方法中实现具体的行为,每个方法都知道它将与具体类型一起工作。

interface UserVisitor<T> {
    T visitStandardUser(StandardUser user);
    T visitAdminUser(AdminUser user);
    T visitSuperuser(Superuser user);
}
class StandardUser {
    public <T> T accept(UserVisitor<T> visitor) {
        return visitor.visitStandardUser(this);
    }
}

Here we have our UserVisitor interface with three different visitor methods on it. Our example StandardUser calls the appropriate method, and the same will be done in AdminUser and Superuser. We can then write our visitors to work with these as needed:

这里我们的UserVisitor接口上有三个不同的访问者方法。我们的例子StandardUser调用了相应的方法,同样的情况也将在AdminUserSuperuser中进行。然后我们可以根据需要编写我们的访问者来处理这些。

class AuthenticatingVisitor {
    public Boolean visitStandardUser(StandardUser user) {
        return false;
    }
    public Boolean visitAdminUser(AdminUser user) {
        return user.hasPermission("write");
    }
    public Boolean visitSuperuser(Superuser user) {
        return true;
    }
}

Our StandardUser never has permission, our Superuser always has permission, and our AdminUser might have permission but this needs to be looked up in the user itself.

我们的StandardUser从来没有权限,我们的Superuser总是有权限,而我们的AdminUser可能有权限,但这需要在用户本身查找。

9.1. Examples in the JVM

9.1.JVM中的例子

The Java NIO2 framework uses this pattern with Files.walkFileTree(). This takes an implementation of FileVisitor that has methods to handle various different aspects of walking the file tree. Our code can then use this for searching files, printing out matching files, processing many files in a directory, or lots of other things that need to work within a directory:

Java NIO2框架通过Files.walkFileTree()使用这种模式。这需要一个FileVisitor的实现,它具有处理行走文件树的各种不同方面的方法。我们的代码可以用它来搜索文件,打印出匹配的文件,处理一个目录中的许多文件,或者许多其他需要在一个目录中工作的事情

Files.walkFileTree(startingDir, new SimpleFileVisitor() {
    public FileVisitResult visitFile(Path file, BasicFileAttributes attr) {
        System.out.println("Found file: " + file);
    }

    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
        System.out.println("Found directory: " + dir);
    }
});

10. Conclusion

10.结语

In this article, we’ve had a look at various design patterns used for the behavior of objects. We’ve also looked at examples of these patterns as used within the core JVM as well, so we can see them in use in a way that many applications already benefit from.

在这篇文章中,我们已经看了用于对象行为的各种设计模式。我们还看了这些模式在核心JVM中的应用实例,因此我们可以看到它们在许多应用程序中的应用,并且已经从中受益。