MVVM + Clean Architecture로 Refactoring

Groot·2022년 10월 10일
0

TIL

목록 보기
73/148
post-thumbnail

TIL

🌱 난 오늘 무엇을 공부했을까?

📌 MVVM + Clean Architecture로 Refactoring 해보기..

📍 Data Layer

protocol ProjectRepositoryProtocol {
    mutating func create(data: ProjectModel)
    func read() -> [ProjectModel]
    mutating func update(id: String,
                         data: ProjectModel)
    mutating func delete(id: String)
}

struct TemporaryProjectRepository: ProjectRepositoryProtocol {
    private var temporaryStore: [ProjectModel]
    
    init() {
        temporaryStore = [ProjectModel]()
    }
    
    mutating func create(data: ProjectModel) {
        temporaryStore.append(data)
    }
    
    func read() -> [ProjectModel] {
        return temporaryStore
    }
    
    mutating func update(id: String,
                         data: ProjectModel) {
        temporaryStore.indices.forEach {
            temporaryStore[$0] = temporaryStore[$0].id == id ? data : temporaryStore[$0]
        }
    }
    
    mutating func delete(id: String) {
        temporaryStore.removeAll(where: { $0.id == id })
    }
}
  • useCase가 접근해야 할 Data영역이 여러개(LocalDB, RemoteDB)라고 생각해서 CRUD를 추상화 한 ProjectRepositoryProtocol를 채택하는 TemporaryProjectRepository를 구현

📍 Domain Layer

🔗 Entity

struct ProjectModel: Decodable {
    let id: String
    let title: String
    let body: String
    let date: Date
    let workState: ProjectState
}

enum ProjectState: String, Decodable, CaseIterable {
    case todo, doing, done
    
    var name: String {
        switch self {
        case .todo:
            return "TODO"
        case .doing:
            return "DOING"
        case .done:
            return "DONE"
        }
    }
    
    var actionTitles: (first: String,
                       second: String) {
        switch self {
        case .todo:
            return ("Move To DOING", "Move To DONE")
        case .doing:
            return ("Move To TODO", "Move To DONE")
        case .done:
            return ("Move To TODO", "Move To DOING")
        }
    }
}
  • LocalDB, RemoteDB에서 데이터를 모델로 변환하기 위한 Entity Type

🔗 UseCase

protocol ProjectUseCaseProtocol {
    var repository: ProjectRepositoryProtocol { get }
    mutating func create(data: ProjectTranslaterModel)
    func read() -> [ProjectTranslaterModel]
    mutating func update(id: String,
                         data: ProjectTranslaterModel)
    mutating func delete(id: String)
}

struct ProjectUseCase: ProjectUseCaseProtocol, ProjectTranslater {
    var repository: ProjectRepositoryProtocol
    
    init() {
        repository = TemporaryProjectRepository()
    }
    
    mutating func create(data: ProjectTranslaterModel) {
        repository.create(data: translateTo(ProjectModel: data))
    }
    
    func read() -> [ProjectTranslaterModel] {
        let models = repository.read().map {
            translateTo(ProjectTranslaterModel: $0)
        }
        
        return models
    }
    
    mutating func update(id: String,
                         data: ProjectTranslaterModel) {
        
        repository.update(id: id, data: translateTo(ProjectModel: data))
    }
    
    mutating func delete(id: String) {
        repository.delete(id: id)
    }
}

struct ProjectTranslaterModel {
    let id: String
    let title: String
    let body: String
    let date: String
    let workState: ProjectState
}

protocol ProjectTranslater {
    func translateTo(ProjectModel data: ProjectTranslaterModel) -> ProjectModel
    func translateTo(ProjectTranslaterModel data: ProjectModel) -> ProjectTranslaterModel
}

extension ProjectTranslater {
    func translateTo(ProjectModel data: ProjectTranslaterModel) -> ProjectModel {
        let date = Date(timeIntervalSince1970: Double(data.date) ?? 0)
        let model = ProjectModel(id: data.id,
                                 title: data.title,
                                 body: data.body,
                                 date: date,
                                 workState: data.workState)
        
        return model
    }
    
