11.24 構造函數(constructor)和觀察者模式,誰略勝一籌呢?

全文共3564字,預計學習時長

11分鐘

構造函數(constructor)和觀察者模式,誰略勝一籌呢?

構造函數(constructor)和觀察者模式,誰略勝一籌呢?這要看情況。

誰屬於誰?

通常我們使用構造函數(constructor)參數連接兩個組件。例如,在構造圖形表面時可以非常清楚地看到此過程。比如以下源代碼:

public classSubView {

private MainView mainView;

public SubView(MainView mainView) {

this.mainView = mainView;

}

public void buttonClicked(String input) {

mainView.setInputValue(input);

}

}

此子組件通過構造函數(constructor)獲取周圍的主組件。此組件的實例只是值傳輸所需的。該值是子組件內部用戶交互的結果,以及周圍元素內部消耗的結果。

此過程會面對各種挑戰,但這些挑戰可以避免。首先,此子組件是硬綁定到主要元件的。但這種硬耦合類型沒有意義,因為這種結合純粹基於生成值的使用。

此外,子組件的測試更加困難,因為必須使用主組件的實例或相應的MOCK。同樣,這是一個附加要求,增加了複雜性,同時阻礙了各個組件的提取。那麼,此時如何將其添加到項目中而無需依賴其他框架呢?

構造函數(constructor)和觀察者模式,誰略勝一籌呢?

圖源:pexels

經典方法:觀察者模式(Observer)

有一個簡單的設計模式可以提供幫助。那就是觀察者模式。

public classObservable {

private final Map> listeners = new ConcurrentHashMap<>();

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> listeners = new ConcurrentHashMap<>();

public static Registry instance() {

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());

構造函數(constructor)和觀察者模式,誰略勝一籌呢?

圖源:pexels

總結

通過幾行源代碼,不僅使組件的耦合更好,而且還簡化了各個元件的測試,從而不再需要mock。增加的抽象允許一個以上的組件在此處顯示的子組件上註冊。當然,此時不應忘記註銷註冊表以使垃圾收集器正常運行。

祝大家編碼愉快!

構造函數(constructor)和觀察者模式,誰略勝一籌呢?

構造函數(constructor)和觀察者模式,誰略勝一籌呢?

我們一起分享AI學習與發展的乾貨


分享到:


相關文章: