или иллюстрированный сверхинженер

Начнем с классического фрагмента кода Hello-world, который, например, предоставляется Xcode при создании инструмента командной строки:

print("Hello world!")

Это работает, но для серьезного предприятия этого недостаточно. Давайте на этот раз попробуем сосредоточиться на ремонтопригодности и расширяемости. Во-первых, давайте завершим вызов функцией:

(Добавленный или измененный код выделен курсивом.)

func printHelloWorld() {
  print("Hello world!")
}
printHelloWorld()

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

func printMessage(_ message: String) {
  print(message)
}
printMessage("Hello world!")

Лучше. Давайте сделаем решение более объектно-ориентированным -y и представим объект, который отвечает за вывод:

struct MessagePrinter {
  func printMessage(_ message: String) {
    print(message)
  }
}
let printer = MessagePrinter()
printer.printMessage("Hello world!")

Хм, изменение не кажется очень полезным, поскольку struct только обертывает метод. Сделаем выводимое сообщение зависимостью:

struct MessagePrinter {
  let message: String
  func printMessage() {
    print(message)
  }
}
let printer = MessagePrinter(message: "Hello world!")
printer.printMessage()

Выглядит круто! Однако давайте сделаем это немного абстрактным и разрешим несколько реализаций для принтера:

protocol MessagePrinter {
  func printMessage()
}
struct MessageDefaultPrinter: MessagePrinter {
  let message: String
  func printMessage() {
    print(message)
  }
}
let printer = MessageDefaultPrinter(message: "Hello world!")
printer.printMessage()

Теперь, похоже, что конкретные реализации - это большая часть знаний для пользователей класса. Давайте спрячем реализации за упрощенной фабрикой:

protocol MessagePrinter {
  func printMessage()
}
struct MessageDefaultPrinter: MessagePrinter {
  let message: String
  func printMessage() {
    print(message)
  }
}
struct MessagePrinterFactory {
  static func makeHelloWorldPrinter() -> MessagePrinter {
    return MessageDefaultPrinter(message: "Hello world!")
  }
}
let printer = MessagePrinterFactory.makeHelloWorldPrinter()
printer.printMessage()

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

protocol MessagePrinter {
  func printMessage()
}
struct MessageDefaultPrinter: MessagePrinter {
  let message: String
  func printMessage() {
    print(message)
  }
}
final class MessageDefaultPrinterBuilder {
  enum MessageDefaultPrinterBuilderError: Error {
    case emptyMessage
  }
  private var message: String?
  func with(message: String) -> MessageDefaultPrinterBuilder {
    self.message = message
    return self
  }
  func build() throws -> MessagePrinter {
    guard let message = message else {
      throw MessageDefaultPrinterBuilderError.emptyMessage
    }
    return MessageDefaultPrinter(message: message)
  }
}
struct MessagePrinterFactory {
  enum MessagePrinterFactoryError: Error {
    case builderError
  }
  static func makeHelloWorldPrinter() throws -> MessagePrinter {
    do {
      return try MessageDefaultPrinterBuilder()
        .with(message: "Hello world!")
        .build()
    } catch {
        throw MessagePrinterFactoryError.builderError
    }
  }
}
if let printer 
  = try? MessagePrinterFactory.makeHelloWorldPrinter() {
  printer.printMessage()
}

Да, и поскольку мы начали абстрагироваться от интерфейсов, давайте не будем ограничиваться только одним типом, который можно печатать - S tring. Конечно, это самый важный тип для печати, но, похоже, это такое ограничение. Что, если мы хотим представить наши настраиваемые типы для печати?

protocol MessagePrinter {
  func printMessage()
}
protocol Printable { }
extension String: Printable { }
struct MessageDefaultPrinter: MessagePrinter {
  let message: Printable
  func printMessage() {
    print(message)
  }
}
final class MessageDefaultPrinterBuilder {

  enum MessageDefaultPrinterBuilderError: Error {
    case emptyMessage
  }
  private var message: Printable?
  func with(message: Printable) -> MessageDefaultPrinterBuilder {
    self.message = message
    return self
  }

