Created
November 12, 2018 10:55
-
-
Save caiozullo/547ed5483221e6cb9509e4134cb1ce09 to your computer and use it in GitHub Desktop.
Careful With “Singleton” Lookalikes episode
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Copyright © 2018 Essential Developer. All rights reserved. | |
// | |
import UIKit | |
/* | |
The Singleton design pattern is intended to enforce | |
a class has only one instance. | |
However, it is often used only as a convenience design pattern, | |
since the instance can be easily located | |
(usually Singleton.instance or Singleton.shared) | |
Many consider "Singleton as a convenience" an anti-pattern | |
because it can introduce (bad) global mutable state, | |
can increase coupling (immobility+viscosity), | |
make dependencies implicit, make code harder to test, and more. | |
In the episode "Careful With “Singleton” Lookalikes" | |
we explain the problems and propose some solutions. | |
Learn more at https://www.essentialdeveloper.com/articles/careful-with-singleton-lookalikes-way-too-common | |
Below is one of the modular solutions proposed in the video where one wouldn't need Singletons in the API layer. | |
*/ | |
// Main Module | |
// Modules Composition (dependency injection) + | |
// Adapter Implementations (We can also create a separate adapter module if necessary) | |
extension ApiClient: LoginApi { | |
func login(completion: (LoggedInUser) -> Void) {} | |
} | |
extension ApiClient { | |
func loadFeed(completion: ([FeedItem]) -> Void) {} | |
} | |
// Api Module | |
// Generic/Reusable module (can be URLSession/AFNetworking/etc.) | |
// In this modular approach, the ApiClient doesn't need to be a Singleton. | |
// But if one decides to use a Singleton, it's downsides are mostly | |
// contained and won't affect the features. | |
class ApiClient { | |
static let shared = ApiClient() | |
func execute(_ : URLRequest, completion: (Data) -> Void) {} | |
} | |
// Login Module (Protocol example) | |
// A feature module agnostic of API clients / HTTP requests. | |
// It exposes a protocol which must be implemented by a another module | |
struct LoggedInUser {} | |
protocol LoginApi { | |
func login(completion: @escaping (LoggedInUser) -> Void) | |
} | |
class LoginViewController: UIViewController { | |
var api: LoginApi? | |
func didTapLoginButton() { | |
api?.login { user in | |
// do something | |
} | |
} | |
} | |
// Feed Module (Closure example) | |
// Another feature module agnostic of API clients / HTTP requests. | |
// It requires a closure which must be passed in by a another module. | |
struct FeedItem {} | |
class FeedService { | |
let loadFeed: (([FeedItem]) -> Void) -> Void | |
init(loadFeed: @escaping (([FeedItem]) -> Void) -> Void) { | |
self.loadFeed = loadFeed | |
} | |
func load() { | |
loadFeed { loadedItems in | |
// do something | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment