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学习与发展的干货


分享到:


相關文章: