Je Go objektno usmerjen? Je lahko? Go (ali “Golang”) je programski jezik po OOP, ki si izposoja svojo strukturo (paketi, tipi, funkcije) iz družine jezikov Algol / Pascal / Modula. Kljub temu je v Pojdi , objektno usmerjeni vzorci so še vedno koristni za strukturiranje programa na jasen in razumljiv način. Ta vadnica za Golang bo vzel preprost primer in prikazal, kako uporabiti koncepte funkcij vezave na tipe (aka razrede), konstruktorje, podtipiranje, polimorfizem, vbrizgavanje odvisnosti in testiranje z lažnimi deli.
Edinstveno identifikacijska številka vozila vsakega avtomobila poleg 'tekoče' (tj. serijske) številke vsebuje tudi podatke o avtomobilu, kot so proizvajalec, tovarna proizvajalca, model avtomobila in če se vozi z leve ali desne strani.
Funkcija za določanje kode proizvajalca je lahko videti takole:
package vin func Manufacturer(vin string) string { manufacturer := vin[: 3] // if the last digit of the manufacturer ID is a 9 // the digits 12 to 14 are the second part of the ID if manufacturer[2] == '9' { manufacturer += vin[11: 14] } return manufacturer }
Tukaj je test, ki dokazuje, da primer VIN deluje:
package vin_test import ( 'vin-stages/1' 'testing' ) const testVIN = 'W09000051T2123456' func TestVIN_Manufacturer(t *testing.T) { manufacturer := vin.Manufacturer(testVIN) if manufacturer != 'W09123' { t.Errorf('unexpected manufacturer %s for VIN %s', manufacturer, testVIN) } }
Torej ta funkcija deluje pravilno, če dobi pravi vnos, vendar ima nekaj težav:
panic
.Da bi rešili te težave, ga bomo preoblikovali z uporabo objektno usmerjenih vzorcev.
Prvo preoblikovanje je, da VIN-i postanejo lastni in povežejo Manufacturer()
funkcijo. Tako je namen funkcije bolj jasen in preprečuje nepremišljeno uporabo.
v katerem jeziku so napisani neskladni roboti
package vin type VIN string func (v VIN) Manufacturer() string { manufacturer := v[: 3] if manufacturer[2] == '9' { manufacturer += v[11: 14] } return string(manufacturer) }
Nato prilagodimo test in predstavimo težavo z neveljavnimi VIN-i:
package vin_test import( 'vin-stages/2' 'testing' ) const ( validVIN = vin.VIN('W0L000051T2123456') invalidVIN = vin.VIN('W0') ) func TestVIN_Manufacturer(t * testing.T) { manufacturer := validVIN.Manufacturer() if manufacturer != 'W0L' { t.Errorf('unexpected manufacturer %s for VIN %s', manufacturer, validVIN) } invalidVIN.Manufacturer() // panic! }
Zadnja vrstica je bila vstavljena, da bi prikazala, kako sprožiti panic
med uporabo Manufacturer()
funkcijo. Zunaj testa bi to zrušilo izvajani program.
oblikovalsko razmišljanje lahko zagotovi postopek za ________.
Da bi se izognili panic
pri obdelavi neveljavnega VIN-a je mogoče Manufacturer()
dodati preverjanja veljavnosti sama funkcija. Slabosti so, da bi se preverjanja opravila pri vsakem klicu Manufacturer()
funkcijo in da bi bilo treba uvesti vrnjeno vrednost napake, zaradi česar bi bila vrnjena vrednost nemogoča neposredno uporabiti brez vmesne spremenljivke (npr. kot ključ zemljevida).
Elegantnejši način je vstaviti preverjanja veljavnosti v konstruktor za VIN
vnesite, tako da Manufacturer()
funkcija je poklicana samo za veljavne VIN-ove in ne potrebuje preverjanj in obdelave napak:
package vin import 'fmt' type VIN string // it is debatable if this func should be named New or NewVIN // but NewVIN is better for greping and leaves room for other // NewXY funcs in the same package func NewVIN(code string)(VIN, error) { if len(code) != 17 { return '', fmt.Errorf('invalid VIN %s: more or less than 17 characters', code) } // ... check for disallowed characters ... return VIN(code), nil } func (v VIN) Manufacturer() string { manufacturer := v[: 3] if manufacturer[2] == '9' { manufacturer += v[11: 14] } return string(manufacturer) }
Seveda dodamo test za NewVIN
funkcijo. Konstruktor je zdaj zavrnil neveljavne VIN-ove:
package vin_test import ( 'vin-stages/3' 'testing' ) const ( validVIN = 'W0L000051T2123456' invalidVIN = 'W0' ) func TestVIN_New(t *testing.T) { _, err := vin.NewVIN(validVIN) if err != nil { t.Errorf('creating valid VIN returned an error: %s', err.Error()) } _, err = vin.NewVIN(invalidVIN) if err == nil { t.Error('creating invalid VIN did not return an error') } } func TestVIN_Manufacturer(t *testing.T) { testVIN, _ := vin.NewVIN(validVIN) manufacturer := testVIN.Manufacturer() if manufacturer != 'W0L' { t.Errorf('unexpected manufacturer %s for VIN %s', manufacturer, testVIN) } }
Preskus za Manufacturer()
funkcija lahko zdaj izpusti testiranje neveljavnega VIN-a, ker bi ga NewVIN
že zavrnil konstruktor.
Nato želimo razlikovati med evropskimi in neevropskimi VIN. Eden od pristopov bi bil razširitev VIN type
do struct
in shranite, ali je VIN evropski ali ne, s tem pa izboljšajte konstruktor:
type VIN struct { code string european bool } func NewVIN(code string, european bool)(*VIN, error) { // ... checks ... return &VIN { code, european }, nil }
Elegantnejša rešitev je ustvariti podvrsto VIN
za evropske VIN. Tu je zastava implicitno shranjena v informacijah o tipu in Manufacturer()
funkcija za neevropske VIN postane lepa in jedrnata:
package vin import 'fmt' type VIN string func NewVIN(code string)(VIN, error) { if len(code) != 17 { return '', fmt.Errorf('invalid VIN %s: more or less than 17 characters', code) } // ... check for disallowed characters ... return VIN(code), nil } func (v VIN) Manufacturer() string { return string(v[: 3]) } type EUVIN VIN func NewEUVIN(code string)(EUVIN, error) { // call super constructor v, err := NewVIN(code) // and cast to subtype return EUVIN(v), err } func (v EUVIN) Manufacturer() string { // call manufacturer on supertype manufacturer := VIN(v).Manufacturer() // add EU specific postfix if appropriate if manufacturer[2] == '9' { manufacturer += string(v[11: 14]) } return manufacturer }
V jezikih OOP, kot je Java, bi pričakovali podvrsto EUVIN
biti uporaben na vseh mestih, kjer VIN
vrsta je določena. Na žalost to v Golang OOP ne deluje.
package vin_test import ( 'vin-stages/4' 'testing' ) const euSmallVIN = 'W09000051T2123456' // this works! func TestVIN_EU_SmallManufacturer(t *testing.T) { testVIN, _ := vin.NewEUVIN(euSmallVIN) manufacturer := testVIN.Manufacturer() if manufacturer != 'W09123' { t.Errorf('unexpected manufacturer %s for VIN %s', manufacturer, testVIN) } } // this fails with an error func TestVIN_EU_SmallManufacturer_Polymorphism(t *testing.T) { var testVINs[] vin.VIN testVIN, _ := vin.NewEUVIN(euSmallVIN) // having to cast testVIN already hints something is odd testVINs = append(testVINs, vin.VIN(testVIN)) for _, vin := range testVINs { manufacturer := vin.Manufacturer() if manufacturer != 'W09123' { t.Errorf('unexpected manufacturer %s for VIN %s', manufacturer, testVIN) } } }
To vedenje je mogoče razložiti z namerno odločitvijo razvojne skupine Go, da ne podpira dinamične vezave za vrste, ki niso vmesniki. Prevajalniku omogoča, da ve, katera funkcija bo poklicana v času prevajanja, in se izogne dodatnim stroškom pošiljanja dinamične metode. Ta izbira tudi odsvetuje uporabo dedovanja kot splošnega vzorca sestave. Namesto tega so vmesniki prava pot (pardon).
Prevajalnik Go tip obravnava kot izvedbo vmesnika, ko izvaja deklarirane funkcije (račje tipkanje). Za uporabo polimorfizma torej VIN
type pretvori v vmesnik, ki ga izvajata splošni in evropski tip VIN. Upoštevajte, da ni nujno, da je evropski tip VIN podtip splošnega.
package vin import 'fmt' type VIN interface { Manufacturer() string } type vin string func NewVIN(code string)(vin, error) { if len(code) != 17 { return '', fmt.Errorf('invalid VIN %s: more or less than 17 characters', code) } // ... check for disallowed characters ... return vin(code), nil } func (v vin) Manufacturer() string { return string(v[: 3]) } type vinEU vin func NewEUVIN(code string)(vinEU, error) { // call super constructor v, err := NewVIN(code) // and cast to own type return vinEU(v), err } func (v vinEU) Manufacturer() string { // call manufacturer on supertype manufacturer := vin(v).Manufacturer() // add EU specific postfix if appropriate if manufacturer[2] == '9' { manufacturer += string(v[11: 14]) } return manufacturer }
Preizkus polimorfizma zdaj poteka z rahlo spremembo:
// this works! func TestVIN_EU_SmallManufacturer_Polymorphism(t *testing.T) { var testVINs[] vin.VIN testVIN, _ := vin.NewEUVIN(euSmallVIN) // now there is no need to cast! testVINs = append(testVINs, testVIN) for _, vin := range testVINs { manufacturer := vin.Manufacturer() if manufacturer != 'W09123' { t.Errorf('unexpected manufacturer %s for VIN %s', manufacturer, testVIN) } } }
Dejansko lahko obe vrsti VIN zdaj uporabljate na vseh mestih, ki določajo VIN
vmesnika, saj sta obe vrsti v skladu z VIN
definicija vmesnika.
Nenazadnje se moramo odločiti, ali je VIN evropski ali ne. Recimo, da smo našli zunanji API, ki nam daje te informacije, in smo zanj ustvarili odjemalca:
kaj naredi dobro bazo podatkov
package vin type VINAPIClient struct { apiURL string apiKey string // ... internals go here ... } func NewVINAPIClient(apiURL, apiKey string) *VINAPIClient { return &VINAPIClient {apiURL, apiKey} } func (client *VINAPIClient) IsEuropean(code string) bool { // calls external API and returns correct value return true }
Izdelali smo tudi storitev, ki obravnava VIN-ove in jih lahko zlasti ustvari:
package vin type VINService struct { client *VINAPIClient } type VINServiceConfig struct { APIURL string APIKey string // more configuration values } func NewVINService(config *VINServiceConfig) *VINService { // use config to create the API client apiClient := NewVINAPIClient(config.APIURL, config.APIKey) return &VINService {apiClient} } func (s *VINService) CreateFromCode(code string)(VIN, error) { if s.client.IsEuropean(code) { return NewEUVIN(code) } return NewVIN(code) }
Kot kaže spremenjeni test, to deluje v redu:
func TestVIN_EU_SmallManufacturer(t *testing.T) { service := vin.NewVINService( & vin.VINServiceConfig {}) testVIN, _ := service.CreateFromCode(euSmallVIN) manufacturer := testVIN.Manufacturer() if manufacturer != 'W09123' { t.Errorf('unexpected manufacturer %s for VIN %s', manufacturer, testVIN) } }
Edino vprašanje je, da test zahteva povezavo v živo z zunanjim API-jem. To je žalostno, saj je API lahko brez povezave ali pa preprosto nedosegljiv. Tudi klicanje zunanjega API-ja zahteva čas in lahko stane.
Ker je rezultat klica API znan, bi ga bilo mogoče zamenjati z lažnim. Na žalost je v zgornji kodi VINService
sam ustvari odjemalca API, zato ga ni enostavno nadomestiti. Da bi bilo to mogoče, je treba odjemalca API vstaviti v VINService
. To pomeni, da ga je treba ustvariti, preden pokličete VINService
konstruktor.
Tu je smernica za Golang OOP noben konstruktor ne sme poklicati drugega konstruktorja . Če se to temeljito uporabi, bo vsak posamezen uporabljeni program ustvarjen na najvišji ravni. Običajno je to zagonska funkcija, ki ustvari vse potrebne predmete tako, da pokliče njihove konstruktorje v ustreznem vrstnem redu in izbere primerno izvedbo za predvideno funkcionalnost programa.
kako ustvariti telegram bot -
Prvi korak je, da VINAPIClient
vmesnik:
package vin type VINAPIClient interface { IsEuropean(code string) bool } type vinAPIClient struct { apiURL string apiKey string // .. internals go here ... } func NewVINAPIClient(apiURL, apiKey string) *VINAPIClient { return &vinAPIClient {apiURL, apiKey} } func (client *VINAPIClient) IsEuropean(code string) bool { // calls external API and returns something more useful return true }
Nato lahko novo stranko vbrizgamo v VINService
:
package vin type VINService struct { client VINAPIClient } type VINServiceConfig struct { // more configuration values } func NewVINService(config *VINServiceConfig, apiClient VINAPIClient) *VINService { // apiClient is created elsewhere and injected here return &VINService {apiClient} } func (s *VINService) CreateFromCode(code string)(VIN, error) { if s.client.IsEuropean(code) { return NewEUVIN(code) } return NewVIN(code) }
S tem je zdaj mogoče uporabiti Lažna odjemalca API za test. Poleg tega, da se med preizkusi izogiba klicem zunanjega API-ja, lahko model deluje tudi kot sonda za zbiranje podatkov o uporabi API-ja. V spodnjem primeru samo preverimo, ali IsEuropean
funkcija se dejansko imenuje.
package vin_test import ( 'vin-stages/5' 'testing' ) const euSmallVIN = 'W09000051T2123456' type mockAPIClient struct { apiCalls int } func NewMockAPIClient() *mockAPIClient { return &mockAPIClient {} } func (client *mockAPIClient) IsEuropean(code string) bool { client.apiCalls++ return true } func TestVIN_EU_SmallManufacturer(t *testing.T) { apiClient := NewMockAPIClient() service := vin.NewVINService( & vin.VINServiceConfig {}, apiClient) testVIN, _ := service.CreateFromCode(euSmallVIN) manufacturer := testVIN.Manufacturer() if manufacturer != 'W09123' { t.Errorf('unexpected manufacturer %s for VIN %s', manufacturer, testVIN) } if apiClient.apiCalls != 1 { t.Errorf('unexpected number of API calls: %d', apiClient.apiCalls) } }
Ta test je uspešno opravljen, saj je naš IsEuropean
sonda se med klicem na CreateFromCode
zažene enkrat.
Kritiki bi lahko rekel: 'Zakaj ne bi uporabljal Jave, če vseeno uporabljaš OOP?' No, ker dobite vse druge čudovite prednosti Go, hkrati pa se izognete VM / JIT, ki ne potrebuje virov, prekleti ogrodji z anotacijskim vudujem, ravnanjem z izjemami in odmori za kavo med izvajanjem testov (slednje bi lahko za nekatere predstavljalo težavo).
Z zgornjim primerom je jasno, kako lahko z objektno usmerjenim programiranjem v programu Go dobimo bolj razumljivo in hitreje delujočo kodo v primerjavi z navadno, nujno izvajanje . Čeprav Go ni mišljen kot jezik OOP, ponuja orodja, potrebna za to strukturirati aplikacijo na objektno usmerjen način. Skupaj z združevanjem funkcionalnosti v pakete je mogoče OOP v Golangu uporabiti za zagotovitev modulov za večkratno uporabo kot gradnikov za velike aplikacije .
Kot Googlov partner v oblaku so podjetjem na voljo strokovnjaki, certificirani z Googlom ApeeScape na zahtevo za njihove najpomembnejše projekte.
Golang (ali preprosto 'Go') je jezik za splošno uporabo, ki je primeren za razvoj zapletenih sistemskih orodij in API-jev. Z avtomatskim upravljanjem pomnilnika, sistemom statičnega tipa, vgrajenim sočasnim delovanjem in bogato spletno usmerjeno izvajalno knjižnico je še posebej uporaben za porazdeljene sisteme in storitve v oblaku.
c ++ vključuje druge datoteke cpp
Golang in njegovi izvajalni paketi so napisani v programu Go. Do Golang 1.5, ki je bil izdan leta 2015, so bili prevajalnik in deli izvajalnega okolja napisani v jeziku C.
Gradniki Golanga so vrste, funkcije in paketi - za razliko od razredov v objektno usmerjenih jezikih, kot je Java. Na voljo pa so trije od štirih konceptov OOP (inkapsulacija, abstrakcija in polimorfizem), manjkajočo hierarhijo tipa pa nadomeščajo vmesniki in tipkanje rac.
Golang je jezik, ki ga upravlja pomnilnik, z zmogljivimi primitivi za običajne podatkovne strukture in sočasno programiranje. Predeluje se neposredno v strojno kodo in tako ponuja C-podobne zmogljivosti in učinkovitost virov. Njegova hitra priprava in vključeni preizkusni prostori spodbujajo produktivnost razvijalcev.
Golang se veselo razvija in je nedavno praznoval 10. obletnico. Trenutno po vsem svetu približno 1 milijon aktivnih razvijalcev Golanga uporablja Golang za vse vrste projektov. Prav tako je v nastajanju Golang 2.0, ki bo Gophersom (tj. Širši skupnosti razvijalcev Go) prinesel zanimive nove funkcije.)
Go je razvil Google za nadomestitev interne uporabe Pythona, C ++ in drugih sistemskih jezikov. Jezikovno jedro, ki je bilo prvič izdano leta 2009, se posodablja vsakih šest mesecev, hkrati pa ohranja stabilen programski vmesnik. Go je odprtokoden in ima bogato skupnost, ki aktivno sodeluje pri njegovem razvoju.