Uncategorized

Prefer functions to protocols

Let’s look at a simple concrete example.

Suppose we want to test a part of our code that’s time dependent:

class SomethingImportant {
  func aTimeDependentAction() {
    var currentTime = Date()
    // ...do something with the current time
  }
}

To test this code, rather than using Date() to get the current time, we want to inject a dependency that returns the time so we can swap it out in our tests. Great! This dependency injection is a classic case for a protocol, or so we might believe. Let’s create a protocol and a default implementation:

protocol DateProvider {
  func currentDate() -> Date
}
class DefaultDateProvider: DateProvider {
  func currentDate() -> Date {
    return Date()
  }
}

Well that was easy! Now let’s add a dependency on this to the class we want to test:

class SomethingImportant {
  var dateProvider: DateProvider

  init(dateProvider: DateProvider = DefaultDateProvider()) {
    self.dateProvider = dateProvider
  }

  func aTimeDependentAction() {
    var currentTime = dateProvider.currentDate()
    // ...do something with the current time
  }
}

So far so good – we have a date provider variable that we accept in the initializer that will give us the current date. Now let’s make a version we’ll use in our tests:

class MockDateProvider: DateProvider {
  func currentDate() -> Date {
    return Date(timeIntervalSince(1632745282432))
  }
}

Rad! Now all we need to do is instantiate our SomethingImportant class with our mock provider:

SomethingImportant(dateProvider: MockDateProvider())

Now, let’s add some complexity: what if we now want to test a different time in addition? Well, now we need to make a whole new implementation of DateProvider. In fact, we’ll need to create a different provider for every time we want to test. Creating a whole new class for each time we’re looking to test is… frustrating. This is a simple example, so it’s relatively easy to copy paste these classes, but imagine a more complex example and all those extra classes get a little overwhelming.

What if we use functions instead? Well, ok, let’s change our SomethingImportant to depend on a function:

class SomethingImportant {
  var dateProvider: () -> Date

  init(dateProvider: () -> Date = Date.init) {
    self.dateProvider = dateProvider
  }

  func aTimeDependentAction() {
    var currentTime = dateProvider()
    // ...do something with the current time
  }
}

This already looks simpler: no protocol, no implementations, just the raw functions. How could we mock this? Easy:

SomethingImportant { Date(timeIntervalSince(1632745282432)) }

If we add the additional complexity of wanting to test multiple times, it’s still very simple: no need for a whole class for each time, we’re just passing in a different function directly – something we can easily do in the test function itself, making the tests easier to understand.

This is why we say functions to protocols – protocols require an implementation, which often involve functions, so you may as well cutout the middleman in every case you can. I’ve written elsewhere how applying functional programming concepts to protocol implementations themselves can save you time, energy, and lines of code.

If you liked this article, I’d highly recommend checking out PointFree – they go into this and a bunch of other incredible demonstrations of functional programming’s benefits.

1 thought on “Prefer functions to protocols”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s