全文共3564字,預計學習時長
11分鐘構造函數(constructor)和觀察者模式,誰略勝一籌呢?這要看情況。
誰屬於誰?
通常我們使用構造函數(constructor)參數連接兩個組件。例如,在構造圖形表面時可以非常清楚地看到此過程。比如以下源代碼:
public classSubView {
private MainView mainView;
public SubView(MainView mainView) {
this.mainView = mainView;
}
public void buttonClicked(String input) {
mainView.setInputValue(input);
}
}
此子組件通過構造函數(constructor)獲取周圍的主組件。此組件的實例只是值傳輸所需的。該值是子組件內部用戶交互的結果,以及周圍元素內部消耗的結果。
此過程會面對各種挑戰,但這些挑戰可以避免。首先,此子組件是硬綁定到主要元件的。但這種硬耦合類型沒有意義,因為這種結合純粹基於生成值的使用。
此外,子組件的測試更加困難,因為必須使用主組件的實例或相應的MOCK。同樣,這是一個附加要求,增加了複雜性,同時阻礙了各個組件的提取。那麼,此時如何將其添加到項目中而無需依賴其他框架呢?
經典方法:觀察者模式(Observer)
有一個簡單的設計模式可以提供幫助。那就是觀察者模式。
public classObservable
private final Map
public void register(KEY key, Consumer<value> listener) {/<value>
listeners.put(key, listener);
}
public void unregister(KEY key) {
listeners.remove(key);
}
public void sentEvent(VALUE event) {
listeners.values()
.forEach(listener -> listener.accept(event));
}
}
其基本原理包括三個交互過程。在映射(map)中,會為給定的密鑰而存儲(消費者(consumer))信息。消費者(consumer)是事件的利用率單位,或者是由映射(map)的第二種類型定義所定義的輸入類型。可以使用密鑰註冊,以後如果需要的話,還可以從此映射(map)中再次刪除關聯的消費者(consumer)。
待處理的數據包會用post event將其發送給此時所有已註冊的消費者(consumer)。實際上,映射(map)中的所有現有消費者(consumer)都將以未定義的順序處理值。發送的事件本身必須是不可變的。
註冊
為了簡化註冊和退訂的過程,可以稍微修改觀察者設計模式。
public interfaceRegistration {
void remove();
}
public classRegistry
private final Map
public static
return new Registry<>();
}
public Registration register(KEY key, Consumer<value> listener) {/<value>
listeners.put(key, listener);
return () -> listeners.remove(key);
}
public void sentEvent(VALUE event) {
listeners.values()
.forEach(listener -> listener.accept(event));
}
}
為此,定義了一個名為Registration 的函數接口,它只能從註冊表中自行刪除。這同樣適用於相應的註銷過程。Registration是註冊過程本身的返回值。
事件數據的處理與Observable的處理方法相同。JUnit5測試的實際用途如下所示:
final Registry<string> eventBus = Registry.instance();/<string>
finalString expected = "message 001";
final AtomicInteger counter = new AtomicInteger(0);
finalString key01 = "Consumer-01";
finalString key02 = "Consumer-02";
final Registration register01 = eventBus.register(key01, (event) -> {
assertEquals(expected, event.getMessage());
counter.incrementAndGet();
});
final Registration register02 = eventBus.register(key02, (event) -> {
assertEquals(expected, event.getMessage());
counter.incrementAndGet();
});
eventBus.sentEvent(new Event(expected, ""));
Assertions.assertEquals(2, counter.get());
register01.remove();
eventBus.sentEvent(new Event(expected, ""));
Assertions.assertEquals(3, counter.get());
組件耦合
讓我們從子組件開始吧。該元件將不再鏈接到主組件。此例使用的是Registry類的常規靜態eventbus。當然,還可以為每個組件使用event bus,這將進一步分離組件。
public classSubView {
public void buttonClicked(String input) {
EVENT_BUS.sentEvent(new Event(input));
}
public Registration register(String key, Consumer<event> listener) {/<event>
return EVENT_BUS.register(key, listener);
}
}
如果另一個組件需要使用虛擬用戶交互的值,則可以在子組件的實例註冊。
public staticclass MainView {
//for demo public
public SubView subView = new SubView();
private Registration registration = subView.register("keyXYZ",
e -> inputValue = e.getValue());
private String inputValue;
public String getInputValue() {
return inputValue;
}
public void release() {
registration.remove();
}
}
相應的JUnit5測試如下所示。
final MainView mainView = new MainView();
finalString inputValue = "inputValue";
//subview is public for demo
mainView.subView.buttonClicked(inputValue);
Assertions.assertEquals(inputValue, mainView.getInputValue());
總結
通過幾行源代碼,不僅使組件的耦合更好,而且還簡化了各個元件的測試,從而不再需要mock。增加的抽象允許一個以上的組件在此處顯示的子組件上註冊。當然,此時不應忘記註銷註冊表以使垃圾收集器正常運行。
祝大家編碼愉快!
我們一起分享AI學習與發展的乾貨
閱讀更多 讀芯術 的文章