Go metódy: priradenie funkcií typom

Monday, May 17, 2021 by Lenka
Go nie je striktne objektovo orientovaný programovací jazyk, umožňuje však objektovo orientovaný štýl programovania - polymorfizmus, kompozíciu typov, zapuzdrenie a metódy.

Objektovo orientované programovanie v Go

Striktne vzaté, Go nie je čistý objektovo orientovaný programovací jazyk, pretože nemá triedy a hierarchickú dedičnosť. Go však má zložené typy ako štruktúry, polymorfizmus prostredníctvom rozhraní, má prepracovanejšiu kompozíciu ako bežné objektovo orientované jazyky, zapuzdrenie na úrovni balíkov a podporuje definovanie metód na typoch. Tieto vlastnosti umožňujú Go objektovo orientovaný štýl programovania.

Metódy

Metóda je funkcia so špeciálnym argumentom prijímača správy - receivera. Pomocou prijímača môže metóda získať prístup k jeho vlastnostiam. Prijímača definujeme medzi kľúčovým slovom func a názvom metódy.

func (receiverName receiverType) Name(argumentTypes) (returnTypes) {}

Keď vo svojom kóde vytvoríme metódu, prijímač a typ prijímača musia byť v rovnakom balíku. Nie je dovolené vytvárať metódu, v ktorej je typ prijímača už definovaný v inom balíku vrátane zabudovaného typu, ako int, string atď. Ak sa o to pokúsime, kompilátor vráti chybu.

Prijímač môže byť typu struct alebo non-struct.

Metóda na type štruktúra

Pozrime sa ako sa definujú metódy na štruktúre.

Go Playground

package main

import (
    "fmt"
)

type Point struct {
    X, Y float64
}

func (p Point) IsAbove(y float64) bool {
    return p.Y > y
}

func main() {
    p := Point{2.0, 4.0}

    fmt.Println("Point : ", p)

    fmt.Println("Is Point p located above the line y = 1.0 ? : ", p.IsAbove(1))
}

Keďže metóda je iba funkcia s argumentom prijímača, vyššie uvedený príklad môžeme napísať aj pomocou bežnej funkcie.

func IsAboveFunc(p Point, y float64) bool {
    return p.Y > y
}

Prečo teda potrebujeme metódy, keď si vystačíme s funkciami? Metódy nám pomáhajú napísať objektovo orientovaný kód v Go. Volania metód sú oveľa ľahšie čitateľné a zrozumiteľnejšie ako volania funkcií. Metódy nám navyše pomôžu vyhnúť sa konfliktom pri pomenovaní. Pretože je metóda viazaná na konkrétny prijímač, môžeme mať rovnaké názvy metód na rôznych typoch prijímačov.

Metóda na type inom ako štruktúra

Metódu môžeme vytvoriť aj na inom type ako je štruktúra. Musíme však dodržať jednoduché pravidlo.

Definícia typu a metódy musia byť v rovnakom balíku.

Ak by sme si chceli vytvoriť metódu na stringu, ktorá zmení všetky písmená na veľké, museli by sme si v prvom rade v danom balíku definovať nový typ - v tomto prípade myString.

type MyString string

Ak by sme to neurobili, program by nám vracal chybu: cannot define new methods on non-local type string.

Go Playground

package main

import (
    "fmt"
    "strings"
)

type MyString string

func (s MyString) toUpperCase() string {
    normalString := string(s)
    return strings.ToUpper(normalString)
}

func main() {
    str := MyString("Hello World")
    fmt.Println(str.toUpperCase())
}

Metóda s ukazovateľom

Doteraz sme pracovali s metódami, ktoré mali prijímač typu hodnota (value receiver). Takáto metóda dostane kópiu objektu, na ktorom bola volaná. Aby sme to overili, môžeme vytvoriť metódu, ktorá mení stav štruktúry, s ktorou je zviazaná.

Go Playground

package main

import "fmt"

type Student struct {
    name string
    id string
}

func (s Student) SetName(name string) {
    s.name = name
}

func main() {
    student := Student{
        name: "Joseph",
        id: "456962",
    }
    
    fmt.Println("initial name:", student.name)
    
    student.SetName("Chandler")
    
    fmt.Println("name after set:", student.name)
}

