Cum să definiți și să implementați o interfață Go

Modelul Go orientat pe obiect se învârte în jurul interfețelor. Eu personal cred că interfețele sunt cele mai importante construcții de limbi și deci toate deciziile de proiectare ar trebui să se concentreze mai întâi pe interfețe. 

În acest tutorial, veți afla ce este o interfață, luați-o pe interfețe, cum să implementați o interfață în Go și, în sfârșit, limitările interfețelor față de contracte.

Ce este o interfață de navigare?

Interfața A Go este un tip care constă dintr-o colecție de semnături de metode. Iată un exemplu de interfață Go:

tip Serializable interfață Serialize () (șir, eroare) Deserialize (s string) eroare

serializabil interfața are două metode. Serialize () metoda nu ia argumente și returnează un șir și o eroare, și Deserializati () metoda ia un șir și returnează o eroare. Dacă ați fost în jurul blocului, serializabil interfața este, probabil, cunoscută de dvs. din alte limbi, și puteți ghici că Serialize () metoda returnează o versiune serializată a obiectului țintă care poate fi reconstituită prin apelare Deserializati () și trecând rezultatul apelului inițial la Serialize ().

Rețineți că nu este necesar să furnizați cuvântul cheie "func" la începutul fiecărei declarații de metodă. Go deja știe că o interfață poate conține numai metode și nu are nevoie de nici un ajutor de la tine spunându-i că este o "func".

Mergeți la cele mai bune practici ale interfețelor

Go interfețele sunt cel mai bun mod de a construi coloana vertebrală a programului. Obiectele ar trebui să interacționeze între ele prin interfețe și nu prin obiecte concrete. Acest lucru înseamnă că ar trebui să construiți un model de obiect pentru programul dvs. care să conŃină doar interfeŃe și tipuri de bază sau obiecte de date (structuri ale căror membri sunt tipuri de bază sau alte obiecte de date). Iată câteva dintre cele mai bune practici pe care ar trebui să le urmăriți cu ajutorul interfețelor.

Intenții clare

Este important ca intenția din spatele fiecărei metode și succesiunea apelurilor să fie clară și bine definită atât pentru apelanți, cât și pentru implementatori. Nu există suport la nivel de limbă în Go pentru asta. O să discut mai mult în secțiunea "Interfață vs. contract" mai târziu.

Dependența de injecție

Injectarea dependenței înseamnă că un obiect care interacționează cu un alt obiect printr-o interfață va primi interfața din exterior ca o funcție de argument sau de metodă și nu va crea obiectul (sau nu va apela o funcție care returnează obiectul din beton). Rețineți că acest principiu se aplică și funcțiilor independente și nu doar obiectelor. O funcție ar trebui să primească toate dependențele sale ca interfețe. De exemplu:

tip SomeInterface DoSomethingAesome () func foo (s SomeInterface) s.DoSomethingAwesome () 

Acum, numiți funcție foo () cu implementări diferite ale SomeInterface, și va funcționa cu toți.

Unitati de productie

Evident, cineva trebuie să creeze obiectele concrete. Aceasta este lucrarea obiectelor dedicate fabricii. Uzinele sunt utilizate în două situații:

  1. La începutul programului, fabricile sunt folosite pentru a crea toate obiectele de lungă durată a căror durată de viață se potrivește de obicei cu durata de viață a programului.
  2. În timpul runtime-ului programului, de multe ori obiectele trebuie să instanțieze obiecte în mod dinamic. În acest scop ar trebui folosite și fabrici.

Este adesea utilă furnizarea de interfețe dinamice din fabrică obiectelor pentru a susține modelul de interacțiune numai cu interfața. În exemplul următor, definesc a Widget interfață și a WidgetFactory interfață care returnează a Widget interfață de la ei CreateWidget () metodă. 

PerformMainLogic () funcția primește a WidgetFactory interfață de la apelantul său. Acum este capabil să creeze în mod dinamic un nou widget bazat pe spec. De widget și să îl invocă Widgetize () fără a cunoaște nimic despre tipul său concret (ceea ce struct implementează interfața).

(widgetSpec: widgetSpec = widgetSpec = widgetSpec) WidgetFactory (widgetSpec string) widgetSpec = widgetSpec = widgetSpec widget WidgetFactory ) 

testabilitatea

Testabilitatea este una dintre cele mai importante practici pentru dezvoltarea corectă a software-ului. Go interfețele sunt cel mai bun mecanism pentru a sprijini testabilitatea în programele Go. Pentru a testa o funcție sau o metodă, trebuie să controlați și / sau să măsurați toate intrările, ieșirile și efectele secundare ale funcției testate. 

Pentru codul non-trivial care comunică direct cu sistemul de fișiere, ceasul de sistem, bazele de date, serviciile de la distanță și interfața cu utilizatorul, este foarte dificil de realizat. Dar, dacă toate interacțiunile parcurg interfețele, este foarte ușor să machetați și să gestionați dependențele externe. 

Luați în considerare o funcție care rulează numai la sfârșitul lunii și rulează un anumit cod pentru a curăța tranzacțiile rău. Fără interfețe, ar trebui să mergi la măsuri extreme, cum ar fi schimbarea ceasului computerului real pentru a simula sfârșitul lunii. Cu o interfață care oferă ora curentă, trebuie doar să treci un struct pe care ai setat ora dorită.

În loc să importați timp și chemând direct time.Now (), puteți trece o interfață cu un Acum() metodă care, în producție, va fi pusă în aplicare prin transmiterea către time.Now (), dar în timpul testelor va fi implementat de un obiect care returnează un timp fix pentru a îngheța mediul de testare.

Folosind o interfață Go

Folosirea interfeței Go este complet simplă. Pur și simplu numiți metodele sale ca și cum ai numi orice altă funcție. Marea diferență este că nu puteți fi sigur ce se va întâmpla, deoarece pot exista diferite implementări.

Implementarea unei interfețe Go

Go interfețele pot fi implementate ca metode pentru structuri. Luați în considerare următoarea interfață:

tip Interfață Shape GetPerimeter () int GetArea () int 

Iată două implementări concrete ale interfeței Shape:

() * () * () * * * * * * * * * * * * * * uint func (r * dreptunghi) GetPerimetru () uint return (r.width + r.height) 

Pătratul și dreptunghiul implementează calculele în mod diferit pe baza câmpurilor și a proprietăților geometrice. Următoarea mostră de cod demonstrează modul în care se folosește o felie de interfață Shape cu obiecte concrete care implementează interfața și apoi se repetă pe felie și se invocă GetArea () metoda fiecărei forme pentru a calcula suprafața totală a tuturor formelor.

func general () shapes: = [] Formă & Pătrat latură: 2, și dreptunghi lățime: 3, înălțime: 5 var totalArea uint pentru _, shape: = range shapes totalArea + = shape.GetArea  fmt.Println ("Suprafața totală:", totalArea) 

Implementarea bazei

În multe limbi de programare, există un concept de clasă de bază care poate fi utilizat pentru a implementa funcționalitatea partajată utilizată de toate sub-clasele. Du-te (pe drept) preferă compoziția la moștenire. 

Puteți obține un efect similar prin încorporarea unui struct. Să definim a ascunzătoare struct care poate stoca valoarea calculelor anterioare. Atunci când o valoare este extrasă din caz, se imprimă de asemenea pe ecran "cache hit", iar atunci când valoarea nu este cazul, se imprimă "cache miss" și se întoarce -1 (valorile valide sunt numere nesemnate).

tip cache structură cache map [string] uint func (c * Cache) GetValue (nume șir) int value, ok: = c.cache [nume] if ok fmt.Println valoare else fmt.Println ("cache miss") return -1) func (c * Cache) SetValue (șir de nume, valoare uint) c.cache [name]

Acum, voi încorpora această memorie cache în formele Square și Rectangle. Rețineți că punerea în aplicare a GetPerimeter () și GetArea () acum verifică mai întâi memoria cache și calculează valoarea numai dacă nu este în memoria cache.

() = () = () () () () () () ()) return uint (valoare) func (s * Pătrat) GetArea () uint value: = s.GetValue (" ()) return uint (value) tip Rectangle struct Lățimea cache uint height uint func (r * Rectangle) GetPerimeter () uint value : = r.GetValue ("perimetru") dacă valoarea == -1 value = int (r.width + r.height) * 2 r.SetValue ("perimetru"  func (r * dreptunghi) GetArea () uint value: = r.GetValue ("area") if value == -1 value = int (r.width * r.height) r.SetValue uint (valoare)) retur uint (valoare)

În cele din urmă, principal() funcția calculează suprafața totală de două ori pentru a vedea efectul cache.

func () [shapes: = [] Formă & Pătrat Cache cache: make (map [string] uint), 2, and Rectangle cache   var totalArea uint pentru _, forma: = gama de forme totalArea + = shape.GetArea () fmt.Println ("Suprafața totală", totalArea) totalArea = 0 pentru _, shape: shape.GetArea () fmt.Println ("Suprafața totală:", totalArea) 

Aici este rezultatul:

cache dor de cache dor Total domeniu: 19 cache hit hit cache hit Total area: 19

Interfață vs. contract

Interfețele sunt excelente, însă nu se asigură că structurarea implementării interfeței îndeplinește de fapt intenția din spatele interfeței. Nu există nici un fel în Go să-și exprime această intenție. Tot ce trebuie să specificați este semnătura metodelor. 

Pentru a depăși acest nivel de bază, aveți nevoie de un contract. Un contract pentru un obiect specifică exact ceea ce face fiecare metodă, ce efecte secundare sunt efectuate și care este starea obiectului în fiecare moment. Contractul există întotdeauna. Singura întrebare este dacă este explicită sau implicită. În cazul API-urilor externe, contractele sunt critice.

Concluzie

Modelul de programare Go a fost proiectat în jurul interfețelor. Puteți programa în Go fără interfețe, dar veți pierde multe beneficii. Vă recomandăm să profitați din plin de interfețele din aventurile de programare Go.

Cod