Лучшая архитектура для ViewModels (RxSwift)

Я хочу использовать архитектурный дизайн, который позволяет мне четко обозначать ввод и вывод в моей модели представления (How To Feed ViewModels), но мне любопытно, как я могу лучше всего интегрировать" рабочую "часть модели представления в эту структуру.

Я имел тенденцию использовать Действия (возможно, не очень элегантно) для привязки элементов пользовательского интерфейса к работе, которую они должны выполнять. Проблема, конечно, в том, что некоторые из этих действий полагаются на свойства модели представления, поэтому я не могу создать их в init () так же, как входные и выходные данные, поскольку свойства еще не инициализированы. Это можно обойти, определив их как частные ленивые вары, а затем выставив их через структуру, которая, по сути, представляет собой общедоступный интерфейс для Action. Хотя это, похоже, не очень хорошо, и я узнал, что если вы затрачиваете много усилий, чтобы заставить структуру сгибаться по своей воле, это, вероятно, запах кода. Пример кода ниже - предложения приветствуются :-)

protocol PatientListViewModelType: ViewModelType { }

final class PatientListViewModel: PatientListViewModelType {
    // MARK:- Protocol conformance
    typealias Dependencies =  HasPatientService

    struct Input {
        let patient: AnyObserver<Patient>
    }
    struct Output {
        let sectionedPatients: Observable<[PatientSection]>
        let patient: Observable<Patient>
    }
    let input: Input
    let output: Output

    struct Actions {
        let deletePatient: Action<Patient, Void>
        let togglePatient: (Patient) -> CocoaAction
        let updatePatient: (Patient) -> Action<String, Void>
    }

    lazy var action: Actions = Actions(deletePatient: self.deletePatient,
                                       togglePatient: self.togglePatient,
                                       updatePatient: self.updatePatient)

    // MARK: Setup
    private let dependencies: Dependencies
    private let patientSubject = ReplaySubject<Patient>.create(bufferSize: 1)

    // MARK:- Init
    init(dependencies: Dependencies) {
        self.dependencies = dependencies

        let sectionedPatients =
            dependencies.patientService.patients()
                .map { results -> [PatientSection] in
                    let scheduledPatients = results
                        .filter("checked == nil")
                        .sorted(byKeyPath: "created", ascending: false)

                    let admittedPatients = results
                        .filter("checked != nil")
                        .sorted(byKeyPath: "checked", ascending: false)

                    return [
                        PatientSection(model: "Scheduled Patients", items: scheduledPatients.toArray()),
                        PatientSection(model: "Admitted Patients", items: admittedPatients.toArray())
                    ]
        }

        self.output = Output(sectionedPatients: sectionedPatients,
                             patient: patientSubject.asObservable() )
        // this is immediately overriden during binding to VC - it just allows us to exit the init without errors
        self.input = Input(patient: patientSubject.asObserver())


    }

    // MARK:- Actions        
    private lazy var deletePatient: Action<Patient, Void> = { (service: PatientServiceType) in
        return Action { patient in
            return service.delete(realmObject: patient)
        }
    }(self.dependencies.patientService)

    lazy var togglePatient: (Patient) -> CocoaAction = { [unowned self] (patient: Patient) -> CocoaAction in
        return CocoaAction {
            return self.dependencies.patientService.toggle(patient: patient).map { _ in }
        }
    }

    private lazy var updatePatient: (Patient) -> Action<String, Void> = { [unowned self] (patient: Patient) in
        return Action { newName in
            return self.dependencies.patientService.update(patient: patient, name: newName).map { _ in }
        }
    }
}

person rustproofFish    schedule 07.05.2019    source источник


Ответы (1)


Ответ на самом деле достаточно прост, как только я получил возможность сесть и поиграть. Я поместил действия в структуру вывода (поскольку это казалось наиболее логичным местом) вместо того, чтобы создавать выделенный интерфейс, как раньше. Следующий вопрос, конечно же, заключается в том, подходят ли действия для этой проблемы, но я займусь этим позже ...

final class PatientListViewModel: PatientListViewModelType {
    // MARK:- Protocol conformance
    typealias Dependencies =  HasPatientService

    struct Input {
        let patient: AnyObserver<Patient>
    }
    let input: Input

    struct Output {
        let sectionedPatients: Observable<[PatientSection]>
        let patient: Observable<Patient>
        let deletePatient: Action<Patient, Void>
        let togglePatient: (Patient) -> CocoaAction
        let updatePatient: (Patient) -> Action<String, Void>
    }
    let output: Output

    // MARK: Setup
    private let dependencies: Dependencies
    private let patientSubject = ReplaySubject<Patient>.create(bufferSize: 1)

    // MARK:- Init
    init(dependencies: Dependencies) {
        self.dependencies = dependencies

        let sectionedPatients =
            dependencies.patientService.patients()
                .map { results -> [PatientSection] in
                    let scheduledPatients = results
                        .filter("checked == nil")
                        .sorted(byKeyPath: "created", ascending: false)

                    let admittedPatients = results
                        .filter("checked != nil")
                        .sorted(byKeyPath: "checked", ascending: false)

                    return [
                        PatientSection(model: "Scheduled Patients", items: scheduledPatients.toArray()),
                        PatientSection(model: "Admitted Patients", items: admittedPatients.toArray())
                    ]
        }

        let deletePatient: Action<Patient, Void> = { patientService in
            return Action { patient in
                return patientService.delete(realmObject: patient)
            }
        }(dependencies.patientService)

        let togglePatient: (Patient) -> CocoaAction = { patient in
            return CocoaAction {
                return dependencies.patientService.toggle(patient: patient)
                    .map { _ in }
            }
        }

        let updatePatient: (Patient) -> Action<String, Void> = { patient in
            return Action { newName in
                return dependencies.patientService.update(patient: patient, name: newName)
                    .map { _ in }
            }
        }

        // this is immediately overriden during binding to VC - it just allows us to exit the init without errors
        self.input = Input(patient: patientSubject.asObserver())

        self.output = Output(sectionedPatients: sectionedPatients,
                             patient: patientSubject.asObservable(),
                             deletePatient: deletePatient,
                             togglePatient: togglePatient,
                             updatePatient: updatePatient)
    }
person rustproofFish    schedule 07.05.2019
comment
О, и Observables в структуре Output, вероятно, были бы лучше в качестве драйверов ... - person rustproofFish; 07.05.2019