Onko Go olio-suuntautunut? Voiko se olla? Go (tai “Golang”) on OOP: n jälkeinen ohjelmointikieli, joka lainaa sen rakenteen (paketit, tyypit, toiminnot) Algol / Pascal / Modula -kieliperheestä. Siitä huolimatta Mennä , olio-suuntautuneet mallit ovat edelleen hyödyllisiä ohjelman jäsentämiseksi selkeällä ja ymmärrettävällä tavalla. Tämä Golang-opetusohjelma ottaa yksinkertaisen esimerkin ja osoittaa, kuinka sitovien toimintojen käsitteitä voidaan soveltaa tyyppeihin (eli luokkiin), konstruktoreihin, alatyyppiin, polymorfismiin, riippuvuusinjektioihin ja testeihin pilkillä.
Ainutlaatuinen ajoneuvon tunnistenumero jokaisen auton sisältää - juoksevan (ts. sarjanumeron) lisäksi tiedot autosta, kuten valmistaja, valmistava tehdas, automalli ja jos sitä ajetaan vasemmalta tai oikealta puolelta.
Toiminto valmistajakoodin määrittämiseksi voi näyttää tältä:
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 }
Ja tässä on testi, joka osoittaa, että esimerkki VIN toimii:
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) } }
Joten tämä toiminto toimii oikein, kun sille annetaan oikea syöttö, mutta sillä on joitain ongelmia:
panic
.Ratkaistaksemme nämä ongelmat korjaamme sen uudelleen olio-suuntautuneiden kuvioiden avulla.
Ensimmäinen korjaus on tehdä VIN: istä oma tyyppi ja sitoa Manufacturer()
toiminto sille. Tämä tekee toiminnon tarkoituksesta selkeämmän ja estää ajattelemattoman käytön.
package vin type VIN string func (v VIN) Manufacturer() string { manufacturer := v[: 3] if manufacturer[2] == '9' { manufacturer += v[11: 14] } return string(manufacturer) }
Sitten mukautamme testiä ja esitämme virheellisten VIN-tunnusten ongelman:
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! }
Viimeinen rivi lisättiin osoittamaan, kuinka panic
käynnistetään kun käytät Manufacturer()
toiminto. Testin ulkopuolella tämä kaataa käynnissä olevan ohjelman.
Välttääksesi panic
kun käsitellään virheellistä VIN-numeroa, on mahdollista lisätä voimassaolotarkistuksia Manufacturer()
itse toiminto. Haittapuolena on, että tarkastukset suoritettaisiin jokaisessa puhelussa Manufacturer()
toiminto, ja että on syötettävä virheen palautusarvo, mikä tekee palautusarvon käytöstä mahdottomaksi suoraan ilman välimuuttujaa (esim. kartta-avaimena).
Tyylikkäämpi tapa on laittaa kelpoisuustarkastukset VIN
-konstruktoriin tyyppi, niin että Manufacturer()
toiminto vaaditaan vain kelvollisiin VIN-tunnuksiin, eikä se vaadi tarkastuksia ja virheiden käsittelyä:
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) }
Tietenkin lisätään testi NewVIN
: lle toiminto. Rakentaja hylkäsi virheelliset VIN-tunnukset:
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) } }
Testi Manufacturer()
funktio voi nyt jättää virheellisen VIN-tunnuksen testaamisen, koska NewVIN
on jo hylännyt sen rakentaja.
Seuraavaksi haluamme tehdä eron eurooppalaisten ja muiden kuin eurooppalaisten tunnistenumeroiden välillä. Yksi lähestymistapa olisi laajentaa VIN type
kohtaan struct
ja tallenna, onko VIN eurooppalainen vai ei, parantamalla konstruktoria vastaavasti:
type VIN struct { code string european bool } func NewVIN(code string, european bool)(*VIN, error) { // ... checks ... return &VIN { code, european }, nil }
Tyylikkäämpi ratkaisu on luoda VIN
-alatyyppi eurooppalaisille VIN-numeroille. Tällöin lippu tallennetaan implisiittisesti tyyppitietoihin ja Manufacturer()
toiminto muille kuin eurooppalaisille VIN-numeroille tulee mukavaksi ja ytimekkääksi:
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 }
OOP-kielillä, kuten Java, odotamme alatyypin EUVIN
olla käyttökelpoinen kaikissa paikoissa, joissa VIN
tyyppi on määritetty. Valitettavasti tämä ei toimi Golang OOP: ssa.
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) } } }
Tämä käyttäytyminen voidaan selittää Go-kehitystiimin tarkoituksellisella valinnalla, joka ei tue dynaamista sitomista muille kuin käyttöliittymätyypeille. Sen avulla kääntäjä tietää, mikä funktio kutsutaan kääntämisajankohtana, ja välttää dynaamisen menetelmän lähetyksen. Tämä valinta estää myös perinnön käytön yleisenä sommittelumallina. Sen sijaan käyttöliittymät ovat tie eteenpäin (anteeksi sanaleikki).
Go-kääntäjä käsittelee tyyppiä käyttöliittymän toteutuksena, kun se toteuttaa ilmoitetut toiminnot (ankka-kirjoittaminen). Siksi polymorfismin hyödyntämiseksi VIN
tyyppi muunnetaan käyttöliittymäksi, jonka toteuttaa yleinen ja eurooppalainen VIN-tyyppi. Huomaa, että eurooppalaisen VIN-tyypin ei tarvitse olla yleisen alatyyppi.
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 }
Polymorfismitesti läpäisee nyt pienen muutoksen:
// 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) } } }
Itse asiassa molempia VIN-tyyppejä voidaan nyt käyttää kaikissa paikoissa, joissa määritetään VIN
käyttöliittymä, koska molemmat tyypit noudattavat VIN
käyttöliittymän määritelmä.
Viimeisenä mutta ei vähäisimpänä, meidän on päätettävä, onko VIN eurooppalainen vai ei. Oletetaan, että olemme löytäneet ulkoisen sovellusliittymän, joka antaa meille nämä tiedot, ja olemme rakentaneet sille asiakkaan:
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 }
Olemme myös rakentaneet palvelun, joka käsittelee VIN-tunnuksia ja erityisesti voi luoda ne:
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) }
Tämä toimii hyvin, koska muokattu testi osoittaa:
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) } }
Ainoa asia tässä on, että testi vaatii suoraa yhteyttä ulkoiseen sovellusliittymään. Tämä on valitettavaa, koska sovellusliittymä voi olla offline-tilassa tai vain tavoitettavissa. Ulkoisen sovellusliittymän soittaminen vie myös aikaa ja voi maksaa.
Koska API-kutsun tulos on tiedossa, sen pitäisi olla mahdollista korvata pilkalla. Valitettavasti yllä olevassa koodissa VINService
itse luo API-asiakkaan, joten sitä ei ole helppo korvata. Tämän mahdollistamiseksi API-asiakasriippuvuus tulisi injektoida VINService
Eli se on luotava ennen kuin kutsutaan VINService
rakentaja.
Golang OOP -ohje on tässä yksikään rakentaja ei saisi soittaa toiselle rakentajalle . Jos tätä sovelletaan perusteellisesti, jokainen sovelluksessa käytetty yksikkö luodaan ylimmälle tasolle. Tyypillisesti tämä on käynnistysfunktio, joka luo kaikki tarvittavat objektit soittamalla niiden rakentajille sopivassa järjestyksessä ja valitsemalla sopivan toteutuksen ohjelman aiotulle toiminnallisuudelle.
Ensimmäinen askel on tehdä VINAPIClient
käyttöliittymä:
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 }
Sitten uusi asiakas voidaan pistää 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) }
Sen avulla on nyt mahdollista käyttää API-asiakasmalli testiä varten. Sen lisäksi, että vältetään puhelut ulkoiselle sovellusliittymälle testien aikana, pilkata voi toimia myös koettimena tietojen keräämiseksi API: n käytöstä. Seuraavassa esimerkissä tarkistamme vain, onko IsEuropean
toimintoa kutsutaan.
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) } }
Tämä testi läpäisee, koska IsEuropean
koetin toimii kerran puhelun aikana CreateFromCode
Kriitikot saattaa sanoa: 'Miksi et käytä Java-ohjelmaa, jos silti teet OOP: ta?' No, koska saat kaikki muut Go: n hienot edut välttäen resursseja kaipaavaa VM / JIT: ää, puitteita, joissa on merkintävuode, poikkeusten käsittely ja kahvitauot testien suorittamisen aikana (jälkimmäinen voi olla ongelma joillekin).
Yllä olevan esimerkin avulla on selvää, kuinka olio-ohjelmoinnin tekeminen Gossa voi tuottaa paremmin ymmärrettävän ja nopeammin toimivan koodin kuin tavallinen, välttämätön täytäntöönpano . Vaikka Go ei ole tarkoitettu OOP-kieleksi, se tarjoaa tarvittavat työkalut jäsennellä sovellus olio-suuntautuneella tavalla. Yhdessä toimintojen ryhmittelyn paketteihin Golangin OOP: ta voidaan hyödyntää tarjoamaan uudelleenkäytettäviä moduuleja rakennuspalikoiksi suuret sovellukset .
Google Cloud Partner -kumppanina ApeeScapen Googlen hyväksymät asiantuntijat ovat yritysten käytettävissä tarpeen vaatiessa heidän tärkeimmille hankkeilleen.
Golang (tai yksinkertaisesti 'Go') on yleiskieli, joka soveltuu monimutkaisten järjestelmätyökalujen ja sovellusliittymien kehittämiseen. Automaattisella muistinhallinnalla, staattisella järjestelmällä, sisäänrakennetulla samanaikaisuudella ja rikkaalla verkkokeskeisellä ajonaikaisella kirjastolla se on erityisen hyödyllinen hajautetuissa järjestelmissä ja pilvipalveluissa.
Golang ja sen ajonaikaiset paketit on kirjoitettu Go-muodossa. Golang 1.5: een asti, joka julkaistiin vuonna 2015, kääntäjä ja osat ajon ajasta kirjoitettiin C-kirjaimella.
Golangin rakennuspalikat ovat tyyppejä, toimintoja ja paketteja - toisin kuin olio-orientoitujen kielten, kuten Java, luokat. Kolme neljästä OOP: n käsitteestä (kapselointi, abstraktio ja polymorfismi) on kuitenkin käytettävissä, ja puuttuva tyyppihierarkia korvataan rajapinnoilla ja ankka-tyypillä.
Golang on muistiohjattu kieli, jolla on tehokkaat primitiivit yleisille tietorakenteille ja samanaikaiselle ohjelmoinnille. Se kääntyy suoraan konekoodiin tarjoten siten C-tyyppisen suorituskyvyn ja resurssitehokkuuden. Sen nopea kokoaminen ja mukana olevat testausmahdollisuudet lisäävät kehittäjien tuottavuutta.
Golang kehittyy onnekkaasti ja on äskettäin juhlistanut 10-vuotisjuhliaan. Tällä hetkellä maailmassa on noin miljoona aktiivista Golang-kehittäjää, jotka käyttävät Golangia kaikenlaisiin projekteihin. Lisäksi Golang 2.0 on tekemisissä ja tuo uusia jännittäviä ominaisuuksia Gophersille (eli laajemmalle Go-kehittäjäyhteisölle).
Google on kehittänyt Go korvaamaan Python-, C ++ - ja muiden järjestelmäkielien sisäisen käytön. Kieliydin päivitettiin ensimmäisen kerran vuonna 2009, ja se päivitetään kuuden kuukauden välein pitäen ohjelmointirajapinta vakaana. Go on avoimen lähdekoodin ja sillä on rikas yhteisö, joka osallistuu aktiivisesti sen kehittämiseen.