Refactoring (리펙토링)
[Refactoring] 10-3 API 리펙토링 (공통 모델에서 필요한 부분을 protocol을 사용하여 가져오기)
jake-kim
2023. 7. 7. 00:32
공통 모델이 있을 때 리펙토링 방법
- 보통 공통 모델에서 특정 프로퍼티만 필요한 경우, 따로 DTO를 만들어서 convert 로직을 만드는데, protocol을 활용하면 간결하게 해결이 가능
ex) 공통 모델 CommonModel이 있고, 각 모델에서 필요한 모델도 각각 있어서 convert해서 쓰는 패턴
- 공통 모델이 CommonModel처럼 있는 경우
struct CommonModel {
let age: Int
var name: String
let date: String
let visited: Bool
let imageData: Data
var message: String
}
- VC2, VC3에서 필요한 모델이 있어서 struct로 만들고 이걸 CommonModel에서 convert해서 쓰는 방식
struct Vc2Model {
let date: String
let message: String
}
extension CommonModel {
var toVc2Model: Vc2Model {
.init(date: date, message: message)
}
}
struct Vc3Model {
let age: Int
let name: String
}
extension CommonModel {
var toVc3Model: Vc3Model {
.init(age: age, name: name)
}
}
- 문제점)
- CommonModel 데이터가 있을때 각 모델에 넘겨주려면 모두 각 모델에 맞는 인스턴스를 생성해서 넘겨주어야함 (Vc2Model, Vc3Model)
프로토콜을 사용하여 리펙토링
- Vc2Model, Vc3Model 구조체를 만들필요 없이 각 VC2, VC3 에서 필요한 필드를 protocol로 만들고 이를 CommonModel에 준수도록 구현
- CommonModel에서 VC2, VC3에서 필요한 프로토콜을 준수하고 있는 상태이므로 VC2, VC3에는 그저 CommonModel 인스턴스만 넘겨주면 완료
- VC2, VC3안쪽에서도 프로토콜에 정의된 필드에만 접근할 수 있으므로 convert 로직도 필요가 없는 상태
ex) VC2, VC3에 필요한 프로토콜을 정의
- VC2에는 VC2Modelable 선언
import UIKit
protocol VC2Modelable {
var date: String { get }
var message: String { get set }
}
final class VC2: UIViewController {
private let button = UIButton()
var vc2Model: VC2Modelable?
func updateUI(model: VC2Modelable) {
button.setTitle(model.date, for: .normal)
button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
}
@objc private func didTapButton() {
vc2Model?.message = "300"
}
}
- VC3에는 VC3Modelable 선언
import UIKit
protocol VC3Modelable {
var age: Int { get }
var name: String { get set }
}
final class VC3: UIViewController {
private let button = UIButton()
var vc3Model: VC3Modelable?
func updateUI(model: VC3Modelable) {
button.setTitle("\(model.age)", for: .normal)
button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
}
@objc private func didTapButton() {
vc3Model?.name = "300"
}
}
- vc2, vc3에서 필요한 모델을 주입할땐 CommonModel 하나로 주입하면 되기 때문에 convert 로직 없이 하나의 인스턴스로 간편하게 주입이 가능한 상태
let myModel = CommonModel(
age: 1,
name: "jake",
date: "20230101",
visited: true,
imageData: Data(),
message: "message1"
)
let vc2 = VC2()
vc2.updateUI(model: myModel)
let vc3 = VC3()
vc3.updateUI(model: myModel)
정리
- API로 부터 데이터를 여러가지 필드를 한꺼번에 내려받는 경우, 몸집이 큰 struct에서 필요한 값이 있는 경우 모델을 따로 만들지 말고 필요한 곳에서 원하는 필드만 나타낸 protocol로 정의
- 몸집이 큰 structd에서 이 protocol을 따르게한 다음 이 값을 바로 주입
- protocol로 구현했을때의 장점
- 별도의 struct가 필요 없고, protocol로 정의하여 protocol만 만족하면 되는 코드가 되므로 DIP 원칙을 지키며 결합도를 느슨하게 한 형태
- struct인 별도의 모델로 만들었으면 convert하는 로직이 필요하지만 protocol로 정의하여 필요하지 않아 간결한 코드 유지보수가 가능