type Shaper interface {
Area() float64
}
Go, štruktúry a správanie
Objekty sú hlavne o správaní.
A to bez ohľadu na jazyk, v ktorom ich používame, čiže aj v jazyku Go.
Geometrické útvary
Zoberme si také geometrické útvary, ktorým chceme rátať plochu.
Vyrobíme teda interfejs, ktorý reprezentuje kontrakt pre akýkoľvek objekt, ktorý dokáže vyrátať svoju plochu.
Najprv štvorce
Prvým takýmto útvarom bude útvar — štvorec.
Na výpočet plochy potrebujeme jediný stav: veľkosť jeho strany side
.
type Square struct {
side float64
}
Ak má štvorec implementovať interfejs Shaper
, stačí mu dodať príslušnú metódu:
Tip
|
V Go neexistuje kľúčové slovo Toto je niekde nazývané „duck typing“ — ak to kváka ako kačka, a chodí ako kačka, tak je to kačka. U nás: ak to vie zrátať plochu, tak je to interfejs |
func (s Square) Area() float64 {
return s.side * s.side
}
Tip
|
Budeme používať metódy na prijímači, ktorý je hodnotou (receiver je typu value) a nie smerníkom (pointer). |
Testovať vieme jednoducho:
blueSquare := Square{side: 2}
println(blueSquare.Area())
Urobme funkciu, ktorá vypíše plochu útvaru na konzolu:
func printArea(s Shaper) {
fmt.Printf("%.2f\n", s.Area())
}
Testujme:
func main() {
blueSquare := Square{side: 2}
printArea(blueSquare)
}
Uvidíme:
4.0
Funkcia zobrala skutočný objekt typu Square
, ktorý vyhovuje interfejsu Shaper
.
Slovom, štvorec kváka ako interfejs Shaper
(má všetky — jednu — jeho metódu) a teda sa dá použiť na každom mieste, ktoré pracuje s interfejsom Shaper
.
Kruhy
Zaveďme kruhy:
type Circle struct {
diameter float64
}
func (c Circle) Area() float64 {
return math.Pi * math.Pow(c.diameter, 2)
}
Kruh spĺňa interfejs Shaper
a preto ho môžeme ho poslať do funkcie printArea
.
redCircle := Circle{diameter: 3}
printArea(redCircle)
Uvidíme:
28.27
Urobme si rez útvarov a iterujme:
shapes := []Shaper{blueSquare, redSquare, redCircle}
for _, shape := range shapes {
printArea(shape)
}
Premenná shapes
je typu []Shaper
a obsahuje tri položky — dve sú typu Square
a jednu typu Circle
.
Vďaka interfejsom máme podtypy a polymorfizmus: premenná shape
v cykle sa správa raz tak, raz onak.
Pomenované útvary
Zaveďme pomenované útvary.
type NamedShape struct {
name string
}
Vyhlásme štvorec za pomenovaný útvar:
type Square struct {
side float64
NamedShape
}
Toto síce vyzerá ako dedičnosť, ale Go ju nemá. V skutočnosti je to embedding („zapustenie“) či kompozícia, ktorá dedičnosť simuluje.
Zmeňme tvorbu štvorcov:
blueSquare := Square{2, NamedShape{"Blue"}}
redSquare := Square{1000, NamedShape{"Red"}}
Vytvorme rez pomenovaných štvorcov:
squares := []Square{blueSquare, redSquare}
Vytlačme ich mená:
for _, square := range squares {
fmt.Printf("Name: %s\n", square.name)
}
Premenná square
je typu Square
, ale „zdedila“ premennú name
od „rodičovského“ typu NamedShape
.
Úvodzovky sú namieste, lebo v skutočnosti štruktúra Square
je zložená z typu NamedShape
a pristupuje k jeho stavu rovnako ako keby ho mala sama.
Polymorfizmus
Ukážme si polymorfizmus — zaveďme funkciu pre výpis mena pomenovaného útvaru.
func printName(s NamedShape) {
fmt.Printf("Name: %s\n", s.name)
}
Skúsme teraz vypisovať mená:
squares := []Square{blueSquare, redSquare}
for _, square := range squares {
printName(square)
}
Uvidíme chybu!
Cannot use 'square' (type Square) as the type NamedShape
Takýto typ polymorfizmu v Go nefunguje.
Hoci štruktúra Square
„dedí“ od štruktúry NamedShape
— presnejšie „je s ňou zložená“, nevieme ju použiť v metóde, ktorá berie parameter NamedSquare
.
Tip
|
Takýto subtyping funguje len pri interfejsoch, nie pri kompozícii! |
Vytvorme si teraz rez pomenovaných útvarov:
squares := []NamedShape{blueSquare, redSquare}
Tiež uvidíme kýbel chýb:
Cannot use 'blueSquare' (type Square) as the type NamedShape
Cannot use 'redSquare' (type Square) as the type NamedShape
Obísť to môžeme interfejsom:
type Namer interface {
Name() string
}
Pridajme pomenovanému útvaru metódu na získanie mena:
func (n NamedShape) Name() string {
return n.name
}
Tip
|
Štruktúra NamedShape bude implementovať interfejs Namer .
|
Upravme funkciu tak, že jej zmeníme typ z konkrétnej štruktúry na interfejs.
func printName(n Namer) {
fmt.Printf("Name: %s\n", n.Name())
}
Toto sme v skutočnosti vylepšili na viacerých úrovniach.
Funkcia na tlačenie mena nepotrebuje pomenovaný útvar, ale akúkoľvek všeobecnú vec, ktorá má meno — a toto meno je získateľné cez metódu Name()
.
Ak by sme si vymysleli pomenované zvieratá, vedeli by sme ich vypisovať tiež!
Vytvorme teraz rez pomenovaných vecí typu []Namer
a vypíšme ho:
squares := []Namer{blueSquare, redSquare}
for _, square := range squares {
printName(square)
}
Toto funguje korektne, lebo využívame podtypy cez interfejsy.
Štvorec Square
„dedí“ od NamedShape
a keďže NamedShape
implementuje Namer
s metódou Name
, i na štvorci môžeme volať metódu Name()
.
Jej implementácia je „zdedená“ od NamedShape
, teda vypisuje názov štvorca.
Upravme teraz výpis mena na štruktúre Square
.
Dodajme štvorcu metódu:
func (s Square) Name() string {
return "Square " + s.name
}
Týmto sme prekryli (override) metódu Name()
rodičovského NamedShape
a poskytli vlastnú implementáciu.
Ak spustíme výpis nanovo, uvidíme:
Name: Square Blue
Name: Square Red
Pomenované kruhy
Použime aj pomenované kruhy:
type Circle struct {
diameter float64
NamedShape
}
Implementujme prekrytú metódu Name
:
func (c Circle) Name() string {
return "Circle " + c.name
}
Urobme si rez pomenovaných útvarov:
namedShapes := []Namer{blueSquare, redSquare, redCircle}
for _, shape := range namedShapes {
printName(shape)
}
Uvidíme výpis:
Name: Square Blue
Name: Square Red
Name: Circle
Kruh nemá meno, pretože … sme mu pri konštrukcii nepriradili meno.
Môžeme tiež vyrobiť interfejs pre pomenované veci s plochou:
type NamedShaper interface {
Namer
Shaper
}
Potom vieme urobiť rez pomenovaných útvarov!
namedShapes := []NamedShaper{blueSquare, redSquare, redCircle}
for _, shape := range namedShapes {
printName(shape)
printArea(shape)
}