Friends, food, and flourishing

Command pattern

This post is about using the command pattern to simulate a place with beasts in it. A command is an object representing one thing that happened. Objects send commands to a command broker, that broadcasts the commands to all objects that previously said they want to be notified about commands.

Some commands are environmental, like dawn breaking. Something happened that affects all beasts. The command has no particular target, nor did it come from any particular source. When it’s dawn, the beasts will just need to know that it happened, with no other data about any other objects.

The command pattern promotes decoupling. Beasts need to know about the broker, but that’s it.

Some commands result from specific beast actions, like one beast spitting on another. In this implementation, these events require one beast to know who spat at it, and what was spat. Milk, acid, whatevs.

In this case, the domain model requires beasts to know about beasts they spit on, or beasts they are spat on by. They don’t need to know about other beasts, though. There could be a thousand beasts other than the spitter and spittee.

We want to write code like this:

				
					Console.WriteLine("Beast fight!");

// Make the beasts.
Beast joe = new Beast("Joe");
Beast jim = new Beast("Jim");

// Dawn.
DawnCommand dawnCommand = new DawnCommand();
CommandBroker.DistributeCommand(dawnCommand);
// Joe spits milk at Jim.
joe.SpitAt(jim, "milk");
// Jim throws mud at Joe.
jim.ThrowAt(joe, "mud");
// Joe throws chocolate at himself.
joe.ThrowAt(joe, "chocolate");


				
			

Result:

Beast fight!
Joe: Morning has broken.
Jim: Morning has broken.
Joe: I'm spitting milk at Jim.
Jim: Joe spat milk at me.
Jim: I'm throwing mud at Joe.
Joe: Jim threw mud at me.
Joe: I'm throwing chocolate at Joe.
Joe: Joe threw chocolate at me.

Let’s look at the code to get that.

DawnCommand

DawnCommand is a command that tells all beasts dawn has happened. The code to send the command is:

				
					DawnCommand dawnCommand = new DawnCommand();
CommandBroker.DistributeCommand(dawnCommand);
				
			

DawnCommand knows nothing about the beasts.

Here’s the code to make DawnCommand work.

				
					    /// <summary>
    /// Interface for commands. Any object implementing this interface
    /// is an ICommand.
    /// </summary>
    internal interface ICommand
    {
    }


    internal class DawnCommand : ICommand
    {
        // Nothing here. No particular target. Dawn is just something that happens.
    }


    /// <summary>
    /// Sends commands to watchers.
    /// </summary>
    internal static class CommandBroker
    {
        // Notice that ICommands are sent, rather than implementations of ICommands.
        public delegate void CommandReceivers(ICommand command);
        public static CommandReceivers? commandReceivers;

        /// <summary>
        /// Send a command to whatever wants it.
        /// </summary>
        /// <param name="command">An object of a class implementing ICommand</param>
        public static void DistributeCommand(ICommand command) {
            commandReceivers?.Invoke(command);
        }
    }
				
			

The key to this is the way interfaces work. Every DawnCommand is also an ICommand. So in this…

public delegate void CommandReceivers(ICommand command);

… the argument command can be a DawnCommand, or anything else that implements the ICommand interface. Even if DawnCommand added fields, like when dawn happened, it would still be an ICommand.

The beasts:

  • Tell CommandBroker they want to be notified of events
  • Do something when they get a DawnCommand.

This is from the Beast class:

				
					        // Constructor
        public Beast(string name) {
            ...
            // Register with the command broker.
            // In this version, all commands are sent to all command receiving methods.
            ...
            CommandBroker.commandReceivers += DawnBreaks;
        }
        
        
        /// <summary>
        /// Dawn. Called by the broker, when any command is distributed.
        /// </summary>
        /// <param name="command">The command.</param>
        public void DawnBreaks(ICommand command) {
            if (command is DawnCommand) {
                Console.WriteLine($"{Name}: Morning has broken.");
            }
        }

				
			

The Beast constructor tells CommandBroker to add the DawnBreaks method of the Beast instance to CommandBroker.commandReceivers. This is from the broker:

public delegate void CommandReceivers(ICommand command);
public static CommandReceivers? commandReceivers;

commandReceivers is a set of references to methods. Each reference is to a method with one ICommand argument that returns void. The method’s name can be whatevs. That’s commandReceivers is: a set of references. Using those references is another step.

Code like this…

DawnCommand dawnCommand = new DawnCommand();
CommandBroker.DistributeCommand(dawnCommand);

… calls…

public static void DistributeCommand(ICommand command) {
    commandReceivers?.Invoke(command);
}

The broker runs each method referenced in commandReceivers, sending the argument passed into DistributeCommand.

When DawnBreaks runs, it gets an ICommand. Remember, anything that implements the ICommand interface is an ICommand. That’s why this works…

