Давайте поучимся работать с объектно-ориентированной архитектурой в 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
Специально для сайта ITWORLD.UZ. Новость взята с сайта NOP::Nuances of programming