Dependency Injection In CAB/SCSF, And When Not To Use It
Chris Lately it seems like I’ve been asked this question a lot when I’m doling out advice on using CAB: How come I don’t use [ComponentDependency] or [State] to pass model state to the Presenters/Controllers via Dependency Injection?
In answering this question I learned something about the way I think about architecture, classes, dependencies and data. And since the question gets asked frequently enough, I figured it deserved a blog post.
There Is A Difference Between A Dependency And The Model
I’ll start by saying that I think there is a big difference between a dependency on another class and model state (data). In my opinion, the calling class should not be able to exist without its dependencies - it’s like a car without wheels or a an iPod without memory - it’s fairly useless. (This is, incidentally, why I prefer constructor injection over property injection; I don’t like the idea of being able to create an object without all the necessary parts.) But a calling class could (and should) be able to exist without the model - the data. The calling class should be able to accept input at any time, not just at construction.
Dependencies, to me, are classes that the calling class must have in order to do its job. Dependencies perform a critical task in the overall use case, and as such they have to be there when the object is created. And hopefully these dependent objects exist as such because we’ve separated out individual responsibilities of the larger use case into small classes that do just one job and do it well. Our calling class should be relying on only interfaces to these dependencies - a contract and nothing more. They key point is that the calling class can’t do its job without its dependencies. It’s like a crock pot without a chord to plug it in; it’s useless if you want to cook anything. If a user tries to operate the calling class without its dependencies it should fail miserably.
Model state, on the other hand, is something the calling class should be able to live without and accept at any time. It’s data that should be passed in whenever. It’s like the food for a crock pot. The crock pot (calling class) can exist just fine without any food in it. You can plug it in, turn it on, and turned the knobs. When you use a crock pot, you put one type of food into it (uncooked data) and you expect to get another type of food out of it (cooked data). The crock pot processes the input and gives an output, and this is the way most classes operate. And like a good class, a crock pot can accept input at any time, be it 9AM or 5PM (you, as the user, just have to be prepared to wait for the expected result, but I’m not going to get into background threads here
)
This is the main reason why I don’t find it acceptable to use Dependency Injection to get the model into a Presenter. A model isn’t a dependency - it’s data. It is input that should be acceptable to pass to the class at any time. Dependency Injection is for creating types that the calling class needs to operate, not for passing data.
So How Do We Pass The Model Around In A Multi-View, Shared-State Scenario?
In CAB, I find the EventBroker to be the best way to pass model state to child views in a large, multi-view scenario where each view needs to rely on a shared model state. I leave it up to the Controlling class (typically a WorkItem) to handle the fetching of the model (from a service, repository or factory). When it is ready to load the child views, it fires a Load event and passes the model state as an event argument. Since objects are pass by reference in .NET, every listening class gets the same instance of the model. An update to some portion of the model on one view can be immediately reflected in the other views (if they’re looking at the relevant portion of the model).
I like the EventBroker in this case because it keeps a clear separation between dependencies and data.
With the EventBroker, the Controller has the flexibility to determine when to give the child views the model. This ends up being useful later on, because now a View can receive updated data at times other than object instantiation, and we get to use the same input mechanism (namely, an event argument). With the EventBroker I don’t have to worry about when a Presenter receives its data, just that when it does, it knows what to do with it.
The other thing I really like about using the EventBroker is that it becomes super-easy to test the Presenter. The EventBroker uses attributes to decorate methods, and that makes testing a Presenter nothing more than simple method calls to invoke the logic. This frees me from having to utilize a TestableRootWorkItem, for instance, to test a Presenter. I don’t like having to rely on big, bloated test stubs just to test simple method calls.