单片机 总线编程
在上周的文章中 ,我们使用事件驱动编程解决了现在熟悉的文本文件中前25个词频问题。
这是10的编程风格焦点series.Other职位练习日交包括:
- 以编程风格介绍练习
- 以编程风格进行练习,将内容堆叠起来
- 编程风格的练习,Kwisatz Haderach风格
- 编程风格的练习,递归
- 具有高阶功能的编程风格的练习
- 以编程风格进行练习
- 以编程风格进行练习,回到面向对象的编程
- 编程风格的练习:地图也是对象
- 编程风格的练习:事件驱动的编程
- 编程风格和事件总线的练习 (本文)
- 反思编程风格的练习
- 面向方面的编程风格的练习
- 编程风格的练习:FP&I / O
- 关系数据库风格的练习
- 编程风格的练习:电子表格
- 并发编程风格的练习
- 编程风格的练习:在线程之间共享数据
- 使用Hazelcast以编程风格进行练习
- MapReduce风格的练习
- 编程风格的练习总结
一点理论
观察者模式是一种消息传递模式: Observer
订阅了Subject
。 当后者发出事件时,将通知前者。 这就是点对点消息传递:如果多个观察者对某个事件感兴趣,那么他们都必须订阅该主题。
点对点消息传递具有很大的缺点:每个观察者都需要在一个或另一个点引用每个相关主题。 当观察者-对象对的数量增加时,这成倍地增加了复杂性。
进行消息传递的另一种方法是Publish-Subscribe 。 在这种情况下,消息将发送到队列 。 订户可以注册到特定队列,以通知发送到该队列的消息。 作为一种选择,可以保留消息,以便可以向注册到队列的订户通知在注册之前发送的消息。
事件总线是Publish-Subscribe模式的实现 ,其中每种事件类型都有一个专用队列。 这是类图:
![事件总线类图](https://i-blog.csdnimg.cn/blog_migrate/346d3a1a70187183c7505b34ddb34343.png)
建模事件总线
与上周的Observer
模式一样,事件处理程序是一个简单的函数,包装在lambda中。
在Python代码中名为EventManager
的事件总线需要:
-
注册事件处理程序
-
这会将lambda与事件类型相关联
处理事件
-
传递事件时,它会调用与事件类型关联的lambda
该模型如下所示:
![在Kotlin中建模事件总线](https://i-blog.csdnimg.cn/blog_migrate/84d49e10313dc776caa4c16d03992658.png)
请注意,只有一个具有Type
属性的通用事件类。 或者,可以为每种事件类型设计一个子类。
这是使用巴士的方法:
classDataStorage(privatevaleventManager:EventManager){ (1)
privatelateinitvardata:List<String>
init{
eventManager.subscribe<String>(Load){load(it)} (2)
eventManager.subscribe<Unit>(Start){produceWords()} (2)
}
privatefunload(event:Event<String>){
if(event.payload!=null){
data=read(event.payload)
.flatMap{it.split("\\W|_".toRegex())}
.filter{it.isNotBlank()&&it.length>=2}
.map(String::toLowerCase)
}
}
privatefunproduceWords(){
for(wordindata){
eventManager.publish(Event(Word,word)) (3)
}
eventManager.publish(Event(EOF)) (3)
}
}
- 嵌入事件管理器。 希望参与相同事件管理系统的每个类都需要使用相同的事件管理器实例。
- 在特定事件类型下关联事件处理程序。
subscribe()
函数允许与事件的有效负载匹配的通用类型。 无负载事件使用Unit
。 - 发送特定事件,触发存储的lambda的调用
不重新发明轮子
可以轻松地使事件管理器具有足够的通用性,以便可以在不同项目中重复使用它。 但是,作为一名开发人员,我很懒惰,所以即使存在的库超出了本练习的范围,我还是喜欢使用现有的库。 好消息是,那里有不同的事件总线库,它们的确很棒:
其中,我使用了-并且很喜欢-番石榴。 番石榴使注册和发送事件的过程变得轻而易举。 这是与上面移植到番石榴的代码相同的代码:
classDataStorage(privatevaleventBus:EventBus){
privatelateinitvardata:List<String>
init{
eventBus.register(this) (1)
}
@Subscribe (2)
privatefunload(event:LoadEvent){ (3)
data=read(event.filename)
.flatMap{it.split("\\W|_".toRegex())}
.filter{it.isNotBlank()&&it.length>=2}
.map(String::toLowerCase)
}
@Subscribe (2)
privatefunproduceWords(event:StartEvent){ (3)
for(wordindata){
eventBus.post(WordEvent(word)) (4)
}
eventBus.post(EOFEvent) (4)
}
}
- 与上面的代码相比,注册非常简单:
register()
不会逐个注册处理程序,而是扫描@Subscribe
注释的类的所有函数。 - 用
@Subscribe
注释的函数被认为是事件处理函数。 它必须接受单个参数,并且不返回任何内容。 - 在我们的自定义实现中,参数的类型扮演
EventManager.Type
的角色。 每个事件都需要一种专用的类型。 - 发布也很简单:创建事件的实例-或将单例对象用于没有有效负载的事件-并使用
post()
函数将其发送到事件总线
结论
消息传递有两种形式:一方面, 观察者模式是点对点的,并且随着观察者需要获取相关主题的参考,观察者-对象对的数量也随之增加。 另一方面,事件总线具有发布-订阅语义。 它允许在观察者-对象对之间引入一个中间组件-事件总线-这样,每个组件仅需要对总线的引用。
尽管完全可以自己实现事件总线的实现,但使用现有的经过战斗力增强的事件总线更容易。 它不在本练习的范围之内,但是Guava的EventBus在这方面是一个可靠的库。
翻译自: https://blog.frankel.ch/exercises-programming-style/10/
单片机 总线编程