Použití metod v Go
Metoda v Go je speciální typ funkce s jednoduchým rozdílem: před názvem funkce musíte zahrnout další parametr. Tento dodatečný parametr se označuje jako přijímač.
Metody jsou užitečné, když chcete seskupit funkce a svázat je s vlastním typem. Tento přístup v go se podobá vytvoření třídy v jiných programovacích jazycích, protože umožňuje implementovat určité funkce z objektově orientovaného programovacího modelu (OOP), jako je vkládání, přetížení a zapouzdření.
Abychom pochopili, proč jsou metody v Go důležité, začněme tím, jak ho deklarujete.
Deklarace metod
Zatím jste použili struktury pouze jako jiný vlastní typ, který můžete vytvořit v Go. V tomto modulu se dozvíte, že přidáním metod můžete do struktur, které vytvoříte, přidat chování.
Syntaxe deklarace metody je podobná této:
func (variable type) MethodName(parameters ...) {
// method functionality
}
Než však budete moci deklarovat metodu, musíte vytvořit strukturu. Řekněme, že chcete vytvořit geometrický balíček a v rámci tohoto balíčku se rozhodnete vytvořit trojúhelníkovou strukturu s názvem triangle
. Pak chcete použít metodu k výpočtu obvodu tohoto trojúhelníku. Můžete ho znázorňovat v go takto:
type triangle struct {
size int
}
func (t triangle) perimeter() int {
return t.size * 3
}
Struktura vypadá jako normální, ale perimeter()
funkce má před názvem funkce další parametr typu triangle
. Tento přijímač znamená, že při použití struktury můžete volat funkci takto:
func main() {
t := triangle{3}
fmt.Println("Perimeter:", t.perimeter())
}
Pokud se pokusíte volat perimeter()
funkci jako obvykle, nebude fungovat, protože podpis funkce říká, že potřebuje příjemce. Jediný způsob, jak tuto metodu volat, je deklarovat nejprve strukturu, která poskytuje přístup k metodě. Pro metodu můžete mít dokonce stejný název, pokud patří do jiné struktury. Můžete například deklarovat square
strukturu pomocí perimeter()
funkce, například takto:
package main
import "fmt"
type triangle struct {
size int
}
type square struct {
size int
}
func (t triangle) perimeter() int {
return t.size * 3
}
func (s square) perimeter() int {
return s.size * 4
}
func main() {
t := triangle{3}
s := square{4}
fmt.Println("Perimeter (triangle):", t.perimeter())
fmt.Println("Perimeter (square):", s.perimeter())
}
Když spustíte předchozí kód, všimněte si, že neexistuje žádná chyba a zobrazí se následující výstup:
Perimeter (triangle): 9
Perimeter (square): 16
Z těchto dvou volání perimeter()
funkce kompilátor určuje, která funkce se má volat na základě typu příjemce. Toto chování pomáhá udržovat konzistenci a krátké názvy funkcí mezi balíčky a vyhnout se zahrnutí názvu balíčku jako předpony. O tom, proč toto chování může být důležité, když se budeme zabývat rozhraními v další lekci.
Ukazatele v metodách
Někdy bude potřeba aktualizovat proměnnou metodou. Nebo pokud je argument metody příliš velký, můžete se vyhnout kopírování. V těchto případech je potřeba použít ukazatele k předání adresy proměnné. Když jsme v předchozím modulu probrali ukazatele, řekli jsme, že pokaždé, když zavoláte funkci v Go, Go vytvoří kopii každé hodnoty argumentu, která se má použít.
Stejné chování se vyskytuje, když potřebujete aktualizovat proměnnou příjemce v metodě. Řekněme například, že chcete vytvořit novou metodu, která zdvojnásobí velikost trojúhelníku. Musíte použít ukazatel v proměnné přijímače, například takto:
func (t *triangle) doubleSize() {
t.size *= 2
}
Můžete prokázat, že metoda funguje, například takto:
func main() {
t := triangle{3}
t.doubleSize()
fmt.Println("Size:", t.size)
fmt.Println("Perimeter:", t.perimeter())
}
Při spuštění předchozího kódu byste měli získat následující výstup:
Size: 6
Perimeter: 18
Pokud metoda pouze přistupuje k informacím příjemce, nepotřebujete ukazatel v proměnné příjemce. Konvence Go však určuje, že pokud má jakákoli metoda struktury příjemce ukazatele, musí mít všechny metody této struktury příjemce ukazatele. I když ji metoda struktury nepotřebuje.
Deklarace metod pro jiné typy
Jedním ze zásadních aspektů metod je jejich definování pro jakýkoli typ, nejen pro vlastní typy, jako jsou struktury. Nemůžete ale definovat strukturu z typu, který patří do jiného balíčku. Proto nemůžete vytvořit metodu pro základní typ, například string
.
Hack ale můžete použít k vytvoření vlastního typu ze základního typu a pak ho použít jako základní typ. Řekněme například, že chcete vytvořit metodu pro transformaci řetězce z malých písmen na velká písmena. Můžete napsat něco takového:
package main
import (
"fmt"
"strings"
)
type upperstring string
func (s upperstring) Upper() string {
return strings.ToUpper(string(s))
}
func main() {
s := upperstring("Learning Go!")
fmt.Println(s)
fmt.Println(s.Upper())
}
Při spuštění předchozího kódu získáte následující výstup:
Learning Go!
LEARNING GO!
Všimněte si, jak můžete použít nový objekt s
, jako by byl řetězec při prvním tisku jeho hodnoty. Při volání Upper
metody s
pak vytiskne všechna velká písmena typu řetězec.
Metody vložení
V předchozím modulu jste se dozvěděli, že můžete použít vlastnost v jedné struktuře a vložit stejnou vlastnost do jiné struktury. To znamená, že můžete opakovaně používat vlastnosti z jedné struktury, abyste se vyhnuli opakování a zachovali konzistenci v základu kódu. Podobný nápad se vztahuje na metody. Metody vložené struktury můžete volat i v případě, že se příjemce liší.
Řekněme například, že chcete vytvořit novou strukturu trojúhelníku s logikou, která bude obsahovat barvu. Kromě toho chcete pokračovat v používání struktury trojúhelníku, kterou jste deklarovali dříve. Struktura barevného trojúhelníku by tedy vypadala takto:
type coloredTriangle struct {
triangle
color string
}
Pak byste mohli inicializovat coloredTriangle
strukturu a volat metodu perimeter()
triangle
ze struktury (a dokonce přistupovat k jeho polím), například takto:
func main() {
t := coloredTriangle{triangle{3}, "blue"}
fmt.Println("Size:", t.size)
fmt.Println("Perimeter", t.perimeter())
}
Pokračujte a zahrňte do programu předchozí změny, abyste viděli, jak vkládání funguje. Při spuštění programu s metodou, main()
jako je předchozí, byste měli získat následující výstup:
Size: 3
Perimeter 9
Pokud znáte jazyk OOP, jako je Java nebo C++, můžete si myslet, že triangle
struktura vypadá jako základní třída a coloredTriangle
je podtřídou (například dědičnost), ale to není správné. Ve skutečnosti se děje, že kompilátor Go podporuje metodu perimeter()
vytvořením metody obálky, která vypadá nějak takto:
func (t coloredTriangle) perimeter() int {
return t.triangle.perimeter()
}
Všimněte si, že příjemce je coloredTriangle
, který volá metodu perimeter()
z pole trojúhelníku. Dobrou zprávou je, že předchozí metodu nemusíte vytvářet. Mohl bys, ale Go to udělá pro tebe pod kapotou. Předchozí příklad jsme zahrnuli pouze pro účely výuky.
Metody přetížení
Vraťme se k příkladu triangle
, který jsme probrali dříve. Co se stane, když chcete změnit implementaci perimeter()
metody ve coloredTriangle
struktuře? Nemůžete mít dvě funkce se stejným názvem. Vzhledem k tomu, že metody vyžadují dodatečný parametr (příjemce), můžete mít metodu se stejným názvem, pokud je specifická pro příjemce, který chcete použít. Použití tohoto rozdílu spočívá v tom, jak přetížíte metody.
Jinými slovy, můžete napsat metodu obálky, kterou jsme probrali, pokud chcete změnit jeho chování. Pokud je obvod barevného trojúhelníku dvakrát obvodem normálního trojúhelníku, bude kód podobný tomuto:
func (t coloredTriangle) perimeter() int {
return t.size * 3 * 2
}
Teď, aniž byste změnili cokoli jiného main()
v metodě, kterou jste předtím napsali, by to vypadalo takto:
func main() {
t := coloredTriangle{triangle{3}, "blue"}
fmt.Println("Size:", t.size)
fmt.Println("Perimeter", t.perimeter())
}
Když ho spustíte, zobrazí se jiný výstup:
Size: 3
Perimeter 18
Pokud ale stále potřebujete metodu perimeter()
triangle
volat ze struktury, můžete to udělat tak, že k ní explicitně přistupujete, například takto:
func main() {
t := coloredTriangle{triangle{3}, "blue"}
fmt.Println("Size:", t.size)
fmt.Println("Perimeter (colored)", t.perimeter())
fmt.Println("Perimeter (normal)", t.triangle.perimeter())
}
Při spuštění tohoto kódu byste měli získat následující výstup:
Size: 3
Perimeter (colored) 18
Perimeter (normal) 9
Jak jste si možná všimli, v Go můžete přepsat metodu a stále přistupovat k původní , pokud ji potřebujete.
Zapouzdření v metodách
Zapouzdření znamená, že metoda je nepřístupná volajícímu (klientovi) objektu. Obvykle v jiných programovacích jazycích umístíte private
klíčová slova před public
název metody. V go, musíte použít pouze velkými písmeny identifikátor, aby byla metoda veřejná a neapitalizovaný identifikátor, aby byla metoda soukromá.
Zapouzdření v Go se projeví pouze mezi balíčky. Jinými slovy, můžete skrýt pouze podrobnosti implementace z jiného balíčku, ne samotný balíček.
Pokud ho chcete vyzkoušet, vytvořte nový balíček geometry
a přesuňte tam strukturu trojúhelníku, například takto:
package geometry
type Triangle struct {
size int
}
func (t *Triangle) doubleSize() {
t.size *= 2
}
func (t *Triangle) SetSize(size int) {
t.size = size
}
func (t *Triangle) Perimeter() int {
t.doubleSize()
return t.size * 3
}
Můžete použít předchozí balíček, například takto:
func main() {
t := geometry.Triangle{}
t.SetSize(3)
fmt.Println("Perimeter", t.Perimeter())
}
A měli byste získat následující výstup:
Perimeter 18
Pokud se pokusíte volat size
pole nebo metodu doubleSize()
main()
z funkce, program zpanikaří, například takto:
func main() {
t := geometry.Triangle{}
t.SetSize(3)
fmt.Println("Size", t.size)
fmt.Println("Perimeter", t.Perimeter())
}
Při spuštění předchozího kódu se zobrazí následující chyba:
./main.go:12:23: t.size undefined (cannot refer to unexported field or method size)