La oss gå Objektorientert programmering i Golang

Go er en merkelig blanding av gamle og nye ideer. Den har en veldig forfriskende tilnærming der det ikke er redd for å kaste bort etablerte forestillinger om "hvordan å gjøre ting". Mange er ikke engang sikker på om Go er et objektorientert språk. La meg legge det til hvile akkurat nå. Det er! 

I denne opplæringen lærer du om alle intrikatene i objektorientert design i Go, hvordan pilarene til objektorientert programmering som innkapsling, arv og polymorfisme uttrykkes i Go, og hvordan går det til andre språk.

Go er et utrolig kraftig programmeringsspråk, lære alt fra å skrive enkle verktøy for å bygge skalerbare, fleksible webservere i hele kurset.

The Go Design Filosofi

Gos røtter er basert på C og mer generelt på Algol-familien. Ken Thompson sa halvt av det at Rob Pike, Robert Granger og han kom sammen og bestemte seg for å hate C ++. Enten det er en vits eller ikke, Go er veldig forskjellig fra C ++. Mer om det senere. Go er om ultimate enkelhet. Dette forklares i detalj av Rob Pike i Less er eksponentielt mer.

Gå vs. andre språk

Go har ingen klasser, ingen objekter, ingen unntak og ingen maler. Den har søppelsamling og innebygd samtidighet. Den mest slående forsømmelsen så langt som objektorientert er bekymret, er at det ikke er noe type hierarki i Go. Dette er i kontrast til de fleste objektorienterte språk som C ++, Java, C #, Scala, og til og med dynamiske språk som Python og Ruby.

Gå til objektorienterte språkfunksjoner

Go har ingen klasser, men det har typer. Spesielt har den strukturer. Strukturer er brukerdefinerte typer. Strukturtyper (med metoder) tjener lignende formål til klasser på andre språk.

structs

En struktur definerer tilstand. Her er en skapningsstruktur. Den har et navnefelt og et boolesk flagg kalt Real, som forteller oss om det er en ekte skapning eller en imaginær skapning. Strukturer holder bare tilstand og ingen oppførsel.

skriv Creature struct Name streng Real bool 

metoder

Metoder er funksjoner som opererer på bestemte typer. De har en mottakerklausul som bestemmer hvilken type de opererer på. Her er en Dump () Metode som opererer på Creature structs og skriver ut deres tilstand:

func (c Creature) Dump () fmt.Printf ("Navn: '% s', Real:% t \ n", c.Name, c.Real)

Dette er en uvanlig syntaks, men det er veldig eksplisitt og tydelig (i motsetning til det implisitte "dette" eller Pythons forvirrende "selv").

embedding

Du kan legge inn anonyme typer inni hverandre. Hvis du legger inn en navnløs struktur, gir den innebygde strukten sin tilstand (og metoder) til den innebygde strukturen direkte. For eksempel, FlyingCreature har en navnløs skapning struktur innebygd i den, noe som betyr a FlyingCreature er en skapning.

skriv FlyingCreature struct Creature WingSpan int

Nå, hvis du har en forekomst av en FlyingCreature, kan du få tilgang til navn og reelle attributter direkte.

drage: = & FlyingCreature Creature "Dragon", false), 15, fmt.Println (dragon.Name) fmt.Println (dragon.Real) fmt.Println (dragon.WingSpan)

grensesnitt

Grensesnitt er kjennetegnet til Gos objektorientert støtte. Grensesnitt er typer som erklærer sett med metoder. På samme måte som grensesnitt på andre språk, har de ingen implementering. 

Objekter som implementerer alle grensesnittmetoder, implementerer automatisk grensesnittet. Det er ingen arv eller subclassing eller "implementer" søkeord. I følgende kodestykke, implementerer Foo Fooer-grensesnittet (etter konvensjon, Gå grensesnittnavn slutt med "er").

type Fooer-grensesnitt Foo1 () Foo2 () Foo3 () type Foo struct  func (f Foo) Foo1 () fmt.Println ("Foo1 () her") func (f Foo) Foo2 () fmt .Println ("Foo2 () her") func (f Foo) Foo3 () fmt.Println ("Foo3 () her")

Objektorientert design: The Go Way

La oss se hvordan Go måler mot søyler i objektorientert programmering: innkapsling, arv og polymorfisme. Det er funksjoner i klassebaserte programmeringsspråk, som er de mest populære objektorienterte programmeringsspråkene.

Kjerne er objekter språkkonstruksjoner som har tilstand og atferd som opererer på staten og selektivt utsetter den for andre deler av programmet. 

innkapsling

Gå innkapsler ting på pakkenivå. Navn som starter med små bokstaver, er bare synlige i den pakken. Du kan skjule alt i en privat pakke og bare avsløre spesifikke typer, grensesnitt og fabrikkfunksjoner. 

For eksempel, her for å skjule foo skriv over og avslør bare grensesnittet du kan omdøpe det til små bokstaver foo og gi en NewFoo ()funksjon som returnerer det offentlige Fooer-grensesnittet:

skriv foo struct  func (f foo) Foo1 () fmt.Println ("Foo1 () her") func (f foo) Foo2 () fmt.Println ("Foo2 () her") func foo3 () fmt.Println ("Foo3 () her") func NewFoo () Fooer return & Foo 

