Factory Method (Фабричный метод)
Проблема
Вам нужно создавать объекты, не привязываясь к конкретным типам. Клиентский код не должен знать, какой именно тип будет создан -- он работает через интерфейс.
В классических ООП-языках это решается через наследование и виртуальные методы. В Go всё гораздо проще -- у нас есть интерфейсы и функции-конструкторы.
Диаграмма паттерна
+-------------------+
| Storage | <-- interface
+-------------------+
| Save(data []byte) |
| Load(key string) |
+-------------------+
^
+--------------+--------------+
| | |
+-------+------+ +----+-------+ +----+-------+
| FileStorage | | S3Storage | | MemStorage |
+--------------+ +------------+ +------------+
NewStorage(typ string) Storage <-- factory function
Реализация: Функция-конструктор NewXxx
Самый распространённый паттерн в Go -- функция NewXxx, которая возвращает интерфейс или конкретный тип.
package storage
import (
"fmt"
"io"
"os"
"sync"
)
// Storage defines the contract for data persistence.
type Storage interface {
Save(key string, data []byte) error
Load(key string) ([]byte, error)
Delete(key string) error
}
// --- File storage implementation ---
type fileStorage struct {
basePath string
}
func newFileStorage(basePath string) *fileStorage {
return &fileStorage{basePath: basePath}
}
func (fs *fileStorage) Save(key string, data []byte) error {
path := fs.basePath + "/" + key
return os.WriteFile(path, data, 0644)
}
func (fs *fileStorage) Load(key string) ([]byte, error) {
path := fs.basePath + "/" + key
return os.ReadFile(path)
}
func (fs *fileStorage) Delete(key string) error {
path := fs.basePath + "/" + key
return os.Remove(path)
}
// --- In-memory storage implementation ---
type memStorage struct {
mu sync.RWMutex
data map[string][]byte
}
func newMemStorage() *memStorage {
return &memStorage{
data: make(map[string][]byte),
}
}
func (ms *memStorage) Save(key string, data []byte) error {
ms.mu.Lock()
defer ms.mu.Unlock()
ms.data[key] = append([]byte(nil), data...) // defensive copy
return nil
}
func (ms *memStorage) Load(key string) ([]byte, error) {
ms.mu.RLock()
defer ms.mu.RUnlock()
d, ok := ms.data[key]
if !ok {
return nil, fmt.Errorf("key %q not found", key)
}
return append([]byte(nil), d...), nil // defensive copy
}
func (ms *memStorage) Delete(key string) error {
ms.mu.Lock()
defer ms.mu.Unlock()
delete(ms.data, key)
return nil
}
Фабричная функция
// NewStorage creates a Storage based on the given type.
// This is the factory method -- the client doesn't know
// which concrete type is returned.
func NewStorage(typ string, opts ...string) (Storage, error) {
switch typ {
case "file":
if len(opts) == 0 {
return nil, fmt.Errorf("file storage requires base path")
}
return newFileStorage(opts[0]), nil
case "memory":
return newMemStorage(), nil
default:
return nil, fmt.Errorf("unknown storage type: %q", typ)
}
}
Реализация: Фабрика через реестр
Более гибкий подход -- реестр фабрик. Позволяет регистрировать новые типы без изменения фабричной функции.
package storage
import (
"fmt"
"sync"
)
// Factory is a function that creates a Storage instance.
type Factory func(opts map[string]string) (Storage, error)
// Registry holds registered storage factories.
type Registry struct {
mu sync.RWMutex
factories map[string]Factory
}
// NewRegistry creates an empty factory registry.
func NewRegistry() *Registry {
return &Registry{
factories: make(map[string]Factory),
}
}
// Register adds a factory for the given storage type.
func (r *Registry) Register(name string, f Factory) {
r.mu.Lock()
defer r.mu.Unlock()
r.factories[name] = f
}
// Create instantiates a Storage by name.
func (r *Registry) Create(name string, opts map[string]string) (Storage, error) {
r.mu.RLock()
f, ok := r.factories[name]
r.mu.RUnlock()
if !ok {
return nil, fmt.Errorf("unknown storage type: %q", name)
}
return f(opts)
}
Использование реестра:
func main() {
reg := storage.NewRegistry()
// Register factories
reg.Register("file", func(opts map[string]string) (storage.Storage, error) {
path, ok := opts["path"]
if !ok {
return nil, fmt.Errorf("file storage: missing path option")
}
return storage.NewFileStorage(path), nil
})
reg.Register("memory", func(opts map[string]string) (storage.Storage, error) {
return storage.NewMemStorage(), nil
})
// Create storage without knowing the concrete type
s, err := reg.Create("memory", nil)
if err != nil {
log.Fatal(err)
}
_ = s.Save("greeting", []byte("Hello, Go!"))
}
Go vs Java: сравнение фабрик
| Аспект | Java | Go |
|---|---|---|
| Механизм | Абстрактный класс + наследование | Интерфейс + функция |
| Сложность | Высокая (abstract class, extends) | Низкая (interface + NewXxx) |
| Гибкость | Через наследование | Через композицию |
| Типичный вид | StorageFactory.create() |
NewStorage() или storage.New() |
| Реестр | Map<String, Factory> |
map[string]Factory |
В Go нет необходимости в абстрактных классах -- интерфейсы и функции первого класса решают задачу проще и чище.
Когда использовать
Используйте, когда:
- Нужно создавать объекты без привязки к конкретному типу
- Тип объекта определяется конфигурацией или параметром
- Нужен реестр для расширения через плагины
- Тестирование требует подмены реализаций
Не используйте, когда:
- Есть только одна реализация (просто используйте
New()) - Тип известен на этапе компиляции
- Создание объекта тривиально
Реальные примеры в стандартной библиотеке
// sql.Open -- фабрика для database/sql
db, err := sql.Open("postgres", connStr)
// image.Decode -- фабрика для декодеров изображений
img, format, err := image.Decode(reader)
// crypto.Hash -- реестр хеш-функций
h := crypto.SHA256.New()
Сравнение с похожими паттернами
| Паттерн | Отличие |
|---|---|
| Abstract Factory | Создаёт семейства связанных объектов |
| Builder | Пошаговое создание сложных объектов |
| Prototype | Клонирование существующего объекта |