Від Singleton до Factory: Глибоке занурення в креаційні шаблони проектування

pic

джерело: Pinterest

У розробці програмного забезпечення спосіб створення об'єктів може значно вплинути на підтримку, масштабованість та гнучкість додатку. Створювальні (creational) шаблони проектування надають ефективні рішення для ініціалізації об'єктів, пропонуючи механізми для створення об'єктів контрольованим та оптимізованим способом. Замість того, щоб безпосередньо створювати об'єкти за допомогою ключового слова new, ці шаблони абстрагують процес створення об'єктів, сприяючи ослабленому зв'язку та повторному використанню.

Існує кілька типів створювальних шаблонів проектування, таких як Singleton, Prototype, Builder, Abstract Factory та Factory Method. Кожен шаблон має свою специфічну мету, таку як забезпечення єдиного екземпляра класу, спрощення створення складних об'єктів або поліпшення продуктивності шляхом повторного використання існуючих екземплярів. Використовуючи створювальні шаблони проектування, розробники можуть покращити організацію коду та його підтримку, що веде до більш масштабованих архітектур програмного забезпечення.

Singleton

Шаблон Singleton є створювальним шаблоном проектування, який гарантує, що клас має лише один екземпляр і надає глобальну точку доступу до цього екземпляра. Це корисно, коли вам потрібен єдиний спільний ресурс, наприклад, менеджер конфігурації, з'єднання з базою даних або унікальна сутність у вашому додатку.

Аналогія з тваринами

Уявімо, що ми маємо Правителя Лісу, який керує всіма істотами в дикій природі. Має бути лише один Правитель, який стоїть над усіма. Якщо створити кілька Правителів, ліс може впасти в хаос через конфлікти між кожним Правителем та його послідовниками.

Шаблон Singleton гарантує, що в програмі існує лише один Правитель Лісу, підтримуючи гармонію та порядок в дикій природі.

package main  

import (  
 "fmt"  
 "sync"  
)  

// Структура Ruler (Singleton)  
type RulerOfForest struct {  
 name string  
}  

// Екземпляр Singleton і sync.Once для забезпечення безпеки потоків  
var instance *RulerOfForest  
var once sync.Once  

// GetRulerOfForest повертає єдиний екземпляр RulerOfForest  
func GetRulerOfForest(name string) *RulerOfForest {  
 once.Do(func() {  
 instance = &RulerOfForest{name: name}  
 })  
 return instance  
}  

// Метод Announce для RulerOfForest  
func (zd *RulerOfForest) Announce() {  
 fmt.Println("Я Правитель Лісу, моє ім'я", zd.name)  
}  

func main() {  
 // Перший екземпляр  
 ruler1 := GetRulerOfForest("Лев")  
 ruler1.Announce() // Виведе: Я Правитель Лісу, моє ім'я Лев  

 // Спроба створити інший екземпляр  
 ruler2 := GetRulerOfForest("Орел")  
 ruler2.Announce() // Виведе: Я Правитель Лісу, моє ім'я Лев (все ще Лев!)  

 // Перевірка, чи є ці два екземпляри однаковими  
 fmt.Println(ruler1 == ruler2) // Виведе: true (обидва посилаються на той самий екземпляр)  
}

Як це працює

  1. var instance *RulerOfForest → Зберігає єдиний екземпляр.
  2. sync.Once → Гарантує, що екземпляр буде створений лише один раз, навіть у багатопоточних програмах.
  3. GetRulerOfForest(name string) → Створює екземпляр лише якщо його ще немає і повертає той самий екземпляр кожного разу.
  4. Коли ми викликаємо GetRulerOfForest("Орел") після GetRulerOfForest("Лев"), новий екземпляр не створюється, тому ім'я залишатиметься "Лев".

