I keep searching for concrete examples in my quest to convince you that Go is a great language for business. I found in finance a great source of ideas to prove my point. I already explored this subject twice (1 and 2) and today I’m going to explore it again.
The strategy pattern is one of the simplest yet underutilized design patterns out there. It is useful when we have one goal and several ways to achieve that. In this case, our goal is to get stock prices from different stock exchanges. Each stock exchange requires a dedicated strategy to fetch the prices since they have different APIs and abstractions. The advantage of this approach is that if we want to support a new stock exchange all we have to do is to add a new strategy and barely change anywhere else. The code that calls the strategies don’t change, which also prevents breaking exising tests and introducing unexpected bugs.
“The Strategy Pattern is a behavioral pattern that defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.”
Calling the Strategy Pattern Implementation
We start the implementation with constants that represent the supported stock exchanges and a struct to represent the stocks:
Concerning the attributes, the
Exchange is one of those constants declared above. The
Ticker is an acronym that represents the stock. The
Name helps to decode the ticker. The
Price is what we are looking for, so it will be filled later. The following
main() function has a list of pointers to instances of Stock. Yes, we are working with the same instances from the begining to the end. You may not like the mutability aspect of it, but it will help to keep the order of the original list unchanged. I honestly think this is a responsible case of multability because it is reducing accidental complexityhere.
main() function is the entry point of the application, working like a controller. It calls the function
getPrices(), which is like a service function, and passes a list of stocks. The function is expected to return the same list with the prices defined. In the sequence, the list is printed as an output of the program.
getPrices() function counts with the help of two other functions to be consistent with the single responsibility principle. Let’s take a look at them:
prepareExchangeStrategies() function groups stocks by exchange to optimize the calls to the exchange pricing services. Notice that it’s using the strategies to hold the stocks (
pricing.addStock(stock)). It illustrates that a strategy can keep some temporary state to be used when convinient.
Now, look at the
searchStocks() function and notice how clean it is. Those few lines of code are calling distinct algorithms doing different things to achieve the same goal: retrieve the prices of the stocks they hold. The
ExchangeFactory struct and the
search() function are shaped according to the strategy pattern, making things cleaner and elegant.
Implemeting the Strategy Pattern
Pricing interface is the core of the strategy pattern. It makes the caller think that all strategies look the same, but they actually have unique souls.
We have seem
Pricing behaviours been called from the functions
searchStocks(). These behaviors have an implementation per stock exchange.
But the pricing instances are not created from nowhere. The
ExchangeFactory struct and its method
GetExchangePricing() figure out which strategy the caller needs based on the stock exchange. It does the job using a Map with the exchange’s acronym as keys.
Instances of the strategies are kept in memory for the length of the call. They can’t be kept longer because they cache the stocks they work with, as we can see in the following three implementations.
Our goal is to explain the strategy pattern, not fetching prices from stock exchanges. Maybe we can do it in another time, but let’s keep things short. The
NasdaqPricing struct caches Nasdaq’s stocks using a list and the
addStock() method. The cached stocks are then processed in the
getName() is used just for friendly error handling messages.
The other two strategies are essentially the same, except for the
search() method implementation.
Here, Go shows a glance of its elegance. It doesn’t require you to explicitly indicate the interfaces your structs implement, like in Java (
public class Foo implements Bar). It is enough to have the same method signatures. Simplicity matters and it makes me love this language more than any other.
A full implementation of this code is available in my Blog Examples Repo.