Koden fra en annen pakke kan da brukes NewFoo () og få tilgang til a Fooer grensesnitt implementert av det interne foo type:

f: = NewFoo () f.Foo1 () f.Foo2 () f.Foo3 ()

Arv

Arv eller underklasse var alltid et kontroversielt problem. Det er mange problemer med implementering arv (i motsetning til grensesnitt arv). Flere arv som implementert av C ++ og Python og andre språk lider av den dødelige diamanten av dødsproblemet, men selv arvelig er ingen piknik med det skjøre grunnskoleproblemet. 

Moderne språk og objektorientert tenkning favoriserer nå komposisjon over arv. Go tar det til hjerte og har ingen type hierarki overhodet. Det lar deg dele implementeringsdetaljer via komposisjon. Men Go, i en veldig merkelig vri (som sannsynligvis kommer fra pragmatiske bekymringer), tillater anonym sammensetning via embedding. 

For all hensikt, sammensetning ved å legge inn en anonym type, er ekvivalent med implementeringsarven. En innebygd struktur er like skrøbelig som en grunnklasse. Du kan også legge inn et grensesnitt, som tilsvarer å arve fra et grensesnitt på språk som Java eller C ++. Det kan til og med føre til en kjøretidsfeil som ikke blir oppdaget på kompileringstid hvis innebygd type ikke implementerer alle grensesnittmetodene. 

Her innebærer SuperFoo Fooer-grensesnittet, men implementerer ikke metodene sine. Go-kompilatoren vil gjerne la deg lage en ny SuperFoo og ringe til Fooer-metodene, men vil selvsagt mislykkes ved kjøring. Dette kompilerer:

skriv SuperFooer struct Fooer func main () s: = SuperFooer  s.Foo2 ()

Kjører dette programmet resulterer i panikk:

panikk: kjøretid feil: ugyldig minne adresse eller null pointer dereference [signal 0xb kode = 0x1 addr = 0x28 pc = 0x2a78] goroutine 1 [kjører]: panikk (0xde180, 0xc82000a0d0) /usr/local/Cellar/go/1.6/libexec/ src / runtime / panic.go: 464 + 0x3e6 main.main () /Users/gigi/Documents/dev/go/src/github.com/oop_test/main.go:104 + 0x48 utgangsstatus 2 Prosess ferdig med utgangskode 1

polymorfisme

Polymorfisme er essensen av objektorientert programmering: evnen til å behandle objekter av ulike typer jevnt så lenge de holder seg til samme grensesnitt. Go-grensesnitt gir denne muligheten på en svært direkte og intuitiv måte. 

Her er et forseggjort eksempel hvor flere skapninger (og en dør!) Som implementerer Dumper-grensesnittet, opprettes og lagres i et stykke og deretter Dump () Metoden kreves for hver enkelt. Du vil legge merke til forskjellige stiler ved å instantiere objektene også.

pakke viktigste import "fmt" type Creature struktur Navn streng Real bool func Dump (c * Creature) fmt.Printf ("Navn: '% s', Real:% t \ n", c.Name, c.Real ) func (c Creature) Dump () fmt.Printf ("Navn: '% s', Real:% t \ n", c.Name, c.Real) skriv FlyingCreature struct Creature WingSpan int func fc FlyingCreature) Dump () fmt.Printf ("Navn: '% s', Real:% t, WingSpan:% d \ n", fc.Name, fc.Real, fc.WingSpan) skriv Unicorn struct Creature  type Dragon struktur FlyingCreature type Pterodactyl struct FlyingCreature func NewPterodactyl (wingSpan int) * Pterodactyl kjæledyr: = & Pterodactyl FlyingCreature Creature "Pterodactyl", true,, wingSpan,, return pet type Dumper-grensesnitt Dump () type Dørstruktur Tykkelse int Fargestreng func (d Dør) Dump () fmt.Printf ("Dør => Tykkelse:% d, Farge:% s", d.Thickness, d.Color)  func main () creature: = & Creature "noen skapning", falsk, uni: = Unicorn Creature "Unicorn", false pet1: = & Pterodactyl FlyingCreature Creature "Pterodactyl", sant,, 5,, pet2: = NewPterodactyl (8) dør: = & Dør 3, "rød" Dump (skapning) creature.Dump () uni.Dump () pet1.Dump ) pet2.Dump () skapninger: = [] Creature * creature, uni.Creature, pet1.Creature, pet2.Creature fmt.Println ("Dump () gjennom Creature embedded type") for _, skapning: = utvalg skapninger creature.Dump () dumpers: = [] Dumper skapning, uni, pet1, pet2, dør fmt.Println ("Dump () gjennom Dumper-grensesnitt") for _, dumper: = dumpere )

Konklusjon

Go er et bona fide objektorientert programmeringsspråk. Det muliggjør objektbasert modellering og fremmer den beste praksisen med å bruke grensesnitt i stedet for konkrete typehierarkier. Go gjorde noen uvanlige syntaktiske valg, men generelt arbeider med typer, metoder og grensesnitt føles enkelt, lett og naturlig. 

Innlegg er ikke veldig rent, men pragmatisme var tilsynelatende på jobb, og innebygd ble gitt i stedet for bare sammensetning ved navn.