A Go programozási nyelv

Objektum orientáltság a Go-ban



A Go nem objektum-orientált nyelv, hanem procedurális, azonban a mai programozói világ nagy része objektum-orientált paradigmát követi, amely használatára a Go is lehetőséget ad. Azonban amint majd láthatjuk a Go egy más gondolkodásmódot követ más megszokott objektum elvű nyelvekkel szemben.

A Go nem rendelkezik osztályokkal. Azokhoz legközelebb a struct típusuk állnak és ezekkel fogjuk megvalósítani a típusainkat.

Metódusok deklarációja

Mint azt a structokról szóló fejezetben láthattuk a structok tartalmazhatnak adattagokat. A structokhoz ezen felül műveletek is rendelhetők metódusok formájában. A metódusok deklarációja nagyon hasonló a függvények deklarációjához, a különbség csupán annyi, hogy meg kell határoznunk a hozzá kötődő típust a func kulcsszó után. Ezt a következőképpen tehetjük meg:

type my_type struct { } func (m my_type) my_func() int { //kód }

Ezzel a metódusdefinícióval a my_type nevű típusunkhoz rendeltünk egy my_func nevű paraméter nélküli műveletet int visszatérési értékkel. Az objektumpéldányt a deklarációban megadott „m” névvel érhetjük el. A metódus meghívása a „.” operátor segítségével történik egy my_type egy példányból. A metódust egy structra mutató pointer típushoz is rendelhetjük, azonban az ilyen függvényt nem érhetjük el egy statikus példányból:

type my_type struct { } func (m *my_type) my_func() int { //kód } var m my_type{} var p *my_type = &m p.my_func() // helyes m.my_func() //fordítási hiba

Metódusokat structokon kívül bármilyen type kulcsszóval definiált típushoz definiálhatunk. A szabály az, hogy csak a lokális csomagunkban lévő típusokra definiálhatunk metódusokat:

func (i int) foo(){} //hiba

Azonban ez a szabály megkerülhető új saját típus definiálásával:

type MyInt int func (i MyInt) foo() {} //helyes

Öröklődés

Ahogy a structok leírásánál láthattuk, hogy ez a típus hasonlít leginkább az osztály fogalmára. Láthattuk továbbá azt is, hogy lehetőséget ad a nyelv beágyazásra. A beágyazás megvalósítása anonim mezők deklarálásával történik a structon belül. Ezt felhasználhatjuk az öröklődés megvalósításához. Beágyazás után elérhetővé válik a beágyazott struct minden metódusa és minden mezője a külső struct számára is:

package main import "fmt" type Car struct { wheelCount int } func (car Car) numberOfWheels() int { return car.wheelCount } type Ferrari struct { Car } // csak a Ferrarira vonatkozó viselkedés func (f Ferrari) sayHiToSchumacher() { fmt.Println("Hi Schumacher!") } type AstonMartin struct { Car } // csak az Aston Martinra vonatkozó viselkedés func (a AstonMartin) sayHiToBond() { fmt.Println("Hi Bond, James Bond!") } func main() { f := Ferrari{Car{4}} fmt.Println("A Ferrari has this many wheels: ", f.numberOfWheels()) //autó viselkedése f.sayHiToSchumacher() //Ferrari viselkedése a := AstonMartin{Car{4}} fmt.Println("An Aston Martin has this many wheels: ", a.numberOfWheels()) //autó viselkedése a.sayHiToBond() //AstonMartin viselkedése }

Ebben a példában láthatjuk, hogy mind az Aston Martin és a Ferrari is autóként viselkedik – mivel mindkettő számára elérhető a numOfWheels metódus a Car structból. Továbbá mind a kettő definiálja a saját viselkedését is, amit csak ő használhat.

A beágyazásnak segítségével megvalósítható a többszörös öröklődés is, mivel a nyelv nem korlátozza le az anonim mezők számát egy structon belül. Így egyszerre több más struct videlkedését is implementálhatjuk egy általunk definiáltban.

package main import "fmt" type Camera struct { } func (_ Camera) takePicture() string { //nem használjuk a Camera típus változóját ezt jelöljük _vel return "Click" } type Phone struct { } func (_ Phone ) call() string { //nem használjuk a Phone típus változóját ezt jelöljük _vel return "Ring Ring" } // multiple inheritance type CameraPhone struct { Camera //anonim Camera mező Phone //anonim Phone mező } func main() { cp := new (CameraPhone) //új CameraPhone példány fmt.Println("Our new CameraPhone exhibits multiple behaviors ...") fmt.Println("It can take a picture: ", cp.takePicture()) //Camera viselkedése fmt.Println("It can also make calls: ", cp.call()) //Phone viselkedése }

A példánkban szerepel egy Camera és egy Phone típus amelyekből leszármazik a CameraPhone típus amely megvalósítja mind a Camera, mind a Phone típus műveleteit.

Enkapszuláció és láthatóság

Más programozási nyelvekben a láthatóság jelölésére megtalálhatunk olyan kulcsszavakat, mint public, private, package, protected stb. melyek segítségével a fejlesztő jelölheti az adattagok láthatóságát. Go erre egy teljesen más megközelítést használ. A nyelvben két féle láthatóság található: csak csomagon belül láthatóság vagy csomagon belüli és kívüli láthatóság. A Go-ban erre nem találhatóak kulcsszavak ehelyett a következőképpen jelöljük: minden kis betűvel kezdődő struct vagy változó csak csomagon belüli, minden nagy betűvel kezdődő változó vagy struct pedig csomagon belüli és kívüli láthatóságot kap. Így nincs szükség a változó visszakeresésére, amint ránézünk a nevére tudjuk annak láthatóságát. Más nyelvek ezt konvencióként alkalmazzák, a Go azonban kényszeríti a fejlesztőt ennek használatára.

type notExported struct { //csak a lokális csomagon belül látható } type Exported struct { //csomagon kívül is látható struct notExportedVariable int //csomagon kívül nem látható csak lokálisan ExportedVariable int //csomagon kívül is látható változó }

Polimorfizmus

Mivel a Go-ban nincs hierarchia, nem tudunk alosztályokat megvalósítani. Azonban a polimorfizmus és annak előnyei itt is megtalálhatóak, melyet interfészek segítségével valósítunk meg. Ha különböző típusú objektumok egy azonos metódusát szeretnénk meghívni egy erre definiált interfész segítségével megtehetjük:

package main import "fmt" type Human interface { myStereotype() string } type Man struct { } func (m Man) myStereotype() string { return "I'm going fishing." } type Woman struct { } func (m Woman) myStereotype() string { return "I'm going shopping." } func main() { m := new (Man) w := new (Woman) //an array of Humans - we don’t know whether Man or Woman hArr := [...]Human{m, w} //array of 2 Humans. One is the type Man, one is the type Woman. for n, _ := range (hArr) { fmt.Println("I'm a human, and my stereotype is: ", hArr[n].myStereotype()) //appears as human type, but behavior changes depending on actual instance } }