Andiamo programmazione orientata agli oggetti in Golang

Go è uno strano mix di idee vecchie e nuove. Ha un approccio molto rinfrescante in cui non ha paura di gettare le nozioni consolidate sul "come fare le cose". Molte persone non sono nemmeno sicure che Go sia un linguaggio orientato agli oggetti. Lascia che ti metta a riposo adesso. È! 

In questo tutorial scoprirai tutte le complessità della progettazione orientata agli oggetti in Go, in che modo i pilastri della programmazione orientata agli oggetti come l'incapsulamento, l'ereditarietà e il polimorfismo sono espressi in Go, e come Go si confronta con altre lingue.

Go è un linguaggio di programmazione incredibilmente potente, impara tutto da scrivere semplici utility a costruire server web scalabili e flessibili nel nostro corso completo.

The Go Design Philosophy

Le radici di Go sono basate su C e più in generale sulla famiglia Algol. Ken Thompson ha detto scherzosamente che Rob Pike, Robert Granger e lui stesso si sono messi insieme e hanno deciso di odiare il C ++. Che si tratti di uno scherzo o meno, Go è molto diverso dal C ++. Maggiori informazioni in seguito. Go è sulla massima semplicità. Questo è spiegato in dettaglio da Rob Pike in Less è esponenzialmente di più.

Vai contro altre lingue

Go non ha classi, oggetti, eccezioni e modelli. Dispone di garbage collection e concomitanza integrata. L'omissione più eclatante per quanto riguarda l'orientamento agli oggetti è che non esiste una gerarchia di tipi in Go. Ciò è in contrasto con la maggior parte dei linguaggi orientati agli oggetti come C ++, Java, C #, Scala e persino i linguaggi dinamici come Python e Ruby.

Vai a funzioni linguistiche orientate agli oggetti

Go non ha classi, ma ha tipi. In particolare, ha delle strutture. Le strutture sono tipi definiti dall'utente. I tipi di Struct (con metodi) hanno scopi simili a quelli di altre lingue.

Structs

Una struct definisce lo stato. Ecco una struttura di creature. Ha un campo Nome e una bandiera booleana chiamata Reale, che ci dice se è una creatura reale o una creatura immaginaria. Le strutture mantengono solo lo stato e nessun comportamento.

type Creature struct Name string Real bool 

metodi

I metodi sono funzioni che operano su tipi particolari. Hanno una clausola ricevente che impone il tipo su cui operano. Ecco un Dump () metodo che opera sulle strutture di Creature e stampa il loro stato:

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