  func build() throws -> MessagePrinter {
    guard let message = message else {
      throw MessageDefaultPrinterBuilderError.emptyMessage
    }
    return MessageDefaultPrinter(message: message)
  }
}
struct MessagePrinterFactory {
  enum MessagePrinterFactoryError: Error {
    case builderError
  }
  static func makeHelloWorldPrinter() throws -> MessagePrinter {
    do {
      return try MessageDefaultPrinterBuilder()
        .with(message: "Hello world!")
        .build()
    } catch {
      throw MessagePrinterFactoryError.builderError
    }
  }
}
if let printer
  = try? MessagePrinterFactory.makeHelloWorldPrinter() {
  printer.printMessage()
}

И наконец, глазурь на торте. Swift очень сильно ассоциируется с разработкой программного обеспечения для платформ Apple. Там очень часто сочетаются типы с делегированием. Давайте добавим один к принтеру:

protocol MessagePrinterDelegate: AnyObject {
  func messagePrinterWillStartPrinting(
    _ messagePrinter: MessagePrinter
  )
  func messagePrinterDidEndPrinting(
    _ messagePrinter: MessagePrinter
  )
}
protocol MessagePrinter {
  var delegate: MessagePrinterDelegate? { get set }
  func printMessage()
}
protocol Printable { }
extension String: Printable { }
struct MessageDefaultPrinter: MessagePrinter {
  let message: Printable
  weak var delegate: MessagePrinterDelegate?
  func printMessage() {
    delegate?.messagePrinterWillStartPrinting(self)
    print(message)
    delegate?.messagePrinterDidEndPrinting(self)
  }
}
final class MessageDefaultPrinterBuilder {
  enum MessageDefaultPrinterBuilderError: Error {
    case emptyMessage
  }
  private weak var delegate: MessagePrinterDelegate?
  private var message: Printable?
  func with(message: Printable) -> MessageDefaultPrinterBuilder {
    self.message = message
    return self
  }
  func with(
    delegate: MessagePrinterDelegate?
  ) -> MessageDefaultPrinterBuilder {
    self.delegate = delegate
    return self
  }
  func build() throws -> MessagePrinter {
    guard let message = message else {
      throw MessageDefaultPrinterBuilderError.emptyMessage
    }
    var printer = MessageDefaultPrinter(message: message)
    printer.delegate = delegate
    return printer
  }
}
struct MessagePrinterFactory {
  enum MessagePrinterFactoryError: Error {
    case builderError
  }
  static func makeHelloWorldPrinter(
    delegate: MessagePrinterDelegate? = nil
  ) throws -> MessagePrinter {
    do {
      return try MessageDefaultPrinterBuilder()
        .with(message: "Hello world!")
        .with(delegate: delegate)
        .build()
    } catch {
      throw MessagePrinterFactoryError.builderError
    }
  }
}
final class MessagePrinterDefaultDelegate: MessagePrinterDelegate {
  func messagePrinterWillStartPrinting(
    _ messagePrinter: MessagePrinter
  ) {
    print("Message printer starts printing.")
  }
  func messagePrinterDidEndPrinting(
    _ messagePrinter: MessagePrinter
  ) {
    print("Message printer finished printing.")
  }
}
let delegate = MessagePrinterDefaultDelegate()
if let printer = try? MessagePrinterFactory
  .makeHelloWorldPrinter(delegate: delegate) {
  printer.printMessage()
}

Я абсолютно уверен, что есть еще много возможностей для улучшения, но я думаю, что мы, тем не менее, проделали большую работу сегодня. Фух!

Здесь вы можете найти полный код, красиво организованный, соответствующий руководствам по стилю кода и хорошо документированный.

P.S. Идея, как вы знаете, не нова. Я встречал как минимум версии Java и C #, а также эту программу на C 1990 года.

Некоторые рекомендации по шаблонам дизайна:







Если вам нравится читать мои сообщения (и сообщения других авторов) на Medium, вы можете стать полноправным участником Medium (если еще не сделали) здесь.