type Shape struct {
name string
}
func (s *Shape) Name() string {
return s.name
}
V Go neexistujú abstraktné metódy. To často spôsobuje prekvapivé správanie.
Vytvorme si útvar s menom:
Použime ho:
func main() {
s := Shape{"some square"}
fmt.Println(s.Name())
}
Vyrobme na pomenovanom útvare metódu, ktorá popíše jeho vlastnosti ako reťazec, pričom detaily o útvare zabezpečí príslušný útvar, ktorý embedduje Shape — kruh uvedie polomer, štvorec uvedie dĺžku strany a pod.
Dodajme:
func (s *Shape) Describe() string {
desc := s.DoDescribe()
if desc != "" {
desc = " " + desc
}
return "<" + s.Name() + desc + ">"
}
func (s *Shape) DoDescribe() string {
return ""
}
Následne skúsme:
func main() {
s := Shape{"some square"}
fmt.Println(s.Describe())
}
Uvidíme výpis v lomených zátvorkách:
<some square>
Metóda DoDescribe je „abstraktná“ a jej kód majú dodať potomkovské štruktúry.
Štvorec a jeho popis
Dodajme štvorec a jeho popis.
type Square struct {
side float64
Shape
}
func (s *Square) DoDescribe() string {
return fmt.Sprintf("Square %s, side %.2f", s.Name(), s.side)
}
Štvorec embedduje („dedí“) od útvaru Shape a pokúša sa prekryť „abstraktnú“ metódu DoDescribe, žiaľ, neúspešne.
Použime:
redSquare := Square{
2,
Shape{"Red"},
}
fmt.Println(redSquare.Describe())
Čo uvidíme? Očakávame, že uvidíme podrobnosti o štvorci, ale namiesto toho vidíme výsledok rodičovskej metódy:
<Red>
Samotný štvorec Square nemá metódu Describe, takže sa zavolá rodičovská metóda z Shape.
V bežných objektovo orientovaných jazykov by metóda Describe zavolala metódu DoDescribe na skutočnom type premennej — teda na Square.
Toto však v Go nefunguje.
V skutočnosti sa volá metóda DoDescribe na tom type, ktorý naozaj obsahuje metódu Describe.
Trik pre abstraktné metódy
Obídeme to elegantným trikom z z blogu Hackthology.
Konštruktory
Najprv si však pripravme funkciu, čo sa bude tváriť ako konštruktor:
func NewSquare(side float64, name string) *Square {
s := new(Square)
s.side = 0
s.Shape = Shape{name}
return s
}
Následne ju použijeme:
redSquare := NewSquare(2, "Red")
fmt.Println(redSquare.Describe())
Situácia sa nezlepšila, ale otvorili sme si priestor na ďalšie zmeny.
Funkcia pre abstraktné volanie
Dodajme do útvaru stav reprezentujúci funkciu pre jeho popis.
Funkcie sú v Go rovnoprávne dátové typy, s ktorými možno veselo narábať ako s premennými!
type Shape struct {
name string
describer func() string
}
Následne upravme konštruktor pre štvorec:
func NewSquare(side float64, name string) *Square {
s := new(Square)
s.side = side //(1)
s.name = name //(2)
s.describer = s.DoDescribe //(3)
return s
}
-
Štvorcu priradíme dĺžku strany.
-
Zároveň štvorec pomenujeme, pričom využijeme premennú
name„zdedenú“ z útvaruShape. -
Funkcii, ktorá dokáže vrátiť popis, priradíme metódu (!)
DoDescribezo štvorca.
Štvorec Square, ktorý má metódu DoDescribe ju môže použiť ako funkciu.
Keďže DoDescribe neberie žiaden parameter a vracia reťazec, je možné ju považovať za príslušnú funkciu s 0 parametrami a s návratovou hodnotou string a teda ju priradiť do premennej v útvare Shape.
|
Tip
|
Táto vlastnosť sa nazýva Method Value. Metódu štruktúry dokážeme považovať na samostatne stojacu funkciu. |
Aby to naozaj fungovalo, musíme ešte upraviť metódu Describe na útvare Shape.
func (s *Shape) Describe() string {
desc := s.describer() //(1)
if desc != "" {
desc = " " + desc
}
return "<" + s.Name() + desc + ">"
}
-
Dôležité informácie o útvare z konkrétnej implementácie už nezískame priamo — volaním metódy
DoDescribe, ale „dokola“ — z funkcie v premennejdescriber.
Ak zavoláme príslušný kód, uvidíme správny výsledok.
<Red Square Red, side 2.00>
Ak chceme naozaj vybudiť dojem, že metóda DoDescribe na útvare Shape je „abstraktná”, dodáme ju.
func (s *Shape) Describe() string {
desc := s.DoDescribe() //(1)
if desc != "" {
desc = " " + desc
}
return "<" + s.Name() + desc + ">"
}
func (s *Shape) DoDescribe() string {
return s.describer() //(2)
}
-
Voláme „abstraktnú“ metódu
DoDescribe, ktorá na útvareShapelen deleguje vykonávanie do medzifunkcie v premennejdescriber. -
Metóda
DoDescribe()rieši zavolanie medzifunkcie.
|
Note
|
Metóda DoDescribe je na konkrétnom útvare — napr. štvorci — prekrytá korektne.
Medzifunkcia je inicializovaná v konštruktore štvorca — teda v metóde NewSquare, kde sa do nej priradí metóda DoDescribe štvorca Square.
|
Kruhy
Kruhy už urobíme v podobnom duchu.
// Circle
type Circle struct {
diameter float64
Shape
}
func NewCircle(diameter float64, name string) *Circle {
c := &Circle{diameter, Shape{name: name}} //(1)
c.describer = c.DoDescribe //(2)
return c
}
func (c *Circle) DoDescribe() string { //(3)
return fmt.Sprintf("Circle with diameter %.2f", c.diameter)
}
-
Inicializáciu urobíme na jeden riadok.
-
Chýbajúcu medzifunkciu dodáme samostatne. Nezabudnime, že
describerje „zdedený“ z útvaruShapea preto ho môžeme zavolať. -
Pridáme vlastný popis kruhu.
|
Warning
|
Metóda DoDescribe musí byť volaná na prijímači typu smerník — teda method receiver musí byť pointrový *Circle!
Inak tento trik nebude fungovať.
|
Abstraktný útvar
Pozor na to, že útvar Shape nie je určený na vytváranie premenných napriamo.
blob := Shape{name: "blob"}
fmt.Println(blob.Describe())
Výsledok bude „segmentaiton fault“:
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x108e9bd]
goroutine 1 [running]:
main.(*Shape).DoDescribe(...)
Je to preto, že funkcia v premennej describer nie je inicializovaná.
Útvar Shape nebol korektne inicializovaný a preto pokus o volanie nedefinovanej medzifunkcie (nil) zlyhá.