2013-08-08 9 views
99

Mam struct i chciałbym, aby został zainicjowany z niektórych rozsądnych wartości domyślnych.Konstruktorzy w Go

Zazwyczaj rzeczą do zrobienia jest użycie konstruktora, ale ponieważ go nie jest tak naprawdę OOP w tradycyjnym znaczeniu, nie są to obiekty prawdziwe i nie ma konstruktorów.

Zauważyłem metodę init, ale to jest na poziomie pakietu. Czy jest coś podobnego, co można zastosować na poziomie struktury?

Jeśli nie, to jaka jest najlepsza praktyka dla tego typu rzeczy w Go?

Odpowiedz

87

Są to właściwie dwa zaakceptowanych najlepsze praktyki:

  1. podjęciu zero swojej struktury rozsądny domyślnej. (Chociaż wygląda to dziwnie na większość ludzi pochodzących z "tradycyjnego" oopa, to często działa i jest naprawdę wygodne).
  2. Podaj funkcję func New() YourTyp lub jeśli masz kilka takich typów w funkcjach pakietu func NewYourType1() YourType1 i tak dalej.

Dokument jeśli wartość zero typu jest użyteczny, czy nie (w takim przypadku musi być utworzona przez jedną z funkcji New... (Dla „tradycjonalistów” oops:. Ktoś, kto nie czyta dokumentacji nie będzie mógł poprawnie używać twoich typów, nawet jeśli nie może tworzyć obiektów w nieokreślonych stanach.)

+1

jak by to działało dla właściwości takich jak mapy. Domyślną wartością jest zero, prawda? Czy zatem należy je zawsze inicjować za pomocą nowej funkcji? –

+2

Tak i nie, to zależy. Najprawdopodobniej tak, podaj 'func New() T'. Ale w zależności od przypadku można sprawdzić tę zerową mapę i "zrobić" tylko raz, gdy jest to potrzebne. W takim przypadku: Dokumentuj, czy tworzenie mapy jest bezpieczne dla równoczesnego użycia (czyli Twój kod, który sprawia, że ​​mapa jest chroniona np. Przez muteks). Zależy trochę od tego, czy mapa jest eksportowana czy nie ... Trudno powiedzieć, nie widząc kodu. – Volker

121

Istnieje kilka odpowiedników konstruktorów, gdy wartości zerowe nie mogą tworzyć wartości domyślnych lub gdy jakiś parametr jest niezbędny do inicjalizacji struktury.

Przypuśćmy, że masz tak: struct

type Thing struct { 
    Name string 
    Num int 
} 

wtedy, jeśli wartości zerowej nie są dopasowane, byś zazwyczaj konstruować wystąpienie z NewThing funkcji powrocie wskaźnik:

func NewThing(someParameter string) *Thing { 
    p := new(Thing) 
    p.Name = someParameter 
    p.Num = 33 // <- a very sensible default value 
    return p 
} 

Gdy struktura jest wystarczająco prosta, można użyć tego skondensowanego konstruktu:

func NewThing(someParameter string) *Thing { 
    return &Thing{someParameter, 33} 
} 

Jeśli nie chcesz, aby powrócić wskaźnik, to praktyka jest wywołanie funkcji makeThing zamiast NewThing:

func makeThing(name string) Thing { 
    return Thing{name, 33} 
} 

referencyjny: Allocation with new in Effective Go.

+1

OK, więc ma to sens, ale oznacza, że ​​klienci muszą wiedzieć o funkcjach Nowy i tworzyć. to jest nie jest standardowe wśród wszystkich struktur. Wyobrażam sobie, że można sobie z nim poradzić za pomocą interfejsów –

+2

Nie jestem pewien co masz na myśli. Posiadanie funkcji NewThing jest czymś standardowym. Jeśli masz na myśli, że nie są automatycznie wywoływani, tak, ale i tak nie możesz automatycznie używać struktur. Nie sądzę, że powinieneś próbować ukryć konstruktory za pomocą interfejsów, kod jest wyraźniejszy, gdy się pojawiają. –

+5

Nie jest bardzo powszechne przypisywanie struktur do 'new' i ustawianie ich później. Preferowane są tutaj literały strukturalne. I nie jestem pewien co do konwencji "makeThing". Standardowa biblioteka wywołuje konstruktory New() lub NewThing() konsekwentnie i nigdy nie spotkałem żadnej funkcji makeThing() mojej jaźni ... – tux21b

23

Go ma obiekty, obiekty mogą mieć konstruktory (chociaż nie są to konstruktory automatyczne), a na koniec Go jest językiem OOP (typy danych mają dołączone metody, ale wprawdzie istnieją nieskończone definicje tego, czym jest OOP.)

Niemniej jednak najlepszą przyjętą praktyką jest pisanie zero lub więcej konstruktorów dla twoich typów.

Jak @dystroy pisał swoją odpowiedź zanim mnie kończąc tę ​​odpowiedź, niech mi tylko dodać alternatywną wersję swojego przykład konstruktora, który będzie prawdopodobnie napisać zamiast jak:

func NewThing(someParameter string) *Thing { 
    return &Thing{someParameter, 33} // <- 33: a very sensible default value 
} 

Powodem chcę pokazać ta wersja jest taka, że ​​dość często można używać literałów "inline" zamiast wywołania "constructor".

a := NewThing("foo") 
b := &Thing{"foo", 33} 

Teraz *a == *b.

+1

+1 ze względu na sprawę równości. Może to być trochę (lub całkowicie) poza tematem, ale jest ważne. Myślę, że przyszedł z Go1, nie? –

+0

@dystroy: Myślę, że tak było. I dziękuję ;-) – zzzz

