Let's Go objectgeoriënteerde programmering in Golang

Go is een vreemde mix van oude en nieuwe ideeën. Het heeft een zeer verfrissende aanpak waarbij het niet bang is om gevestigde noties van "hoe dingen te doen" weg te gooien. Veel mensen weten niet eens zeker of Go een objectgerichte taal is. Laat me dat nu meteen doen. Het is! 

In deze tutorial leer je over alle fijne kneepjes van object-georiënteerd ontwerp in Go, hoe de pijlers van object-georiënteerd programmeren zoals encapsulation, inheritance en polymorphism worden uitgedrukt in Go, en hoe Go vergeleken wordt met andere talen.

Go is een ongelooflijk krachtige programmeertaal, leer alles van het schrijven van eenvoudige hulpprogramma's tot het bouwen van schaalbare, flexibele webservers in onze volledige cursus.

The Go Design Philosophy

Go's roots zijn gebaseerd op C en meer in het algemeen op de Algol-familie. Ken Thompson zei voor de grap dat Rob Pike, Robert Granger en hij elkaar ontmoetten en besloten dat ze C ++ haten. Of het een grap is of niet, Go is heel anders dan C ++. Daarover later meer. Go gaat over ultieme eenvoud. Dit wordt in detail uitgelegd door Rob Pike in Less is exponentieel meer.

Ga versus andere talen

Go heeft geen klassen, geen objecten, geen uitzonderingen en geen sjablonen. Het heeft garbage collection en ingebouwde concurrency. De meest opvallende omissie wat object-georiënteerd betreft, is dat er in Go geen typehiërarchie is. Dit staat in contrast met de meeste objectgeoriënteerde talen zoals C ++, Java, C #, Scala en zelfs dynamische talen zoals Python en Ruby.

Ga naar objectgeoriënteerde taalfuncties

Go heeft geen klassen, maar het heeft typen. In het bijzonder heeft het structs. Structuren zijn door de gebruiker gedefinieerde typen. Structuurtypes (met methoden) dienen soortgelijke doeleinden als klassen in andere talen.

structs

Een struct definieert status. Hier is een Creature struct. Het heeft een veld Naam en een booleaanse vlag genaamd Real, die ons vertelt of het een echt wezen of een denkbeeldig wezen is. Structuren bevatten alleen status en geen gedrag.

type Creature struct Name string Real bool 

methoden

Methoden zijn functies die op bepaalde typen werken. Ze hebben een ontvangerclausule die bepaalt welk type ze gebruiken. Hier is een Dump () methode die werkt op Creature structs en print hun staat:

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

Dit is een ongewone syntaxis, maar het is heel expliciet en duidelijk (in tegenstelling tot het impliciete "dit" of het verwarrende "zelf" van Python).

Inbedding

U kunt anonieme typen insluiten in elkaar. Als u een naamloze struct insluit, biedt de ingesloten structuur zijn status (en methoden) rechtstreeks aan de inbeddingsstructuur. Bijvoorbeeld de FlyingCreature heeft een naamloos Schepsel struct erin ingebed, wat betekent a FlyingCreature is een Schepsel.

type FlyingCreature struct Creature WingSpan int

Als u nu een instantie van een FlyingCreature hebt, kunt u rechtstreeks toegang krijgen tot de attributen Name en Real.

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

interfaces

Interfaces zijn het kenmerk van de objectgerichte ondersteuning van Go. Interfaces zijn types die reeksen methoden declareren. Net als bij interfaces in andere talen hebben ze geen implementatie. 

Objecten die alle interfacemethoden implementeren, implementeren de interface automatisch. Er is geen sleutelwoord overerving of subklassen of "implementeert". Typ in het volgende codefragment Foo implementeert de Fooer-interface (volgens afspraak eindigen de interfacenamen met "er").

type Fooer interface Foo1 () Foo2 () Foo3 () type Foo struct  func (f Foo) Foo1 () fmt.Println ("Foo1 () hier") func (f Foo) Foo2 () fmt .Println ("Foo2 () hier") func (f Foo) Foo3 () fmt.Println ("Foo3 () hier")

Object-georiënteerd ontwerp: The Go Way

Laten we eens kijken hoe Go meet tegen de pijlers van objectgeoriënteerd programmeren: inkapseling, overerving en polymorfisme. Dat zijn kenmerken van op klassen gebaseerde programmeertalen, die de meest populaire objectgeoriënteerde programmeertalen zijn.

In de kern zijn objecten taalconstructies met staat en gedrag die op de staat werken en deze selectief blootstellen aan andere delen van het programma. 

inkapseling

Ga inkapselen dingen op het niveau van het pakket. Namen die beginnen met een kleine letter zijn alleen zichtbaar binnen dat pakket. U kunt alles in een privépakket verbergen en alleen bepaalde typen, interfaces en fabrieksfuncties blootleggen. 

Hier bijvoorbeeld om de te verbergen Foo typ hierboven en laat alleen de interface zien die je zou kunnen hernoemen naar kleine letters foo en geef een NewFoo ()functie die de openbare Fooer-interface retourneert:

