Как создать абстрактную функцию для уменьшения дублирования кода - PullRequest
2 голосов
/ 03 апреля 2019

У меня есть несколько функций, которые в основном одинаковы, за исключением нескольких имен переменных, на которые они ссылаются.Я хочу абстрагировать функцию, чтобы мне не приходилось дублировать код.Это пример функции:

func listenToParticipantNumber() {
    guard let reference = participantNumberReference else {
        return
    }
    guard participantNumberListener == nil else {
        return
    }
    participantNumberListener = backendClient.listenToRtdbProperty(reference) { [weak self] (result: Result<Int, RequestError>) in
        guard let strongSelf = self else {
            return
        }
        switch result {
        case .success(let participantNumber):
            strongSelf.participantNumber = participantNumber
        case .failure:
            break
        }
    }
}

В другой функции я бы отключил participantNumberReference, participantNumber и participantNumberListener для разных переменных (которые все являются частными для моего класса), итип возвращаемого блока Int.Но основная структура функции была бы такой же.

Как я могу сделать этот процесс более чистым, чтобы использовать этот код, а не дублировать его?Можно ли как-то использовать KeyPaths для ссылки на различные переменные моего класса?

Ответы [ 3 ]

0 голосов
/ 03 апреля 2019

Я думаю, что вы на правильном пути с KeyPath. Я бы, вероятно, немного реорганизовал бы это так, чтобы метод listen возвращал Listener? (независимо от того, какой это тип), и вывел бы «если слушатель уже установлен, не делайте этого» вне этой функции.

Это оставило бы вас в таком свете:

func listen<Response>(reference: Int?,  // Or whatever type this is
                      updating keyPath: WritableKeyPath<C, Response>,
                      completion: (Result<Response, Error>) -> Void) -> Listener? {

    guard let reference = reference else { return nil }

    return backendClient.listenToRtdbProperty(reference) { [weak self] (result: Result<Response, Error>) in
        guard var strongSelf = self else {
            return
        }
        switch result {
        case .success(let result):
            strongSelf[keyPath: keyPath] = result
        case .failure:
            break
        }
}

Или вы можете сохранить существующую структуру примерно так:

func listen<Response>(reference: Int?,
                      updating keyPath: WritableKeyPath<C, Response>,
                      forListener listener: WritableKeyPath<C, Listener?>,
                      completion: (Result<Response, Error>) -> Void) {

    guard let reference = reference else { return }

    guard self[keyPath: listener] == nil else {
        return
    }

    var mutableSelf = self
    mutableSelf[keyPath: listener] = backendClient.listenToRtdbProperty(reference) { [weak self] (result: Result<Response, Error>) in
        guard var strongSelf = self else {
            return
        }
        switch result {
        case .success(let result):
            strongSelf[keyPath: keyPath] = result
        case .failure:
            break
        }
    }
}

(C во всем этом коде означает «этот класс». Мы еще не можем написать Self для обозначения «текущего класса».)

0 голосов
/ 03 апреля 2019

Итак, я решил это так:

private func listen<T>(
    to property: ReferenceWritableKeyPath<LivestreamModel, T?>,
    withReference propertyReference: ReferenceWritableKeyPath<LivestreamModel, String?>,
    listener: ReferenceWritableKeyPath<LivestreamModel, DatabaseHandle?>) {
    guard let reference = self[keyPath: propertyReference] else {
        return
    }
    guard self[keyPath: listener] == nil else {
        return
    }
    self[keyPath: listener] = backendClient.listenToRtdbProperty(reference) { [weak self] (result: Result<T, RequestError>) in
        switch result {
        case .success(let value):
            self?[keyPath: property] = value
        case .failure:
            self?[keyPath: property] = nil
        }
    }
}

Я действительно сделал это до того, как Роб ответил, но мне пришлось использовать ReferenceWritableKeyPath, потому что он выдает предупреждение о том, что self не имеет индексов при использованииWritableKeyPath.

0 голосов
/ 03 апреля 2019

Для достижения этого уровня абстракции требуется много предварительных знаний, поэтому вам нужно задать более масштабные вопросы о том, что вы действительно пытаетесь сделать, и стоит ли это того, но вот схема. Суть в том, чтобы использовать словарь со свойствами вместо явно объявленных переменных.

Теперь вы можете добавить будущие дела в PersonType и инициализировать их (что может быть автоматизировано), и функция будет применяться ко всем из них независимо от этого. PersonType может быть объявлено вне класса для увеличения разделения.

Это, вероятно, лучше подходит для Code Review, где вы можете опубликовать гораздо больше контекста.

enum PersonType { case Participant, case Leader, case Observer }
struct TrackedObject { var number: Int; var numberReference: ReferenceProtocol; var numberListener: ListenerProtocol; }
// Instead of 9 private variables, make a Dictionary of 3 objects each of
// which have 3 properties
private var persons: [ PersonType: TrackedObject ] = ...

func listenToPersonOfType(personType: PersonType) {
    guard let reference = persons[personType].numberReference else {
        return
    }
    guard persons[personType].numberListener == nil else {
        return
    }
    persons[personType].numberListener = backendClient.listenToRtdbProperty(reference) { [weak self] (result: Result<Int, RequestError>) in
        guard let strongSelf = self else {
            return
        }
        switch result {
        case .success(let number):
            strongSelf.persons[personType].number = number
        case .failure:
            break
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...