Introduzione
Scrivere codice flessibile, riutilizzabile e modulare è fondamentale per lo sviluppo di programmi versatili. Lavorare in questo modo garantisce che il codice sia più facile da mantenere evitando la necessità di apportare lo stesso cambiamento in diversi posti. Come raggiungere questo obiettivo varia da linguaggio a linguaggio. Per esempio, l’eredità è un approcio comune utilizzato nei linguaggi come Java, C++, C# e così via.
I sviluppatori possono anche raggiungere gli stessi obiettivi di progettazione attraverso la composizione. La composizione è un modo per combinare oggetti o tipi di dati in più complessi. Questo è l’approcio che Go utilizza per promuovere il riutilizzo del codice, la modularità e la flessibilità. Le interfacce in Go forniscono un metodo per organizzare composizioni complesse, e imparare come usarle permetterà di creare codice comune e riutilizzabile.
In questo articolo, impareremo come comporre tipi personalizzati che hanno comportamenti comuni, consentendoci di riutilizzare il nostro codice. Impareremo anche come implementare le interfacce per i nostri tipi personalizzati in modo da soddisfare le interfacce definite da un altro pacchetto.
Definizione di un Comportamento
Una delle implementazioni core della composizione è l’uso di interfacce. Un’interfaccia definisce un comportamento di un tipo. Una delle interfacce più comunemente usate nella libreria standard di Go è l’fmt.Stringer
interfaccia:
La prima riga di codice definisce un type
chiamato Stringer
. Poi dice che è un’interface
.Come definire una struct, Go usa le parentesi graffe ({}
) per circondare la definizione dell’interfaccia. In confronto alla definizione delle struct, noi solo definiamo il comportamento dell’interfaccia; ossia, “cosa può fare questo tipo”.
Nel caso dell’interfaccia Stringer
, l’unico comportamento è il metodo String()
. Il metodo non richiede argomenti e restituisce una stringa.
Poi, guardiamo qualche codice che ha il comportamento dell’fmt.Stringer
:
Il primo thing che facciamo è creare un nuovo tipo chiamato Article
. Questo tipo ha un Title
e un Author
campo e entrambi sono del tipo stringa data type:
Successivamente, definiamo un method
chiamato String
sul tipo Article
. Il metodo String
restituirà una stringa che rappresenta il tipo Article
:
Allora, nella nostra main
funzione, creiamo un’istanza del tipo Article
e la assegniamo alla variabile chiamata a
. Forniamo i valori di "Understanding Interfaces in Go"
per il campo Title
e "Sammy Shark"
per il campo Author
:
Successivamente, stampiamo il risultato del metodo String
chiamando fmt.Println
e passando il risultato della chiamata del metodo a.String()
:
Dopo aver eseguito il programma, vedrete l’output seguente:
OutputThe "Understanding Interfaces in Go" article was written by Sammy Shark.
Fino ad ora, non abbiamo usato un’interfaccia, ma abbiamo creato un tipo che ha un comportamento. quel comportamento corrisponde all’interfaccia fmt.Stringer
. Passando ora, vedremo come possiamo usare quel comportamento per rendere il nostro codice più riutilizzabile.
Definizione di un’Interfaccia
Ora che abbiamo definito il nostro tipo con il comportamento desiderato, possiamo osservare come usare quel comportamento.
Però, prima di farlo, considerate ciò che dovremmo fare se volessimo chiamare il metodo String
dal tipo Article
in una funzione:
package main
import "fmt"
type Article struct {
Title string
Author string
}
func (a Article) String() string {
return fmt.Sprintf("The %q article was written by %s.", a.Title, a.Author)
}
func main() {
a := Article{
Title: "Understanding Interfaces in Go",
Author: "Sammy Shark",
}
Print(a)
}
func Print(a Article) {
fmt.Println(a.String())
}
Nella presente codifica aggiungiamo una nuova funzione chiamata Print
che accetta un’Article
come argomento. Notare che la funzione Print
fa solo chiamare il metodo String
. Per questo motivo potremmo definire invece un interfaccia per passarla alla funzione:
package main
import "fmt"
type Article struct {
Title string
Author string
}
func (a Article) String() string {
return fmt.Sprintf("The %q article was written by %s.", a.Title, a.Author)
}
type Stringer interface {
String() string
}
func main() {
a := Article{
Title: "Understanding Interfaces in Go",
Author: "Sammy Shark",
}
Print(a)
}
func Print(s Stringer) {
fmt.Println(s.String())
}
Abbiamo creato un’interfaccia chiamata Stringer
:
L’interfaccia Stringer
ha solo un metodo, chiamato String()
che restituisce una stringa
. Un metodo è una funzione speciale definita nello stesso tipo di dati in Go. Al contrario di una funzione, un metodo può essere chiamato solo dall’istanza del tipo su cui è stato definito.
Riprovediamo quindi l’intestazione della funzione Print
ad accettare un Stringer
, e non un tipo concreto di Article
. Perché il compilatore sa che un’interfaccia Stringer
definisce il metodo String
, accetterà solo tipi che possiedono anche il metodo String
.
Ora possiamo usare la funzione Print
con qualsiasi cosa soddisfa l’interfaccia Stringer
. Creiamo un altro tipo per mostrarci questo:
package main
import "fmt"
type Article struct {
Title string
Author string
}
func (a Article) String() string {
return fmt.Sprintf("The %q article was written by %s.", a.Title, a.Author)
}
type Book struct {
Title string
Author string
Pages int
}
func (b Book) String() string {
return fmt.Sprintf("The %q book was written by %s.", b.Title, b.Author)
}
type Stringer interface {
String() string
}
func main() {
a := Article{
Title: "Understanding Interfaces in Go",
Author: "Sammy Shark",
}
Print(a)
b := Book{
Title: "All About Go",
Author: "Jenny Dolphin",
Pages: 25,
}
Print(b)
}
func Print(s Stringer) {
fmt.Println(s.String())
}
Aggiorniamo ora il tipo Book
. Anche lui ha definito il metodo String
. Questo significa che soddisfa anche l’interfaccia Stringer
. Per questo motivo, posso invocare anche la nostra funzione Print
:
OutputThe "Understanding Interfaces in Go" article was written by Sammy Shark.
The "All About Go" book was written by Jenny Dolphin. It has 25 pages.
Finora abbiamo mostrato come usarli solo con un’interfaccia. Tuttavia, un’interfaccia può avere più di uno stile definito. Proseguiremo osservando come possiamo rendere le nostre interfacce più flessibili dichiarando più metodi.
Multipli comportamenti nell’interfaccia
Un principio fondamentale della scrittura del codice Go è quello di scrivere tipi piccoli e concisi che si compongono per creare tipi più complessi. La stessa regola vale per la composizione delle interfacce. Per vedere come costruiamo una interfaccia, prima di tutto definire solo una interfaccia. Definiriamo due tipi, Circle
e Square
, che entrambi definiscono un metodo chiamato Area
. Questo metodo restituirà l’area geometrica rispettivamente dei loro risultati:
Dato che ogni tipo definisce il metodo Area
, possiamo creare un’interfaccia che definisca questo comportamento. Definiamo quindi l’interfaccia Sizer
come segue:
Poi definiamo una funzione chiamata Less
che accetta due argomenti Sizer
e restituisce il più piccolo:
Notiamo che non accettiamo solo i parametri come tipi Sizer
, ma anche il risultato come Sizer
! Ci significa che non restituzioniamo più un Square
o un Circle
, ma piuttosto l’interfaccia di Sizer
.
Infine stampiamo quale ha avuto l’area più piccola:
Output{Width:5 Height:10} is the smallest
Poi, aggiungeremo un altro comportamento a ciascun tipo. Questa volta aggiungeremo il metodo String()
che restituisce una stringa. questo soddisfarà l’interfaccia fmt.Stringer
:
package main
import (
"fmt"
"math"
)
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * math.Pow(c.Radius, 2)
}
func (c Circle) String() string {
return fmt.Sprintf("Circle {Radius: %.2f}", c.Radius)
}
type Square struct {
Width float64
Height float64
}
func (s Square) Area() float64 {
return s.Width * s.Height
}
func (s Square) String() string {
return fmt.Sprintf("Square {Width: %.2f, Height: %.2f}", s.Width, s.Height)
}
type Sizer interface {
Area() float64
}
type Shaper interface {
Sizer
fmt.Stringer
}
func main() {
c := Circle{Radius: 10}
PrintArea(c)
s := Square{Height: 10, Width: 5}
PrintArea(s)
l := Less(c, s)
fmt.Printf("%v is the smallest\n", l)
}
func Less(s1, s2 Sizer) Sizer {
if s1.Area() < s2.Area() {
return s1
}
return s2
}
func PrintArea(s Shaper) {
fmt.Printf("area of %s is %.2f\n", s.String(), s.Area())
}
Poiché sia il tipo Circle
che il tipo Square
implementano sia il metodo Area
che il metodo String
, ora possiamo creare un’altra interfaccia per descrivere quell’insieme più ampio di comportamenti. Per fare questo, creeremo un’interfaccia chiamata Shaper
. Composeremo questa dell’interfaccia Sizer
e dell’interfaccia fmt.Stringer
:
Nota: è considerato idiomatico cercare di chiamare la tua interfaccia finendo con er
, come in fmt.Stringer
, io.Writer
, ecc. questo è il motivo per cui abbiamo chiamato la nostra interfaccia Shaper
e non Shape
.
Ora possiamo creare una funzione chiamata PrintArea
che riceve come argomento un Shaper
. questo significa che possiamo chiamare entrambi i metodi sull’argomento passato per entrambi i metodi Area
e String
:
Se eseguiamo il programma, ricevermo il seguente output:
Outputarea of Circle {Radius: 10.00} is 314.16
area of Square {Width: 5.00, Height: 10.00} is 50.00
Square {Width: 5.00, Height: 10.00} is the smallest
Abbiamo ora visto come possiamo creare interfacce più piccole e costruirle ininterfacce più grandi come necessario. Anche se avremmo potuto cominciare con l’interfaccia più grande e passarla a tutte le nostre funzioni, è considerata la migliore pratica inviare solo l’interfaccia più piccola a una funzione che è necessaria. questotypicamente risulta in codice più chiaro, poiché tutto ciò che accetta una specifica interfaccia più piccola intende solo lavorare con quel comportamento definito.
Per esempio, se passassimo
Conclusione
Abbiamo visto come creare interfacce più piccole e costruirlle per ottenere interfacce più grandi consente di condividere solo quello che è necessario a una funzione o metodo. Abbiamo anche imparato che possiamo componer le nostre interfacce da altre interfacce, incluse quelle definite da pacchetti differenti, non solo da quelli del nostro pacchetto.
Se vuoi apprendere più sul linguaggio di programmazione Go, controlla tutta la serie Come scrivere codice in Go.
Source:
https://www.digitalocean.com/community/tutorials/how-to-use-interfaces-in-go