public void DawnBreaks(ICommand command) {

… when you send it a DawnCommand.

But there could be other commands that implement ICommand, besides DawnCommand. We want the DawnBreaks method to only respond to DawnCommands. So, we need to test what command is:

public void DawnBreaks(ICommand command) {
    if (command is DawnCommand) {

OK, lots of detail here. In sum, the key to all this is these lines:

class DawnCommand : ICommand

public static void DistributeCommand(ICommand command) {

DawnCommand dawnCommand = new DawnCommand();
CommandBroker.DistributeCommand(dawnCommand);

This works because a DawnCommand is also an ICommand.

Commands with properties

DawnCommand has no fields or properties. Some commands do. Like this one:

				
					namespace Command
{
    /// <summary>
    /// Represents a beast spitting at another beast.
    /// </summary>
    internal class SpitCommand : ICommand
    {
        // The spitter.
        internal Beast Spitter { get; private set; }
        // The beast spat at.
        internal Beast Spitee { get; private set; }
        // What was spat.
        public string Substance { get; private set; }

        /// <summary>
        /// Make a SpitCommand
        /// </summary>
        /// <param name="spitter">Who spat.</param>
        /// <param name="spitee">Who was spat at.</param>
        /// <param name="substance">What was spat.</param>
        public SpitCommand(Beast spitter, Beast spitee, string substance) {
            Spitter = spitter;
            Spitee = spitee;
            Substance = substance;
        }

    }
}

				
			

SpitCommand is still an ICommand, though, so we can use it wherever we can use an ICommand.

Here’s how beasts spit at each other. First, the original call from main:

				
					Beast joe = new Beast("Joe");
Beast jim = new Beast("Jim");
...
// Joe spits milk at Jim.
joe.SpitAt(jim, "milk");
				
			

Here’s the SpitAt method of Beast.

				
					        /// <summary>
        /// Spit at another beast.
        /// </summary>
        /// <param name="target">What to spit at.</param>
        /// <param name="substance">What to spit.</param>
        public void SpitAt(Beast target, string substance) {
            Console.WriteLine($"{Name}: I'm spitting {substance} at {target.Name}.");
            // Make a new command. The runtime object is a SpitCommand, but it's put
            // into a variable of type ICommand.
            ICommand spitCommand = new SpitCommand(this, target, substance);
            // Tell the broker to send it.
            CommandBroker.DistributeCommand(spitCommand);
        }
				
			

Notice this line:

ICommand spitCommand = new SpitCommand(this, target, substance);

We can put a SpitCommand object into a variable of type ICommand.

Here’s the Beast constructor, telling the broker to add SpatAtMe to commandReceivers:

				
					        public Beast(string name) {
            Name = name;
            // Register with the command broker.
            // In this version, all commands are sent to all command receiving methods.
            CommandBroker.commandReceivers += SpatAtMe;
            CommandBroker.commandReceivers += ThrowsAtMe;
            CommandBroker.commandReceivers += DawnBreaks;
        }
				
			

So, when CommandBroker gets a command to distribute, it will SpatAtMe. No matter which command it is, it will call SpatAtMe.

Here’s SpatAtMe. It starts by checking that the command is the one it processes.

				
					        /// <summary>
        /// Process spit commands.
        /// Called by the broker, when any command is distributed.
        /// </summary>
        /// <param name="command">The command.</param>
        public void SpatAtMe(ICommand command) {
            // What type of command was it?
            if (command is SpitCommand) {
                // A SpitCommand, so this method handles it.
                // Downcast, so we can get the command's fields.
                SpitCommand spitCommand = (SpitCommand)command;
                // Was This the thing that was spat at?
                if (spitCommand.Spitee == this) {
                    // Aye. Something spat at This (me).
                    Beast spitter = spitCommand.Spitter;
                    string substance = spitCommand.Substance;
                    Console.WriteLine($"{Name}: {spitter.Name} spat {substance} at me.");
                }
            }
        }
				
			

Notice the downcast, that is, casting a supertype variable into one of its subtypes. SpatAtMe gets a SpitCommand from the broker, but the method is defined as accepting an ICommand. So, it has to convert the argument to a SpitCommand before it can SpitCommand‘s properties.

ThrowCommand is another ICommand.

				
					    /// <summary>
    /// Represents a beast throwing something at another beast.
    /// </summary>
    internal class ThrowCommand : ICommand
    {
        // The thrower.
        internal Beast Thrower { get; private set; }
        // The target.
        internal Beast Target { get; private set; }
        // What was thrown.
        public string Substance { get; private set; }

        /// <summary>
        /// Make a ThrowCommand.
        /// </summary>
        /// <param name="thrower">Thrower</param>
        /// <param name="target">Target</param>
        /// <param name="substance">What was thrown</param>
        public ThrowCommand(Beast thrower, Beast target, string substance) {
            Thrower = thrower;
            Target = target;
            Substance = substance;
        }
    }

				
			

In Beast, it’s handled like this:

				
					        /// <summary>
        /// Throw something at another beast.
        /// </summary>
        /// <param name="target">What to throw at.</param>
        /// <param name="substance">What to throw.</param>
        public void ThrowAt(Beast target, string substance) {
            Console.WriteLine($"{Name}: I'm throwing {substance} at {target.Name}.");
            // Make a throw command, but put it into a variable of type ICommand.
            ICommand throwCommand = new ThrowCommand(this, target, substance);
            // Send it.
            CommandBroker.DistributeCommand(throwCommand);
        }

        /// <summary>
        /// Process throw commands.
        /// Called by the broker, when any command is distributed.
        /// </summary>
        /// <param name="command">The command</param>
        public void ThrowsAtMe(ICommand command) {
            // Was it a throw command?
            if (command is ThrowCommand) {
                // Yes. Downcast, so we can get its fields.
                ThrowCommand throwCommand = (ThrowCommand)command;
                // Was This the target of the throw?
                if (throwCommand.Target == this) {
                    // Yes, something threw something at This (me).
                    Beast thrower = throwCommand.Thrower;
                    string substance = throwCommand.Substance;
                    Console.WriteLine($"{Name}: {thrower.Name} threw {substance} at me.");
                }
            }
        }

				
			

Improvements

Every Beast receives every command. Some improvements:

  • Have different delegates for different command types.
  • When a beast spits on another, send the command just to the spat on beast, rather than all beasts.

Later, I’ll move this into Unity, and maybe add MVC along with the command pattern.

 

Leave a Reply

Your email address will not be published. Required fields are marked *

css.php