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 (!)
DoDescribe
zo š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 útvareShape
len 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
describer
je „zdedený“ z útvaruShape
a 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á.