I have wrote a notification manager that objects can subscribe to for specific message types, providing the manager with a callback. The objects are then notified via a publication from the notification manager. It looks something like this:
[TestMethod]
[TestCategory("Runtime.Game - NotificationManager")]
public void Publish_invokes_callbacks()
{
// Arrange
bool callbackCalled = false;
string messageContent = "Test";
INotificationCenter notificationCenter = new NotificationManager();
ISubscription subscription = notificationCenter.Subscribe<ShoutMessage>(
(msg, sub) => callback = msg.Content == messageContent);
// Act
notificationCenter.Publish(new ShoutMessage(messageContent));
subscription.Unsubscribe();
// Assert
Assert.IsTrue(callbackCalled, "The subscriber did not have its callback invoked.");
}
My objects would take a NotificationManager instance as a dependency in its constructor. My DI system is responsible for making sure that it provides a singleton instance of the NotificationManager.
The other approach I have been playing with is just using eventing. This works fine, except that I have to make sure and provide all of my objects, a reference to the objects they need to subscribe to. In the following example, I have to give the class below (ClientHandler
class) a reference to the IPlayer
instance. I also have a Server
class that has event handlers for events on both the ClientHandler
and the IPlayer
instance associated with the ClientHandler
.
/// <summary>
/// Starts listening for network communication sent from the client to the server
/// </summary>
public void StartListeningForData()
{
// Start listening for messages from the client.
this.CurrentSocket.BeginReceive(this.Buffer, 0, bufferSize, 0, new AsyncCallback(this.ReceiveData), null);
// Subscribe to the result of processing a command from the client.
this.Player.CommandManager.CommandCompleted += this.HandleCommandExecutionCompleted;
}
private void HandleCommandExecutionCompleted(object sender, CommandCompletionArgs e)
{
if (!this.IsConnectionValid)
{
return;
}
// Temporarily just send back the same content we received from the client.
this.SendMessage($"{e.CommandResult.Result}");
}
The nice part of the notification manager is that I can pattern out the way things communicate, by having all the objects that need to react to events, use the NotificationManager to subscribe for those events. Is there any downsides to this? It also feels safer, as I can unsubscribe from the NotificationManager singleton instead of trying to make sure I unregister my event handlers prior to an object being disposed. I can do a quick null check, and if the IPlayer
is null, just unsubscribe from the subscription I'm holding.
The NotificationManager also supports predicates, so that I can only handle messages that are applicable to what the object actually needs to see. An example is only handling chat messages that are sent from a player in the same room as the character receiving the message
ISubscription subscription = notificationCenter.Subscribe<ShoutMessage>(
(msg, sub) => { /* ... Do stuff .. */ sub.Unsubscribe(); },
msg => ((DefaultPlayer)msg.Sender).CurrentRoom == this.CurrentRoom);
Do you guys see a downside to building out an entire application using pub/sub vs just using eventing? They both have the same issue of subscribing to an object using strong references. I think debugging pub/sub can often times be difficult because the callstack is bloated.
The only other difference I see between the two is being a little more loosely coupled by having everything subscribe through the NotificationManager
only, and not the individual objects.
What kind of experience have you guys had with pub/sub vs eventing? I'm not asking for which is better. I'm wanting to here your experience with both of them and how it turned out for you.