Введение
предыдущий постЕжедневно копайтесь в библиотекеПредставлена платформа внедрения зависимостей с открытым исходным кодом Uber.dig
. Прочитав эту статью,@overtalkРекомендуется открытый исходный код Googlewire
инструмент. Поэтому сегодняшняя статья здесь, спасибо за рекомендацию 👍
wire
Это инструмент внедрения зависимостей с открытым исходным кодом от Google. Это генератор кода, а не фреймворк. Нам просто нужен специальныйgo
документ говоритwire
Зависимости между типами, он автоматически сгенерирует для нас код, поможет нам создать объекты указанного типа и собрать его зависимости.
быстрый в использовании
Сначала установите инструменты:
$ go get github.com/google/wire/cmd/wire
Вышеупомянутая команда будет в$GOPATH/bin
создать исполняемую программу вwire
, который является генератором кода. Я привык к$GOPATH/bin
Добавьте переменные системной среды$PATH
, так что его можно запустить прямо из командной строкиwire
Заказ.
Давайте посмотрим, как это использовать на примереwire
.
Теперь мы приходим в темный мир со злым монстром. Мы представляем его следующей структурой при написании метода create:
type Monster struct {
Name string
}
func NewMonster() Monster {
return Monster{Name: "kitty"}
}
Если есть монстры, должны быть и воины.Структура такая, а так же имеет метод создания:
type Player struct {
Name string
}
func NewPlayer(name string) Player {
return Player{Name: name}
}
Наконец однажды воин выполнил свою миссию и победил монстра:
type Mission struct {
Player Player
Monster Monster
}
func NewMission(p Player, m Monster) Mission {
return Mission{p, m}
}
func (m Mission) Start() {
fmt.Printf("%s defeats %s, world peace!\n", m.Player.Name, m.Monster.Name)
}
Это может быть сцена в игре, давайте посмотрим, как собрать приведенную выше структуру и поместить ее в приложение:
func main() {
monster := NewMonster()
player := NewPlayer("dj")
mission := NewMission(player, monster)
mission.Start()
}
Когда объем кода невелик, а структура несложна, приведенная выше реализация действительно не представляет проблемы. Но когда проект достаточно большой и отношения между структурами становятся очень сложными, такой способ ручного создания каждой зависимости и последующей их сборки становится чрезвычайно громоздким и подверженным ошибкам. На этот раз воиныwire
Появившийся!
wire
Требования очень простые, создайте новыйwire.go
файл (имя файла может быть произвольным), создадим нашу функцию инициализации. Например, мы хотим создать и инициализироватьMission
объект, мы можем сделать это:
//+build wireinject
package main
import "github.com/google/wire"
func InitMission(name string) Mission {
wire.Build(NewMonster, NewPlayer, NewMission)
return Mission{}
}
Прежде всего, возвращаемое значение этой функции — это тип объекта, который нам нужно создать,wire
Просто нужно знать тип,return
Неважно, что вы вернетесь после. Затем в функции мы вызываемwire.Build()
будет создаватьMission
В него передается конструктор типа, от которого он зависит. Например, вам нужно позвонитьNewMission()
СоздайтеMission
тип,NewMission()
принимает два параметра одинMonster
наберите "АPlayer
тип.Monster
Тип объект должен быть вызванNewMonster()
Создайте,Player
Тип объект должен быть вызванNewPlayer()
Создайте. такNewMonster()
иNewPlayer()
Нам тоже нужно пройтиwire
.
После того, как файл будет записан, выполнитеwire
Заказ:
$ wire
wire: github.com/darjun/go-daily-lib/wire/get-started/after: \
wrote D:\code\golang\src\github.com\darjun\go-daily-lib\wire\get-started\after\wire_gen.go
Смотрим на сгенерированныйwire_gen.go
документ:
// Code generated by Wire. DO NOT EDIT.
//go:generate wire
//+build !wireinject
package main
// Injectors from wire.go:
func InitMission(name string) Mission {
player := NewPlayer(name)
monster := NewMonster()
mission := NewMission(player, monster)
return mission
}
этоInitMission()
функция не с намиmain.go
Код написан таким же, как волосы! Далее мы можем напрямуюmain.go
перечислитьInitMission()
:
func main() {
mission := InitMission("dj")
mission.Start()
}
Осторожная детская обувь, возможно, нашла,wire.go
иwire_gen.go
Позиция заголовка файла имеет+build
, но один, за которым следуетwireinject
, другой!wireinject
.+build
По сути, это особенность языка Go. Условная компиляция, аналогичная C/C++, при выполненииgo build
Вы можете передать некоторые параметры, в соответствии с этим параметром, чтобы решить, следует ли компилировать определенные файлы.wire
Инструмент работает только сwireinject
файл, поэтому нашwire.go
файл, чтобы добавить это. Сгенерированоwire_gen.go
для нас использовать,wire
Никакой обработки не требуется, поэтому есть!wireinject
.
Так как теперь есть два файла, мы не можем использоватьgo run main.go
Для запуска программы вы можете использоватьgo run .
бегать. Результат точно такой же, как и в предыдущем примере!
Обратите внимание, что если вы бежите,InitMission
переопределить, а затем проверить//+build wireinject
иpackage main
Если между этими двумя строками есть пустая строка, эта пустая строка должна присутствовать! ВидетьGitHub.com/Google/wire…. Тот, кого завербовали, молча ударил 1 в моем сердце, хорошо?
Базовые концепты
wire
Есть два основных понятия,Provider
(конструктор) иInjector
(инжектор).Provider
По сути, это создать функцию, это всем понятно. над намиInitMission
этоInjector
. Каждый инжектор на самом деле является функцией создания и инициализации объекта. В этой функции нам просто нужно сказатьwire
какой тип объекта создать, этот тип зависимости,wire
Инструмент сгенерирует для нас функцию для завершения создания и инициализации объекта.
параметр
Столь же внимательный, вы должны были обнаружить, что мы написали вышеInitMission()
функция сstring
тип параметра. и генерируетсяInitMission()
В функции этот параметр передаетсяNewPlayer()
.NewPlayer()
необходимостьstring
параметр типа, а тип параметраstring
. так сгенерированоInitMission()
функция, этот параметр передается вNewPlayer()
. если мы позволимNewMonster()
также принятьstring
А параметры?
func NewMonster(name string) Monster {
return Monster{Name: name}
}
затем генерируетсяInitMission()
в функцииNewPlayer()
иNewMonster()
получит этот параметр:
func InitMission(name string) Mission {
player := NewPlayer(name)
monster := NewMonster(name)
mission := NewMission(player, monster)
return mission
}
По факту,wire
При генерации кода параметры (или зависимости), требуемые конструктором, ищутся в параметрах или генерируются другими конструкторами. Выбор параметра или конструктора полностью зависит от типа. Если параметры или объекты, сгенерированные конструктором, имеют одинаковый тип, запуститеwire
Инструмент сообщит об ошибке. Если мы хотим настроить поведение создания, нам нужно создать разные структуры параметров для разных типов:
type PlayerParam string
type MonsterParam string
func NewPlayer(name PlayerParam) Player {
return Player{Name: string(name)}
}
func NewMonster(name MonsterParam) Monster {
return Monster{Name: string(name)}
}
func main() {
mission := InitMission("dj", "kitty")
mission.Start()
}
// wire.go
func InitMission(p PlayerParam, m MonsterParam) Mission {
wire.Build(NewPlayer, NewMonster, NewMission)
return Mission{}
}
Сгенерированный код выглядит следующим образом:
func InitMission(m MonsterParam, p PlayerParam) Mission {
player := NewPlayer(p)
monster := NewMonster(m)
mission := NewMission(player, monster)
return mission
}
Когда параметры более сложные, рекомендуется помещать параметры в структуру.
Ошибка
Не все строительные работы могут быть успешными, возможно, воины погибнут от рук злодеев, прежде чем покинут гору:
func NewPlayer(name string) (Player, error) {
if time.Now().Unix()%2 == 0 {
return Player{}, errors.New("player dead")
}
return Player{Name: name}, nil
}
мы создаемслучайный сбой, модифицировать форсункуInitMission()
подпись, добавлениеerror
возвращаемое значение:
func InitMission(name string) (Mission, error) {
wire.Build(NewMonster, NewPlayer, NewMission)
return Mission{}, nil
}
Сгенерированный код будетNewPlayer()
возвращенная ошибка, какInitMission()
Возвращаемое значение:
func InitMission(name string) (Mission, error) {
player, err := NewPlayer(name)
if err != nil {
return Mission{}, err
}
monster := NewMonster()
mission := NewMission(player, monster)
return mission, nil
}
wire
следитьfail-fastПринцип, что ошибки должны быть обработаны. Если наш инжектор не возвращает ошибку, но конструктор возвращает ошибку,wire
Инструмент сообщит об ошибке!
Расширенные возможности
Ниже приводится краткое введениеwire
расширенные возможности.
ProviderSet
Иногда несколько типов могут иметь одни и те же зависимости, и мы передаем один и тот же конструктор вwire.Build()
Не только громоздкая, но и сложная в обслуживании, зависимость модифицируется, все входящиеwire.Build()
место для модификации. к этому концу,wire
предоставилProviderSet
(коллекция конструкторов), вы можете упаковать несколько конструкторов в коллекцию, и вам нужно будет использовать эту коллекцию позже. Предположим, наша история о воинах и монстрах имеет две концовки:
type EndingA struct {
Player Player
Monster Monster
}
func NewEndingA(p Player, m Monster) EndingA {
return EndingA{p, m}
}
func (p EndingA) Appear() {
fmt.Printf("%s defeats %s, world peace!\n", p.Player.Name, p.Monster.Name)
}
type EndingB struct {
Player Player
Monster Monster
}
func NewEndingB(p Player, m Monster) EndingB {
return EndingB{p, m}
}
func (p EndingB) Appear() {
fmt.Printf("%s defeats %s, but become monster, world darker!\n", p.Player.Name, p.Monster.Name)
}
Напишите два инжектора:
func InitEndingA(name string) EndingA {
wire.Build(NewMonster, NewPlayer, NewEndingA)
return EndingA{}
}
func InitEndingB(name string) EndingB {
wire.Build(NewMonster, NewPlayer, NewEndingB)
return EndingB{}
}
Наблюдаем два звонкаwire.Build()
нужно пройти вNewMonster
иNewPlayer
. Два — это нормально, если их слишком много, писать будет проблематично, а изменить — непросто. В этом случае мы можем сначала определитьProviderSet
:
var monsterPlayerSet = wire.NewSet(NewMonster, NewPlayer)
Используйте это напрямуюset
:
func InitEndingA(name string) EndingA {
wire.Build(monsterPlayerSet, NewEndingA)
return EndingA{}
}
func InitEndingB(name string) EndingB {
wire.Build(monsterPlayerSet, NewEndingB)
return EndingB{}
}
Затем, если вы хотите добавить или удалить конструктор, измените его напрямую.set
можно определить.
конструктор конструкций
потому что нашEndingA
иEndingB
Поля толькоPlayer
иMonster
, нам не нужно явно указывать для них конструктор, мы можем использовать его напрямуюwire
который предоставилконструктор конструкций(Поставщик структуры). Конструктор структуры создает структуру некоторого типа, а затем заполняет ее поля параметрами или вызывает другие конструкторы. Например, в приведенном выше примере мы удаляемNewEndingA()
иNewEndingB()
, а затем предоставить для них конструкторы структур:
var monsterPlayerSet = wire.NewSet(NewMonster, NewPlayer)
var endingASet = wire.NewSet(monsterPlayerSet, wire.Struct(new(EndingA), "Player", "Monster"))
var endingBSet = wire.NewSet(monsterPlayerSet, wire.Struct(new(EndingB), "Player", "Monster"))
func InitEndingA(name string) EndingA {
wire.Build(endingASet)
return EndingA{}
}
func InitEndingB(name string) EndingB {
wire.Build(endingBSet)
return EndingB{}
}
Использование конструктора структурыwire.Struct
инъекции, первый параметр фиксируется какnew(结构名)
, за которым следует любое количество параметров, указывающих, в какие поля структуры необходимо ввести значения. Сверху нам нужно ввестиPlayer
иMonster
два поля. Или мы также можем использовать подстановочные знаки*
Указывает на вставку всех полей:
var endingASet = wire.NewSet(monsterPlayerSet, wire.Struct(new(EndingA), "*"))
var endingBSet = wire.NewSet(monsterPlayerSet, wire.Struct(new(EndingB), "*"))
wire
Генерирует правильный код для нас, и это здорово:
func InitEndingA(name string) EndingA {
player := NewPlayer(name)
monster := NewMonster()
endingA := EndingA{
Player: player,
Monster: monster,
}
return endingA
}
значение привязки
Иногда нам нужно привязать значение к типу, не полагаясь на конструктор для создания нового значения каждый раз. Некоторые типы по своей сути являются синглтонами, например конфигурация, объекты базы данных (sql.DB
). В этот момент мы можем использоватьwire.Value
привязать значение, использоватьwire.InterfaceValue
Привязать интерфейс. Например, наш монстр всегда былKitty
, нам не нужно создавать его каждый раз, можно напрямую привязать это значение:
var kitty = Monster{Name: "kitty"}
func InitEndingA(name string) EndingA {
wire.Build(NewPlayer, wire.Value(kitty), NewEndingA)
return EndingA{}
}
func InitEndingB(name string) EndingB {
wire.Build(NewPlayer, wire.Value(kitty), NewEndingB)
return EndingB{}
}
Обратите внимание, что это значение будет копироваться каждый раз, когда оно используется, вам нужно убедиться, что копия не имеет побочных эффектов:
// wire_gen.go
func InitEndingA(name string) EndingA {
player := NewPlayer(name)
monster := _wireMonsterValue
endingA := NewEndingA(player, monster)
return endingA
}
var (
_wireMonsterValue = kitty
)
Структурные поля как конструкторы
Иногда мы пишем конструктор, который просто возвращает поле структуры, тогда мы можем использоватьwire.FieldsOf
Упрощенная операция. Теперь мы непосредственно создаемMission
структуру, если вы хотите получитьMonster
иPlayer
тип объекта, вы можетеMission
использоватьwire.FieldsOf
:
func NewMission() Mission {
p := Player{Name: "dj"}
m := Monster{Name: "kitty"}
return Mission{p, m}
}
// wire.go
func InitPlayer() Player {
wire.Build(NewMission, wire.FieldsOf(new(Mission), "Player"))
}
func InitMonster() Monster {
wire.Build(NewMission, wire.FieldsOf(new(Mission), "Monster"))
}
// main.go
func main() {
p := InitPlayer()
fmt.Println(p.Name)
}
Аналогично, первый параметрnew(结构名)
, за которым следует несколько параметров, указывающих, какие поля использовать в качестве конструкторов,*
значит все.
функция очистки
Конструктор может предоставить функцию очистки. Если последующий конструктор не вернет значение, будет вызвана функция очистки, возвращенная предыдущим конструктором:
func NewPlayer(name string) (Player, func(), error) {
cleanup := func() {
fmt.Println("cleanup!")
}
if time.Now().Unix()%2 == 0 {
return Player{}, cleanup, errors.New("player dead")
}
return Player{Name: name}, cleanup, nil
}
func main() {
mission, cleanup, err := InitMission("dj")
if err != nil {
log.Fatal(err)
}
mission.Start()
cleanup()
}
// wire.go
func InitMission(name string) (Mission, func(), error) {
wire.Build(NewMonster, NewPlayer, NewMission)
return Mission{}, nil, nil
}
некоторые детали
Во-первых, мы звонимwire
генерироватьwire_gen.go
После этого, еслиwire.go
Файл был изменен, просто нужно выполнитьgo generate
Вот и все.go generate
Очень удобно, пока не написал статьюgenerate
, можете посмотреть, если интересноГлубокое понимание генерации Go.
Суммировать
wire
это сgo-cloud
примерguestbook
Опубликовано вместе, вы можете прочитатьguestbook
посмотреть, как это используетсяwire
из. иdig
разные,wire
просто сгенерируйте код, не используйтеreflect
Библиотека, производительность не о чем беспокоиться. Потому что код, который он генерирует, в основном такой же, как и тот, который вы написали сами. Если сгенерированный код имеет проблемы с производительностью, велика вероятность, что вы напишете его сами.
Если вы найдете забавную и простую в использовании языковую библиотеку Go, вы можете отправить сообщение о проблеме в ежедневной библиотеке Go GitHub😄
Ссылаться на
- провод GitHub:github.com/google/wire
- Перейти на ежедневный репозиторий GitHub:GitHub.com/Darenjun/go-of…
я
Добро пожаловать, чтобы обратить внимание на мою общедоступную учетную запись WeChat [GoUpUp], учитесь вместе и добивайтесь прогресса вместе ~
Эта статья опубликована в блогеOpenWriteвыпуск!