The Hanami::Events is a small experimental events framework for Hanami. Its codebase is small, simple and and contains some real-world examples of the strategy pattern usage - one of the GoF patterns for object-oriented design.
The main purpose of the strategy is to deal with situations when we have two or more different classes, that have to interact with another class via a unified interface. The first thing that comes into my head when talking about strategy is the code that has to perform some command via interaction with different third-party API.
Let’s look at a simplified example of strategy:
We can underline the following concepts regarding the strategy pattern:
The main public API that is visible to library’s user. This may be a separate declarative class, or a completely virtual abstraction. The latter may turn strategy into the template method.
Any object using interfaces public API methods. It knows nothing about implementation details.
Implementation of the interface’s public API methods.
Classes containing the implementation details.
These concepts are not taken from original GoF book, by the way.
Looking at the Hanami::Events code, you can easily spot the usage of the strategy pattern:
Formatter[type].new is a typical way to initialize the concrete class when using the Strategy pattern. The syntax assumes that
you use the dry-container library, but for simplicity, you can imagine that all available concrete classes are stored in some
data structure and being fetched from it by key lookup. Concrete classes themselves reside in lib/hanami/events/formatters
json.rb plain_text.rb xml.rb
There is only one interface method,
format, which delegates its arguments to one of the concrete classes, and thus
we can have the output formatted by three different formatters.
This example recalls the respective example from Russ Olsens’ “Design Patterns in Ruby” book, where the Strategy pattern is also described via the example of some formatter.
Also, there is another example of the strategy in the Hanami::Events codebase. Though the naming may confuse you - the directory named adapter, this is not an adapter pattern, but strategy. The key differences between strategy and adapter can be described as following:
The Adapter is used when you already have classes that have to collaborate, but their interfaces differ. Thus, the adapter provides an additional layer to make one class support the given interface. The Strategy is also about providing a single interface but from the point of interchangeability. The Adapter classes are a completely different creatures, but the Strategy classes have a very similar behavior, differing only by implementation details.
The code in
lib/hanami/events/adapter is all about interchangeability between classes that perform the same work, so this code is
definitely about strategy pattern.
This method initializes the class with given concrete class and therefore, all the public API calls will be directed to that class:
def initialize(adapter_name, options) @adapter = Adapter[adapter_name.to_sym].new(options) end
Then we have the three methods, that are forming the interface,
subscribers (the latter is called from
subscribed_events method). All classes collaborating with
Hanami::Events::Base class have to implements their version of these methods.
This piece of code registers three concrete classes, MemorySync, MemoryAsync and Redis using dry-container.
And then each of these classes implement aforementioned interface methods:
The Redis and MemorySync classes will implement these methods in their own way.
The code we have seen in Hanami::Events library showed the real-world usage of the strategy pattern and helped to make the difference between Adapter and Strategy patterns more clear.