Events in an Inversion of Control (Dependency Inversion) system go which way?
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, ie "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? Eg 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? Eg 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.
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.
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.
链接地址: http://www.djcxy.com/p/82234.html上一篇: 如何在不破坏封装的情况下使用依赖注入?