API Design
There are several important concerns that needs to be taken into account when designing an API. Clarity is an important concern, of course, but the responsibilities of the users and implementers of the API should be given a lot of consideration. Let us take a look at a couple of designs for a simple notification observer. We need to observe a set of actions (with context). I don't want to have force mutable state on the users, so I have started with this approach (using out parameters instead of return values in order to name the parameter):
public interface INotificationObserver { void OnNewSession(out object sessionTag); void OnNewStatement(object sessionTag, StatementInformation statementInformation, out object statementTag); void OnNewAction(object statementTag, ActionInformation actionInformation); }
I don't really like this, too much magic objects here, and too much work for the client. We can do it in a slightly different way, however:
public delegate void OnNewAction(ActionInformation actionInformation); public delegate void OnNewStatement(StatementInformation statementInformation, out OnNewAction onNewAction); public interface INotificationObserver { void OnNewSession(out OnNewStatement onNewStatement); }
Comments
What's exactly the point in not using events here?
You could easily create three events: NewSessionCreated, NewStatementCreated and NewActionInvoked. Then any client can use an arbitrary subset of these events to implement his desired behaviour...
I have to admit that I never understood the need for the observation pattern in c#, for me it's integrated in the language.
Your solution is tailored for a specific use case that you envision, and that specific use case is probably easier done your way. However, the user will also have to understand and conform to your use case, which may be a problem.
I agree with Frederik:
event EventHandler <sessioneventargs NewSession;
event EventHandler <statementeventargs NewStatement;
event EventHandler <actioneventargs NewAction;
// names can be better, but I do not know what they actually do
So you can handle all actions without even thinking on what a session is. If you want to handle them in a specific way for a specific statement, you can have NewAction event on the StatementContext available through the StatementEventArgs.
Sorry, the blog ate my generics.
event EventHandler<SessionEventArgs> NewSession;
event EventHandler<StatementEventArgs> NewStatement;
event EventHandler<ActionEventArgs> NewAction;
Frederik & Andrey,
The problem is state. I don't want to just just any old NewStatement, I want to call NewStatement for a particular session
And which state exactly can't you add to the event as EventArgs?
The general pattern for using EventArgs is to pass the "state" data to the constructor and refer to that data using read only properties.
This effectively makes EventArgs (and its properly designed descendants) immutable and therefore, I think, suitable for the scenarios you're envisioning.
There are, of course, some exceptions to this rule (namely, CancelEventArgs), but they are few and far between, target a different use case and do not inhibit using EventArgs correctly otherwise.
You don't have to use events and EventArgs; you can use a similar pattern if you want, as long as you pass around an immutable state-holding object.
On the other hand, using out arguments in the API forces the programmer to create variables that clutter up the code and may not be needed to begin with.
They also make the code difficult to maintain when (not if) the API changes because there's no encapsulation, as with EventArgs.
Another disadvantage is that they inhibit fluent programming.
In my experience, out arguments are either used in "Try" kind of statements for safe conversions or other scenarios where exceptions are ignored, or in opaque difficult-to-document and non-standard scenarios. While your example specifically does not seem to fall into either category, usually they do, and probably should not be recommended as a programming style.
Anyway, that's my take. I'd be happy to hear a contrary opinion...
__I don't want to just just any old NewStatement, I want to call NewStatement for a particular session.
That's easy, just add NewStatement event to a SessionContext provided in a SessionEventArgs.
Yes, I can.
However, I find the usage of the out parameter make it explicit that I have to fill in the value, and make the purpose of the code clearer.
It is actually a delegate which has another out parameter for a deeper layer in the code, which also maintains state. Seems simpler that way to me
Do you __have to subscribe to all of them?
What if you do not care about Actions?
It probably depends on what observer is for.
I my scenario, yes I do. There is a well defined contract between the observer and the observee.
Comment preview