Affordances: Factory Pattern
This post is part of an ongoing series about Affordances and Programming Languages.
Many programming languages provide support for reflection, which allows for very creative metaprogramming when solving problems. The reflection facilities in various languages differ in the kinds of features they provide, their ease of use, and their verbosity.
Reflection is a kind of affordance.
Reflection is often used to implement the Factory Pattern. As an example, consider an application that receives commands over a socket. Each command takes a different set of arguments and performs some more or less complex action in response. This example was inspired by John Pignata’s excellent talk at Mountain West Ruby Conference 2013, Code Smells: Your Refactoring Cheat Codes.
Assuming that each command has been encapsulated into its own subclass
of Command
, the obvious solution is to dispatch commands with a case
statement.
Note that C++ doesn’t allow strings as case options in a switch
statement, so we’re forced to use a series of if
statements
instead. This is also a kind of affordance.
std::unique_ptr<Command> command;
if (commandToken == "PING")
{
command.reset(new Ping);
}
else if (commandToken == "SEND")
{
command.reset(new Send);
}
...
else
{
throw CommandNotFound(commandToken);
}
command->execute(arguments);
Every time we add a new command, we have to update this if
statement. Since C++ only has minimal reflection capabilities, we
might choose to stop there, or we might introduce some kind of command
registry using macros or explicit initialization.
In Ruby, since we have reflection and metaprogramming facilities, we can come up with something a little more maintainable:
module Commands
COMMANDS = {
"PING" => Ping,
"SEND" => Send,
...
}
def self.dispatch(command_token, arguments)
klass = COMMANDS[command_token] || raise CommandNotFound.new(command_token)
klass.new.execute(arguments)
end
end
Now, adding a new command is as simple as adding one more entry in the
COMMANDS
hash.
In Ruby, superclasses don’t know about their subclasses by default. This is not true of Smalltalk, though, so we can take advantage of that:
Command class>>dispatch: aString arguments: anArray
class = self allSubclasses
detect: [:each | each token = aString]
ifNone: [(CommandNotFound command: aString) raise].
class new execute: anArray
Command class>>token
^self subclassResponsibility
Ping class>>token
^'PING'
Send class>>token
^'SEND'
By taking advantage of Smalltalk’s reflection, we can completely
eliminate any central registry of commands. Now, adding a new command
is as simple as introducing a new Command subclass. As long as the
new class inherits from Command
and implements the class-side
token
method, it will automatically be found by the dispatcher.
It is possible to implement a similar approach in Ruby using
Class#inherited
, but that’s a bit more work to set up.
And that’s actually the point of affordances. Because Smalltalk provides direct access to subclasses, it affords the ability to find the commands dynamically. Because Ruby and Smalltalk provide reflection, they afford a cleaner implementation of the factory pattern than languages without reflection.