Объектно-ориентированное программирование в Golang


Давайте поучимся работать с объектно-ориентированной архитектурой в 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. Горутины.

    2)Введение в каналы Golang

    3) Обработка ошибок в Golang с помощью Panic, Defer и Recover


    Перевод статьи Mayank Gupta: Golang Object-Oriented Programming


    Поделиться статьей:


    Вернуться к статьям

    Комментарии

      Ничего не найдено.