Освоение шаблона проектирования Factory в Go: практическое руководство

Описание:

Шаблон проектирования Factory — это порождающий шаблон, который предоставляет интерфейс для создания объектов в суперклассе, но позволяет подклассам изменять тип создаваемых объектов. Этот шаблон отделяет процесс создания объектов от фактической реализации этих объектов. Фабричный класс отвечает за создание объектов и делает это путем реализации фабричного метода, который возвращает объект определенного класса.

Фабричный метод может быть реализован несколькими способами, например, с помощью простого оператора switch, который проверяет тип создаваемого объекта, или с помощью карты для хранения конструкторов объектов. Помимо создания объектов, фабричный класс также может управлять жизненным циклом объектов и предоставлять способ их повторного использования.

В Go фабричный шаблон можно реализовать с помощью структуры, имеющей метод, возвращающий новый экземпляр интерфейса. Этот метод может использовать оператор switch или карту для определения типа создаваемого объекта. Фабрика также может использовать sync.Pool для управления жизненным циклом объектов и их повторного использования.

Случаи использования:

  • Инверсия управления: когда вы хотите отделить создание объектов от клиента, который их использует.
  • Абстракция процесса создания: когда вы хотите скрыть сложность создания объектов за интерфейсом.
  • Расширяемость: когда вы хотите разрешить создание объектов, принадлежащих к иерархии классов, но не хотите указывать точный класс объектов, которые будут созданы.

Плюсы:

  • Он предоставляет способ создания объектов без указания точного класса объекта, который будет создан.
  • Это обеспечивает гибкость в процессе создания, упрощая добавление новых типов структур или изменение процесса создания, не затрагивая клиентский код.
  • Это позволяет абстрагироваться от процесса создания, что позволяет легко скрыть сложность создания структур.

Минусы:

  • Это может затруднить понимание кода, поскольку из клиентского кода может быть неясно, какая структура создается.
  • Это может затруднить тестирование, поскольку может быть сложно протестировать клиентский код, не зная точной создаваемой структуры.

Пример:

Представьте, что вы создаете игру, в которой игрок может использовать различные виды оружия, такие как мечи, луки и огнестрельное оружие. Каждое оружие имеет свои уникальные свойства, такие как урон, дальность и время перезарядки.

Чтобы справиться с созданием этого оружия, вы можете создать структуру WeaponFactory, в которой есть метод для создания нового оружия на основе типа оружия, переданного в качестве аргумента.

package factory

import "fmt"

const (
 SwordType = 1
 BowType = 2
 GunType = 3
)

type Weapon interface {
 Use()
}

type Sword struct {
 Damage  int
 Range   int
 Attack  string
}

func (s *Sword) Use() {
 s.Attack = "Slash!"
 fmt.Printf("Sword attacks with %d damage and %d range, %s", s.Damage, s.Range, s.Attack)
}

type Bow struct {
 Damage  int
 Range   int
 Attack  string
}

func (b *Bow) Use() {
 b.Attack = "Twang!"
 fmt.Printf("Bow attacks with %d damage and %d range, %s", b.Damage, b.Range, b.Attack)
}

type Gun struct {
 Damage    int
 Range     int
 ReloadTime int
 Attack    string
}

func (g *Gun) Use() {
 g.Attack = "Bang!"
 fmt.Printf("Gun attacks with %d damage and %d range, reload time is %d sec, %s", g.Damage, g.Range, g.ReloadTime, g.Attack)
}

type WeaponFactory struct{}

func (w *WeaponFactory) CreateWeapon(t int) (Weapon, error) {
 switch t {
 case 1:
  return &Sword{Damage: 10, Range: 1}, nil
 case 2:
  return &Bow{Damage: 7, Range: 10}, nil
 case 3:
  return &Gun{Damage: 15, Range: 20, ReloadTime: 2}, nil
 default:
  return nil, fmt.Errorf("weapon %d not implemented", t)
 }
}

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

Вы также можете использовать карту для хранения конструкторов объектов, это будет выглядеть так:

var weaponCreators = map[int]func() Weapon {
 1: func() Weapon { return &Sword{Damage: 10, Range: 1} },
 2:   func() Weapon { return &Bow{Damage: 7, Range: 10} },
 3: func() Weapon { return &Gun{Damage: 15, Range: 20, ReloadTime: 2} },
}

func (w *WeaponFactory) CreateWeapon(t int) Weapon {
 if f, ok := weaponCreators[t]; ok {
  return f()
 }
 return nil
}

Поехали на испытания!

Тест:

package factory

import (
 "fmt"
 "testing"
)

func TestCreateWeapon(t *testing.T) {
 wf := &WeaponFactory{}

 tests := []struct {
  name     string
  t        int
  wantType string
  wantErr  error
 }{
  {"Sword", SwordType, "*factory.Sword", nil},
  {"Bow", BowType, "*factory.Bow", nil},
  {"Gun", GunType, "*factory.Gun", nil},
  {"Invalid", 128, "nil", fmt.Errorf("weapon %d not implemented", 128)},
 }

 for _, test := range tests {
  t.Run(test.name, func(t *testing.T) {
   got, err := wf.CreateWeapon(test.t)
   if err != nil && err.Error() != test.wantErr.Error() {
    t.Errorf("CreateWeapon() error = %v, wantErr %v", err, test.wantErr)
    return
   }
   if got != nil {
    if gotType := fmt.Sprintf("%T", got); gotType != test.wantType {
     t.Errorf("CreateWeapon() = %v, want %v", gotType, test.wantType)
    }
   } else {
    if test.wantErr == nil {
     t.Errorf("CreateWeapon() = %v, want %v", got, test.wantType)
    }
   }
  })
 }
}

Заключение:

Шаблон проектирования «Фабрика» — это мощный инструмент для создания объектов структурированным и организованным образом. Он позволяет создавать сложные объекты, не раскрывая детали базовой реализации, что упрощает добавление новых типов и изменение существующих. В Go паттерн Factory можно реализовать с помощью структуры, содержащей метод для создания объектов разных типов. Использование интерфейсов позволяет создавать объекты, реализующие одинаковое поведение, но имеющие разные внутренние реализации. Шаблон Factory обычно используется в Go для создания объектов, являющихся частью более крупной системы, таких как объекты, взаимодействующие с базами данных или сетевыми службами. Пример, представленный в этом посте, фабрика оружия, является простым, но мощным примером того, как паттерн Factory можно применить в Go. В целом, шаблон проектирования Factory является обязательным для любого разработчика Go, который хочет создавать надежный, удобный в сопровождении и расширяемый код.