欢迎大家来到IT世界,在知识的湖畔探索吧!
全文共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<KEY, VALUE> {
private final Map<KEY, Consumer<VALUE>> listeners = new ConcurrentHashMap<>();
public void register(KEY key, Consumer<VALUE> listener) {
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<KEY, VALUE> {
private final Map<KEY, Consumer<VALUE>> listeners = new ConcurrentHashMap<>();
public static <K, V> Registry<K, V> instance() {
return new Registry<>();
}
public Registration register(KEY key, Consumer<VALUE> listener) {
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, Event> eventBus = Registry.instance();
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) {
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学习与发展的干货
如转载,请后台留言,遵守转载规范
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/37240.html