Abstract Factory (Абстрактная фабрика)
Проблема
Вам нужно создавать семейства связанных объектов, которые должны быть совместимы друг с другом. Например, UI-элементы для разных ОС: кнопка и чекбокс для macOS должны выглядеть в одном стиле, а для Linux -- в другом.
Abstract Factory гарантирует, что клиент получает совместимые объекты, не зная их конкретных типов.
Диаграмма паттерна
+------------------+ +------------------+
| UIFactory | | Button | <-- interface
+------------------+ +------------------+
| CreateButton() | | Render() |
| CreateCheckbox() | +------------------+
| CreateInput() | ^
+------------------+ +----+----+
^ | |
+------+------+ MacBtn LinuxBtn
| |
MacFactory LinuxFactory
+------------------+
| Checkbox | <-- interface
+------------------+
| Render() |
| Toggle() |
+------------------+
^
+----+----+
| |
MacCB LinuxCB
Реализация
Определение интерфейсов продуктов
package ui
// Button represents a clickable UI element.
type Button interface {
Render() string
OnClick(handler func())
}
// Checkbox represents a togglable UI element.
type Checkbox interface {
Render() string
Toggle()
IsChecked() bool
}
// Input represents a text input UI element.
type Input interface {
Render() string
SetValue(v string)
Value() string
}
Определение интерфейса фабрики
// Factory creates a family of related UI components.
type Factory interface {
CreateButton(label string) Button
CreateCheckbox(label string) Checkbox
CreateInput(placeholder string) Input
}
macOS реализация
package ui
import "fmt"
// --- macOS Button ---
type macButton struct {
label string
onClick func()
}
func (b *macButton) Render() string {
return fmt.Sprintf("[macOS Button: %s]", b.label)
}
func (b *macButton) OnClick(handler func()) {
b.onClick = handler
}
// --- macOS Checkbox ---
type macCheckbox struct {
label string
checked bool
}
func (c *macCheckbox) Render() string {
mark := " "
if c.checked {
mark = "x"
}
return fmt.Sprintf("[macOS (%s) %s]", mark, c.label)
}
func (c *macCheckbox) Toggle() {
c.checked = !c.checked
}
func (c *macCheckbox) IsChecked() bool {
return c.checked
}
// --- macOS Input ---
type macInput struct {
placeholder string
value string
}
func (i *macInput) Render() string {
if i.value == "" {
return fmt.Sprintf("[macOS Input: %s]", i.placeholder)
}
return fmt.Sprintf("[macOS Input: %s]", i.value)
}
func (i *macInput) SetValue(v string) { i.value = v }
func (i *macInput) Value() string { return i.value }
// --- macOS Factory ---
type macFactory struct{}
// NewMacFactory creates a UI factory for macOS components.
func NewMacFactory() Factory {
return &macFactory{}
}
func (f *macFactory) CreateButton(label string) Button {
return &macButton{label: label}
}
func (f *macFactory) CreateCheckbox(label string) Checkbox {
return &macCheckbox{label: label}
}
func (f *macFactory) CreateInput(placeholder string) Input {
return &macInput{placeholder: placeholder}
}
Linux реализация
package ui
import "fmt"
type linuxButton struct {
label string
onClick func()
}
func (b *linuxButton) Render() string {
return fmt.Sprintf("< GTK Button: %s >", b.label)
}
func (b *linuxButton) OnClick(handler func()) {
b.onClick = handler
}
type linuxCheckbox struct {
label string
checked bool
}
func (c *linuxCheckbox) Render() string {
mark := " "
if c.checked {
mark = "*"
}
return fmt.Sprintf("[ GTK [%s] %s ]", mark, c.label)
}
func (c *linuxCheckbox) Toggle() { c.checked = !c.checked }
func (c *linuxCheckbox) IsChecked() bool { return c.checked }
type linuxInput struct {
placeholder string
value string
}
func (i *linuxInput) Render() string {
if i.value == "" {
return fmt.Sprintf("| GTK Input: %s |", i.placeholder)
}
return fmt.Sprintf("| GTK Input: %s |", i.value)
}
func (i *linuxInput) SetValue(v string) { i.value = v }
func (i *linuxInput) Value() string { return i.value }
type linuxFactory struct{}
// NewLinuxFactory creates a UI factory for Linux/GTK components.
func NewLinuxFactory() Factory {
return &linuxFactory{}
}
func (f *linuxFactory) CreateButton(label string) Button {
return &linuxButton{label: label}
}
func (f *linuxFactory) CreateCheckbox(label string) Checkbox {
return &linuxCheckbox{label: label}
}
func (f *linuxFactory) CreateInput(placeholder string) Input {
return &linuxInput{placeholder: placeholder}
}
Выбор фабрики по конфигурации
package ui
import (
"fmt"
"runtime"
)
// NewFactory returns a UI factory for the current operating system.
func NewFactory() (Factory, error) {
return NewFactoryForOS(runtime.GOOS)
}
// NewFactoryForOS returns a UI factory for the specified OS.
func NewFactoryForOS(os string) (Factory, error) {
switch os {
case "darwin":
return NewMacFactory(), nil
case "linux":
return NewLinuxFactory(), nil
default:
return nil, fmt.Errorf("unsupported OS: %s", os)
}
}
Клиентский код
func RenderLoginForm(factory ui.Factory) {
// Client works with abstract interfaces only.
// It doesn't know if it's macOS or Linux.
username := factory.CreateInput("Username")
password := factory.CreateInput("Password")
remember := factory.CreateCheckbox("Remember me")
submit := factory.CreateButton("Log In")
fmt.Println(username.Render())
fmt.Println(password.Render())
fmt.Println(remember.Render())
fmt.Println(submit.Render())
}
func main() {
factory, err := ui.NewFactory()
if err != nil {
log.Fatal(err)
}
RenderLoginForm(factory)
}
Реальный пример: драйвер базы данных
package db
import "context"
// Row represents a single database row.
type Row interface {
Scan(dest ...any) error
}
// Rows represents a result set.
type Rows interface {
Next() bool
Scan(dest ...any) error
Close() error
}
// Conn represents a database connection.
type Conn interface {
QueryRow(ctx context.Context, query string, args ...any) Row
Query(ctx context.Context, query string, args ...any) (Rows, error)
Exec(ctx context.Context, query string, args ...any) (int64, error)
Close() error
}
// TxManager represents transaction support.
type TxManager interface {
Begin(ctx context.Context) (Tx, error)
}
// Tx represents a database transaction.
type Tx interface {
Conn
Commit() error
Rollback() error
}
// Factory creates database-specific implementations.
type Factory interface {
NewConn(ctx context.Context, dsn string) (Conn, error)
NewTxManager(conn Conn) TxManager
}
Когда использовать
Используйте, когда:
- Система должна работать с несколькими семействами связанных объектов
- Нужно гарантировать совместимость объектов одного семейства
- Плагинная система с набором связанных компонентов
- Поддержка разных платформ/протоколов/баз данных
Не используйте, когда:
- Есть только одно семейство объектов (достаточно Factory Method)
- Объекты семейства не связаны между собой
- Количество типов продуктов часто меняется (сложно расширять интерфейс фабрики)
Сравнение с похожими паттернами
| Паттерн | Отличие |
|---|---|
| Factory Method | Создаёт один тип объекта; Abstract Factory -- семейство |
| Builder | Пошаговое создание одного сложного объекта |
| Prototype | Клонирование вместо создания с нуля |