Почти естественно, что мы, разработчики, склонны абстрагироваться от каждой части кода, который мы создаем, вводя новые интерфейсы в надежде, что они могут быть полезны или необходимы в будущем.

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

Мы должны стремиться добавлять абстракции в следующих случаях:

1. У нас есть как минимум две или более реализации.

2. Нам нужно отделиться от изменчивых зависимостей (например, технологий баз данных или сетевых технологий) или разорвать зависимости между модулями (посредством инверсии зависимостей).

3. Если тестируемость компонента улучшена или облегчит ее.

Позвольте мне привести пример того, когда абстракция может быть отложена. У меня была задача написать парсер полезной нагрузки WebSocket, который в основном получает двоичные данные и должен преобразовываться в какое-то событие полезной нагрузки домена. Для моей конкретной задачи мне просто нужно проанализировать ответы JSON из WebSocket. Я начал реализацию с JSONWebSocketPayloadParser.

struct JSONWebSocketPayloadParser {
    public typealias WebSocketPayloadType = String
    private let types: [WebSocketPayloadType: WebSocketPayload.Type]
    public init(types: [WebSocketPayloadType: WebSocketPayload.Type]) {
        self.types = types
    }
    func payload(from data: Data) -> WebSocketPayload {
      do {
        let decoder = JSONDecoder()
        let payload = try decoder.decode(Payload.self, from: data)  
        return types[payload.type]?.decodeIfPresent(data) ??WebSocketUnknownPayload()
      } catch {
        return WebSocketUnknownPayload()
      }
    }
    private struct Payload: Decodable {
       let type: String
    }
}
// Implementation added just for reference, but not needed to understand the idea

А компонент, использующий мой парсер, имеет следующий вид:

class WebSocket {
    ...
    init(parser: JSONWebSocketPayloadParser) {
       ...
    }
    ...
}

Как видите, компонент WebSocket напрямую использует зависимость парсера JSON вместо использования некоторой абстракции WebSocketPayloadParser. Давайте проанализируем JSONWebSocketPayloadParser с точки зрения критериев, определенных выше.

  1. Имеет ли синтаксический анализатор полезной нагрузки две или более зависимости?

Для текущего варианта использования нет. У нас есть только требование анализировать данные из JSON. Если позже мы столкнемся с задачей анализа данных из другого формата, тогда можно было бы добавить абстракцию. Например, представьте, что теперь нам нужно проанализировать данные из XML.

// 1. Extract interface for Parser
protocol WebSocketPayloadParser {
    func payload(from data: Data) -> WebSocketPayload
}
// 2. JSON Parser, just need to conform to this protocol
struct JSONWebSocketPayloadParser: WebSocketPayloadParser {
    ...
}
// 3. Add new XML Parser
struct XMLWebSocketPayloadParser: WebSocketPayloadParser {
    ...
}
// 4. Update your components to depend on WebSocketPayloadParser
class WebSocket {
    ...
    init(parser: WebSocketPayloadParser) {
       ...
    }
    ...
}

2. Есть ли у нас какая-либо связь между WebSocket и JSONWebSocketPayloadParser?

JSONWebSocketPayloadParser принадлежит к тому же модулю компонента WebSocket, поэтому у нас нет никакой связи на уровне модуля. Если синтаксический анализатор принадлежит другому модулю потребителя (WebSocket), тогда в этом случае потребуется абстракция (определенная в модуле потребителя), если мы хотим избежать связи между этими компонентами / модулями.

3. Улучшает ли добавление абстракции или помогает в тестировании компонента с помощью синтаксического анализатора?

В этом конкретном случае я использовал классический подход к тестированию, когда в основном тестировал компонент с его реальным сотрудником. Итак, при тестировании WebSocket я косвенно тестирую парсер. Имеет смысл протестировать настоящего соавтора, ведь у нас есть компоненты ввода-вывода (парсер). Поскольку мне не нужно заменять синтаксический анализатор тестовым двойником, добавление абстракции не улучшает тестируемость компонента и не помогает ему.