Výstup:

initial name: Joseph
name after set: Joseph

Vidíme, že úprava objektu student sa neprejavila na originálnom objekte. Metóda dostala len kópiu objektu a jeho upravením sme neovplyvnili originálny objekt. K tomu nám pomôže prijímač s ukazovateľom (pointer receiver). Takáto metóda dostane namiesto kópie objektu ukazovateľ na objekt a všetky zmeny sú zapisované do originálneho objektu.

Syntax je veľmi podobná, len nám pribudne ukazovateľ pri prijímači:

func (receiverName *receiverType) Name(argumentTypes) (returnTypes) {}

Vyskúšajme si to na rovnakom príklade:

Go Playground

package main

import "fmt"

type Student struct {
    name string
    id string
}

func (s *Student) SetName(name string) {
    s.name = name
}

func main() {
    student := Student{
        name: "Joseph",
        id: "456962",
    }
    
    fmt.Println("initial name:", student.name)
    
    student.SetName("Chandler")
    
    fmt.Println("name after set:", student.name)
}

Výstup:

initial name: Joseph
name after set: Chandler

Môžete sa rozhodnúť medzi metódou s prijímačom ukazovateľom alebo prijímačom hodnotou v závislosti od prípadu použitia.

Metódu s prijímačom ukazovateľom musíte použiť, ak potrebujete zmeniť stav prijímača, alebo ak štruktúra obsahuje synchronizačný objekt (napríklad sync.Mutex), ktorý sa nesmie kopírovať. Je tiež vhodný pre veľmi frekventované operácie nad veľkými objektami, pretože pre operácie sa nevytvára kópia v novej pamäti.

Metódu s prijímačom hodnotou je vhodné použiť pri menších objektoch ak nechcete zmeny zapísať do originálneho objektu, čo vám prekladač garantuje. Ak je prijímač map, func, chan, alebo slice, ktorý nechcete realokovať, alebo jednoduchý typ ako int, alebo string, nepoužívajte ukazovateľ.

Všeobecne sa odporúča byť konzistentný, ak máte pochybnosti, alebo ak čo len jedna metóda vyžaduje zmenu stavu originálneho objektu, použite pre všetky metódy prijímače ukazovateľom.

Metóda dokáže prijať ukazovateľ aj hodnotu

Keď má normálna funkcia definíciu parametra, bude akceptovať iba argument typu definovaného parametrom. Ak predáme ukazovateľ na funkciu, ktorá očakáva hodnotu, program nebude fungovať. To platí aj vtedy, keď funkcia prijíma ukazovateľ, ale namiesto toho odovzdávame hodnotu.

Pokiaľ však ide o metódy, nejde o striktné pravidlo. Môžeme definovať metódu s prijímačom hodnoty alebo ukazovateľa a zavolať ju na ukazovateľ alebo hodnotu. Go robí úlohu konverzie typov automaticky.

Go Playground

package main

import "fmt"

type Student struct {
    name string
    id string
}

func (s *Student) SetName(name string) {
    s.name = name
}

func (s Student) ID() string {
    return s.id
}

func main() {
    student := Student{
        name: "Joseph",
        id: "456962",
    }
    
    fmt.Println(student)
    student.SetName("Chandler")
    fmt.Println(student)
    
    fmt.Println(student.ID())
}

Výsledok:

{Joseph 456962}
{Chandler 456962}
456962

Záver

Ukázali sme si, že práca s metódami v Go nie je nič náročné. Metódy majú v deklarácii typ prijímača = receivera, pričom rozlišujeme, či je prijímač určený hodnotou (value receiver) alebo ukazovateľom (pointer receiver).

Referencie

  1. Golang FAQ
  2. Effective Go
  3. Tour:Choosing a value or pointer receiver

Vaše otázky, návrhy a komentáre

Verím, že vás tento návod inšpiroval a budem vďačný ak dáte spätnú väzbu a pomôžete mi zamerať sa na to čo by vás zaujímalo.

TAK ČO HOVORÍŠ ?

Kontaktuj nás ak potrebuješ pomoc, veľmi radi pomôžeme.

Kontaktuj nás