Skip to content

Instantly share code, notes, and snippets.

@alexnikol
Last active December 13, 2022 07:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexnikol/068434afbe586d9dd755810acb71eeb3 to your computer and use it in GitHub Desktop.
Save alexnikol/068434afbe586d9dd755810acb71eeb3 to your computer and use it in GitHub Desktop.
From procedural code to decorator pattern
// CODE 1
class HTTPClient {
func load(url: URL, completion: @escaping (Response) -> Void) {
// call server
// complete with result
}
}
// Responsibilities:
/// - Send request and return response
// CODE 10
/// Initialization of controller with dependency injection
let vc = ClientViewController(httpClient: HTTPClientFactory.makeHTTPClient())
class ClientViewController: UIViewController {
let httpClient: HTTPClientProtocol
init(httpClient: HTTPClientProtocol) {
self.httpClient = httpClient
}
override func viewDidLoad() {
super.viewDidLoad()
let someURL = URL(string: "http://some-url.com")!
httpClient.load(url: someURL, completion: { response in
// handle result
})
}
}
// CODE 2
class HTTPClient {
let storage = CredentialsStorage()
func load(url: URL, completion: @escaping (Response) -> Void) {
if storage.isTokenExpired {
refreshToken(token: storage.token, completion: { _ in
// call server
// complete with response
})
} else {
// call server
// complete with response
}
}
private func refreshToken(token: String, completion: @escaping (Response) -> Void) {
// refresh token
// complete with new tokens or error
}
}
// Responsibilities:
/// - Send request and return response
/// - Managing auth token lifetime, credentials storage
/// - Refreshing credentials
// CODE 3
class HTTPClient {
let storage = CredentialsStorage()
let permanentBlockList = PermanentBlockList()
func load(url: URL, completion: @escaping (Response) -> Void) {
guard !permanentBlockList.isBlocked else {
completion(.failure(NSError(domain: "PERMANENTLY__BLOCKED", code: 418)))
return
}
if storage.isTokenExpired {
refreshToken(token: storage.token, completion: { _ in
// call server
// if statusC0de = 418 blockList.blockCurrentAccout()
// complete with response
})
} else {
// call server
// if statusC0de = 418 blockList.blockCurrentAccout()
// complete with response
}
}
private func refreshToken(token: String, completion: @escaping (Response) -> Void) {
// refresh token
// completion with result
}
}
// Responsibilities:
/// - Send request and return response
/// - Managing auth token lifetime, credentials storage
/// - Refreshing credentials
/// - Managing blocked users list, blocked users storage
/// - Saving current user to list, and block it from any interactions with our production server
// CODE 4
protocol HTTPClientProtocol {
func load(url: URL, completion: @escaping (Response) -> Void)
}
// CODE 5
class HTTPClient: HTTPClientProtocol {
func load(url: URL, completion: @escaping (Response) -> Void) {
// refresh token
// completion with result
}
}
// Responsibilities:
/// - Send request and return response
// CODE 6
class RefreshCredentialsHTTPClientDecorator: HTTPClientProtocol {
private let storage = CredentialsStorage()
private let decoratee: HTTPClientProtocol
init(decoratee: HTTPClientProtocol) {
self.decoratee = decoratee
}
func load(url: URL, completion: @escaping (Response) -> Void) {
if storage.isTokenExpired {
refreshToken(token: storage.token, completion: { [weak self] _ in
// refresh token
self?.decoratee.load(url: url, completion: completion)
})
} else {
// refresh token
decoratee.load(url: url, completion: completion)
}
}
private func refreshToken(token: String, completion: @escaping (Response) -> Void) {
// refresh token
// completion with result
}
}
// Responsibilities:
/// - Managing auth token lifetime, credentials storage
/// - Refreshing credentials
// CODE 7
class PermanentBlockHTTPClientDecorator: HTTPClientProtocol {
private let permanentBlockList = PermanentBlockList()
private let decoratee: HTTPClientProtocol
init(decoratee: HTTPClientProtocol) {
self.decoratee = decoratee
}
func load(url: URL, completion: @escaping (Response) -> Void) {
guard !permanentBlockList.isBlocked else {
completion(.failure(NSError(domain: "PERMANENTLY__BLOCKED", code: 418)))
return
}
decoratee.load(url: url, completion: { result in
// if statusC0de = 418 blockList.blockCurrentAccout() complete with block error
// else complete with decoratee's result
completion(result)
})
}
}
// Responsibilities:
/// - Managing blocked users list, blocked users storage
/// - Saving current user to list, and block it from any interactions with our production server
// CODE 8
class HTTPClientLoggerDecorator: HTTPClientProtocol {
private let decoratee: HTTPClientProtocol
init(decoratee: HTTPClientProtocol) {
self.decoratee = decoratee
}
func load(url: URL, completion: @escaping (Response) -> Void) {
// start measure
decoratee.load(url: url) { result in
// end measure and save to some storage the results
// completion with result
}
}
}
// Responsibilities:
/// - Measure real network requests to the server
// CODE 9
//// MARK: - Objects graph:
/// PermanentBlockHTTPClientDecorator
/// -> RefreshCredentialsHTTPClientDecorator
/// -> HTTPClientLoggerDecorator
/// -> HTTPClient
class HTTPClientFactory {
private init() {}
static func makeHTTPClient() -> HTTPClientProtocol {
let httpClient = HTTPClient()
let httpClientLoggerDecorator = HTTPClientLoggerDecorator(decoratee: httpClient)
let refreshCredentialsHTTPClientDecorator = RefreshCredentialsHTTPClientDecorator(decoratee: httpClientLoggerDecorator)
let permanentBlockHTTPClientDecorator = PermanentBlockHTTPClientDecorator(decoratee: refreshCredentialsHTTPClientDecorator)
return permanentBlockHTTPClientDecorator
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment