После изучения данного урока вы сможете:
- Создавать методы для структурированных данных;
- Применять на практике принципы объектно-ориентированного дизайна.
Go не похож на классические языки программирования. В нем нет классов и объектов, он также не использует наследование. Однако Go по-прежнему предоставляет инструменты для внедрения идей объектно-ориентированного программирования. В данном уроке будет рассмотрена комбинация структур и методов.
Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎
Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.
Содержание статьи
Синергия — модное слово, широко распространенное в предпринимательских кругах. Оно означает «больше, чем сумма всех частей». В языке Go есть типы, методы и структуры. Вместе они более функциональны, нежели классы других языков. Плюс ко всему отпадает необходимость ввода в язык новой концепции.
Какие другие аспекты Go обладают свойством объединения для создания чего-то большего?
Прикрепление методов к структурам в Golang
В уроке о методах мы прикрепляли методы celsius
и fahrenheit
к типу kelvin
для конвертации температур. Методы можно прикрепить к любому объявленному типу. Это работает одинаково, будь-то базовым типом float64 или struct
.
Для начала нам нужно объявить тип. В следующем пример объявляется структура coordinate
:
1 2 3 4 5 |
// координаты в градусах, минутах, секундах для сферы N/S/E/W type coordinate struct { d, m, s float64 h rune } |
Координаты Кратера Брэдбери 4°35’22.2” S, 137°26’30.1” E в формате DMS, или ГМС (градусы, минуты, секунды). В одной минуте 60 секунд («), в одном градусе 60 минут (‘), но в данном случае минуты и секунды указывают на место, а не на время.
Метод decimal
в следующем примере конвертирует координаты DMS в градусы с десятичными значениями:
1 2 3 4 5 6 7 8 9 |
// decimal конвертирует координаты d/m/s в десятичные градусы. func (c coordinate) decimal() float64 { sign := 1.0 switch c.h { case 'S', 'W', 's', 'w': sign = -1 } return sign * (c.d + c.m/60 + c.s/3600) } |
Теперь можно предоставить координаты в удобном формате DMS и конвертировать их в десятичные градусы для совершения вычислений:
1 2 3 4 5 |
// Кратер Брэдбери: 4°35'22.2" S, 137°26'30.1" E lat := coordinate{4, 35, 22.2, 'S'} long := coordinate{137, 26, 30.12, 'E'} fmt.Println(lat.decimal(), long.decimal()) // Выводит: -4.5895 137.4417 |
Вопрос для проверки:
Что является приемником для десятичного метода в Листинге 2?
Функции конструктора в Golang
Для создания локации в десятичных градусах из градусов, минут и секунд можно использовать метод decimal
из Листинга 2 с композитным литералом:
1 2 3 4 5 |
type location struct { lat, long float64 } curiosity := location{lat.decimal(), long.decimal()} |
В случае необходимости композитного литерала, что более, чем просто список значений, подумайте о создании функции конструктора. В следующем примере объявляется функция конструктора под названием newLocation
.
1 2 3 4 |
// newLocation из координат d/m/s широты и долготы. func newLocation(lat, long coordinate) location { return location{lat.decimal(), long.decimal()} } |
В классических языках есть конструкторы в виде специальных инструментов для создания объектов. В Python есть __init__()
, в Ruby — initialize
, а в PHP — __construct()
. В Go нет языковых инструментов для конструкторов. Здесь newLocation
является обычной функцией с названием, что соответствует правилам именования конструктора.
Функции в форме newType
или NewType
используются для создания значения указанного типа. Выбор названия newLocation
или NewLocation
зависит от того, экспортируется ли функция из других пакетов, как было описано в уроке о функциях. newLocation
применяется как и любая другая функция.
1 2 |
curiosity := newLocation(coordinate{4, 35, 22.2, 'S'}, coordinate{137, 26, 30.12, 'E'}) fmt.Println(curiosity) // Выводит: {-4.5895 137.4417} |
Если вы хотите создать локации из различных входных данных, просто объявите несколько функций с подходящими названиями — возможно, newLocationDMS
и newLocationDD
для градусов, минут, секунд и десятичных градусов соответственно.
На заметку: Иногда функции конструктора называют
New
, как в случае с функциейNew
из пакетаerrors
. Это все оттого, что в вызовах функции ставится префикс пакета, к которому она относится. НазваниеNewError
читалось бы какerrors.NewError
, а не как более краткий и предпочтительный вариантerrors.New
.
Вопрос для проверки:
Как можно назвать функцию для создания переменной типа Universe
?
Классы в Golang
В Go нет понятия class
, как в других популярных языках программирования вроде Python, Ruby или Java. Однако структуры в сочетании с несколькими методами можно использовать для тех же целей. Они не так уж сильно отличаются.
Для создания точки координат для возвращения домой, мы с нуля напишем новый тип world
. У него будет поле для радиуса планеты, что будет использовано для вычисления расстояния между двумя локациями, как показано в следующем примере.
1 2 3 |
type world struct { radius float64 } |
Средний объемный радиус Марса составляет 3389,5 км. Вместо объявления 3389,5 как константы, используйте тип world
, чтобы объявить Марс одним из многих возможных миров:
1 |
var mars = world{radius: 3389.5} |
Затем метод distance
прикрепляется к типу world
, давая доступ к полю radius
. Он принимает два параметра, оба типа location
, и возвращает расстояние в километрах:
1 2 3 |
func (w world) distance(p1, p2 location) float64 { // TODO: Добавить математические выражения, используя w.radius } |
Мы будем использовать математические вычисления, для этого нужно импортировать пакет math
, что делается следующим образом:
1 |
import "math" |
Тип location
использует градусы для широты и долготы, однако математические функции стандартной библиотеки использует радианы. Учитывая, что у круга 360° или 2π, следующая функция потребуется для конвертации:
1 2 3 4 |
// rad конвертирует градусы в радианы. func rad(deg float64) float64 { return deg * math.Pi / 180 } |
Теперь займемся вычислением расстояния. Используем несколько тригонометрических функций, включая функции для синуса, косинуса и арккосинуса. Если вы увлекаетесь математикой, можете самостоятельно найти формулы, а также вспомнить Сферическую теорему косинусов, чтобы лучше понимать, как работает программа. Марс не является идеальной сферой, но все-таки используем следующую формулу, что позволит добиться приблизительного значения:
1 2 3 4 5 6 7 |
// вычисление расстояния через Сферическую теорему косинусов. func (w world) distance(p1, p2 location) float64 { s1, c1 := math.Sincos(rad(p1.lat)) s2, c2 := math.Sincos(rad(p2.lat)) clong := math.Cos(rad(p1.long - p2.long)) return w.radius * math.Acos(s1*s2+c1*c2*clong) // Использует поле радиуса world } |
Если вы чувствуете, что запутались, не волнуйтесь. В программе математика нужна для вычисления расстояния, и пока distance
возвращает верные результаты, вовсе не обязательно полностью понимать принцип работы всех математических формул. Хотя, это не помешает.
К слову о результатах. Чтобы посмотреть на distance
в действии, объявите несколько локаций и используйте переменную mars
, объявленную ранее:
1 2 3 4 5 |
spirit := location{-14.5684, 175.472636} opportunity := location{-1.9462, 354.4734} dist := mars.distance(spirit, opportunity) // Использует метод distance для mars fmt.Printf("%.2f km\n", dist) // Выводит: 9669.71 km |
Если ваш результат отличается, вернитесь назад и убедитесь, что код набран правильно. Отсутствие даже одного rad
приведет к неверным вычислениям. Если вы все равно не можете разобраться, можете скачать код и вставить его в свою программу.
Метод distance
был взят из формул для Земли, но используется радиус Марса. Объявляя distance
как метода типа world
, вы можете вычислить расстояние для других миров, к примеру, Земли. Радиус для каждой планеты можете найти в Таблице 2. Данные взяты из Планетарного информационного бюллетеня.
Вопрос для проверки:
В чем преимущество объявления метода distance
для типа world
по сравнению с менее объектно-ориентированным подходом?
Заключение
- Сочетание методов и структур предоставляет возможности классических языков программирования без необходимости ввода новых языковых аспектов;
- Функции конструктора являются обыкновенными функциями.
Итоговое задание для проверки #1:
Используя Листинги 1, 2 и 3, напишите программу, что объявляет location
для каждой локации из Таблицы 1. Код должен выводить каждую локацию в десятичных градусах.
Итоговое задание для проверки #2:
Используйте метод distance
из Листинга 4 и напишите программу, что определяет расстояние между каждой парой мест посадки из Таблицы 1.
- Какие места посадки находятся ближе всего друг к другу?
- Какие места дальше всего друг от друга?
Для определения расстояния между следующими местами вам понадобиться объявить другие миры, основываясь на данных из Таблицы 2:
- Найдите расстояние от Лондона, Англия (London, England, 51°30’N 0°08’W) до Парижа, Франция (Paris, France, 48°51’N 2°21’E);
- Найдите расстояние от вашего города до столицы вашей страны;
- Найдите расстояние между точками Mount Sharp (5°4’ 48”S, 137°51’E) и Olympus Mons (18°39’N, 226°12’E) на Марсе.
Администрирую данный сайт с целью распространения как можно большего объема обучающего материала для языка программирования Go. В IT с 2008 года, с тех пор изучаю и применяю интересующие меня технологии. Проявляю огромный интерес к машинному обучению и анализу данных.
E-mail: vasile.buldumac@ati.utm.md
Образование
Технический Университет Молдовы (utm.md), Факультет Вычислительной Техники, Информатики и Микроэлектроники
- 2014 — 2018 Universitatea Tehnică a Moldovei, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Universitatea Tehnică a Moldovei, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»
Наследование через встраивание
Почему в python и java нет понятия классов?