У реальних додатках шаблон Singleton часто використовується для ефективного керування ресурсами по всій системі. Наприклад:

  • З'єднання з базою даних, підтримка одного екземпляра запобігає створенню кількох зайвих з'єднань, що зменшує споживання ресурсів та забезпечує узгодженість даних.
  • Служби журналювання, коли єдиний екземпляр логера гарантує, що всі частини програми записують логи централізовано і структуровано, що робить налагодження та моніторинг більш ефективними.
  • Управління конфігурацією, де екземпляр Singleton зберігає налаштування додатка, такі як змінні середовища та API-ключі, забезпечуючи доступ до одних і тих самих налаштувань для всіх компонентів без непослідовностей.
    За допомогою шаблону Singleton в цих сценаріях розробники можуть підтримувати ефективність, уникати конфліктів і забезпечувати кращу управління ресурсами в додатку.

Prototype

Шаблон Prototype є створювальним шаблоном проектування, який дозволяє клонуати об'єкти, а не створювати їх з нуля. Це корисно, коли створення об'єктів є дорогим або складним, і нам потрібно кілька подібних копій з невеликими змінами. Замість того, щоб створювати нові об'єкти, ми дублюємо існуючий, забезпечуючи ефективність і узгодженість.

Аналогія з тваринами

Уявімо ліс, де є різні типи тварин. Замість того, щоб вручну збирати дані кожної тварини з її характеристиками (ім'я, вид, тип їжі, місце проживання), ми можемо створити "прототип" тварини і клонувати його, коли це потрібно. Такий підхід економить час і забезпечує узгодженість у характеристиках тварин.

package main  

import (  
 "fmt"  
)  

// Інтерфейс Animal з методами Clone та GetInfo  
type Animal interface {  
 Clone() Animal  
 GetInfo() string  
}  

// Конкретна структура для певного типу тварини  
type ForestAnimal struct {  
 name string  
 species string  
 typeOfFood string  
 livingPlace string  
}  

// Метод Clone для створення копії ForestAnimal  
func (f *ForestAnimal) Clone() Animal {  
 return &ForestAnimal{  
 name: f.name,  
 species: f.species,  
 typeOfFood: f.typeOfFood,  
 livingPlace: f.livingPlace,  
 }  
}  

// Метод GetInfo для відображення деталей ForestAnimal  
func (f *ForestAnimal) GetInfo() string {  
 return fmt.Sprintf("Name: %s, Species: %s, Type of Food: %s, Living Place: %s", f.name, f.species, f.typeOfFood, f.livingPlace)  
}  

func main() {  
 // Створення прототипу ForestAnimal  
 firstBear := &ForestAnimal{  
 name: "Baloo",  
 species: "Bear",  
 typeOfFood: "Omnivore",  
 livingPlace: "Cave",  
 }  
 fmt.Println(originalBear.GetInfo()) // Виведе: Name: Baloo, Species: Bear, Type of Food: Omnivore, Living Place: Cave  

 // Клонування ForestAnimal для створення нового  
 secondBear := firstBear.Clone().(*ForestAnimal)  
 secondBear.name = "Yogi" // Зміна імені клона  

 fmt.Println(secondBear.GetInfo()) // Виведе: Name: Yogi, Species: Bear, Type of Food: Omnivore, Living Place: Cave  
 fmt.Println(firstBear.GetInfo()) // Виведе: Name: Baloo, Species: Bear, Type of Food: Omnivore, Living Place: Cave  
}

Як це працює

  1. Інтерфейс Animal: Визначає методи Clone та GetInfo, які повинні реалізувати всі тварини.
  2. Структура ForestAnimal: Представляє тварину в лісі з атрибутами name, species, typeOfFood і livingPlace.
  3. Метод Clone: Створює глибоку копію об'єкта ForestAnimal.
  4. Метод GetInfo: Повертає відформатовану строку з деталями тварини.
  5. Основна функція: Створює прототип ForestAnimal (наприклад, ведмідь на ім'я "Baloo"). Клонує прототип і змінює ім'я клона на “Yogi”. Виводить деталі як оригінальної, так і клонированої тварини.

Цей підхід має кілька ключових переваг, що робить його потужним інструментом у сценаріях, де створення об'єктів є ресурсозатратним або потребує узгодженості. Ось кілька його переваг:

  • Ефективність: Створення нового об'єкта часто включає складну ініціалізацію, запити до бази даних або розподіл ресурсів. Клонуючи існуючий об'єкт, ви обходите ці дорогі операції, заощаджуючи час і обчислювальні ресурси.
  • Узгодженість: Клонуючи об'єкт, новий екземпляр успадковує стан і властивості оригіналу. Це забезпечує узгодженість між кількома екземплярами, що є корисним, коли працюєш зі складними об'єктами, які вимагають точних налаштувань.
  • Гнучкість: Після клонування об'єкта ви можете налаштувати копію незалежно від оригіналу.
    Ця гнучкість корисна, коли вам потрібно створити кілька варіацій об'єкта без змінення прототипу.

Builder

Шаблон Builder є створювальним шаблоном проектування, який використовується для поетапного створення складних об'єктів. Замість того, щоб створювати об'єкт із довгим списком параметрів, шаблон Builder надає структурований спосіб налаштування та ініціалізації об'єктів з гнучкістю та читаністю.

Аналогія з тваринами

Уявімо, що ми хочемо класифікувати тварин у лісі, і нам потрібно отримати дані про різних тварин з різними атрибутами, такими як ім'я, вид, тип їжі та місце проживання. Замість того, щоб використовувати довгий конструктор з кількома параметрами, що може бути незрозуміло, ми використовуємо шаблон Builder, щоб класифікувати тварину поетапно в читабельний і гнучкий спосіб.

package main  

import (  
 "fmt"  
)  

// Структура Animal, що представляє тварину в лісі  
type Animal struct {  
 name string  
 species string  
 typeOfFood string  
 livingPlace string  
}  

// Структура AnimalBuilder для створення об'єктів Animal  
type AnimalBuilder struct {  
 animal Animal  
}  

// NewAnimalBuilder створює новий екземпляр AnimalBuilder  
func NewAnimalBuilder() *AnimalBuilder {  
 return &AnimalBuilder{}  
}  

// SetName встановлює ім'я тварини  
func (b *AnimalBuilder) SetName(name string) *AnimalBuilder {  
 b.animal.name = name  
 return b  
}  

// SetSpecies встановлює вид тварини  
func (b *AnimalBuilder) SetSpecies(species string) *AnimalBuilder {  
 b.animal.species = species  
 return b  
}  

// SetTypeOfFood встановлює тип їжі, яку їсть тварина  
func (b *AnimalBuilder) SetTypeOfFood(typeOfFood string) *AnimalBuilder {  
 b.animal.typeOfFood = typeOfFood  
 return b  
}  

// SetLivingPlace встановлює місце проживання тварини  
func (b *AnimalBuilder) SetLivingPlace(livingPlace string) *AnimalBuilder {  
 b.animal.livingPlace = livingPlace  
 return b  
}  

// Build завершить створення об'єкта  
func (b *AnimalBuilder) Build() Animal {  
 return b.animal  
}  

func main() {  
 // Створення тварини за допомогою шаблону Builder  
 bear := NewAnimalBuilder().  
 SetName("Baloo").  
 SetSpecies("Bear").  
 SetTypeOfFood("Omnivore").  
 SetLivingPlace("Cave").  
 Build()  

 // Створення іншої тварини  
 deer := NewAnimalBuilder().  
 SetName("Bambi").  
 SetSpecies("Deer").  
 SetTypeOfFood("Herbivore").  
 SetLivingPlace("Meadow").  
 Build()  

 // Виведення створених тварин  
 fmt.Printf("Animal: %s, Species: %s, Type of Food: %s, Living Place: %s\n", bear.name, bear.species, bear.typeOfFood, bear.livingPlace)  
 fmt.Printf("Animal: %s, Species: %s, Type of Food: %s, Living Place: %s\n", deer.name, deer.species, deer.typeOfFood, deer.livingPlace)  
}

Як це працює

  1. Структура Animal: Представляє тварину в лісі з атрибутами name, species, typeOfFood, і livingPlace.
  2. Структура AnimalBuilder: Допомагає поетапно створювати об'єкт Animal.
  3. Методи Set: Кожен метод встановлює певний атрибут для Animal і повертає сам об'єкт Builder, що дозволяє ланцюгове викликане методів.
  4. Метод Build: Завершує створення і повертає об'єкт Animal.
  5. Основна функція: Показує, як використовувати Builder для створення різних тварин з конкретними атрибутами.

Замість того, щоб покладатися на великі, незручні конструктори або кілька перевантажених методів, шаблон Builder надає чіткий, поетапний підхід до створення об'єктів. Ось кілька переваг цього підходу, що робить його широко використовуваним у різних сценаріях:

  • Покращена читаність: Коли об'єкт вимагає численних параметрів для ініціалізації, традиційні конструктори можуть стати заплутаними і важкими для читання.
    Ця гнучкість корисна, коли вам потрібно створити кілька варіацій об'єкта без змінення прототипу.

Builder

Шаблон Builder є створювальним шаблоном проектування, який використовується для поетапного створення складних об'єктів. Замість того, щоб створювати об'єкт із довгим списком параметрів, шаблон Builder надає структурований спосіб налаштування та ініціалізації об'єктів з гнучкістю та читаністю.

Аналогія з тваринами

Уявімо, що ми хочемо класифікувати тварин у лісі, і нам потрібно отримати дані про різних тварин з різними атрибутами, такими як ім'я, вид, тип їжі та місце проживання. Замість того, щоб використовувати довгий конструктор з кількома параметрами, що може бути незрозуміло, ми використовуємо шаблон Builder, щоб класифікувати тварину поетапно в читабельний і гнучкий спосіб.

package main  

import (  
 "fmt"  
)  

// Структура Animal, що представляє тварину в лісі  
type Animal struct {  
 name string  
 species string  
 typeOfFood string  
 livingPlace string  
}  

// Структура AnimalBuilder для створення об'єктів Animal  
type AnimalBuilder struct {  
 animal Animal  
}  

// NewAnimalBuilder створює новий екземпляр AnimalBuilder  
func NewAnimalBuilder() *AnimalBuilder {  
 return &AnimalBuilder{}  
}  

// SetName встановлює ім'я тварини  
func (b *AnimalBuilder) SetName(name string) *AnimalBuilder {  
 b.animal.name = name  
 return b  
}  

// SetSpecies встановлює вид тварини  
func (b *AnimalBuilder) SetSpecies(species string) *AnimalBuilder {  
 b.animal.species = species  
 return b  
}  

// SetTypeOfFood встановлює тип їжі, яку їсть тварина  
func (b *AnimalBuilder) SetTypeOfFood(typeOfFood string) *AnimalBuilder {  
 b.animal.typeOfFood = typeOfFood  
 return b  
}  

// SetLivingPlace встановлює місце проживання тварини  
func (b *AnimalBuilder) SetLivingPlace(livingPlace string) *AnimalBuilder {  
 b.animal.livingPlace = livingPlace  
 return b  
}  

// Build завершить створення об'єкта  
func (b *AnimalBuilder) Build() Animal {  
 return b.animal  
}  

func main() {  
 // Створення тварини за допомогою шаблону Builder  
 bear := NewAnimalBuilder().  
 SetName("Baloo").  
 SetSpecies("Bear").  
 SetTypeOfFood("Omnivore").  
 SetLivingPlace("Cave").  
 Build()  

 // Створення іншої тварини  
 deer := NewAnimalBuilder().  
 SetName("Bambi").  
 SetSpecies("Deer").  
 SetTypeOfFood("Herbivore").  
 SetLivingPlace("Meadow").  
 Build()  

 // Виведення створених тварин  
 fmt.Printf("Animal: %s, Species: %s, Type of Food: %s, Living Place: %s\n", bear.name, bear.species, bear.typeOfFood, bear.livingPlace)  
 fmt.Printf("Animal: %s, Species: %s, Type of Food: %s, Living Place: %s\n", deer.name, deer.species, deer.typeOfFood, deer.livingPlace)  
}

Як це працює

  1. Структура Animal: Представляє тварину в лісі з атрибутами name, species, typeOfFood, і livingPlace.
  2. Структура AnimalBuilder: Допомагає поетапно створювати об'єкт Animal.
  3. Методи Set: Кожен метод встановлює певний атрибут для Animal і повертає сам об'єкт Builder, що дозволяє ланцюгове викликане методів.
  4. Метод Build: Завершує створення і повертає об'єкт Animal.
  5. Основна функція: Показує, як використовувати Builder для створення різних тварин з конкретними атрибутами.

Замість того, щоб покладатися на великі, незручні конструктори або кілька перевантажених методів, шаблон Builder надає чіткий, поетапний підхід до створення об'єктів. Ось кілька переваг цього підходу, що робить його широко використовуваним у різних сценаріях:

  • Покращена читаність: Коли об'єкт вимагає численних параметрів для ініціалізації, традиційні конструктори можуть стати заплутаними і важкими для читання.
    Шаблон Abstract Factory (AbstractFactory):

  • Визначає інтерфейс для створення сімейства пов'язаних об'єктів (наприклад, CreateAnimal і CreatePlant).

  • Кожна конкретна фабрика (наприклад, MammalFactory, BirdFactory) реалізує цей інтерфейс для створення конкретних типів тварин і рослин.

2. Конкретні фабрики:

  • MammalFactory: Створює ссавців (наприклад, Lion) і їх супутні рослини (наприклад, Tree).
  • BirdFactory: Створює птахів (наприклад, Eagle) і їх супутні рослини (наприклад, Shrub).

3. Продукти:

  • Тварини: Lion і Eagle — конкретні тварини, що реалізують інтерфейс Animal.
  • Рослини: Tree і Shrub — конкретні рослини, що реалізують інтерфейс Plant.

4. Код клієнта:

  • Функція main демонструє, як використовувати абстрактну фабрику для створення сімейств пов'язаних об'єктів (наприклад, ссавців або птахів) без знання їх конкретних типів.

Шаблон Abstract Factory є створювальним шаблоном проектування, який надає інтерфейс для створення сімейства пов'язаних або залежних об'єктів без вказування їх конкретних класів. Він інкапсулює логіку створення об'єктів, сприяє узгодженості та робить системи більш адаптивними до змін. Нижче ми розглянемо його основні переваги:

  • Інкапсуляція створення об'єктів: Шаблон Abstract Factory приховує деталі створення об'єктів, дозволяючи клієнтському коду працювати з абстрактними інтерфейсами, а не з конкретними реалізаціями. Це розділення відповідальностей робить код чистішим і легшим для підтримки.
  • Легке розширення: Шаблон дотримується принципу відкритості/закритості, що означає, що ви можете додавати нові типи об'єктів або сімейства без зміни існуючого коду. Це робить систему високошаблонною.
  • Забезпечення узгодженості: Шаблон Abstract Factory гарантує, що об'єкти, створені фабрикою, є сумісними та належать до однієї родини. Це запобігає невідповідності та підтримує узгодженість у системі.

Factory Method

Шаблон Factory Method використовується, коли клас хоче делегувати ініціалізацію об'єктів своїм підкласам. Замість того, щоб безпосередньо викликати ключове слово new для створення об'єктів, метод фабрики дозволяє класу викликати метод, який визначає, який об'єкт створити.

Аналогія з тваринами

У лісі існують різні типи диких тварин (наприклад, леви, орли, ведмеді), які мешкають у своїх природних середовищах. Кожна тварина створюється природою (фабрикою) таким чином, щоб відповідати її середовищу.
Наприклад:

  • Леви створюються на савані.
  • Орли створюються в горах.
  • Ведмеді створюються в лісах.

Шаблон Factory Method можна використовувати для моделювання того, як ці тварини створюються в їхніх природних середовищах без розкриття логіки створення об'єктів.

package main  

import "fmt"  

// Інтерфейс Animal представляє дику тварину в лісі  
type Animal interface {  
 Speak() string  
 Live() string  
}  

// Інтерфейс AnimalFactory визначає метод фабрики  
type AnimalFactory interface {  
 CreateAnimal() Animal  
}  

// LionFactory створює левів на савані  
type LionFactory struct{}  

func (l *LionFactory) CreateAnimal() Animal {  
 return &Lion{name: "Simba", habitat: "Savanna"}  
}  

// EagleFactory створює орлів в горах  
type EagleFactory struct{}  

func (e *EagleFactory) CreateAnimal() Animal {  
 return &Eagle{name: "Eddy", habitat: "Mountains"}  
}  

// BearFactory створює ведмедів в лісах  
type BearFactory struct{}  

func (b *BearFactory) CreateAnimal() Animal {  
 return &Bear{name: "Baloo", habitat: "Woods"}  
}  

// Lion є конкретною дикою твариною  
type Lion struct {  
 name string  
 habitat string  
}  

func (l *Lion) Speak() string {  
 return fmt.Sprintf("%s the Lion says Roar!", l.name)  
}  

func (l *Lion) Live() string {  
 return fmt.Sprintf("%s lives in the %s.", l.name, l.habitat)  
}  

// Eagle є конкретною дикою твариною  
type Eagle struct {  
 name string  
 habitat string  
}  

func (e *Eagle) Speak() string {  
 return fmt.Sprintf("%s the Eagle says Screech!", e.name)  
}  

func (e *Eagle) Live() string {  
 return fmt.Sprintf("%s lives in the %s.", e.name, e.habitat)  
}  

// Bear є конкретною дикою твариною  
type Bear struct {  
 name string  
 habitat string  
}  

func (b *Bear) Speak() string {  
 return fmt.Sprintf("%s the Bear says Growl!", b.name)  
}  

func (b *Bear) Live() string {  
 return fmt.Sprintf("%s lives in the %s.", b.name, b.habitat)  
}  

func main() {  
 // Створення лева на савані  
 lionFactory := &LionFactory{}  
 lion := lionFactory.CreateAnimal()  
 fmt.Println(lion.Speak()) // Вивід: Simba the Lion says Roar!  
 fmt.Println(lion.Live()) // Вивід: Simba lives in the Savanna.  

 // Створення орла в горах  
 eagleFactory := &EagleFactory{}  
 eagle := eagleFactory.CreateAnimal()  
 fmt.Println(eagle.Speak()) // Вивід: Eddy the Eagle says Screech!  
 fmt.Println(eagle.Live()) // Вивід: Eddy lives in the Mountains.  

 // Створення ведмедя в лісах  
 bearFactory := &BearFactory{}  
 bear := bearFactory.CreateAnimal()  
 fmt.Println(bear.Speak()) // Вивід: Baloo the Bear says Growl!  
 fmt.Println(bear.Live()) // Вивід: Baloo lives in the Woods.  
}

Як це працює

1. Інтерфейс Animal:

  • Описує загальну поведінку всіх диких тварин (наприклад, методи Speak і Live).

2. Конкретні тварини:

  • Lion, Eagle та Bear — це конкретні реалізації інтерфейсу Animal.
  • Кожна тварина має атрибути name і habitat, що описують, де вона живе.

3. Інтерфейс AnimalFactory:

  • Визначає метод фабрики CreateAnimal, який всі конкретні фабрики повинні реалізувати.

4. Конкретні фабрики:

  • LionFactory, EagleFactory та BearFactory відповідають за створення конкретних типів тварин у їх природних середовищах.
  • Кожна фабрика реалізує метод CreateAnimal, щоб повернути екземпляр відповідної тварини.

5. Код клієнта:

  • Функція main демонструє, як використовувати метод фабрики для створення диких тварин у їх природних середовищах без знання їх конкретних типів.

Шаблон Factory Method спрощує створення об'єктів, інкапсулюючи процес інстанціації, що дозволяє основному коду не мати справи з безпосереднім створенням об'єктів.
Нижче ми розглянемо основні переваги цього шаблону та чому він широко використовується в різних сценаріях.

  • Інкапсуляція: Основний код не повинен знати, як конкретно створюються об'єкти, такі як тварини. Ця абстракція допомагає підтримувати чистоту та зручність коду.
  • Гнучкість: Додавати нові типи, такі як жирафи або тигри, дуже просто — потрібно лише створити нову фабрику без змін в існуючому коді. Це робить систему легким для розширення.

Перекладено з: From Singleton to Factory: A Deep Dive into Creational Design Patterns