控制反转(依赖反转)系统中的事件朝哪个方向发展?
向上还是向下?
我是一个非常注重视觉的人。我将我的应用程序视为一个层次结构,顶部是根,底部是叶子。
我还了解到 IoC 容器不知道其所包含对象的职责/功能。相反,所包含的对象通过一些抽象接口了解它们的容器,即“上下文”。
UP:(非 IoC 方式?) 我的事件是否应该从层次结构的底部分派,并通过责任链模式向上冒泡到其父级,以便所包含的对象不知道其容器?例如,我的 GUI 中的按钮调度一个 CLICKED 事件,该事件被侦听容器捕获,该窗口通过关闭自身进行响应。
向下:(国际奥委会的方式?) 我的事件是否应该由容器从层次结构的顶部分派并到达已直接订阅该容器的包含侦听器,以便容器不知道其内容?例如,容器窗口调度一个 CLOSED 事件,该事件由包含的按钮对象直接接收,这些按钮对象通过关闭自身进行响应,然后窗口也通过关闭自身来进行响应。
“向上”对我来说似乎很自然,但由于 IoC 有一个容器不知道其所包含的对象的行为,所以我不想响应它们的事件。
我意识到系统的几乎任何部分都可以监听事件,但我想了解 IoC 参与者之间的基本关系,这样我就可以正确地构建它们。我假设人们通常不会只是分散有关他们的程序的事件而不考虑结构关系、依赖关系等。
我的问题来自于 IoC 系统中的责任放置——它是包含的对象的责任来调用容器和容器有责任为其依赖对象提供服务(这反转了非 IoC 范式——因此成为“依赖反转”的同义词)。这似乎是国际奥委会的一个非常重要的基本组成部分——职责的转移。我认为调用对象的函数或侦听其事件都是依赖另一个对象的示例。当一个对象基于另一个对象定义自己的行为时,我将其称为依赖关系,因为一个对象知道另一个对象,也知道另一个对象做什么。它在另一个对象的上下文中定义了自己。据我了解,IoC 的设置使得所包含的对象依赖于容器,因此所包含的对象有责任了解有关容器的所有信息。并且容器不应该有责任了解所包含的对象。因此,对于 CONTAINER 来说,监听 CONTAINED 注入对象上的事件在我看来就像是责任的错位,因为这意味着它了解其内容。
在学习依赖项“注入”和“反转”不同后,这个问题已被重新表述。上一个问题在此处找到。
Up or Down?
I'm a very visual person. I'm thinking of my application as a hierarchy, where the top is the root and the bottom is a leaf.
I'm also under the understanding that IoC containers are ignorant of their contained objects' responsibilities/functions. Instead, the contained objects know about their container, i.e. "context", via some abstracted interface.
UP: (The non-IoC way?)
Should my events be dispatched from the bottom of my hierarchy and bubble upwards via a Chain-of-Responsibility pattern to their parents so that the contained objects are unaware of their containers? E.g. a button in my GUI dispatches a CLICKED event, which is caught by a listening container window that responds by closing itself.
DOWN: (The IoC way?)
Should my events be dispatched from the top of my hierarchy by a container and reach contained listeners that have subscribed directly to the container so that the containers are unaware of their contents? E.g. a container window dispatches a CLOSED event, which is received directly by contained button objects that respond by closing themselves and then the window follows suit by closing itself.
'Up' seems natural to me, but since IoC has a container being unaware of its contained objects' behavior, I wouldn't want to respond to their events.
I realize it's POSSIBLE to have nearly any part of a system listen to an event, but I want to understand the fundamental relationship between IoC participants, so I can structure them properly. I'm assuming people don't usually just scatter events about their program without regard to structural relationships, dependencies, etc.
My question arises from the responsibility placement in an IoC system -- it's the contained object's responsibility to make calls on the container and the container's responsibility to provide services to its dependent objects (which inverts the non-IoC paradigm -- hence the synonym "Dependency Inversion"). This seems to be a very important, fundamental component of IoC -- a shifting of responsibilities. I consider calling an object's functions or listening to its events to BOTH be examples of depending on another object. When an object defines its own behavior based on another object, I call that a dependency because one object KNOWS of the other and also KNOWS what the other does. It has defined itself within the context of the other object. As I understand, IoC is set up so that the contained object is dependent on the container, so it should be the contained object's responsibility to know all about the container. And it should NOT be the container's responsibility to know about the contained object. So, for the CONTAINER to be listening to events on the CONTAINED injected object seems to me like like a misplacement of responsibilities because that means it knows something about its contents.
This question has been re-phrased after learning Dependency 'Injection' and 'Inversion' are different. Previous question found here.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
为了简洁起见,我将容器中包含的对象称为“客户端对象”或简称为“客户端”...
事件可以合法地双向流动,而不会在容器和客户端之间引入不需要的依赖关系。事实上,允许容器从客户端接收事件是最小化这些依赖性的好方法。它们精确地提供了松散耦合的路径,以便容器能够在与客户端通信的同时保持对客户端实际情况的无知。 只要所讨论的事件是由容器定义的,而不是由客户端定义的。
您似乎最关心的是客户端对象将触发由容器消耗的事件的想法。如果这些事件是由客户端对象定义的,这将是一个问题。这显然会创建对容器中客户端对象的硬依赖;容器必须了解这些客户端定义的事件并专门为它们进行编码。这将违背国际奥委会的主要思想。但如果这些事件是由容器定义的,那么这不是一个问题 - 事实上,这是保持容器与客户端对象松散耦合的最佳方式。客户端可以触发这些事件以供容器使用,但他们没有定义这些事件。它们可能只会触发容器知道如何侦听的事件集(如容器所定义)。 (当然,他们可以出于其他目的触发其他事件,但容器不会知道或关心)。
例如,考虑一下容器提供了显示和打印“视图”的能力,这些视图是 UI 中的小内容框(这似乎是您在帖子中暗示的那种环境,因为您提到了很多 GUI 示例- 但这些概念适用于与 GUI 无关的任何事情)。该容器公开了一个名为 Print 的事件,该事件采用一些有关打印内容的参数(可能是对 IPrintable 接口的引用,可能是要打印的原始数据,取决于您试图使用 IoC 实现的目标)。当用户按下客户端对象上的按钮之一时,在容器内运行的客户端对象可以触发打印事件。然后容器将接收该事件并代表客户端对象处理打印。或者客户端对象可以触发“CloseMe”事件,容器将接收该事件,并且作为响应,将销毁触发该事件的客户端对象(以及其他处理,包括可能询问用户是否确定等)
相反,当客户端对象感兴趣的事情发生时,容器可以触发事件。同样,这些都只在容器中定义。但客户端对象可以订阅它们。在前面的示例中,容器可能会公开一个 PrintFinished 事件,客户端对象可以使用该事件在用户自己的 UI 中向用户显示一条消息:“您的文档已完成!”容器可以触发 ApplicationClosing 消息,所有客户端对象都可以使用该消息在容器完全关闭之前拆除它们所持有的任何本机资源。这些只是愚蠢、简单的例子,但希望它们能证明这个想法。
总而言之,我认为事件双向流动不仅合法,而且非常有用——从容器到客户端,从客户端到容器。与“向上”或“向下”无关的关键是谁定义了这些事件。容器定义了它们的全部。
For the sake of brevity, I'm going to refer to the objects contained within the container as "client objects" or just "clients"...
Events can legitimately flow in both directions without introducing unwanted dependencies between the container and the clients. In fact, allowing the container to receive events from the clients is a great way to minimize these dependencies. They provide a path for loose coupling precisely so that the container will be able to remain ignorant of what the clients actually are while still communicating with them. As long as the events in question are defined by the container, not by the clients.
You seem mostly concerned with the idea that client objects would ever fire events that are consumed by the container. This would be a concern if those events were defined by the client objects. This would obviously create a hard dependency on the client object from the container; the container would have to know about those client-defined events and be coded specifically for them. This would defeat the main idea of IoC. But if those events are defined by the container, then this is not a concern - in fact, it is the best way to keep the container loosely coupled with the client objects. Clients may fire those events for consumption by the container, but they did not define those events. They may only fire the set of events that the container knows how to listen for, as defined by the container. (Of course they could fire other events for other purposes, but the container wouldn't know or care).
Consider, for example, that the container offers the ability to display and print "views", which are little content boxes in a UI (this seems to be the kind of environment you were hinting at in your post since you mentioned so many GUI examples - but these concepts apply outside of anything to do with GUI). The container exposes an event called Print that takes some parameters about what to print (maybe a reference to a IPrintable interface, maybe raw data to print, depends on what you are trying to achieve with your IoC). Client objects that are running inside of the container could, when a user presses one of the buttons on the client object, fire the Print event. The container would then receive that event and handle the printing on behalf of the client object. Or the client object could fire a "CloseMe" event, which the container would receive and, in response, would destroy the client object that fired the event (along with other processing, including maybe asking the user if he's sure, etc.)
Conversely, the container could fire events when things happen that the client objects are interested in. Again, these are all defined only in the container. But client objects may subscribe to them. From the previous example, the container might expose a PrintFinished event that client objects could use in order to show the user a message within their own UI saying "Your document is done!" The container could fire a ApplicationClosing message that all client objects could use to tear down any native resources they're holding on to before the container closes completely. These are just silly, simple, examples, but hopefully they demonstrate the idea.
So in summary, I think it's not only legitimate but very useful for events to flow in both directions - from container to clients and from clients to container. The key thing, which has nothing to do with "up" or "down", is who defines those events. The container defines all of them.
容器不应该了解其组件或它们的行为方式。容器的职责通常是实例化依赖项的领域,而不是通信依赖项。容器应该是一个广义的工厂。
这就是结构图!=通信图的原因。我相信您已经意识到,作为工厂,容器是对象图的根,但是响应或发布事件的容器根本没有意义。
那么你想要的是一个负责管理事件的人。一种专门的组件,了解事件,并且可以将事件的实例代理给感兴趣的各方。您需要一个通用的事件代理。如果愿意的话,可以使用事件路由器。
您可能会找到其中一种的多种实现。例如 Cab 的 EventBroker、Prism 的事件聚合器或我最喜欢的 EventHub。最后一篇是与 Cab 广泛合作以及与 Jeremy Miller 和 Glenn Block 通信的经验的结晶,由 Kent 撰写,一位非常酷的人家伙。
如果您使用容器,您可能熟悉依赖注入和控制反转。需要发布事件的人会被注入 IEventHub 并使用强类型主题类调用 Publish。需要订阅事件的人也会注入一个 IEventHub 并实现一个强类型的 ISubscriber 接口 [它基本上公开了一个强类型的 Receive 方法]。就是这样。
现在,事件可以按照任何方式进行[在业务要求允许的情况下],正如它们应该的那样。
Containers should not have knowledge of their components or how they behave. A container's responsibilities are typically the domain of instantiation dependency - not communication dependency. A container should be a generalized factory.
This is the reason why structural graph != communication graph. As I am sure you have realized, as a factory a container is the root of your object graph, but a container responding to or publishing an event simply does not make sense.
What you want then, is someone who's job it is to manage events. A specialized component that knows about events, and can broker an instance of one to interested parties. You want a generalized event broker. An event router if you will.
You could probably find many implementations of one. Such as Cab's EventBroker, Prism's event aggregator, or my favourite EventHub. This last one is a culmination of experience from working with Cab extensively and correspondence with Jeremy Miller and Glenn Block, by Kent, a very cool guy.
If you are using a container, you are likely familiar with Dependency Injection and Inversion of Control. Someone who needs to publish events is injected an IEventHub and invokes Publish with a strongly typed subject class. Someone who needs to subscribe to an event is also injected an IEventHub and implements a strongly typed ISubscriber interface [which basically exposes a strongly typed Receive method]. That's it.
Now events can travel any which way [as business requirements permit], as they should.