Как писать Go-пакеты

Go-пакет состоит из файлов Go, которые находятся в одном и том же каталоге и имеют одинаковый оператор пакета в начале. Из пакетов можно включать  дополнительные функции, чтобы сделать программы более сложными. Некоторые пакеты доступны в стандартной библиотеке Go и поэтому устанавливаются вместе с экземпляром Go. Другие пакеты можно установить с помощью команды go get. Вы также можете создавать свои собственные Go-пакеты: для этого нужно создать файлы Go, используя определенный оператор пакета, в том каталоге, внутри которого вы хотите распределить код.

Этот мануал научит вас писать свои пакеты Go и использовать их в других программных файлах.

Требования

1: Написание и импорт пакетов

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

Прежде чем создать новый пакет, нужно перейти в среду разработки Go. Обычно она находится в gopath. К примеру, в этом мануале мы назовем пакет greet. Для этого мы создали в gopath, в среде проекта каталог greet. Например, если бы организация называлась gopherguides, и мы хотели бы создать пакет greet в организации, используя Github в качестве репозитория кода, то путь бы выглядел так:

└── $GOPATH
.    └── src
.        └── github.com
.            └── gopherguides

Каталог greet находится в каталоге gopherguides:

└── $GOPATH
.   └── src
.       └── github.com
.           └── gopherguides
.               └── greet

После этого можно добавить в каталог первый файл. По традиции первичный файл или файл точки входа должен называться так же, как каталог. В этом случае мы создаем файл greet.go внутри каталога greet:

└── $GOPATH
.   └── src
.       └── github.com
.           └── gopherguides
.               └── greet
.                   └── greet.go

Создав данный файл, мы можем начать писать код, который будем повторно использовать внутри этой программы или в других проектах. В этом случае мы создадим функцию Hello, которая выводит на экран фразу «Hello World».

Откройте файл greet.go в текстовом редакторе и добавьте следующий код:

package greet
import "fmt"
func Hello() {
fmt.Println("Hello, World!")
}

Давайте рассмотрим этот файл подробнее. В первой строке каждого файла нужно указать имя пакета, с которым вы работаете. Для этого мы используем ключевое слово package, за которым следует имя пакета:

package greet

Так компилятор будет обрабатывать все в этом файле как часть пакета greet.

Затем через оператор import объявляются любые пакеты, которые вам нужно использовать. В этом файле мы используем только один пакет, fmt:

import "fmt"

В конце мы создали функцию Hello. Она с помощью пакета fmt выводит фразу «Hello, World!»:

func Hello() {
fmt.Println("Hello, World!")
}

Теперь, когда у вас есть пакет greet, вы можете использовать его в любом другом созданном вами пакете. Давайте создадим новый пакет и попробуем использовать в нем свой пакет greet.

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

└── $GOPATH
└── src
└── github.com
└── gopherguides
└── example

Теперь, когда у вас есть каталог для нового пакета, вы можете создать файл точки входа. Поскольку это будет исполняемая программа, рекомендуем назвать файл точки входа main.go:

└── $GOPATH
.    └── src
.        └── github.com
.            └── gopherguides
.                └── example
.                    └── main.go

В текстовом редакторе откройте файл main.go и добавьте в него следующий код для вызова пакета greet:

package main
import "github.com/gopherguides/greet"
func main() {
greet.Hello()
}

Поскольку вы импортируете пакет, вам нужно вызвать функцию, сославшись на имя пакета в точечной нотации. Точечная нотация – это способ записи, по которому между именем используемого вами пакета и ресурсом из этого пакета, который вы хотите использовать, ставится точка. Например, в вашем пакете greet у вас есть функция Hello – ее можно использовать в качестве ресурса. Если вы хотите вызвать этот ресурс, используйте точечную нотацию greet.Hello().

Теперь вы можете открыть свой терминал и запустить программу из командной строки:

go run main.go

Когда вы это сделаете, вы получите следующий вывод:

Hello, World!

Чтобы посмотреть, как можно использовать переменные в пакете, давайте добавим определение переменной в файл greet.go:

package greet
import "fmt"
var Shark = "Shark"
func Hello() {
fmt.Println("Hello, World!")
}

Затем откройте ваш файл main.go и добавьте следующую выделенную строку для вызова переменной из greet.go в функции fmt.Println():

package main
import (
"fmt"
"github.com/gopherguides/greet"
)
func main() {
greet.Hello()
fmt.Println(greet.Shark)
}

Запустите программу еще раз:

go run main.go

И вы увидите такой ответ:

Hello, World!
Shark