    func translateTo(ProjectTranslaterModel data: ProjectModel) -> ProjectTranslaterModel {
        let model = ProjectTranslaterModel(id: data.id,
                                           title: data.title,
                                           body: data.body,
                                           date: data.date.convertLocalization(),
                                           workState: data.workState)
        
        return model
    }
}
  • repository와 마찬가지로 추상화한 Protocol을 채택하도록 구현
  • ViewModel에서 Date와 같이 View로 표현하기 위해 변환이 필요한 부분을 변환없이 바로 사용할 수 있도록 ProjectTranslaterModel 타입을 구현
  • 프로토콜 기본 구현으로 변환기능을 가지고 있는 ProjectTranslater을 구현하고 useCase에서 채택해서 Repository로는 기존 Entity타입으로 데이터를 주입하고 ViewModel로는 데이터를 ProjectTranslaterModel 모델로 변환해서 View에 바로 적용할 수 있도록 구현

📍 Presentation Layer

🔗 View Model

protocol ProjectManagerViewModelProtocol: ViewModelIntput, ViewModelOutput {}

protocol ViewModelIntput {
    var didSelectCreate: (ProjectTranslaterModel) -> Void { get set }
    var didSelectUpdate: (ProjectTranslaterModel, ProjectState) -> Void { get set }
    var didSelectDelete: (String) -> Void { get set }
    var didSelectChangeState: (ProjectTranslaterModel, ProjectState) -> Void { get set }
}

protocol ViewModelOutput {
    var retrieveDateLabelColor: (ProjectTranslaterModel) -> UIColor { get }
    var retrieveItems: (ProjectState) -> [ProjectTranslaterModel] { get }
}

class ProjectListViewModel: ProjectManagerViewModelProtocol {
    private var useCase: ProjectUseCaseProtocol
    var didSelectChangeState: (ProjectTranslaterModel, ProjectState) -> Void
    var didSelectCreate: (ProjectTranslaterModel) -> Void
    var didSelectUpdate: (ProjectTranslaterModel, ProjectState) -> Void
    var didSelectDelete: (String) -> Void
    var retrieveDateLabelColor: (ProjectTranslaterModel) -> UIColor
    var retrieveItems: (ProjectState) -> [ProjectTranslaterModel]
    
    init() {
        useCase = ProjectUseCase()
    }
    
    func didSelectCreate(data: ProjectTranslaterModel) {
        useCase.create(data: data)
    }
    
    private func read() -> [ProjectTranslaterModel] {
        return useCase.read()
    }
    
    func didSelectUpdate(id: String,
                         data: ProjectTranslaterModel) {
        useCase.update(id: id, data: data)
    }
    
    func didSelectDelete(id: String) {
        useCase.delete(id: id)
    }
    
    func changeState(item: ProjectTranslaterModel,
                              state: ProjectState) {
        let newItem = ProjectTranslaterModel(id: item.id,
                                             title: item.title,
                                             body: item.body,
                                             date: item.date,
                                             workState: state)
        
        useCase.update(id: item.id,
                       data: newItem)
    }
    
    func retrieveDateLabelColor(data: ProjectTranslaterModel) -> UIColor {
        let date = Date(timeIntervalSince1970: Double(data.date) ?? 0)
        let currentDate = Date()
        
        if data.date != currentDate.convertLocalization() && date < currentDate {
            return .systemRed
        }
        
        return .black
    }
    
    func retrieveItems(state: ProjectState) -> [ProjectTranslaterModel] {
        let data = read()
        
        switch state {
        case .todo:
            return data.filter {
                $0.workState == .todo
            }
        case .doing:
            return data.filter {
                $0.workState == .doing
            }
        case .done:
            return data.filter {
                $0.workState == .done
            }
        }
    }
}
  • 멘붕.
  • 내가 지금 뭐하는거지?

    MVVM 망해라

profile
I Am Groot

0개의 댓글