Using Protocol Constraints Instead Of Extending

  • Published 10 May 2016

In his great post “Doubling Down on Protocol-Oriented Programming”, Soroush Khanlou describes an improvement of his earlier Protocol-Oriented Networking approach. While this is a great technique, I think there’s a small but significant way in which his approach could be improved.

Let’s look at his definition of the SendableRequest protocol:

protocol SendableRequest: ConstructableRequest { }

This inheritance enforces that every SendableRequest should also be a ConstructableRequest. But what if we don’t care about the request being constructable, but want to take advantage of the handy ResultParsing functionality described in the second half of his post?

With the current implementation, you’re out of luck. You have to implement buildRequest() (or use it’s default implementation) if you want to conform to SendableRequest.

Luckily there’s a good solution for this: by defining SendableRequest without inheritance and adding a constraint to the extension:

protocol SendableRequest {
	func sendRequest(success success: (string: String) -> (), failure: (error: ErrorType) -> ())
}

extension SendableRequest where Self: ConstructableRequest {
    func sendRequest(success success: (string: String) -> (), failure: (error: ErrorType) -> ()) {
        // send the request
        // parse the result
        // fire the blocks on success and failure
    }
}

The key part here is the restriction of the extension:

where Self: ConstructableRequest

This defines a constraint on the protocol extension, such that if your implementation conforms to both SendableRequest and ConstructableRequest, the default implementation defined by the extension is available. Because this extension is limited to the conformance of both protocols, it’s possible to use the buildRequest() function inside sendRequest(), even though the SendableRequest protocol doesn’t specify that method itself. However, it now has become possible to conform to SendableRequest without implementing ConstructableRequest’s method.

This solution improves the composability and reusability of protocols and their extensions, making it easier to use only parts of a framework or implementation, and enforces you to think about the separate concerns different protocols should have.