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.
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.
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á.
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:
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.
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
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.