Давайте поучимся работать с объектно-ориентированной архитектурой в Golang. Здесь нет классов, зато есть структуры, работа с которыми является единственным способом поддержки объектно-ориентированной модели.
Создание структур в Golang
Структуры могут использоваться для представления сложного объекта, состоящего из нескольких пар «ключ — значение».
Обратимся к конкретному примеру. Допустим, нам надо представить в качестве объекта сотрудника нашей организации. Для этого понадобится комбинация пар «ключ — значение» со всеми данными о сотруднике. Объект, представляющий сотрудника, может быть составлен из нескольких ключей/параметров, например, имени
, возраста
, должности
и оклада
. Все эти атрибуты или свойства в совокупности представляют сотрудника организации.
Теперь создадим простую структуру сотрудника Employee
с основными его параметрами.
type Employee struct {
Name string
Age int
Designation string
Salary int
}
В этом коде можно выделить следующие стандартные блоки:
- Ключевое слово
type
, которое используется для определения новоготипа
в Golang. Employee
передан в качестве имени создаваемой структуры.- Ключевое слово
struct
указывает на тип данных — структура. - И, наконец, в структуру добавляются параметры вместе с их типами.
В Golang предусмотрена строгая типобезопасность, поэтому типы параметров надо указывать во время объявления структуры
. Присвоение значения любого другого типа в этих параметрах чревато ошибками, которые обнаружит компилятор.
Создание объектов из структуры
Теперь, когда у нас есть новая структура, можно создавать из неё объекты. Давайте подробно разберём, как это делать на примере уже определённой нами структуры (Employee
). Существует несколько способов создания объектов, и мы рассмотрим разные возможные сценарии со всеми их преимуществами и недостатками.
Передача в структуру значений, разделённых запятыми
Это самый простой способ создания объектов: все необходимые значения передаются в структуру в виде значений, разделённых запятыми. Эти значения должны следовать в том же порядке, в котором они указаны при объявлении структуры.
type Employee struct {
Name string
Age int
Designation string
Salary int
}
var newEmployee = Employee{"Mayank", 30, "Developer", 40}
В этом коде мы создаём объект структуры Employee
(как видите, значения разделены запятыми), причём объект этот присваивается переменной newEmployee
.
Проблемы, которые здесь возникают:
- Во время создания объекта приходится держать в голове все параметры да ещё и в нужном порядке.
- В структуру надо передавать все значения.
Передача пар «ключ — значение»
Решаем эти проблемы, передавая пары «ключ — значение» во время объявления.
Что это нам даёт?
- Во-первых, больше не нужно следить за порядком следования значений.
- Во-вторых, не нужно указывать все пары «ключ — значение».
type Employee struct {
Name string
Age int
Designation string
Salary int
}
var newEmployee = Employee{Name: "Mayank", Age: 40, Designation: "Developer"}
var otherEmployee = Employee{Designation: "Developer", Name: "Mayank", Age: 40}
Видите? Мы передаём пары «ключ — значение» в произвольном порядке и даже пропускаем некоторые параметры. Тем не менее структура создаётся!
Стандартные значения для недостающих параметров
При создании объекта newEmployee
мы пропустили одну из пар «ключ — значение» (Salary
). Golang по умолчанию добавляет к параметру Salary
некое стандартное значение. Каким же именно будет это стандартное значение? А это зависит от типа параметра. В соответствии с типом параметра задаются следующие стандартные значения данных:
- Значение целочисленного типа
int
задаётся равным0
. - Значения строкового типа
string
оставляются пустыми“”
(пустая строка). - Значениям логического типа
bool
по умолчанию задаётсяfalse
.
В нашем случае параметру Salary
соответствует целочисленный тип int
, поэтому стандартным значением этого параметра будет 0
. Теперь давайте посмотрим, как получить доступ к этим значениям параметра, используя объект struct
.
type Employee struct {
Name string
Age int
Designation string
Salary int
}
var newEmployee = Employee{Name: "Mayank", Age: 40, Designation: "Developer"}
// Обращение к параметрам «имени»...
fmt.Println(newEmployee.Name)
Ссылка на объект или значение объекта?
Ещё нам важно в создании структуры
определить, представляет ли параметр newEmployee
тип значения или ссылочный тип. Объект, возвращаемый нам при объявлении, приносит не ссылки, а значение объекта. newEmployee
не указывает на адрес в памяти.
Если этот объект в качестве параметра передаётся какой-то другой функции, мы даём вызываемой функции значения объекта. Исходный объект копируется, и новый объект данных присваивается параметру вызывающей функции. Объект не передаётся как ссылка. А раз так, никакие изменения копированного объекта не дублируются в исходном. Рассмотрим пример:
func UpdateEmployee(empDetials Employee) {
empDetails.Name = "Anshul";
}
var newEmployee = Employee{Name: "Mayank", Age: 40, Designation: "Developer"}
UpdateEmployee(newEmployee)
fmt.Println(newEmployee.Name)
Здесь даже при обновлении значений в функции UpdateEmployee
никакого влияния на исходный объект newEmployee
не оказывается, ведь объект мы передали не как ссылку, а как значение.
Передача объекта как ссылки
Итак, мы увидели, что обновления объекта никак не отразятся на функции, потому что в функцию не была передана ссылка. Для передачи объекта как ссылки можно вместо значений передать ссылку на объект: просто ставим в начале оператор взятия адреса &
. Чтобы принялась ссылка на объект, а не значение, вызываемую функцию тоже надо обновить.
func UpdateEmployee(empDetials *Employee) {
empDetails.Name = "Anshul";
}
var newEmployee = Employee{Name: "Mayank", Age: 40, Designation: "Developer"}
UpdateEmployee(&newEmployee)
// Исходный объект отправлен в обновлённую функцию...
fmt.Println(newEmployee.Name)
Здесь функцию (и вызов функции) обновили, чтобы можно было отправлять и получать не значение объекта, а адрес в памяти. Теперь, если обновятся данные в вызываемой функции, то же самое произойдёт и в исходном объекте данных.
В чём проблема этого подхода? В том, что приходится использовать оператор взятия адреса, чтобы вытащить адрес объекта и отправить его в функцию.
Использование ключевого слова new
для создания объектов
Следующий способ создания объекта из структуры — использовать ключевое слово new
. При этом Golang создаёт новый объект типа struct
и возвращает переменной его адрес в памяти. То есть, ключевое слово new
возвращает адрес этого объекта.
Используя ссылку, возвращаемую от ключевого слова new
, можно присваивать значения параметрам вновь созданного объекта. Все параметры по умолчанию принимают неявные значения, и для каждого конкретного типа это будет своё стандартное значение.
- Стандартным значением для булева типа будет
false
. - Стандартным значением для целочисленного типа будет
0
. - Стандартным значением для строкового типа будет
“”
(пустая строка).
Для лучшего понимания снова обратимся к коду:
type Employee struct {
Name string
Age int
Designation string
Salary int
}
var newEmployee = new(Employee)
fmt.Println(newEmployee.Name)
Мы создаём новый объект с помощью ключевого слова new
, которое не позволяет передавать параметрам объекта стандартные значения. При создании объекта это делает за нас Golang. В нашем случае обращение к параметру name
вернёт пустую строку (“”
).
В коде с помощью ключевого слова new
возвращается адрес вновь созданного объекта. Переменная newEmployee
выступает здесь в роли указателя на объект Employee
.
Передача объекта в функцию
При создании объекта с использованием ключевого слова new
нам возвращается адрес объекта. Поэтому, если мы передаём в функцию в качестве параметра объект, то отправляется ссылка на объект. Любые изменения в параметре входного объекта будут отражаться на исходном объекте.
func UpdateEmployee(empDetials *Employee) {
empDetails.Name = "Anshul";
}
var newEmployee = new(Employee)
newEmployee.Name = "Mayank"
newEmployee.Age = 30
UpdateEmployee(newEmployee)
fmt.Println(newEmployee.Name)
Здесь от ключевого слова new
возвращается ссылка на объект. А значит, что при вызове функции нет необходимости использовать &
для отправки ссылки на объект.
Добавление функции в структуру
Структура не только определяет свойства, относящиеся к объекту, она представляет поведение объекта. Попробуем понять, как это возможно, добавив в структуру функции. Код для этой операции в Golang довольно-таки своеобразный.
Пример:
type Employee struct {
Name string
Age int
Designation string
Salary int
}
func (emp Employee) ShowDetails() {
fmt.Println("User Name: ", emp.Name)
}
Здесь мы добавляем новую функцию, которая бы имела привязку к структуре Employee
. То есть чётко привязываем функцию к структуре. Функция определена и может принимать ссылку на созданный объект с помощью emp
.
Посмотрите, как в Golang вызывается добавленная к структуре функция.
type Employee struct {
Name string
Age int
Designation string
Salary int
}
func (emp Employee) ShowDetails() {
fmt.Println("User Name: ", emp.Name)
}
var newEmployee = new(Employee)
newEmployee.Name = "Mayank"
newEmployee.ShowDetails()
Заключение
Надеюсь, вам понравилась статья.
Если хотите узнать о Golang больше, читайте следующие статьи:
1)Конкурентность и параллелизм в Golang. Горутины.
3) Обработка ошибок в Golang с помощью Panic, Defer и Recover
Читайте также:
- Почему мы создаем инфраструктуру машинного обучения в Go, а не в Python
- Docker для разработки Go с горячей перезагрузкой
- Конкурентность и параллелизм в Golang. Горутины.
Перевод статьи Mayank Gupta: Golang Object-Oriented Programming