[TIL]11.20

rbw·2022년 11월 20일
0

TIL

목록 보기
50/97

좋은 아키텍처의 특징

참조
https://betterprogramming.pub/ios-architectures-explained-which-one-best-fits-my-project-94b4ffaad16

이 글을 보고 적당히 정리해보는 글 실제로 들어가면 더 많은 내용이 있습니다 mvc mvvm viper vip 등등


  • 기능 내에서 각 종류의 책임이 어디에 있는지 이해하기 쉽다
  • 더 쉽게 테스트하고 각 화면에 대한 이해도를 높일 수 있도록 책임을 다른 책임과 분리함
  • 프로젝트를 장기적으로 유지 관리하기가 쉬움
  • 클래스와 구성 요소간의 결합을 줄임
  • 비즈니스 규칙과 UI 분리 (mandatory 의무 !)

디자인패턴

Factory

이 패턴은 서로 의존하는 개체 집합을 구축하고 연결하는 방법을 나타낸다. 각 계층 클래스를 인스턴스화하고 해당 대리자 및 기타 종속성을 할당하기 위한 매우 일반적인 솔루션이다.

// 해당 아키텍처에 따라 인스턴스화되고 연결되는 모든 레이어 아래는 VIP-C 패턴
enum GeneralCharacterListFactory {
    static func make(pageSize: Int) -> GeneralChracterListController {
        let dependencyContainer = DependencyContainer()
        let service = GeneralCharacterListService(dependencies: dependencyContainer)
        let coordinator = GeneralCharacterListCoordinator(dependencies: dependencyContainer)
        let presenter = GeneralCharacterListPresenter(coordinator: coordinator, dependencies: dependencyContainer)
        let interactor = GeneralCharacterListIneractor(service: service, presenter: presenter, pageSize: pageSize, dependencies: dependencyContainer)
        let viewController = GeneralCharacterListController(interactor: interactor, dependencies: dependencyContainer)

        presenter.viewController = viewController
        coordinator.viewController = viewController
        return viewController
    }
}

Facade

이 패턴은 여러 종속성을 한 곳에서 재결합하는 컨테이너를 나타냅니다. 이 방법으로 앱의 모든 위치에서 중요한 리소스에 액세스가 가능합니다. 또한 관련된 여러 개체를 하나의 클래스에 저장하여 강력한 생태계를 구축하고 이와 관련된 비즈니스 규칙을 적용하는 방식으로 사용이 가능합니다.

final class DependencyContainer: Dependencies {
    lazy var jsonDecoder = JSONDecoder()
    lazy var mainQueue: DispatchQueue = .main
    lazy var urlSession: URLSession = .shared
    lazy var userDefaults: UserDefaults = .standard
}

Adapter

아키텍처의 각 계층은 데이터 모델을 다른 방식으로 보는 경우가 있습니다. 이 패턴은 그런 경우를 위해, (일부 모델을 뷰 모델로 변환해야 하는 경우), 이전 객체와 관련된 각 속성을 할당하는 모델 객체로 뷰 모델을 인스턴스화 하는 패턴입니다.

struct Model {
    let title: String
    let age: Int
    let image: String
}
struct ViewModel {
    let title: String
    let age: String 
    let image: UIImage?

    init(from model: Model) {
        self.title = model.title
        self.age = "\(model.age)"
        self.image = UIImage(named: "\(model.image)") ?? nil
    }
}

Coordinator

화면 전환과 관련된 패턴으로, UI 컨텍스트를 유지해야 합니다.

enum GeneralCharacterListAction: Equatable {
    case characterDetails(model: CharacterModel)
}

protocol GeneralCharacterListCoordinating{
    func perform(_ action: GeneralCharacterListAction) 
}

final class GeneralCharacterListCoordinator {
    typealias Dependencies = HasNoDependency

    weak var viewController: UIViewController?
    private let dependencies: Dependencies

    init(dependencies: Dependencies) {
        self.dependencies = dependencies
    }
}

private extension GeneralCharacterListCoordinator {
    func goToCharacterDetails(_ details: CharaterModel) {
        let characterDetailsViewController = CharacterDetailsFactory.make(characterDetails: details)
        viewConroller?.navigationController?.pushViewController(characterDetailsViewController, animated: true)
    }
}

extension GeneralCharacterListCoordinator: GeneralCharacterListCoordinating {
    func perform(_ action: GeneralCharacterListAction) {
        switch action {
            case let .characterDetails(model):
                goToCharacterDetails(model)
        }
    } 
}

위 코드는 이제, coordinator를 만들고, 위에서 설명했던 Factory, Facade패턴을 활용한 함수(goToCharacterDetails)를 선언하고, perform 안에 담아서 구현한 코드라고 볼 수 있겠다. coordinator의 액션들은 딜리게이트 패턴으로 vc랑 연결해주어서 vc에 전달이 가능하다.

위 예제에서 Coordinator는 어디서 호출이 되는건가 생각을 해보았는데, 나의 얕은 생각으로는(틀릴 수 있다는점 저는 뉴비이기 때무네ㅜㅡㅜ)

일단 예제에서의 패턴은 vip-c 패턴으로 생각이 드는데, vc에서의 액션이 Interactor를 거쳐 Presenter -> Coordinator까지 흘러가서(아마 액션이 다른 계층들을 거치면서 다른 액션을 호출하고 반환하고 하여서 좀 다른 액션이 되었을거라고 생각) Coordinator에 전달이 되고, vc에 새로운 페이지를 띄우라고 액션을 전달할 수 있다고 생각한다. (흐름대로 ~ 흘러가서 ~ 결국은 Presenter가 호출하여서 vc에 결과를 만들어 내지 않을가..?)

Dependency Injection

이 패턴을 사용하면 각 레이어가 다른 레이어를 프로퍼티로 가질 수 있고, init을 사용하여 다른 레이어의 객체를 주입할 수 있습니다. 각 레이어는 다른 레이어를 프로토콜 유형으로 참조하므로 해당 유형의 구체적인 구현이 아닌 해당 선언부만 노출이 가능합니다. 해당 클래스를 인스턴스화할 때마다 작성하지 않도록 구체적인 유형을 전달하는 기본값을 정해주는 것이 좋습니다.

init(service: GeneralCharacterListServicing = GeneralCharacterListService(),
    presenter: GeneralCharacterListPresenting,
    pageSize: Int,
    dependencies: Dependencies) {
        self.service = service
        self.presenter = presenter
        self.pageSize = pageSize
        self.dependencies = dependencies
    }

Dependency Inversion

레이어끼리는 프로토콜로 소통을 하므로, 무조건 다른 레이어의 구체적인 타입을 주입하지 않아도 됩니다.

예를 들어, 뷰 모델이나 Interactor를 테스트 하고 있는 경우, 실제로 테스트중인 로직을 방해하지 않는 모의 서비스를 주입하는 것이 가능합니다.

조금 이해가 안되네요 이 부분; 모델, 서비스에 뷰 모델 이나 Interactor를 주입한다는건가 싶기도 하구,, 그렇게 해야 실제 서비스에는 방해가 안되지 않나 라는 생각이 듭니다. 아니면 뷰 모델에 모의 서비스를 주입한다는거 자체가 의존성 역전이라는건지..? 모의 서비스이지만 결국 해당 서비스에 의존하니까 역전은 아니지 않나..? 일단 더 공부하는걸로 ~

Observer

참조

https://jeonyeohun.tistory.com/215


느슨한 결합(Loose Coupling)을 만들어주고, SOLID 법칙에서 개방 폐쇄 원칙을 지킬 수 있도록 도와주는 옵저버 패턴에 대해 알아보겠습니다.

옵저버 패턴은 어떤 객체에서 발생하는 이벤트를 해당 객체를 관찰하고 있는 객체에게 알리는 방식으로 운영되는 패턴입니다. 알림의 주체가 되는 객체(Subject)는 자신을 관찰하는 객체(Observer)들과 일대다 관계를 맺을 수 있습니다.

아래 예제 코드는 어떤 물건이 새로 입고가 되면 알림이 가는 상황을 표현하였습니다.

옵저버 패턴에서 Subject가 반드시 해야 하는 세 가지 역할이 있습니다. 관찰자 등록, 관찰자 제거, 알림 전달 입니다. 아래에서는 두 가지만 구현하였습니다.

// Subject
protocol Observable {
    func notify()
    func add(person: String)
}

class Item: Observable {
    let name: String
    var persons: [Person] = []

    init(name: String) {
        self.name = name
    }

    func notify() {
        for person in persons {
            person.update()
        }
    }

    func add(person: String) {
        persons.append(person)
    }
}

// Obeserver
protocol Likable {
    func update()
}

class Person: Likable {
    let name: String

    init(name: String) {
        self.name = name
    }

    func update() {
        print("\(name)님 물건이 추가로 입고 되었습니다")
    }
}

let newItem = Item(name: "커피")
newItem.add(person: Person(name: "민수"))
newItem.add(person: Person(name: "철수"))
newItem.add(person: Person(name: "영수"))

newItem.notify()
// "민수님 물건이 추가로 입고 되었습니다"
// "철수님 물건이 추가로 입고 되었습니다"
// "영수님 물건이 추가로 입고 되었습니다"

새롭게 Subject가 추가가 되면 될수록 이 패턴의 역할이 중요해집니다. 객체들 사이의 의존성이 낮고 느슨하게 연결이 되어 있어서 Observer에는 변경사항이 없고 Subject에 영향을 주지 않고도 원하는 기능을 추가 할 수 있습니다.

profile
hi there 👋

0개의 댓글