Notice
Recent Posts
Recent Comments
Link
관리 메뉴

김종권의 iOS 앱 개발 알아가기

[Refactoring] 10-3 API 리펙토링 (공통 모델에서 필요한 부분을 protocol을 사용하여 가져오기) 본문

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로 정의하여 필요하지 않아 간결한 코드 유지보수가 가능

* 전체 코드: https://github.com/JK0369/ExRefactor_protocol

Comments