Сегодня мы представляем способ создания объекта, который называется «Абстрактный заводской шаблон».
Иногда нам нужно будет создать некоторые объекты с другой категорией, и, создавая эти объекты с разными категориями, мы можем построить соединение, которое будет соответствовать нашим требованиям.
Я нарисовал схему для демонстрации, с которой, я думаю, вы могли быть знакомы.
Допустим, машине нужны разные детали, такие как корпус, двигатель и шина.
Корпус может быть разного цвета, двигатель может иметь разные модели, а шины могут быть разных типов.
//MARK: - Car
struct Car: ProductInformation {
var shell: Shell
var engine: Engine
var tire: Tire
var price: Int { shell.price + engine.price + tire.price }
}
extension Car {
var description: String {
return "\n Shell: \(shell.description) \n Engine: \(engine.description) \n Tire: \(tire.description) \n Price \(price)"
}
}
Итак, прежде всего, мы определяем тип Car со свойствами корпуса, двигателя и шин. Чтобы рассчитать, сколько стоит автомобиль, у нас есть свойство компьютера — цена, чтобы сложить все цены этих частей карты.
Наконец, мы позволяем Car соответствовать протоколу «ProductionInformation», чтобы показать описание производства.
. . .
Чтобы создать различные части автомобиля, мы создаем спецификацию, используя протокол для разных частей в качестве схемы (ShellSpec, EngineSpec, TireSpec), чтобы мы могли настраивать все части автомобиля в соответствии с этими спецификациями.
В будущем, если нам потребуется настроить другой тип шины или другую модель двигателя, единственное, что нам нужно сделать, это следовать каждой спецификации, тогда мы сможем легко определить новую деталь.
Создание интерфейса между автомобилем и его частями, чтобы мы могли легко создать из него новую деталь, — это то, что мы называем «Абстракт» в «Фабрике абстракций».
В этом примере мы настраиваем красную оболочку и серебряную оболочку, быстрый двигатель и мощный двигатель, гоночную шину и стандартную шину, как показано ниже.
//MARK: Shell
protocol ShellSpec: ProductInformation {
var color: String { get }
var price: Int { get }
}
extension ShellSpec {
var description: String {
return color
}
}
struct Silver: ShellSpec {
var color: String = "Silver"
var price: Int = 200
}
struct Red: ShellSpec {
var color: String = "Red"
var price: Int = 300
}
//MARK: Engine
protocol EngineSpec: ProductInformation {
var model: String { get }
var price: Int { get }
}
extension EngineSpec {
var description: String {
return model
}
}
struct Fast: EngineSpec {
var model: String = "fast"
var price: Int = 300
}
struct Powerful: EngineSpec {
var model: String = "Powerful"
var price: Int = 500
}
//MARK: Tire
protocol TireSpec: ProductInformation {
var type: String { get }
var price: Int { get }
}
extension TireSpec {
var description: String {
return type
}
}
struct Racing: TireSpec {
var type: String = "Racing"
var price: Int = 400
}
struct Standard: TireSpec {
var type: String = "Standard"
var price: Int = 600
}
Вот и все, теперь у нас есть все необходимое для создания автомобиля, и мы можем приступить к его производству.
Наиболее интуитивно понятный способ создания автомобиля выглядит следующим образом:
let racingCar = Car(shell: Silver(),
engine: Fast(),
tire: Racing())
let rv = Car(shell: Red(),
engine: Powerful(),
tire: Standard())
print("Racing car info: \(racingCar.description)")
print("RV car info: \(rv.description)")
Racing car info:
Shell: Silver
Engine: fast
Tire: Racing
Price 900
RV car info:
Shell: Red
Engine: Powerful
Tire: Standard
Price 1400
Выше приведен наиболее распространенный способ создания объекта, мы все уже делали это раньше, верно? но есть ли лучший способ сделать это?
Если вы видите приведенный выше код, вы можете заметить, что вызывающая сторона должна знать компоненты гоночного автомобиля, прежде чем они создадут правильный объект.
Но нужно ли звонящему знать, как сочетается автомобиль? Мы как покупатель, действительно ли мы хотим знать детали двигателя в машине, которую я покупаю? или все, что мы хотим, это просто дать мне Lamborghini?
Хорошая инкапсуляция может помочь пользователям или другому разработчику, с которым вы работаете, правильно использовать ваш код, вы не хотите, чтобы они ожидали Lamborghini, но добавили в него стандартный движок. Правильно?
. . .
Вот как работает часть «Фабрика».
//MARK: - Abstract Factory
class CarFactory {
enum CarType {
case RacingCar
case RV
}
private static func configuration(type: CarType) -> CarConfiguration {
switch type {
case .RacingCar: return RacingCarConfiguration()
case .RV: return RVConfiguration()
}
}
static func createCar(type: CarType) -> Car? {
let configuration = configuration(type: type)
guard let shell = configuration.shell,
let engine = configuration.engine,
let tire = configuration.tire else { return nil}
return Car(shell: shell, engine: engine, tire: tire)
}
}
//MARK: - Car Configuration
protocol CarConfiguration {
var shell: ShellSpec? { get }
var engine: EngineSpec? { get }
var tire: TireSpec? { get }
}
extension CarConfiguration {
var shell: ShellSpec? {
return nil
}
var engine: EngineSpec? {
return nil
}
var tire: TireSpec? {
return nil
}
}
//MARK: - Make Car Category by conform to `CarConfiguration`.
struct RacingCarConfiguration: CarConfiguration {
var shell: ShellSpec? {
return Silver()
}
var engine: EngineSpec? {
return Fast()
}
var tire: TireSpec? {
return Racing()
}
}
struct RVConfiguration: CarConfiguration {
var shell: ShellSpec? {
return Red()
}
var engine: EngineSpec? {
return Powerful()
}
var tire: TireSpec? {
return Standard()
}
Во-первых, мы создаем CarFactory, которая будет отвечать за производство автомобиля.
В этой CarFactory есть список всех автомобильных продуктов. мы включили RacingCar и RV в список. configuration(type: CarType) вернет CarConfiguration, содержащую все детали, необходимые для создания автомобиля, а метод createCar(type: CardType) проверит, были ли выполнены требования к конфигурации, и, если они удовлетворены, вернет автомобиль, который хочет пользователь или возвращает nil, если конфигурация неверна.
После того, как мы определим RacingCarConfiguration и RVConfiguration в качестве схемы конкретного автомобиля, вызывающему объекту нужно будет только выбрать, какой автомобиль ему нужен в методе createCar, и он вернет точный автомобиль без какой-либо ошибки.
let racingCar = CarFactory.createCar(type: .RacingCar)!
print("Racing car info: \(racingCar.description)")
Racing car info:
Shell: Silver
Engine: fast
Tire: Racing
Price 900
. . .
Выше приведен абстрактный фабричный шаблон, который я изучил, я разделил его на две части, чтобы его было легче изучать. если это поможет вам, как я хочу, пожалуйста, похлопайте мне, спасибо 🙂