Как написать пакеты в Go

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

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

Предпосылки

  • Настройте среду программирования Go, следуя одному из руководств из серииHow To Install and Set Up a Local Programming Environment for Go. Создайте свое рабочее пространство Go, выполнив шаг 5 в учебном руководстве по локальной среде программирования Чтобы следовать примеру и соглашениям об именах в этой статье, прочитайте первый раздел Написание и импорт пакетов.

  • Чтобы углубить свои знания о GOPATH, прочтите нашу статьюUnderstanding the GOPATH.

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

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

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

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

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

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

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

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

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

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

greet.go

package greet

import "fmt"

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

Давайте разберем этот первый файл. В первой строке каждого файла необходимо указать имяpackage, с которым вы работаете. Поскольку вы находитесь в пакетеgreet, вы используете ключевое слово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:

main.go

package main

import "github.com/gopherguides/greet"

func main() {
    greet.Hello()
}

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

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

go run main.go

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

OutputHello, World!

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

greet.go

package greet

import "fmt"

var Shark = "Sammy"

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

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

main.go

package main

import (
    "fmt"

    "github.com/gopherguides/greet"
)

func main() {
    greet.Hello()

    fmt.Println(greet.Shark)
}

После того, как вы снова запустите программу:

go run main.go

Вы получите следующий вывод:

OutputHello, World!
Sammy

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

greet.go

package greet

import "fmt"

var Shark = "Sammy"

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, чтобы создать экземпляр этого типа в конце файла:

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 для создания предложения, аreturns - результат, строку, для вызывающего (в данном случае ваш основной программа).

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

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

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

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

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

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

greet.go

package greet

import "fmt"

var Shark = "Sammy"

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:

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()
}

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

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

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

greet.go

package greet

import "fmt"

var Shark = "Sammy"

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()

    fmt.Println(oct.String())
}

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

go run main.go

Вы получите следующий вывод:

OutputHello, World!
Sammy
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, и объяснялись варианты того, где хранить пакет, чтобы получить к нему доступ.