Swift Mediator Pattern - Коллекция общих протоколов - PullRequest
0 голосов
/ 03 января 2019

Я пытаюсь реализовать универсальный шаблон Mediator в Swift и иметь следующие протоколы и классы:

protocol Request {
}

protocol Handler {
    associatedtype TRequest = Request

    func handle(_ request: TRequest)
}

class RequestProcessor {

    func register<THandler: Handler>(_ handler: THandler) {

    }

    func handle(_ request: Request) {

    }

}

При использовании по назначению (например):

struct LoginRequest: Request {
    let username: String
    let password: String
}

struct LogoutRequest: Request {
    let userId: Int
}

class LoginHandler: Handler {
    func handle(_ request: LoginRequest) {
        // do something
    }
}

class LogoutHandler: Handler {
    func handle(_ request: LogoutRequest) {
        // do something
    }
}

// Setup the processor and register handlers

let processor = RequestProcessor()
processor.register(LoginHandler())
processor.register(LogoutHandler())

// The processor handles any kind of Request, in this case a LoginRequest

processor.handle(LoginRequest(username: "steve", password: "..."))

// The LoginHandler's handle method will be called

Однако я не уверен, как хранить коллекцию Handler объектов, так как это протокол со связанным типом.Мне известно о стирании типов, и я прочитал несколько ответов здесь, а также различные статьи по этому вопросу ( 1 , 2 ), но не уверен, как применить его к моей ситуации.

1 Ответ

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

Во-первых, стандартный совет:

Я пытаюсь реализовать универсальный шаблон Mediator в Swift

Не. Начните с фактической проблемы, которую вы пытаетесь решить, и спроектируйте хорошие и необходимые абстракции для этой проблемы. Не создавайте общие вещи, просто чтобы быть общими. Свифт будет кусать тебя снова и снова. Даже stdlib, который на самом деле нуждается в супер-универсальных вещах, часто должен выйти за пределы чистого Swift, чтобы осуществить его (используя специальные знания компилятора и шаблонные шаблоны). «Быть ​​общим» не является самоцелью. Вы почти наверняка делаете это слишком сложным. Все делают.

ОК, это не так. Второй совет: это не очень хорошее использование протокола со связанными типами (PAT). Смысл PAT заключается в добавлении методов к типам, а не к be типам. Вы никогда не передадите себе Collection и не будете хранить вещи такого «типа». Вы создаете методы, которые могут работать с любым типом, который является Collection. Нет такого типа как [Collection].

Основная проблема вашего подхода заключается в том, что невозможно реализовать RequestProcessor.process(), не прибегая к as? приведению, что нарушает точку безопасности типов. Откуда processor знает, как звонить LoginHandler.process? Почему этот? Что, если два разных обработчика принимают LoginRequest? Что если никакой обработчик не принимает этот тип?

То, что вы разработали здесь, не является шаблоном Посредника. Шаблон Mediator объединяет коллег, имеющих общий интерфейс, поэтому он будет выглядеть следующим образом:

class RequestProcessor<Request> {

    var handlers: [(Request) -> Void] = []
    func register(handler: @escaping (Request) -> Void) {
        handlers.append(handler)
    }

    func handle(request: Request) {
        for handler in handlers {
            handler(request)
        }
    }
}

И у вас будет RequestProcessor для каждого типа запроса, а не универсальный «обработчик каждого типа запроса». Создание универсального обязательно (в Swift) удаляет безопасность типов, и в этом случае вы в основном создаете слегка Swiftier NotificationCenter. (Можно создать типобезопасную версию этого, но она требует зависимых типов, что является довольно сложной функцией типа, которой нет у Swift.)

ОК, может, вам действительно нужен этот центральный концентратор, и кому нужна безопасность типов? Почему бы и нет? Вы просто должны сказать, что вы имеете в виду, что любой обработчик должен иметь возможность принять любой запрос, даже если он не действует на него. Компилятор не может доказать ничего более конкретного, чем это, потому что во время компиляции он не знает типов. Так хорошо, as? это до смерти.

protocol Request {}

protocol Handler {
    func canHandle(_ request: Request) -> Bool
    func handle(_ request: Request)
}

class RequestProcessor {

    private var handlers: [Handler] = []

    func register(_ handler: Handler) {
        handlers.append(handler)
    }

    func handle(_ request: Request) {
        for handler in handlers where handler.canHandle(request) {
            handler.handle(request)
        }
    }
}

class LoginHandler: Handler {
    func canHandle(_ request: Request) -> Bool {
        return request is LoginRequest
    }

    func handle(_ request: Request) {
        guard let loginRequest = request as? LoginRequest else { return }
        // handle loginRequest
    }
}

Но я почти наверняка избавлюсь от схемы Посредника. Если цель состоит в том, чтобы загружать и выключать процессоры для тестирования или чего-то еще, я бы просто использовал типичные методы внедрения зависимостей. Передайте LoginHandler любому методу, создающему LoginRequest.

...