Также мы должны определить тип в файле greet.go. Пусть это будет тип Octopus с полями name и color и с функцией, которая будет выводить эти поля при вызове:

package greet
import "fmt"
var Shark = "Shark"
type Octopus struct {
Name  string
Color string
}
func (o Octopus) String() string {
return fmt.Sprintf("The octopus's name is %q and is the color %s.", o.Name, o.Color)
}
func Hello() {
fmt.Println("Hello, World!")
}

Откройте main.go, чтобы создать экземпляр этого типа в конце файла:

package main
import (
"fmt"
"github.com/gopherguides/greet"
)
func main() {
greet.Hello()
fmt.Println(greet.Shark)
oct := greet.Octopus{
Name:  "Jesse",
Color: "orange",
}
fmt.Println(oct.String())
}

Создав экземпляр типа Octopus с помощью oct := greet.Octopus, вы можете получить доступ к функциям и полям этого типа в пространстве имен файла main.go. Это позволяет написать oct.String() в последней строке, не вызывая greet. Вы также можете, например, вызвать одно из полей типа, например oct.Color, не ссылаясь при этом на имя пакета greet.

Метод String типа Octopus использует функцию fmt.Sprintf для создания предложения и возвращает результат (строку) вызывающей стороне (в данном случае вашей основной программе).

Когда вы запустите программу, вы получите следующий вывод:

go run main.go
Hello, World!
Shark
The octopus's name is "Jesse" and is the color orange.

Создав метод String в Octopus, вы теперь можете повторно использовать данные о вашем пользовательском типе. Если в будущем вы захотите изменить поведение этого метода, вам нужно только отредактировать этот метод.

2: Экспортированный код

Возможно, вы заметили, что все объявления в файле greet.go, который вы вызвали, были с заглавной буквы. В Go нет концепции модификаторов public, private и protected, как в других языках. Внешняя видимость управляется заглавными буквами. Типы, переменные, функции и т. д., начинающиеся с заглавной буквы, доступны вне текущего пакета публично. Символ, который отображается за пределами своего пакета, считается экспортированным.

Если вы добавите в Octopus новый метод reset, вы можете вызвать его из пакета greet, но не из файла main.go, который находится вне пакета greet:

package greet
import "fmt"
var Shark = "Shark"
type Octopus struct {
Name  string
Color string
}
func (o Octopus) String() string {
return fmt.Sprintf("The octopus's name is %q and is the color %s.", o.Name, o.Color)
}
func (o *Octopus) reset() {
o.Name = ""
o.Color = ""
}
func Hello() {
fmt.Println("Hello, World!")
}

Если вы попробуете вызвать reset из файла main.go:

package main
import (
"fmt"
"github.com/gopherguides/greet"
)
func main() {
greet.Hello()
fmt.Println(greet.Shark)
oct := greet.Octopus{
Name:  "Jesse",
Color: "orange",
}
fmt.Println(oct.String())
oct.reset()
}

Вы получите следующую ошибку компиляции:

oct.reset undefined (cannot refer to unexported field or method greet.Octopus.reset)

Чтобы экспортировать функцию reset из Octopus, используйте заглавную букву R в reset:

package greet
import "fmt"
var Shark = "Shark"
type Octopus struct {
Name  string
Color string
}
func (o Octopus) String() string {
return fmt.Sprintf("The octopus's name is %q and is the color %s.", o.Name, o.Color)
}
func (o *Octopus) Reset() {
o.Name = ""
o.Color = ""
}
func Hello() {
fmt.Println("Hello, World!")
}

В результате вы можете вызвать Reset из другого пакета и не получить ошибку:

package main
import (
"fmt"
"github.com/gopherguides/greet"
)
func main() {
greet.Hello()
fmt.Println(greet.Shark)
oct := greet.Octopus{
Name:  "Jesse",
Color: "orange",
}
fmt.Println(oct.String())
oct.Reset()
fmt.Println(oct.String())
}

Если вы запустите программу:

go run main.go

Вы получите такой результат:

Hello, World!
Shark
The octopus's name is "Jesse" and is the color orange
The octopus's name is "" and is the color .

Вызвав Reset, вы очистили всю информацию в полях Name и Color . Когда вы вызываете метод String, он ничего не отображает там, где обычно появляются Name и Color, потому что эти поля теперь пусты.

Заключение

Go-пакеты пишутся так же, как и любые другие файлы Go. Размещение Go-пакета в другом каталоге позволяет изолировать код для повторного использования в другом месте. В этом мануале вы научились писать определения в пакетах, использовать эти определения в другом программном файле Go и хранить пакеты так, чтобы получить к нему доступ.

Tags: ,