iOS Developer in search for meaning 🧘‍♂️

Power of Composition

September 01, 2018

error

Object Composition is one of the most important concepts in OOP, and it’s not surprising as it’s so natural as many real-world things are essentially a composition of smaller things.

In this article, I’m not going to talk about why we should prefer it over inheritance, but I would rather like to share an example where in my opinion object composition shines.

Let’s say we have an interface that abstracts how URLReqeust is produced

protocol Request {
    func request() -> URLRequest
}

Basic Request implementation may look like this

struct RequestWithURL: Request {
    private let url: URL

    init(url: URL) {
        self.url = url
    }

    func request() -> URLRequest {
        return URLRequest(url: url)
    }
}

Now let’s say we want to have a Request that uses POST HTTP method

struct POSTRequest: Request {
    private let base: Request

    init(base: Request) {
        self.base = base
    }

    func request() -> URLRequest {
        var request = base.request()
        request.httpMethod = "POST"

        return request
    }
}

The example above benefits from using Object Composition - what we do is we use another request and simply set httpMethod property to POST.

Now let’s create a Request that sets Bearer authentication header

struct BearerRequest: Request {
    private let base: Request
    private let token: String

    init(token: String, base: Request) {
        self.base = base
        self.token = token
    }

    func request() -> URLRequest {
        var request = base.request()
        request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")

        return request
    }
}

As you can experience the power that this approach provides is that we can create small, very reusable types and compose them to achieve complex behavior.

Let’s see it in action, for example, let’s make a composition of requests that make authenticated POST request, to specific URL and sets some data to the body.

For that, we need to implement additional RequestWithBody type

struct RequestWithBody: Request {
    private let base: Request
    private let data: Data

    init(data: Data, base: Request) {
        self.data = data
        self.base = base
    }

    func request() -> URLRequest {
        var request = base.request()
        request.httpBody = data

        return request
    }
}

And compose all required requests together

let request = BearerRequest(
    token: "SuperSecretToken",
    base: RequestWithBody(
        data: Data("Hello World".utf8),
        base: POSTRequest(
            base: RequestWithURL(
                url: URL(string: "http://example.com")!
            )
        )
    )
)

Sure this approach may look verbose, but to some extent, it’s very declarative as the name of the request types are very descriptive and it’s easy to understand from the type definition what each of them does.

And in my opinion, the biggest advantage that this approach provides - is maintainability (all of the Request are small, doing one thing and are very easy to unit test and reuse).

Libraries that benefit from Composition

  • RxSwift, all though from the public API perspective it’s a FRP library, but if you take a look on internal implementation you’ll see that each operator it’s essentially a new Observable type that composes the source and adds additional behavior.
  • React and Flutter are one of my favorites libraries as well, they take Object Composition on the next level, allowing to building small reusable components and compose them to build very sophisticated UI.

Serg Dort

Written by Serg Dort, who works and lives in London builds useful things. You can follow him on Twitter