type foo struct  func (f foo) Foo1 () fmt.Println ("Foo1 () hier") func (f foo) Foo2 () fmt.Println ("Foo2 () hier") func (f foo) Foo3 () fmt.Println ("Foo3 () hier") func NewFoo () Fooer return & Foo 

Dan kan code van een ander pakket gebruiken NewFoo () en krijg toegang tot een Fooer interface geïmplementeerd door de interne foo type:

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

Erfenis

Overerving of subclassering was altijd een controversieel probleem. Er zijn veel problemen met het overnemen van de implementatie (in tegenstelling tot het overerven van de interface). Meerdere overerving zoals geïmplementeerd door C ++ en Python en andere talen lijdt onder het dodelijke diamant van doodsprobleem, maar zelfs enkele erfenis is geen picnic met het fragiele probleem van de basisklasse. 

Moderne talen en objectgericht denken begunstigen nu de compositie ten opzichte van de erfenis. Go neemt het ter harte en heeft helemaal geen typehiërarchie. Hiermee kunt u uitvoeringsdetails via compositie delen. Maar Go, in een heel vreemde draai (die waarschijnlijk is ontstaan ​​vanuit pragmatische bezwaren), laat anonieme compositie toe via embedding. 

Het samenstellen door het insluiten van een anoniem type is voor alle opzichten gelijk aan het overnemen van de implementatie. Een ingesloten structuur is net zo fragiel als een basisklasse. U kunt ook een interface insluiten, die overeenkomt met het overerven van een interface in talen zoals Java of C ++. Het kan zelfs leiden tot een runtime-fout die niet wordt ontdekt tijdens het compileren als het embedding-type niet alle interfacemethoden implementeert. 

Hier sluit SuperFoo de Fooer-interface in, maar zijn methoden worden niet geïmplementeerd. Met de Go-compiler kun je met plezier een nieuwe SuperFoo maken en de Fooer-methoden aanroepen, maar deze zullen vanzelfsprekend mislukken tijdens runtime. Dit compileert:

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

Het uitvoeren van dit programma resulteert in paniek:

paniek: runtime-fout: ongeldig geheugenadres of nulverwijzing derferentie [signaal 0xb code = 0x1 addr = 0x28 pc = 0x2a78] goroutine 1 [actief]: paniek (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 exit-status 2 Proces gereed met afsluitcode 1

polymorfisme

Polymorfisme is de essentie van objectgeoriënteerd programmeren: het vermogen om objecten van verschillende typen gelijkmatig te behandelen zolang ze zich aan dezelfde interface houden. Go-interfaces bieden deze mogelijkheid op een zeer directe en intuïtieve manier. 

Hier is een uitgebreid voorbeeld waarbij meerdere wezens (en een deur!) Die de Dumper-interface implementeren worden gemaakt en opgeslagen in een slice en vervolgens de Dump () methode is voor elke methode vereist. Je zult ook verschillende stijlen van het instantiëren van de objecten opmerken.

pakket main import "fmt" type Creature struct Naamstring Real bool func Dump (c * Creature) fmt.Printf ("Naam: '% s', Real:% t \ n", c.Name, c.Real ) func (c Creature) Dump () fmt.Printf ("Naam: '% s', Echt:% t \ n", c.Name, c.Real) type FlyingCreature struct Creature WingSpan int func ( fc FlyingCreature) Dump () fmt.Printf ("Naam: '% s', Echt:% t, WingSpan:% d \ n", fc.Name, fc.Real, fc.WingSpan) type Unicorn struct Creature  type Dragon struct FlyingCreature type Pterodactyl struct FlyingCreature func NewPterodactyl (wingSpan int) * Pterodactyl huisdier: = & Pterodactyl FlyingCreature Creature "Pterodactyl", true,, wingSpan,, return pet type Dumper-interface Dump () type Door struct Thickness int Color string func (d Door) Dump () fmt.Printf ("Door => Thickness:% d, Color:% s", d.Thickness, d.Color)  func main () creature: = & Creature "some creature", false, uni: = Unicorn Creature "Unicorn", false,, pet1: = & Pterodactyl FlyingCreature Creature "Pterodactyl", true,, 5,, pet2: = NewPterodactyl (8) door: = & Door 3, "red" Dump (schepsel) creature.Dump () uni.Dump () pet1.Dump ( ) wezens van animal2.Dump (): = [] Wezens * creature, uni.Creature, pet1.Creature, pet2.Creature fmt.Println ("Dump () door ingebed schepsel") voor _, creature: = range creatures creature.Dump () dumpers: = [] Dumper creature, uni, pet1, pet2, door fmt.Println ("Dump () door Dumper-interface") voor _, dumper: = bereik dumpers dumper.Dump ( )

Conclusie

Go is een bonafide objectgeoriënteerde programmeertaal. Het maakt object-gebaseerde modellering mogelijk en bevordert de beste praktijk van het gebruik van interfaces in plaats van concrete type hiërarchieën. Go maakte een aantal ongewone syntactische keuzes, maar over het algemeen werkt het werken met typen, methoden en interfaces eenvoudig, lichtgewicht en natuurlijk. 

Inbedding is niet erg puur, maar blijkbaar was pragmatisme aan het werk en werd ingebed in plaats van alleen maar op naam.