+4

Czy możesz dodać więcej wyjaśnić, dlaczego a i b będą równe? – lazywei

0

Golang nie jest językiem OOP w oficjalnych dokumentach. Wszystkie pola struktury Golang mają określoną wartość (nie jak c/C++), więc funkcja konstruktora nie jest tak potrzebna jak cpp. Jeśli potrzebujesz przypisać niektóre pola niektórych wartości specjalnych, użyj funkcji fabrycznych. Społeczność Golanga sugeruje nowe nazwy wzorca.

6

W Go nie ma domyślnych konstruktorów, ale można zadeklarować metody dla dowolnego typu. Można narzucić sobie nawyk deklarowania metody o nazwie "Init". Nie wiem, czy ma to związek z najlepszymi praktykami, ale pomaga utrzymać nazwy w krótkich terminach bez utraty przejrzystości.

package main 

import "fmt" 

type Thing struct { 
    Name string 
    Num int 
} 

func (t *Thing) Init(name string, num int) { 
    t.Name = name 
    t.Num = num 
} 

func main() { 
    t := new(Thing) 
    t.Init("Hello", 5) 
    fmt.Printf("%s: %d\n", t.Name, t.Num) 
} 

Wynikiem jest:

Hello: 5 
+1

Pytanie: semantycznie, 't: = new (Thing) \ n t.Init (...) 'jest taki sam jak' var t Thing \ n t.Init (...) ', prawda? Która z form jest postrzegana jako bardziej idiomatyczna w Go? –

2

innym sposobem jest;

package person 

type Person struct { 
    Name string 
    Old int 
} 

func New(name string, old int) *Person { 
    // set only specific field value with field key 
    return &Person{ 
     Name: name, 
    } 
} 
0

Lubię wyjaśnień od tego blog post:

Funkcja Nowy jest konwencja Go dla pakietów, które tworzą rodzaj rdzenia lub różnych typów do użytku przez deweloperów aplikacji. Funkcja Nowa jest konwencją Go dla pakietów, które tworzą typ podstawowy lub różne typy do użycia przez twórcę aplikacji. Spójrz, jak Nowy jest zdefiniowane i wdrożone log.go, bufio.go i cypto.go:

log.go

// New creates a new Logger. The out variable sets the 
// destination to which log data will be written. 
// The prefix appears at the beginning of each generated log line. 
// The flag argument defines the logging properties. 
func New(out io.Writer, prefix string, flag int) * Logger { 
    return &Logger{out: out, prefix: prefix, flag: flag} 
} 

bufio.go

// NewReader returns a new Reader whose buffer has the default size. 
func NewReader(rd io.Reader) * Reader { 
    return NewReaderSize(rd, defaultBufSize) 
} 

crypto.go

// New returns a new hash.Hash calculating the given hash function. New panics 
// if the hash function is not linked into the binary. 
func (h Hash) New() hash.Hash { 
    if h > 0 && h < maxHash { 
     f := hashes[h] 
     if f != nil { 
      return f() 
     } 
    } 
    panic("crypto: requested hash function is unavailable") 
} 

Ponieważ każdy pakiet działa jako przestrzeń nazw, każdy pakiet może mieć własną wersję nowego. W bufio.go można utworzyć wiele typów, więc nie ma samodzielnej nowej funkcji. Tutaj znajdziesz funkcje takie jak NewReader i NewWriter.