1.简介
最近,我们研究了创作设计模式以及在JVM和其他核心库中的哪里找到它们。现在,我们将研究行为设计模式。这些关注于我们的对象之间如何交互或我们如何与它们交互。
2.责任链
责任链模式允许对象实现一个公共接口,并允许每种实现(如果适用)委托给下一个接口。然后,这允许我们构建一连串的实现,其中每个实现在调用链中下一个元素之前或之后执行一些操作:
interface ChainOfResponsibility {
void perform();
}
class LoggingChain {
private ChainOfResponsibility delegate;
public void perform() {
System.out.println("Starting chain");
delegate.perform();
System.out.println("Ending chain");
}
}
在这里,我们可以看到一个示例,其中在委托调用之前和之后打印出我们的实现。
我们不需要拜访该代表。我们可以决定不这样做,而是尽早终止该链。例如,如果有一些输入参数,我们可以验证它们并在它们无效的情况下尽早终止。
2.1。 JVM中的示例
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模式,我们可以将一些具体的行为(或命令)封装在一个公共界面的后面,以便可以在运行时正确地触发它们。
通常,我们将具有一个Command接口,一个接收命令实例的Receiver实例以及一个负责调用正确命令实例的Invoker。然后,我们可以定义Command接口的不同实例,以对接收方执行不同的操作:
interface DoorCommand {
perform(Door door);
}
class OpenDoorCommand implements DoorCommand {
public void perform(Door door) {
door.setState("open");
}
}
在这里,我们有一个命令实现,它将Door
作为接收器,并使门“打开”。然后,我们的调用方可以在希望打开给定门时调用此命令,并且该命令封装了如何执行此操作。
将来,我们可能需要更改OpenDoorCommand
来检查门是否未首先锁定。此更改将完全在命令内,并且接收方和调用方类不需要进行任何更改。
3.1。 JVM中的示例
这种模式的一个非常常见的示例是Swing中的Action
类:
Action saveAction = new SaveAction();
button = new JButton(saveAction)
在这里, SaveAction
是命令,使用此类的Swing JButton
组件是调用程序,并且Action
实现是使用ActionEvent
作为接收器的。
4.迭代器
迭代器模式使我们可以处理集合中的各个元素,并依次与每个元素进行交互。我们使用它来编写对某些元素进行任意迭代的函数,而不考虑它们来自何处。源可以是有序列表,无序列集或无限流:
void printAll<T>(Iterator<T> iter) {
while (iter.hasNext()) {
System.out.println(iter.next());
}
}
4.1。 JVM中的示例
所有JVM标准集合都通过公开一个iterator()
方法来实现Iterator模式,该方法在集合中的元素上返回一个Iterator<T>
。流也实现相同的方法,除了在这种情况下,它可能是无限流,因此迭代器可能永远不会终止。
5.纪念品
利用Memento模式,我们可以编写能够更改状态的对象,然后将其恢复为先前的状态。本质上是对象状态的“撤消”功能。
每当调用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;
}
}
}
这样就可以撤消对对象所做的最后更改。
这通常是通过将整个对象状态包装在单个对象(称为Memento)中来实现的。这样一来,整个状态就可以保存和恢复,而不必分别保存每个字段。
5.1。 JVM中的示例
JavaServer Faces提供了一个名为StateHolder
的接口,该接口允许实现者保存和恢复其状态。有几种实现此目的的标准组件,包括单个组件-例如, HtmlInputFile
, HtmlInputText
或HtmlSelectManyCheckbox
以及复合组件,例如HtmlForm
。
6.观察员
观察者模式允许一个对象向其他人指示发生了更改。通常,我们会有一个Subject-发出事件的对象,以及一系列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);
}
}
}
这需要一组事件侦听器,并在每次状态以新状态值更改时调用每个事件侦听器。
6.1。 JVM中的示例
Java有一对标准的类,使我们能够做到这一点– java.beans.PropertyChangeSupport
和java.beans.PropertyChangeListener
。
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");
请注意,还有另外两个看起来更合适的类– java.util.Observer
和java.util.Observable
。但是,由于Java 9不灵活且不可靠,因此不推荐使用它们。
7.策略
策略模式允许我们编写通用代码,然后将特定的策略插入其中,从而为我们的确切案例提供特定的行为。
这通常将通过具有代表策略的接口来实现。然后,客户代码能够根据具体情况编写实现该接口的具体类。例如,我们可能有一个需要通知最终用户并将通知机制实现为可插入策略的系统:
interface NotificationStrategy {
void notify(User user, Message message);
}
class EmailNotificationStrategy implements NotificationStrategy {
....
}
class SMSNotificationStrategy implements NotificationStrategy {
....
}
然后,我们可以在运行时准确地确定要实际使用哪些策略向该用户发送此消息。我们还可以编写新策略,以在对系统其余部分影响最小的情况下使用。
7.1。 JVM中的示例
标准的Java库广泛使用这种模式,通常一开始似乎并不明显。例如,Java 8中引入的Streams API大量使用了这种模式。提供给map()
, filter()
和其他方法的lambda都是提供给泛型方法的可插拔策略。
但是,示例可以追溯到更远的地方。 Java 1.2中引入的Comparator
接口是一种策略,可以根据需要对集合中的元素进行排序。我们可以提供Comparator
不同实例,以根据需要以不同的方式对同一列表进行排序:
// Sort by name
Collections.sort(users, new UsersNameComparator());
// Sort by ID
Collections.sort(users, new UsersIdComparator());
8.模板方法
当我们要协调几种不同的方法一起使用时,可以使用模板方法模式。我们将使用模板方法和一组一个或多个抽象方法定义一个基类-未实现或以某种默认行为实现。然后,模板方法以固定模式调用这些抽象方法。然后,我们的代码实现此类的子类,并根据需要实现这些抽象方法:
class Component {
public void render() {
doRender();
addEventListeners();
syncData();
}
protected abstract void doRender();
protected void addEventListeners() {}
protected void syncData() {}
}
在这里,我们有一些任意的UI组件。我们的子类将实现doRender()
方法以实际呈现组件。我们还可以选择实现addEventListeners()
和syncData()
方法。当我们的UI框架呈现此组件时,它将确保以正确的顺序调用这三个组件。
8.1。 JVM中的示例
Java集合使用的AbstractList
, AbstractSet,
和AbstractMap
拥有此模式的许多示例。例如, indexOf()
和lastIndexOf()
方法中的条款都工作listIterator()
方法,它有一个默认的实现,但在某些子类中被覆盖。同样地, add(T)
和addAll(int, T)
方法都根据add(int, T)
方法工作,该方法没有默认实现,需要由子类实现。
Java IO还在InputStream
, OutputStream
, Reader,
和Writer
使用此模式。例如, InputStream
类具有几种可以通过read(byte[], int, int)
,这些方法需要实现子类。
9.访客
Visitor模式允许我们的代码以类型安全的方式处理各种子类,而无需求助于instanceof
检查。我们将为每个需要支持的具体子类提供一个带有一种方法的访客接口。然后,我们的基类将具有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);
}
}
在这里,我们有我们的UserVisitor
界面,上面有三种不同的访问者方法。我们的示例StandardUser
调用了适当的方法,并且将在AdminUser
和Superuser
执行相同的方法。然后,我们可以根据需要写信给访问者以使用它们:
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;
}
}
我们的StandardUser
从来没有权限,我们的Superuser
总是有权限,我们的AdminUser
可能有权限,但这需要在用户本身中进行查找。
9.1。 JVM中的示例
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.结论
在本文中,我们研究了用于对象行为的各种设计模式。我们还查看了核心JVM中使用的这些模式的示例,因此我们可以看到它们已经以许多应用程序已经受益的方式使用。
0 评论