Хабрахабр

[Перевод] Как писать Go-пакеты

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

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

Предварительные требования

  • Настройте программное окружение Go (о том, как это сделать, можно узнать здесь). Создайте рабочее пространство Go (этому посвящён пятый пункт вышеупомянутого материала). В следующем разделе этого материала вы сможете найти примеры, которые рекомендуется воспроизвести у себя. Так вы сможете лучше с ними разобраться.
  • Для того чтобы углубить свои знания по GOPATH — взгляните на этот материал.

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

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

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

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

Мы собираемся назвать пакет, который будем разрабатывать в этом руководстве, greet. Для того чтобы это сделать — создадим директорию greet в директории gopherguides. В результате имеющаяся структура папок приобретёт следующий вид:

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

Теперь мы готовы к тому, чтобы добавить в директорию greet первый файл. Обычно файл, который является входной точкой (entry point) пакета, называют так же, как названа директория пакета. В данном случае это означает, что мы, в директории greet, создаём файл greet.go:

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

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

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

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

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

package greet

Это сообщает компилятору о том, что он должен воспринимать всё, что находится в файле, как часть пакета greet.

В данном случае нам нужен всего один пакет — fmt: Далее выполняется импорт необходимых пакетов с помощью выражения import.

import "fmt"

И, наконец, мы создаём функцию Hello. Она будет использовать возможности пакета fmt для вывода на экран строки Hello, World!:

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

Теперь, после того, как создан пакет greet, вы можете воспользоваться им в любом другом пакете. Создадим новый пакет, в котором воспользуемся пакетом 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()
}

Мы импортировали в файле main.go пакет greet, а это значит, что для вызова функции, объявленной в этом пакете, нам понадобится воспользоваться точечной нотацией. Точечная нотация — это конструкция, в которой между именем пакета и именем ресурса этого пакета, который нужно использовать, ставится точка. Например, в пакете greet роль ресурса играет функция Hello. Если нужно вызвать эту функцию — используется точечная нотация: greet.Hello().

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

go run main.go

После того, как вы это сделаете, в терминале будет выведено следующее:

Hello, World!

Теперь поговорим о том, как использовать переменные, объявляемые в пакетах. Для этого добавим объявление переменной в файл greet.go:

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

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

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

Снова запустите программу:

go run main.go

Теперь она выведет следующее:

Hello, World!
Sammy

А сейчас поговорим о том, как объявлять в пакетах типы. Создадим тип Octopus с полями Name и Color, а также создадим метод типа. Этот метод, при его вызове, будет возвращать особым образом обработанное содержимое полей типа Octopus. Приведём 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, создайте в нём экземпляр структуры нового типа и обратитесь к его методу String():

package main import ( "fmt" "github.com/gopherguides/greet"
) func main() fmt.Println(oct.String())
}

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

Sprintf для формирования предложения и возвращает, с помощью return, результат, строку, в место вызова метода (в данном случае это место находится в main.go). Метод String типа Octopus использует функцию fmt.

Запустим программу снова:

go run main.go

Она выведет в консоль следующее:

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

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

Экспорт сущностей

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

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

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

Это приведёт к появлению следующей ошибки компиляции:

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

Для того чтобы экспортировать метод reset типа Octopus нужно его переименовать, заменив первую букву, строчную r, на прописную R. Сделаем это, отредактировав 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 из других пакетов и при этом не сталкиваться с сообщениями об ошибках:

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!
Sammy
The octopus's name is "Jesse" and is the color orange
The octopus's name is "" and is the color .

Вызвав метод Reset, мы очистили поля Name и Color нашего экземпляра Octopus. В результате, при вызове String, там, где раньше выводилось содержимое полей Name и Color, теперь не выводится ничего.

Итоги

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

Пользуетесь ли вы в них пакетами собственной разработки? Уважаемые читатели! Какие программы вы обычно пишете на Go?

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть