September 01, 2018
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).
Observable
type that composes the source and adds additional behavior.Written by Serg Dort, who works and lives in London builds useful things. You can follow him on Twitter