Go in paragone a altri linguaggi ben conosciuti

G

Go è un mix di vecchie e nuove idee, che con un approccio molto innovativo riesce ha a stabilire una connessione tra il vecchio e il nuovo modo di programmare. Molte persone pensano che Go non sia orientato verso il paradigma OOP, ma si sbagliano assolutamente e di seguito vi spiegherò il perchè.

Di seguito nell’articolo spiegherò la complessità del design OOP adottato da Go e i suoi concetti cardine come l’ereditarietà, il polimorfismo e l’incapsulamento

Go in paragone con altri linguaggi
Go non ha né classi, né oggetti, exceptions o templates. Adotta il GC ovvero il Garbage Collector e la built-in concurrency (ovvero concorrenza integrata). Il punto focale nella OOP ausata da Go è quello che ha differenza dei classici linguaggi OOP-Based come Java, Python e C++ e molti altri, Go non adotta una gerarchia per tipo.

Go e le sue caratteristiche OOP
Come detto prima Go non adopera classi, ma una sorta di strutture. In particolare, adopera gli structs, le quali sono strutture di tipo definito. Le struct (come metodi) si adoperano quasi come delle classi che usate negli altri linguaggi OOP.

Structs
Uno struct definisce solo lo stato, per esempio nella struct Persona, troverete un campo denominato “nome” di tipo string e un campo “eta” di tipo int. tramite questa struttura elencherete solamente il tipo, ma senza avere nessun tipo di comportamento o metodo applicato su di essi.

type Persona struct {
  nome string   
  eta int 
}

 

Metodi
I metodi sono funzioni che operano su tipi particolari, per tanto hanno una clausola di ritorno d’azione che vi indica su quale tipo operano. Ecco un metodo Info() che agisce su Persona e ritorna il suo stato:

func (p Persona) Info() {
  fmt.Printf("Mi chiamo %s, e ho %d anni\n", p.nome, p.eta)
}

Questa è una sintassi insolita, ma è molto esplicita e chiara (a differenza dell’implicito “this” o del confondente “self” di Python).

 

L’ inserimento delle specie
È possibile inserire tipi anonimi all’interno di altri tipi anonimo. Quando si inserisce uno struct senza nome, la struct inserita fornisce lo stato (e metodi) direttamente alla struct di inserimento. Ad esempio, la struct Programmatore ha una struttura di tipo Persona anonima al suo interno, ciò significa che un Programmatore è una Persona.

type Programmatore struct {
  Persona
  linguaggio string
}

Ora, con un’istanza di Programmatore, è possibile accedere direttamente ai suoi attributi nome e eta.

ary := &Programmatore{
    Persona{"Ary", 21, },
    "Golang"
}

 

Interfaccie ( o Interfaces )
Interfaces (o interfaccie) sono il segno distintivo del modello OOP di Go.Le Interfaccie sono tipi che dichiarano un insieme di metodi. A differenza di interfaccie in altri linguaggi, queste non hanno alcuna implementazione.
Gli oggetti che implementano tutti i metodi di un interfaccia implementano automaticamente l’interfaccia. Non c’è alcuna eredità o sottoclasse o implementazioni. Nel seguente frammento di codice, digitare Foo implementa l’interfaccia Fooer (per convenzione, i nomi delle interfacce Go si concludono con “er”) una convenzione dovuta all’inglese.

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

 

OOP in Go
Vediamo come Go schiera i pilastri della OOP: incapsulamento, eredità e polimorfismo, che come ben sappiamo sono i pilastri dei linguaggi OOP più conosciuti.

Internamente, gli oggetti sono costruzioni linguistiche che hanno lo stato e il comportamento che operano sullo stato e lo espone selettivamente ad altre parti del programma.

Incapsulamento
Go incapsula tutto a livello di package. I nomi che iniziano con una lettera minuscola sono visibili solo in quel package, mentre è possibile nascondere tutto il resto in un package privato e rendere visibili solo interfacce, funzioni e/o tipi specifici.
Nel codice che segue per nascondere il tipo di Foo sopra e esporne solo l’interfaccia si può  rinominarla foo minuscolo e fornire una funzione NewFoo() che fornisce l’interfaccia pubblica Fooer:

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

Quindi codice da un altro package può utilizzare NewFoo() e ottenere l’accesso ad un’interfaccia di Fooer implementata dallo specie di foo interno:

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

 

L’ Eredità in Go
L’eredità o la sottoclasse è sempre stata una questione controversa. Ci sono molti problemi nell’implementazione dell’ereditarietà(a differenza dell’eredità di interfaccia). L’eredità multipla di C++ e Python e altri linguaggi soffre del problema del diamante:

