Menu
Coddy logo textTech

Фабричный шаблон

Часть раздела Объектно-ориентированное программирование путешествия по Swift на Coddy. Урок 43 из 57.

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

protocol Shape { func area() -> Double }
struct Square: Shape { let side: Double; func area() -> Double { side * side } }
struct Circle: Shape { let r: Double; func area() -> Double { 3 * r * r } }

enum ShapeFactory {
    static func make(_ kind: String, size: Double) -> (any Shape)? {
        switch kind {
        case "square": return Square(side: size)
        case "circle": return Circle(r: size)
        default: return nil
        }
    }
}

print(ShapeFactory.make("square", size: 3)!.area())
print(ShapeFactory.make("hexagon", size: 3) == nil)

Вывод:

9.0
true

Вызывающая сторона зависит только от протокола. Добавление новой формы означает изменение фабрики, а не каждого места, где создаются формы:

protocol Notifier { func send(_ m: String) -> String }
struct Email: Notifier { func send(_ m: String) -> String { "email: \(m)" } }
struct SMS: Notifier { func send(_ m: String) -> String { "sms: \(m)" } }

func makeNotifier(for user: String) -> any Notifier {
    user.hasSuffix("@mail.com") ? Email() : SMS()
}

print(makeNotifier(for: "ada@mail.com").send("hi"))
print(makeNotifier(for: "555-0100").send("hi"))

Вывод:

email: hi
sms: hi

Статические фабричные методы в самом типе — это более лёгкая форма паттерна. Они дают имена распространённым способам создания значения:

struct Color {
    let r: Int, g: Int, b: Int
    static func gray(_ v: Int) -> Color { Color(r: v, g: v, b: v) }
    static let white = Color(r: 255, g: 255, b: 255)
}

print(Color.gray(100).g)
print(Color.white.b)

Вывод:

100
255

Перечисление без вариантов, такое как ShapeFactory, — распространённый способ сгруппировать фабричные функции: его нельзя создать, поэтому оно используется исключительно как пространство имён.

enum ShapeFactory {        // нет cases: нельзя создать
    static func make(_ kind: String, size: Double) -> (any Shape)? { ... }
}
challenge icon

Задание

Легко

Напишите фабрику для тарифных планов. Протокол Plan содержит name и price(forUsers:) -> Int. Реализуйте Free (0 и только для 3 пользователей включительно: при большем количестве цена равна -1, что означает недопустимое количество пользователей), Team (10 за пользователя) и Enterprise (фиксированные 500 плюс 5 за каждого пользователя свыше 100). Завершите реализацию PlanFactory.make(_:), которая возвращает план для free, team или enterprise, а в остальных случаях — nil.

Предоставленный код считывает строки в формате plan,users и выводит team for 12 users: 120, free does not allow 5 users или unknown plan: gold.

Ваш код должен находиться в файлах Plan.swift, Free.swift, Team.swift, Enterprise.swift и PlanFactory.swift. В main.swift содержится предоставленный код ввода-вывода, и его нельзя редактировать.

Попробуйте сами

// Предоставленный код ввода/вывода: оставьте его как есть
var input: [String] = []
while let line = readLine() { input.append(line) }
for line in input {
    let p = line.split(separator: ",").map(String.init)
    let users = Int(p[1])!
    guard let plan = PlanFactory.make(p[0]) else {
        print("unknown plan: \(p[0])")
        continue
    }
    let price = plan.price(forUsers: users)
    print(price < 0 ? "\(plan.name) does not allow \(users) users" : "\(plan.name) for \(users) users: \(price)")
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Песочница Swift