Давайте поучимся работать с объектно-ориентированной архитектурой в 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. Горутины.
2)Введение в каналы Golang
3) Обработка ошибок в Golang с помощью Panic, Defer и Recover
Перевод статьи Rahul Agarwal: Bamboolib — Learn and use Pandas without Coding
Комментарии