(https://en.wikipedia.org/wiki/Multiple_inheritance)

ma anche l’eredità di suo non è un concetto così semplice e lineare.

I linguaggi moderni e il pensiero orientato all’oggetto ora favoriscono la composizione sull’eredità. Go non ha alcuna gerarchia di specie e permette di condividere i dettagli di implementazione tramite la composizione. Go, in un modo molto strano (probabilmente originato da preoccupazioni pragmatiche), consente la composizione anonima attraverso l’inserimento.

Per qualsaisi scopo, la composizione d’inserire una specie anonima equivale all’eredità di implementazione. Una struct inserita è altrettanto fragile come una classe di base. È inoltre possibile inserire un’interfaccia ed e’ equivalente a ereditare da un’interfaccia in linguaggi come Java o C++. Può anche portare ad un errore di runtime che non viene rilevato al momento della compilazione se lo specie di inserimento non implementa tutti i metodi del interfaccia.

Qui SuperFoo ha l’interfaccia Fooer al suo interno, ma non implementa i suoi metodi. Il compilatore di Go andrà a creare un nuovo SuperFoo e chiamerà i metodi di Fooer, ma ovviamente non finiranno a runtime. Di seguito quanto scritto sotto:

type SuperFooer struct {
  Fooer
}
 
func main() {
  s := SuperFooer{}

L’esecuzione di questo programma provoca una panico o un errore di tipo panic in Go:

 panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x28 pc=0x2a78]
 
goroutine 1 [running]:
panic(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
 
Process finished with exit code 1

 

Polimorfismo

Come ben sappiamo il polimorfismo è l’essenza della OOP e tale pilastro permette di prendere più oggetti e trattarli in modo uniforme per operare sulla stessa interfaccia. Le interfacce di accesso forniscono questa capacità in modo molto diretto ed intuitivo.
Ecco un buon esempio  in cui vengono creati e memorizzati più Persone (e una porta!) in un slice che implementano l’interfaccia Informatore e quindi viene richiamato il metodo Info(). Potete notare più di una sintassi per instanziare un oggetto.

package main
 
import "fmt"
 
type Persona struct {
  nome string
  eta int
}
 
func (p *Persona) Info() {
  fmt.Printf("Mi chiamo %s, e ho %d anni\n", p.nome, p.eta)
}
 
func (p Persona) Info() {
  fmt.Printf("Mi chiamo %s, e ho %d anni\n", p.nome, p.eta)
}
 
type Programmatore struct {
  Persona
  linguaggio string
}
 
func (pg Programmatore) Info() {
  fmt.Printf("Mi chiamo %s, ho %d anni e programmo in %s\n",
    pg.nome,
    pg.eta,
    pg.linguaggio)
}
 
type Insegnante struct {
  Persona
}
 
type Gopher struct {
  Programmatore
}
 
type Pythonista struct {
  Programmatore
}
 
func NewPythonista(linguaggio string) *Pythonista {
  gabbo := &Pythonista{
    Programmatore{
      Persona{
        "Gabrielle",
        17,
      },
      linguaggio,
    },
  }
  return gabbo
}
 
type Informatore interface {
  Info()
}
 
type Porta struct {
  Densita' int
  Colore    string
}
 
func (d Porta) Info() {
  fmt.Printf("Porta => Densita: %d, Color: %s", d.Densita, d.Colore)
}
 
func main() {
  persona := &Persona{
    "Dario",
    25,
  }
 
  gof := Gopher{
    Persona{
      "Ary",
      21,
    },
  }
 
  matt := &Pythonista{
    Programmatore{
      Persona{
        "Matteo",
        25,
      },
      "Python",
    },
  }
 
  filo := NewPythonista("Python")
 
  porta := &Porta{3, "rossa"}
 
  Info(persona)
  persoba.Info()
  gof.Info()
  matt.Info()
  filo.Info()
 
  persone := []Persona{
    *persona,
    gof.Persona,
    matt.Persona,
    filo.Persona}
  fmt.Println("Info() attraverso la specie Persona inserita in se'")
  for _, persona := range persone {
    persona.Info()
  }
 
  informatori := []Informatore{persona, gof, matt, filo, porta}
  fmt.Println("Info() attraverso l'interfaccia Informatore")
  for _, informatore := range informatori {
    informatore.Info()
  }
}

 

 In conclusione
Go è un linguaggio di programmazione OOP vero. Consente di modellare gli oggetti e promuove la migliore pratica di utilizzare le interfacce anziché di gerarchie di specie concreto. Go ha fatto alcune scelte sintattiche insolito, ma nel complesso lavorare con le specie, metodi e interfacce è semplice, leggero e naturale.

Quindi Go è un linguaggio di programmazione orientato agli oggetti come avete potuto vedere. Consente di modellare oggetti e migliorarne il loro uso tramite le interfacce. Questo linguaggio fà alcune scelte sintattiche insolite rispetto ad altri linguaggi, ma che comunque sia permettono di lavorare con i suoi strumenti in modo leggero ed efficace.

Publicata da Ary Homebrew

A proposito di me

Andrew Raieta

Smanettone e appassionato di hardware fin da piccolo a 13 anni monta il suo primo PC, così iniziando ad esplorare quel mondo meraviglioso di linux ed essere sempre di più un utente attivo. Attualmetne studente di computer science presso l'università la Sapienza e nel frattempo decide di dedicarsi allo studio per formarsi come data science. Nell'ottobre del 2017 decide di scrivere sul blog per far scoprire il mondo di linux e condividere interessanti articoli tecnici sullo scripting.

Di Andrew Raieta

I nostri Partner

Gli articoli più letti

Articoli recenti

Commenti recenti