или иллюстрированный сверхинженер
Начнем с классического фрагмента кода 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 (если еще не сделали) здесь.