Mastering Generics with Protocols: The Specification Pattern

Tim Beals 🎸
Swift2Go
Published in
7 min readSep 19, 2018

If you’re reading this, the chances are you already know the benefits of working with generics. You want to code less. You want the code you write to do more. The chances are you have previously tried using generics in your programming only to reach heights of frustration you never thought possible. You’re not alone. Being able to work in a generic way but still successfully infer your types can be tricky. Luckily, combining the use of generics with protocols is really effective, and once you learn a small number of techniques you’ll find that this tricky thing is actually not so tricky after all.

In order to explore this topic we’re going to build and refine a specification design pattern focusing on four techniques that (I hope) will become staples in your coding repertoire. As an aside, this pattern is an excellent way of addressing the Open-Closed Principle; something that I highly recommend you be familiar with, and something I wrote about recently here.

Filtering Products

Our hypothetical situation is that we have an entity called Product, and we want to be able to filter an array of products by some kind of specification. Let’s first take a look at Product:

Now, we want to build a Filter which accepts an array of product instances and returns only those which meet a certain specification. For example, we may want our filter to return only small products. The naive approach would most likely look something like this:

We can see from the print out (line 23), that this approach ‘works’ just fine, but even a little probing reveals its total inflexibility:

What if we want to filter objects that are not of type Product?

What if we want to filter by a different attribute?

What if we want to filter by more than one attribute?

You can imagine all the possible permutations of products and specifications and shudder at the thought of all of the modification that would need to happen to our existing entities. Enter the specification pattern…

The Specification Pattern

Technique 1: Setting associatedtypes with typealias

In this first iteration of our pattern, we will make our filtering specifications adopt a uniform interface through the use of a protocol. To use generic types in a protocol use associatedtype T, then you can set the type in your entity using typealias T = SomeType. Here is how we would implement our color and size specifications using this technique.

In the code above, we conform to our protocol (line 8, 18) and set the associatedtype to be our product using typealias (lines 9, 19). Our specifications have only one color/size property which is set upon initialization (line 11, 21). The protocol method isSatisfied(item:) magically infers the input parameter to be of type Product because we set the typealias (lines 13, 23). The method checks if the color/size property in our product instance has the same value as the one in our specification (lines 14, 24).

The next step is to create a filter class that uses our newly created specification entities. A first attempt at a non-generic approach might look like this:

Uh oh. Swift compiler is yelling.

This makes sense, because our protocol type Specification is generic, and we could feasibly add a specification instance with an associatedtype that is not Product. What we need to do is check that the type in our items argument is the same as the type in our spec argument. Let’s try that.

Technique 2: Checking conditions on generic instances

We can perform checks at the beginning of our func declaration using the following pattern:

func someFunc<T: SomeType>(argument: T) where T.someProperty == self.someProperty

Notice that we are using typical placeholder syntax <> before our input parameters to indicate that our function is using a generic type. To be able to access the internals of the instance of T we need to first say what type our instance is <T: SomeType>; in this case T is of type SomeType. Next we need to indicate that we want to check a condition which is done with the keyword where. Finally, we are able to access the properties of our type T.someProperty and perform our boolean check. Now the function will work provided the generic type is SomeType and has a value of someProperty that matches our own.

With this in mind, lets build a generic Filter that can accept any item and any specification, on the condition that the specification associatedtype matches the item type.

Now instead of setting the specs parameter type as Specification, we can instead use Spec, safe in the knowledge that it has an associatedtype that matches the one in our Filter (line 5). Once the protocol is created, implementing Filter itself is pretty simple. We conform to our protocol (line 9), set the typealias (line 10), and then filter(items: , specs:) magically sets itself up with the correct types. Now we can test with our previously made Product instances

At this point we can certainly say that everything works. Creating more filters and specifications is a pretty simple task with our current design. But let’s see if we can’t refine it more. At the moment, our ColorSpecification entity will only work with instances of Product type. Wouldn’t it make sense for ColorSpecification to be able to work with any type that has a color? Come to think of it, couldn’t we say the same for our SizeSpecification, and our ProductFilter too? It’s time to make our entities every bit as generic as our protocols.

Technique 3: Setting associatedtypes with static initialization

To make our entities generic, we need to use our generic placeholder syntax, much like we did in our previous section. Here’s what it looks like:

struct MyStruct<T> : ProtocolType { }

In this case, when we define our entity we are conforming to our ProtocolType assigning T for the associatedtype. Now we can work with any type at all! When we initialize, we will need to use the syntax:

let a = MyStruct<SomeType>()

Let’s now apply this technique to our existing design:

The first step is to create protocols for Sized and Colored (lines 2–8) and have our Product adopt each of them. You will remember that our Product already has the properties defined in the protocols (color and size), so we don’t need to add anything to it. Now our two specification entities use <T: Colored> and <T: Sized> to assign the associatedtype (line 15, 24). Because Product conforms to these protocols we can set it as our type, but we can just as easily set any other type as long as it also has the necessary protocol adoption. You will notice that we use <Product> in the instantiation of our SizeSpecification (line 49); however, this is not needed when creating our GenericFilter because the only place the generic T is used is in filter(items: , specs: ) and the type can be inferred by the checks that we created in the previous section (lines 36–37).

Now we can truly say that we have a generic, reusable design. However, there is still one promise that we haven’t delivered on. In the beginning we decided that we want to be able to filter by not one, but many specifications. We want to be able to achieve this without writing any new methods in our filter which brings us to our final technique for this article.

Technique 4: Using protocol type for recursive design

You will notice that our filter method has an input parameter of type Spec (line 36), so we can only add one instance that conforms to Specification protocol. However, provided that requirement is met, we can actually check for as many attributes as we like. At the moment our Specification types are scalar (single units), but there’s no reason why we can’t have several Specification types encapsulated within another Specification type. This is called a recursive object. Here’s how we can achieve this using a combination of techniques that we have previously explored:

There is a whole lot going on in the definition of our AndSpecification (line 2) so let’s break it down. First, notice that our entity conforms to Specification itself, so it can be passed into our filter. We also have two properties of type SpecA and SpecB, each of which conforms to Specification protocol. This means that we have a total of three associated types to satisfy. We do this by, declaring three generic types T, SpecA, and SpecB. To ensure that everything works together, we need to check that our associated types for all three generics are the same. We do this in our where statement. We also need to create a custom initializer (line 7) because all the generic checking is enough for the compiler to be unable to create the member-wise initializer that we come to expect for structs.

In the use case, you can see the red and small Specifications being initialized statically with the type Product (lines 18–19). This is not required for the initialization of the AndSpecification because the type checks that are performed on the input arguments of the initializer, are sufficient to infer the type T.

Please note also that the recursive nature of AndSpecification means that you can pass another instance of AndSpecification as one of the input arguments in the initializer, essentially allowing you to filter by any number of specifications.

Wrap Up

In this article we created and honed a specification design pattern in order to achieve our desired level of abstraction. We can now filter any object by any number of specifications, and those specifications are themselves reusable across any number of entities. We achieved this through the use of generics in a protocol oriented way. There were four essential techniques that we covered and when used in combination, they are powerful enough to help you achieve just about any kind of abstraction you want.

You can clone the git repository for this project here

Thank you for reading!

--

--