Questa è una sintassi insolita, ma è molto esplicita e chiara (a differenza dell'implicito "questo" o del "sé" confuso di Python).

Incorporare

Puoi incorporare tipi anonimi l'uno dentro l'altro. Se si incorpora una struttura senza nome, la struttura incorporata fornisce direttamente lo stato (ei metodi) alla struttura di incorporamento. Ad esempio, il FlyingCreature ha un senza nome Creatura struct incorporato in esso, che significa a FlyingCreature è un Creatura.

scrivi la struttura di FlyingCreature Creature WingSpan int

Ora, se si dispone di un'istanza di una FlyingCreature, è possibile accedere direttamente agli attributi Nome e Reale.

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

interfacce

Le interfacce sono il segno distintivo del supporto orientato agli oggetti di Go. Le interfacce sono tipi che dichiarano insiemi di metodi. Analogamente alle interfacce in altre lingue, non hanno implementazione. 

Gli oggetti che implementano tutti i metodi di interfaccia implementano automaticamente l'interfaccia. Non vi è alcuna parola chiave di ereditarietà o sottoclasse o "implementa". Nel seguente frammento di codice, digitare Foo implementa l'interfaccia di Fooer (per convenzione, i nomi delle interfacce Go terminano con "er").

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

Design orientato agli oggetti: The Go Way

Vediamo come Go va contro i pilastri della programmazione orientata agli oggetti: incapsulamento, ereditarietà e polimorfismo. Queste sono le caratteristiche dei linguaggi di programmazione basati sulla classe, che sono i linguaggi di programmazione orientati agli oggetti più popolari.

Al centro, gli oggetti sono costrutti linguistici che hanno stato e comportamento che operano sullo stato e lo espongono selettivamente ad altre parti del programma. 

incapsulamento

Go incapsula le cose a livello di pacchetto. I nomi che iniziano con una lettera minuscola sono visibili solo all'interno di quel pacchetto. È possibile nascondere qualsiasi cosa in un pacchetto privato e solo esporre tipi specifici, interfacce e funzioni di fabbrica. 

Ad esempio, qui per nascondere il foo digita sopra e mostra solo l'interfaccia che potresti rinominare in minuscolo foo e fornire un NewFoo ()funzione che restituisce l'interfaccia Fooer pubblica:

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

Quindi è possibile utilizzare il codice di un altro pacchetto NewFoo () e ottenere l'accesso a a Fooer interfaccia implementata dall'interno foo genere:

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

Eredità

La successione o sottoclasse è sempre stata una questione controversa. Ci sono molti problemi con l'ereditarietà dell'implementazione (al contrario dell'ereditarietà dell'interfaccia). L'ereditarietà multipla come implementata da C ++ e Python e altre lingue soffre del problema mortale del diamante della morte, ma anche l'eredità singola non è un picnic con il fragile problema della classe base. 

I linguaggi moderni e il pensiero orientato agli oggetti ora favoriscono la composizione sull'ereditarietà. Go lo prende a cuore e non ha alcun tipo di gerarchia di sorta. Ti consente di condividere i dettagli di implementazione tramite la composizione. Ma Go, in una strana svolta (che probabilmente ha origine da preoccupazioni pragmatiche), consente la composizione anonima tramite l'incorporamento. 

A tutti gli effetti, la composizione incorporando un tipo anonimo equivale all'ereditarietà dell'implementazione. Una struttura incorporata è fragile come una classe base. È anche possibile incorporare un'interfaccia, che equivale ad ereditare da un'interfaccia in linguaggi come Java o C ++. Può anche portare a un errore di runtime che non viene rilevato in fase di compilazione se il tipo di incorporamento non implementa tutti i metodi di interfaccia. 

Qui SuperFoo incorpora l'interfaccia di Fooer, ma non implementa i suoi metodi. Il compilatore Go ti lascerà felicemente creare un nuovo SuperFoo e chiamare i metodi Fooer, ma ovviamente non funzionerà in fase di runtime. Questo compila:

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

L'esecuzione di questo programma provoca il panico:

panico: errore di runtime: indirizzo di memoria non valido o puntatore nullo derefero [segnale 0xb codice = 0x1 addr = 0x28 pc = 0x2a78] goroutine 1 [in esecuzione]: panico (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 stato di uscita 2 Processo terminato con codice di uscita 1

Polimorfismo

Il polimorfismo è l'essenza della programmazione orientata agli oggetti: la capacità di trattare oggetti di tipi diversi in modo uniforme purché aderiscano alla stessa interfaccia. Le interfacce Go forniscono questa funzionalità in modo molto diretto e intuitivo. 

Ecco un esempio elaborato in cui più creature (e una porta!) Che implementano l'interfaccia Dumper vengono create e memorizzate in una sezione e quindi Dump () il metodo è chiamato per ognuno. Noterai diversi stili di istanziazione degli oggetti.

package main import "fmt" type Creature struct stringa nome Real bool func Dump (c * Creature) fmt.Printf ("Nome: '% s', reale:% t \ n", c.Name, c.Real ) func (c Creature) Dump () fmt.Printf ("Nome: '% s', Reale:% t \ n", c.Name, c.Real) tipo Struttura FlyingCreature Creature WingSpan int func ( fc FlyingCreature) Dump () fmt.Printf ("Nome: '% s', Reale:% t, WingSpan:% d \ n", fc.Name, fc.Real, fc.WingSpan) tipo Unicorn struct Creatura  tipo Dragon struct FlyingCreature tipo Pterodactyl struct FlyingCreature func NewPterodactyl (wingSpan int) * Pterodactyl pet: = & Pterodactyl FlyingCreature Creature "Pterodactyl", true,, wingSpan,, return pet tipo Interfaccia Dumper Dump () tipo Door struct Thickness int Color string func (d Door) Dump () fmt.Printf ("Porta => Spessore:% d, Colore:% s", d.Trattamento, d.Colore)  func main () creature: = & Creature "some creature", false, uni: = Unicorn Creature "Unicorn", false,, pet1: = & Pterodactyl FlyingCreature Creature "Pterodattilo", vero,, 5,, pet2: = NewPterodactyl (8) door: = & Door 3, "red" Dump (creatura) creature.Dump () uni.Dump () pet1.Dump ( ) creature di pet2.Dump (): = [] Creatura * creatura, uni.Creature, pet1.Creature, pet2.Creature fmt.Println ("Dump () attraverso Creature embedded type") per _, creature: = creature della gamma creature.Dump () dumpers: = [] Dumper creatura, uni, pet1, pet2, porta fmt.Println ("Dump () attraverso l'interfaccia Dumper") per _, dumper: = range dumpers dumper.Dump ( )

Conclusione

Go è un linguaggio di programmazione orientato agli oggetti in buona fede. Abilita la modellazione basata su oggetti e promuove la migliore pratica di utilizzo delle interfacce invece delle gerarchie di tipi concreti. Sono state fatte alcune insolite scelte sintattiche, ma nel complesso lavorare con tipi, metodi e interfacce è semplice, leggero e naturale. 

L'incorporamento non è molto puro, ma apparentemente il pragmatismo era all'opera e l'incorporamento era fornito invece della sola composizione per nome.