ozzo-di — это контейнер внедрения зависимостей (DI) на языке Go. Он имеет следующие возможности:
- Внедрение зависимостей через конкретные типы, интерфейсы, и функции-провайдеры.
- Внедрение зависимостей для значений параметров функций и полей структур.
- Создание и внедрение новых объектов.
- Иерархические контейнеры DI.
Go 1.2 или выше.
Выполните следующие команды для установки:
go get github.com/go-ozzo/ozzo-di
Следующий фрагмент кода показывает, как можно использовать DI контейнер.
package main
import (
"fmt"
"reflect"
"github.com/go-ozzo/ozzo-di"
)
type Bar interface {
String() string
}
func test(bar Bar) {
fmt.Println(bar.String())
}
type Foo struct {
s string
}
func (f *Foo) String() string {
return f.s
}
type MyBar struct {
Bar `inject`
}
func main() {
// создаем DI контейнер
c := di.NewContainer()
// регистрируем экземпляр Foo как интерфейс типа Bar
c.RegisterAs(&Foo{"hello"}, di.InterfaceOf((*Bar)(nil)))
// &Foo{"hello"} будет внедрено как параметр Bar для test()
c.Call(test)
// Выведет:
// hello
// создаем объект MyBar и внедряем его в поле Bar
bar := c.Make(reflect.TypeOf(&MyBar{})).(Bar)
fmt.Println(bar.String())
// Выведет:
// hello
}
di.Container
— это DI контейнер, полгающийся для определения значений для внедрения на типы. Для того, чтобы это работало, как правило, типы надо предварительно зарегистрировать. di.Container
поддерживает три вида регистрации типа:
c := di.NewContainer()
// 1. регистрация конкретного типа:
// &Foo{"hello"} зарегистрирован в качестве соответствующего конкретного типа (*Foo)
c.Register(&Foo{"hello"})
// 2. регистрация интерфейса:
// &Foo{"hello"} зарегистрирован как интерфейс Bar
c.RegisterAs(&Foo{"hello"}, di.InterfaceOf((*Bar)(nil)))
// конкретный тип (*Foo) зарегистрирован как интерфейс Bar
c.RegisterAs(reflect.TypeOf(&Foo{}), di.InterfaceOf((*Bar)(nil)))
// 3. регистрация провайдера:
// Функция-провайдер зарегистрирована как Bar интерфейс.
// Она будет вызвана при внедрении Bar.
c.RegisterProvider(func(di.Container) interface{} {
return &Foo{"hello"}
}, di.InterfaceOf((*Bar)(nil)), true)
Совет: Чтобы указать тип интерфейса при регистрации, используйте функцию-помощник
di.InterfaceOf((*InterfaceName)(nil))
. Для конкретных типов используйте рефлексиюreflect.TypeOf(TypeName{})
.
di.Container
поддерживает три типа внедрения значений:
// ...продолжение предыдущего примера регистрации...
type Composite struct {
Bar `inject`
}
// 1. внедрение в поле структуры:
// Значения будут внедрены в экспортированное поле структуры с тегом `inject` и анонимные поля.
// Значение &Foo{"hello"} будет внедрено в поле Composite.Bar
composite := &Composite{}
c.Inject(composite)
// 2. внедрение в параметр функции:
// Значениями в соответствии с их типами будут внедрены в параметры функции.
// Здесь &Foo{"hello"} будет внедрено в bar.
func test(bar Bar) {
fmt.Println(bar.String())
}
c.Call(test)
// 3. создание новых экземпляров:
// Новые экземпляры структуры могут создаваться с внедрением их полей.
// Или может быть возвращён экземпляр-синглтон.
foo := c.Make(reflect.TypeOf(&Foo{})).(*Foo) // возвращает синглтон &Foo{"hello"}
bar := c.Make(di.InterfaceOf((*Bar)(nil))).(*Bar) // возвращает синглтон &Foo{"hello"}
// Возвращает новый экземпляр Composite с внедреным в Bar синглтоном &Foo{"hello"}
composite := c.Make(reflect.TypeOf(&Composite{})).(*Composite)
В том случае, если при внедрении зарегистрированного ранее типа значение уже зарегистрировано как этот тип, для внедрения будет использоваться само значение.
Если в качестве типа зарегистрирован провайдер, для внедрения будет использован результат вызова провайдера. Вы можете использовать третий параметр для Container.RegisterProvider()
чтобы указать, будет провайдер вызываться каждый раз, когда необходимо внедрение, или только в первый раз. Во втором случае провайдер будет вызван только один раз и результат вызова будет использван для внедрения соответствующего зарегистрированного типа.
При внедрении значения не зарегистрированного типа T
будет использована следующая стратегия:
- Если
*T
был зарегистрирован, то соответствующее значение будет разыменовано и возвращено; - Если
T
является указателем наP
, будет возващен указатель на внедрёное дляP
значение; - Если тип
T
— это структура, будет создан новый экземпляр и её поля будут внедрены; - Если
T
— это Slice, Map, или Chan, будет создан и инициализирован новый экземпляр; - Во всех остальных случаях будет возвращено нулевое значение.
ozzo-di ссылается на реализацию codegansta/inject.