核心Java中的行為模式
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中使用的這些模式的示例,因此我們可以看到它們已經以許多應用程序已經受益的方式使用。