High level modules should not depend upon low level modules. Both should depend upon abstractions.
Abstractions should not depend upon details. Details should depend upon abstractions.상위 모듈은 하위 모듈에 의존에서는 안되고, 상위, 하위 모듈 모두 ‘추상’을 의존해야 한다
’추상’은 ‘디테일’을 의존하면 안되고, ‘디테일’은 ‘추상’에 의존해야 함
‘좋은’ 디자인, ‘좋지 않은’ 디자인의 기준은 없지만,
’좋지 않은’ 디자인이 갖는 특징들이 있음
변경을 주기 어려움, 한 곳을 바꾸면 다른 부분을 망가트림
- OCP를 지키지 않았을 때
어떤 부분에 확장이 필요할 때, 다른 부분도 변경해야 함- 어떤 구조가
‘추상’이 아닌 다른 구조에 의존할 때
한 곳에서 변경을 주면 다른 부분도 변경해야 함
- OCP가 지켜지지 않았을 때
- 어떤 구조가
‘추상’이 아닌 다른 구조에 의존할 때
여기저기 연결되어있기 때문에 재사용이 어려움
- 어떤 구조가
‘추상’이 아닌 다른 구조에 의존할 때
이런 이유들 때문에, DIP가 좋은 디자인에 직접적으로 영향을 미침
OCP와 LSP에서의 해결책은 모두 Abstraction
즉, DIP를 사용했을 때 OCP와 LSP를 지킬 수 있다
struct Product {
let name: String
let cost: Int
let image: UIImage
}
final class Network {
private let urlSession = URLSession(configuration: .default)
func getProducts(for userId: String, completion: @escaping ([Product]) -> Void) {
guard let url = URL(string: "baseURL/products/user/\(userId)") else {
completion([])
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
urlSession.dataTask(with: request) { (data, response, error) in
DispatchQueue.main.async {
completion([Product(name: "Just an example", cost: 1000, image: UIImage())])
}
}
}
}
final class ExampleScreenViewController: UIViewController {
private let network: Network
private var products: [Product]
private let userId: String = “user-id"
required init?(coder: NSCoder) {
fatalError()
}
init(network: Network, products: [Product]) {
self.network = network
self.products = products
super.init(nibName: nil, bundle: nil)
}
override func loadView() {
view = UIView()
}
override func viewDidLoad() {
super.viewDidLoad()
getProducts()
}
private func getProducts() {
network.getProducts(for: userId) { [weak self] products in
self?.products = products
}
}
}
Rigidity & Fragility
어떤 이유에서든 Network를 바꿔야 한다면, ExampleViewController에 있는 Network도 변경해야 함
이런 의도치 않은 변경으로 인해, ExampleViewController는 rebuild & retest가 필요하게 됨
**Immobility & Coupling**
다른 곳에서 ExampleViewController을 다시 사용해야 할 때, 동일한 것을 가져다 쓴다면,
Network도 함께 가져다가 사용해야하고, 이 Network가 굉장히 복잡한 구조라면 동일하게 가져다 사용하는게 비효율적임
차라리 동일한 ExampleViewController를 가져다 쓰는 것 보다
새로운 ViewController를 만드는게 더 좋을수도 ..!
상위, 하위 계층 모두 ‘추상’을 의존하게 만들자
// Abstraction
protocol ProductProtocol {
var name: String { get }
var cost: Int { get }
var image: UIImage { get }
}
// Abstraction
protocol NetworkProtocol {
func getProducts(for userId: String, completion: @escaping ([ProductProtocol]) -> Void)
}
struct Product: ProductProtocol {
let name: String
let cost: Int
let image: UIImage
}
final class Network: NetworkProtocol {
private let urlSession = URLSession(configuration: .default)
func getProducts(for userId: String, completion: @escaping ([ProductProtocol]) -> Void) {
guard let url = URL(string: "baseURL/products/user/\(userId)") else {
completion([])
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
urlSession.dataTask(with: request) { (data, response, error) in
completion([Product(name: "Just an example", cost: 1000, image: UIImage())])
}
}
}
final class ExampleScreenViewController: UIViewController {
private let network: NetworkProtocol // Abstraction dependency
private var products: [ProductProtocol] // Abstraction dependency
private let userId: String = "user-id"
required init?(coder: NSCoder) {
fatalError()
}
init(network: NetworkProtocol, products: [ProductProtocol]) { // Abstraction dependency
self.network = network
self.products = products
super.init(nibName: nil, bundle: nil)
}
override func loadView() {
view = UIView()
}
override func viewDidLoad() {
super.viewDidLoad()
getProducts()
}
private func getProducts() {
network.getProducts(for: userId) { [weak self] products in
self?.products = products
}
}
}
예전 레거시를 새로운 레거시로 손쉽게 변경할 수 있음