Od kilku dni czytam sobie o Go, głównie rekreacyjnie, ale z tyłu głowy mam to, że może kiedyś będzie trzeba przejść na Go, albo C/C++ (co chyba bardziej prawdopodobne) po tym jak Oracle zaczyna powoli rozrabiać z Java'ą ;-) [a wolałem żeby Suna kupił Oracle niż IBM... ;-)]
Pierwszym szokiem dla Java'owca będzie to, że choć Go jest językiem obiektowym [także!] to nie ma tam klas!
Ale nie ma obaw, spokojnie możemy dodawać metody do typów danych. Jedyny wymóg by typ danych był w tym samym pakiecie co metody.
Java'owy kod w stylu:
public class Point {
private int x, int y;
public Point(int x,int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public float distanceFrom00() {
return Math.sqrt(x*x+y*y);
}
}
W Go wygląda mniej więcej tak:
type Point struct {
x int;
y int;
}
func (p *Point) GetX() int {
return p.x
}
func (p *Point) GetY() int {
return p.y
}
func (p *Point) DistanceFrom00() float {
return float(math.Sqrt(float64(p.x*p.x)+float64(p.y*p.y)))
}
Czyli definiujemy nowy typ danych, a później metody dla tego typu.
Wołanie metod jest dokładnie takie samo jak w Java'ie czyli po kropce.
Ze złych wieści, tu znów są wskaźniki :-( Na szczęście język trochę pomaga, i np. w deklaracjach metod nie trzeba wcale robić (p *Point) można zrobić (p Point).
To
p
to receiver, który robi za odpowiednik
this
.
Stąd w getterach jest użyte
p.x
.
Dziedziczenie jest, choć ciut inaczej zorganizowane.
Jeżeli np. stworzymy nowy typ Point3, w którym dodamy jeszcze z, to mamy wtedy:
type Point3 struct {
Point
z int
}
Trzeba doimplementować jeszcze GetZ() i nadpisać DistanceFrom00(), co wygląda tak:
func (p *Point3) GetZ() int {
return p.z
}
func (p *Point3) DistanceFrom00() float {
return float(math.Sqrt(math.Pow(float64(p.Point.DistanceFrom00()),2)+float64(p.z*p.z)))
}
Gorzej jest z tworzeniem tych obiektów :-(
p:=Point{3,2} // tworzy strukturę z x=3 i y=2
p3:=Point3{Point{3,2},1} // tworzy Point3 z x=3,y=2,z=1.......
p4:=new(Point) // tworzy nowy Point, w p4 jest wskaźnik do niego z x=0, y=0
Jak widać w tym fragmencie jest to ohydne ;-) dlatego jak rozumiem zaleca się tworzenie metod fabrykujących, które będą zwracały nasze obiekty.
Teraz ważna rzecz, w Go nie ma modyfikatorów dostępu takich jak w Java'ie, nie ma tu
public
,
private
czy
protected
, jest jednak sposób na ograniczanie widoczności.
Jeżeli nazwa pola, typu, czy metody jest pisana dużą literą to takie coś będzie widoczne poza swoim pakietem, wszystko pisane z małej litery widoczne jest tylko w pakiecie.
To wydaje się mieć sens, w Java'ie ewidentnie programiści przynieśli zwyczaje z C++ i to co było pomyślane jako mechanizm namawiający do używania modyfikatorów dostępu tak by to pakiety były głównymi strukturami decydującymi o prywatności stało się mechanizmem do prywatyzowania na poziomie klas.
Jak było widać wyżej przez to jak się "dziedziczy", można dość łatwo wprowadzić wielodziedziczenie, z tym, że jak 2 "dziedziczone" typy mają pola czy metody o tej samej nazwie to się robią problemy i puki ich nie używamy to jest dobrze, ale jak użyjemy to jest błąd [nie przyglądałem się temu jeszcze więc wieżę na słowo temu co czytałem ;-)].
W Go jest za to coś takiego jak interfejsy :-) Ale działają one kompletnie inaczej niż w Java'ie, w każdym bądź razie w sferze deklarowania tego czy się je implementuje.....
Jeśli coś ma odpowiednie metody to implementuje dany interfejs i nie trzeba niczego deklarować!
Popatrzmy na kod:
type Printable interface {
String() string
}
type TosterInt struct { value int }
type TosterFloat struct { value float }
func (i TosterInt) String() string {
return fmt.Sprintf("%d",i.value)
}
func (f TosterFloat) String() string {
return fmt.Sprintf("%f",f.value)
}
I teraz wszędzie w kodzie możemy używać cosiów stworzonych z TosterInt i TosterFloat tak jakby były typu Printable :-) Czyli trzymać je np. w mapie gdzie wartości są Printable.
m:=make(map[string]Printable)
m["TosterInt"]=TosterInt{3}
m["TosterFloat"]=TosterFloat{3.9}
for k,v:=range m {
fmt.Println(k," ",v)
}
Aha, tak biegiem, podstawowy program w Go wygląda tak
package main
import "fmt" // tutaj importujemy co nam potrzebne, zwykle potrzebujemy ;-)
func main() {
// nadziewka
var i = 1 // samo nam stworzy inta o wartości 1
var f float64 = 1.0 // samo nam stworzy float64 o wartości 1
i1:=1 // to samo co var i1=1
fmt.Println(i," ",f," ",i1)
}
W Go można przypisywać jednym := kilka wartości, funkcje też mogą zwracać kilka wartości np:
package main
import "fmt"
func toster() (string, int, bool) {
return "string",1,false
}
func main() {
a,b,c:=toster()
fmt.Println(a," ",b," ",c)
}
Wypisze "string 1 false".
To wieloprzypisywanie trochę na początku przeraża ;-)
Ok, to tyle, jak będę miał nastrój na dalszą naukę to napiszę więcej ;-)
Na razie zachęcam do
przyjrzenia się tutorialowi [szczególnie prezentacji do której linki są na stronie tutoriala].
Nie jestem już tak obrzydzony do Go jak rok temu, ale nadal nie jest to mój ulubieniec ;-)
http://www.blogger.com/img/blank.gif
Podoba mi się to, że jak na razie wydaje się nie skupiać na "architekturze", nie muszę myśleć o hierarchii klas, nie muszę pilnować ciągle by wszystko co powinno dziedziczyło/implementowało to co powinno, to się tu samo wydaje robić. Potrzebuję metody to ją po prostu dopisuję......
Na razie jednak nie wychodzi mi komplikacja z kilku plików ;-) Używam portu dla Windows, o którym
tu już kiedyś pisałem i niestety nie potrafi on chyba do końca pracować z wieloma plikami [jeżeli stworzy się swój własny pakiet], dodatkowo gdy program zrobi out of memory to pod Windows cały komputer zawisa, i po jakichś 30 sekundach program po prostu kończy swoje działanie, bez żadnej informacji........
Do prostych zabaw warto użyć umieszczonego na
stronie Go placu zabaw ;-)
Podobne postybetaWysyłamy pliki do Google Docs przy pomocy Go :-)Język Go dla Windows :-)Go dla Java'owca ;-) odcinek 2 "kontenery dwa ;-)"Go wolniejsze od C i JavaScript, i ciut szybsze niż Java ;-) [a jednak od Java'y też wolniejsze]clone() i Cloneable się mszczą ;-)