Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00250 006576 13230768 na godz. na dobę w sumie
Język Go. Poznaj i programuj - ebook/pdf
Język Go. Poznaj i programuj - ebook/pdf
Autor: , Liczba stron: 360
Wydawca: Helion Język publikacji: polski
ISBN: 978-83-283-2468-8 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> programowanie >> techniki programowania
Porównaj ceny (książka, ebook, audiobook).
Język Go jest nazywany „językiem C XXI wieku”. Podobnie jak C, umożliwia kompilowanie programów do wydajnego kodu maszynowego, który w natywny sposób współpracuje z poszczególnymi systemami operacyjnymi. Go jest elastycznym narzędziem pozwalającym osiągać maksymalny efekt przy minimalnych środkach. Jest bardzo wszechstronny — bardzo dobrze nadaje się do budowania infrastruktury takiej jak serwery sieciowe, do tworzenia narzędzi dla programistów, ale jest też znakomitym językiem do programowania grafiki, aplikacji mobilnych i uczenia maszynowego.

Niniejsza książka jest skierowana do osób, które chcą jak najszybciej rozpocząć tworzenie wydajnego oprogramowania w Go. Autorzy przejrzyście wyjaśnili podstawy tego języka i zasady nim rządzące, a swój wykład uzupełnili setkami interesujących i praktycznych przykładów dobrze napisanego kodu Go. W ten sposób Czytelnik dobrze pozna wszystkie aspekty tego języka, jego najistotniejsze pakiety oraz szeroki zakres zastosowań.

W książce omówiono:

Poznaj język Go — doskonałe narzędzie dla profesjonalisty!


Alan A.A. Donovan od dwudziestu lat zajmuje się programowaniem. Jest członkiem zespołu Go firmy Google w Nowym Jorku. Od 2005 r. pracuje w firmie Google nad projektami infrastrukturalnymi. Brał udział w opracowaniu autorskiego systemu kompilacji Blaze. Zbudował wiele bibliotek i narzędzi do statycznej analizy programów Go.

Brian W. Kernighan jest profesorem Wydziału Informatyki na Uniwersytecie Princeton. W latach 1969 – 2000 pracował nad językami i narzędziami dla systemu Unix w Centrum Badań Informatycznych firmy Bell Labs. Jest współautorem kilku książek, w tym takich jak Lekcja programowania. Najlepsze praktyki (Helion 2011).
Znajdź podobne książki Ostatnio czytane w tej kategorii

Darmowy fragment publikacji:

• Kup książkę • Poleć książkę • Oceń książkę • Księgarnia internetowa • Lubię to! » Nasza społeczność Spis tre(cid:264)ci Przedmowa ................................................................................................ 9 Pochodzenie języka Go ....................................................................................................................10 Projekt Go ..........................................................................................................................................11 Struktura książki ...............................................................................................................................13 Gdzie można znaleźć więcej informacji .........................................................................................14 Podziękowania ..................................................................................................................................15 Rozdzia(cid:228) 1. Przewodnik ............................................................................. 17 1.1. Witaj, świecie ..............................................................................................................................17 1.2. Argumenty wiersza poleceń .....................................................................................................19 1.3. Wyszukiwanie zduplikowanych linii ......................................................................................23 1.4. Animowane GIF-y .....................................................................................................................28 1.5. Pobieranie zawartości adresu URL .........................................................................................30 1.6. Pobieranie zawartości kilku adresów URL równolegle ........................................................32 1.7. Serwer WWW ............................................................................................................................33 1.8. Kilka pominiętych kwestii ........................................................................................................37 Rozdzia(cid:228) 2. Struktura programu ................................................................ 41 2.1. Nazwy ..........................................................................................................................................41 2.2. Deklaracje ...................................................................................................................................42 2.3. Zmienne ......................................................................................................................................43 2.4. Przypisania .................................................................................................................................50 2.5. Deklaracje typów .......................................................................................................................52 2.6. Pakiety i pliki ..............................................................................................................................54 2.7. Zakres ..........................................................................................................................................58 Poleć książkęKup książkę 6 SPIS TRE(cid:263)CI Rozdzia(cid:228) 3. Podstawowe typy danych ........................................................ 63 3.1. Liczby całkowite .........................................................................................................................63 3.2. Liczby zmiennoprzecinkowe ....................................................................................................68 3.3. Liczby zespolone ........................................................................................................................72 3.4. Wartości logiczne ......................................................................................................................75 3.5. Łańcuchy znaków ......................................................................................................................75 3.6. Stałe ..............................................................................................................................................86 Rozdzia(cid:228) 4. Typy z(cid:228)o(cid:276)one ........................................................................... 91 4.1. Tablice .........................................................................................................................................91 4.2. Wycinki .......................................................................................................................................94 4.3. Mapy ......................................................................................................................................... 102 4.4. Struktury .................................................................................................................................. 108 4.5. JSON ......................................................................................................................................... 114 4.6. Szablony tekstowe i HTML ................................................................................................... 120 Rozdzia(cid:228) 5. Funkcje ................................................................................ 125 5.1. Deklaracje funkcji ................................................................................................................... 125 5.2. Rekurencja ............................................................................................................................... 127 5.3. Zwracanie wielu wartości ...................................................................................................... 130 5.4. Błędy ......................................................................................................................................... 132 5.5. Wartości funkcji ...................................................................................................................... 137 5.6. Funkcje anonimowe ............................................................................................................... 139 5.7. Funkcje o zmiennej liczbie argumentów ............................................................................. 146 5.8. Odroczone wywołania funkcji .............................................................................................. 147 5.9. Procedura panic ...................................................................................................................... 152 5.10. Odzyskiwanie sprawności ................................................................................................... 154 Rozdzia(cid:228) 6. Metody ................................................................................ 157 6.1. Deklaracje metod .................................................................................................................... 157 6.2. Metody z odbiornikiem wskaźnikowym ............................................................................. 159 6.3. Komponowanie typów poprzez osadzanie struktur .......................................................... 162 6.4. Wartości i wyrażenia metod .................................................................................................. 165 6.5. Przykład: typ wektora bitowego ........................................................................................... 166 6.6. Hermetyzacja ........................................................................................................................... 169 Rozdzia(cid:228) 7. Interfejsy .............................................................................. 173 7.1. Interfejsy jako kontrakty ........................................................................................................ 173 7.2. Typy interfejsowe ................................................................................................................... 176 Poleć książkęKup książkę SPIS TRE(cid:263)CI 7 7.3. Spełnianie warunków interfejsu ........................................................................................... 177 7.4. Parsowanie flag za pomocą interfejsu flag.Value ............................................................... 180 7.5. Wartości interfejsów .............................................................................................................. 182 7.6. Sortowanie za pomocą interfejsu sort.Interface ................................................................. 187 7.7. Interfejs http.Handler ............................................................................................................ 191 7.8. Interfejs error .......................................................................................................................... 196 7.9. Przykład: ewaluator wyrażeń ................................................................................................ 197 7.10. Asercje typów ........................................................................................................................ 203 7.11. Rozróżnianie błędów za pomocą asercji typów ................................................................ 205 7.12. Kwerendowanie zachowań za pomocą interfejsowych asercji typów ........................... 207 7.13. Przełączniki typów ............................................................................................................... 209 7.14. Przykład: dekodowanie XML oparte na tokenach ........................................................... 211 7.15. Kilka porad ............................................................................................................................ 214 Rozdzia(cid:228) 8. Funkcje goroutine i kana(cid:228)y .................................................... 215 8.1. Funkcje goroutine ................................................................................................................... 215 8.2. Przykład: współbieżny serwer zegara ................................................................................... 217 8.3. Przykład: współbieżny serwer echo ...................................................................................... 220 8.4. Kanały ....................................................................................................................................... 222 8.5. Zapętlenie równoległe ............................................................................................................ 231 8.6. Przykład: współbieżny robot internetowy ........................................................................... 235 8.7. Multipleksowanie za pomocą instrukcji select ................................................................... 239 8.8. Przykład: współbieżna trawersacja katalogów .................................................................... 242 8.9. Anulowanie ............................................................................................................................. 246 8.10. Przykład: serwer czatu ......................................................................................................... 248 Rozdzia(cid:228) 9. Wspó(cid:228)bie(cid:276)no(cid:264)(cid:232) ze wspó(cid:228)dzieleniem zmiennych ....................... 253 9.1. Sytuacje wyścigu ..................................................................................................................... 253 9.2. Wzajemne wykluczanie: sync.mutex ................................................................................... 258 9.3. Muteksy odczytu/zapisu: sync.RWMutex ........................................................................... 261 9.4. Synchronizacja pamięci ......................................................................................................... 262 9.5. Leniwe inicjowanie: sync.Once ............................................................................................. 264 9.6. Detektor wyścigów ................................................................................................................. 266 9.7. Przykład: współbieżna nieblokująca pamięć podręczna ................................................... 267 9.8. Funkcje goroutine i wątki ...................................................................................................... 274 Poleć książkęKup książkę 8 SPIS TRE(cid:263)CI Rozdzia(cid:228) 10. Pakiety i narz(cid:246)dzie go ......................................................... 277 10.1. Wprowadzenie ...................................................................................................................... 277 10.2. Ścieżki importów .................................................................................................................. 278 10.3. Deklaracja package ............................................................................................................... 279 10.4. Deklaracje import ................................................................................................................. 279 10.5. Puste importy ........................................................................................................................ 280 10.6. Pakiety i nazewnictwo .......................................................................................................... 282 10.7. Narzędzie go .......................................................................................................................... 284 Rozdzia(cid:228) 11. Testowanie ......................................................................... 295 11.1. Narzędzie go test ................................................................................................................... 296 11.2. Funkcje testujące ................................................................................................................... 296 11.3. Pokrycie ................................................................................................................................. 310 11.4. Funkcje benchmarkujące ..................................................................................................... 313 11.5. Profilowanie .......................................................................................................................... 315 11.6. Funkcje przykładów ............................................................................................................. 318 Rozdzia(cid:228) 12. Refleksja ............................................................................ 321 12.1. Dlaczego refleksja? ............................................................................................................... 321 12.2. reflect.Type i reflect.Value ................................................................................................... 322 12.3. Display — rekurencyjny wyświetlacz wartości ................................................................. 324 12.4. Przykład: kodowanie S-wyrażeń ......................................................................................... 329 12.5. Ustawianie zmiennych za pomocą reflect.Value .............................................................. 332 12.6. Przykład: dekodowanie S-wyrażeń .................................................................................... 334 12.7. Uzyskiwanie dostępu do znaczników pól struktury ........................................................ 337 12.8. Wyświetlanie metod typu .................................................................................................... 340 12.9. Słowo ostrzeżenia ................................................................................................................. 341 Rozdzia(cid:228) 13. Programowanie niskiego poziomu ........................................ 343 13.1. Funkcje unsafe.Sizeof, Alignof i Offsetof .......................................................................... 344 13.2. Typ unsafe.Pointer ............................................................................................................... 346 13.3. Przykład: głęboka równoważność ...................................................................................... 348 13.4. Wywoływanie kodu C za pomocą narzędzia cgo ............................................................. 351 13.5. Kolejne słowo ostrzeżenia ................................................................................................... 355 Skorowidz .............................................................................................. 356 Poleć książkęKup książkę Rozdzia(cid:228) 7 Interfejsy Typy interfejsowe wyrażają uogólnienia lub abstrakcje dotyczące zachowań innych typów. Dzięki uogólnianiu interfejsy pozwalają pisać funkcje, które są bardziej elastyczne i adaptowalne, ponieważ nie są związane ze szczegółami jednej konkretnej implementacji. Wiele języków obiektowych ma jakąś koncepcję interfejsów, ale interfejsy języka Go wyróżnia to, że ich warunki są spełniane pośrednio. Innymi słowy: nie ma potrzeby deklarowania wszystkich in- terfejsów, których warunki spełnia konkretny typ. Wystarczy po prostu posiadanie niezbędnych metod. Taka konstrukcja pozwala na tworzenie nowych interfejsów, których warunki są spełniane przez istniejące konkretne typy bez ich zmieniania, co jest szczególnie przydatne dla typów zde- finiowanych w niekontrolowanych przez Ciebie pakietach. Ten rozdział rozpoczniemy od przyjrzenia się podstawowym mechanizmom typów interfejso- wych i ich wartościom. Potem przestudiujemy kilka ważnych interfejsów ze standardowej bi- blioteki — wielu programistów języka Go korzysta ze standardowych interfejsów w równym stopniu jak ze swoich własnych. Na koniec przyjrzymy się asercjom typów (zob. podrozdział 7.10) oraz przełącznikom typów (zob. podrozdział 7.13) i zobaczymy, w jaki sposób umożliwiają stoso- wanie innego rodzaju ogólności. 7.1. Interfejsy jako kontrakty Wszystkie omawiane do tej pory typy były typami konkretnymi. Typ konkretny określa dokładną reprezentację swoich wartości i udostępnia wewnętrzne operacje tej reprezentacji, takie jak arytmetyka dla liczb albo indeksowanie, append i range dla wycinków. Konkretny typ może również zapewniać dodatkowe zachowania poprzez swoje metody. Gdy masz wartość konkretnego typu, wiesz dokładnie, czym ona jest i co możesz z nią zrobić. W języku Go istnieje jeszcze inny rodzaj typu, zwany typem interfejsowym. Interfejs jest typem abstrakcyjnym. Nie udostępnia reprezentacji czy wewnętrznej struktury swoich wartości ani też zbioru podstawowych obsługiwanych operacji. Ujawnia tylko niektóre swoje metody. Gdy masz wartość typu interfejsowego, nie wiesz nic na temat tego, czym ona jest. Wiesz tylko, co może robić, lub, mówiąc bardziej precyzyjnie, jakie zachowania są dostarczane przez jej metody. Dotychczas używaliśmy dwóch podobnych funkcji do formatowania łańcuchów znaków: fmt.Printf, która zapisuje wynik do standardowego strumienia wyjściowego (pliku), oraz fmt.Sprintf, która zwraca wynik jako string. Byłoby niedobrze, gdyby ta trudna część, jaką jest formatowanie wyniku, Poleć książkęKup książkę 174 ROZDZIA(cid:227) 7. INTERFEJSY musiała być duplikowana ze względu na te drobne różnice w sposobie wykorzystywania wyniku. Dzięki interfejsom tak nie jest. Obie te funkcje są w istocie funkcjami opakowującymi trzecią funkcję, fmt.Fprintf, która jest neutralna w kwestii tego, co się dzieje z obliczanym przez nią wynikiem: package fmt func Fprintf(w io.Writer, format string, args ...interface{}) (int, error) func Printf(format string, args ...interface{}) (int, error) { return Fprintf(os.Stdout, format, args...) } func Sprintf(format string, args ...interface{}) string { var buf bytes.Buffer Fprintf( buf, format, args...) return buf.String() } Prefiks F w nazwie Fprintf oznacza plik (ang. file) i wskazuje, że sformatowane dane wyjściowe powinny zostać zapisane w pliku dostarczonym jako pierwszy argument. W przypadku Printf tym argumentem (os.Stdout) jest *os.File. Jednak w przypadku Sprintf tym argumentem nie jest plik, chociaż pozornie go przypomina: buf jest wskaźnikiem do bufora pamięci, w którym mogą być zapisywane bajty. Pierwszym parametrem funkcji Fprintf również nie jest plik. Jest nim io.Writer, czyli typ interfej- sowy z następującą deklaracją: package io // Writer jest interfejsem, który opakowuje podstawow(cid:261) metod(cid:266) Write. type Writer interface { // Write zapisuje len(p) bajtów ze zmiennej p do bazowego strumienia danych. // Zwraca liczb(cid:266) bajtów zapisanych z p (0 = n = len(p)) // oraz ka(cid:298)dy napotkany b(cid:225)(cid:261)d, który spowodowa(cid:225) przedwczesne zatrzymanie zapisywania. // Metoda Write musi zwraca(cid:252) b(cid:225)(cid:261)d nieb(cid:266)d(cid:261)cy nil, je(cid:286)li zwraca n len(p). // Metoda Write nie mo(cid:298)e modyfikowa(cid:252) danych wycinka, nawet tymczasowo. // // Implementacje nie mog(cid:261) przechowywa(cid:252) w pami(cid:266)ci zmiennej p. Write(p []byte) (n int, err error) } Interfejs io.Writer definiuje kontrakt między funkcją Fprintf a wywołującymi ją podmiotami. Z jednej strony, kontrakt wymaga, aby podmiot wywołujący zapewnił wartość konkretnego typu, takiego jak *os.File lub *bytes.Buffer, który ma metodę o nazwie Write z odpowiednimi sy- gnaturą i zachowaniem. Z drugiej strony, kontrakt gwarantuje, że funkcja Fprintf będzie wyko- nywać swoje zadania, mając daną dowolną wartość, która spełnia warunki interfejsu io.Writer. Funkcja Fprintf nie może zakładać, że zapisuje w pliku lub pamięci, tylko że może wywołać metodę Write. Ponieważ funkcja fmt.Fprintf nie zakłada niczego w kwestii reprezentacji wartości i opiera się tylko na zachowaniach gwarantowanych przez kontrakt io.Writer, możemy do tej funkcji bez- piecznie przekazywać jako pierwszy argument wartość dowolnego typu konkretnego, który spełnia warunki interfejsu io.Writer. Ta swoboda zastępowania jednego typu innym typem, który spełnia warunki tego samego interfejsu, nazywa się podstawialnością i jest charakterystyczną cechą programowania obiektowego. Poleć książkęKup książkę 7.1. INTERFEJSY JAKO KONTRAKTY 175 Sprawdźmy to przy użyciu nowego typu. Przedstawiona poniżej metoda Write typu *ByteCounter jedynie zlicza zapisane w nim bajty przed ich porzuceniem. (Wymagana jest konwersja, aby za- pewnić dopasowanie typów dla len(p) i *c w instrukcji przypisania +=). code/r07/bytecounter type ByteCounter int func (c *ByteCounter) Write(p []byte) (int, error) { *c += ByteCounter(len(p)) // konwersja int na ByteCounter return len(p), nil } Ponieważ *ByteCounter spełnia warunki kontraktu io.Writer, możemy przekazać go do funkcji Fprintf, która przeprowadza formatowanie swojego łańcucha znaków bez świadomości tej zmiany. ByteCounter poprawnie akumuluje długość wyniku. var c ByteCounter c.Write([]byte( witaj )) fmt.Println(c) // 5 , = len( witaj ) c = 0 // resetowanie licznika var name = Marta fmt.Fprintf( c, witaj, s , name) fmt.Println(c) // 12 , = len( witaj, Marta ) Poza io.Writer istnieje jeszcze inny bardzo ważny interfejs dla pakietu fmt. Funkcje Fprintf i Fprintln zapewniają typom możliwość kontrolowania sposobu, w jaki wyświetlane są ich wartości. W podrozdziale 2.5 zdefiniowaliśmy metodę String dla typu Celsius, aby temperatury były wy- świetlane jako 100 °C , a w podrozdziale 6.5 wyposażyliśmy typ *IntSet w metodę String, aby zbiory były przedstawiane z wykorzystaniem tradycyjnej notacji, np. {1 2 3} . Zadeklaro- wanie metody String sprawia, że dany typ spełnia warunki jednego z najszerzej stosowanych in- terfejsów, czyli interfejsu fmt.Stringer: package fmt // String jest wykorzystywana do wy(cid:286)wietlania warto(cid:286)ci przekazywanych // jako operand do dowolnego formatu, który akceptuje (cid:225)a(cid:276)cuch znaków, // lub do funkcji wypisywania bez ustawionego formatu, takiej jak Print. type Stringer interface { String() string } W podrozdziale 7.10 wyjaśnimy, w jaki sposób pakiet fmt wykrywa, które wartości spełniają warunki tego interfejsu. Ćwiczenie 7.1. Wykorzystując pomysły z ByteCounter, zaimplementuj liczniki dla słów i linii. Przy- dać Ci się może funkcja bufio.ScanWords. Ćwiczenie 7.2. Napisz funkcję CountingWriter (z przedstawioną poniżej sygnaturą), która mając dany interfejs io.Writer, zwraca nowy Writer, opakowujący oryginalny, oraz wskaźnik do zmiennej int64, która w każdym momencie zawiera liczbę bajtów aktualnie zapisanych do nowego interfejsu Writer. func CountingWriter(w io.Writer) (io.Writer, *int64) Ćwiczenie 7.3. Napisz metodę String dla typu *tree z programu code/r04/treesort (zob. podrozdział 4.4), która ujawnia sekwencję wartości w drzewie. Poleć książkęKup książkę 176 ROZDZIA(cid:227) 7. INTERFEJSY 7.2. Typy interfejsowe Typ interfejsowy określa zestaw metod, które musi posiadać typ konkretny, aby został uznany za instancję danego interfejsu. Typ io.Writer jest jednym z najszerzej stosowanych interfejsów, ponieważ zapewnia abstrakcję wszystkich typów, do których mogą być zapisywane bajty, co obejmuje: pliki, bufory pamięci, połączenia sieciowe, klienty HTTP, programy archiwizujące, programy szyfrujące itd. Wiele innych użytecznych interfejsów definiuje pakiet io. Interfejs Reader reprezentuje dowolny typ, z którego można odczytywać bajty, a Closer jest dowolną wartością, którą można zamknąć, taką jak plik lub połączenie sieciowe. (Prawdopodobnie dostrzegłeś już konwencję nazewnictwa stosowaną dla wielu posiadających jedną metodę interfejsów języka Go). package io type Reader interface { Read(p []byte) (n int, err error) } type Closer interface { Close() error } Jeśli poszukamy dalej, znajdziemy deklaracje nowych typów interfejsów jako kombinacje istnieją- cych. Oto dwa przykłady: type ReadWriter interface { Reader Writer } type ReadWriteCloser interface { Reader Writer Closer } Użyta powyżej składnia, przypominająca osadzanie struktur, pozwala nam określać kolejny inter- fejs jako skrót służący do wypisywania wszystkich jego metod. Nazywa się to osadzaniem inter- fejsu. Moglibyśmy zapisać interfejs io.ReadWriter bez osadzania (chociaż mniej zwięźle) w nastę- pujący sposób: type ReadWriter interface { Read(p []byte) (n int, err error) Write(p []byte) (n int, err error) } Moglibyśmy nawet pomieszać te dwa style: type ReadWriter interface { Read(p []byte) (n int, err error) Writer } Wszystkie trzy deklaracje mają ten sam efekt. Kolejność, w jakiej pojawiają się metody, jest nie- istotna. Znaczenie ma jedynie zestaw metod. Poleć książkęKup książkę 7.3. SPE(cid:227)NIANIE WARUNKÓW INTERFEJSU 177 Ćwiczenie 7.4. Funkcja strings.NewReader zwraca wartość, która spełnia warunki interfejsu io.Reader (i innych), odczytując z jego argumentu, czyli łańcucha znaków. Zaimplementuj prostą wersję funkcji NewReader i użyj jej, aby umożliwić przyjmowanie przez parser HTML (zob. podroz- dział 5.2) danych wejściowych z łańcucha znaków. Ćwiczenie 7.5. Funkcja LimitReader z pakietu io akceptuje argument r interfejsu io.Reader oraz liczbę bajtów n, a następnie zwraca kolejny Reader, który odczytuje z argumentu r, ale zgłasza stan końca pliku po n bajtach. Zaimplementuj ją. func LimitReader(r io.Reader, n int64) io.Reader 7.3. Spe(cid:228)nianie warunków interfejsu Typ spełnia warunki interfejsu, jeśli ma wszystkie metody, których wymaga dany interfejs. Przy- kładowo: typ *os.File spełnia warunki interfejsów io.Reader, Writer, Closer i ReadWriter. Typ *bytes.Buffer spełnia warunki interfejsów Reader, Write i ReadWriter, ale nie spełnia wa- runków interfejsu Closer, ponieważ nie ma metody Close. Programiści języka Go często stosują skrót myślowy, mówiąc, że konkretny typ „jest” określonym typem interfejsowym, co oznacza, że spełnia warunki tego interfejsu, np. *bytes.Buffer „jest” typem io.Writer, a *os.File „jest” typem io.ReadWriter. Reguła przypisywalności (zob. punkt 2.4.2) dla interfejsów jest bardzo prosta: wyrażenie może być przypisane do interfejsu tylko wtedy, gdy spełnia warunki tego interfejsu. Więc: var w io.Writer w = os.Stdout // OK: *os.File ma metod(cid:266) Write w = new(bytes.Buffer) // OK: *bytes.Buffer ma metod(cid:266) Write w = time.Second // b(cid:225)(cid:261)d kompilacji: time.Duration nie ma metody Write var rwc io.ReadWriteCloser rwc = os.Stdout // OK: *os.File ma metody Read, Write i Close rwc = new(bytes.Buffer) // b(cid:225)(cid:261)d kompilacji: *bytes.Buffer nie ma metody Close Ta reguła ma zastosowanie nawet wtedy, gdy prawa strona sama jest interfejsem: w = rwc // OK: io.ReadWriteCloser ma metod(cid:266) Write rwc = w // b(cid:225)(cid:261)d kompilacji: io.Writer nie ma metody Close Ponieważ ReadWriter i ReadWriteCloser obejmują wszystkie metody interfejsu Writer, każdy typ spełniający warunki interfejsu ReadWriter lub ReadWriteCloser siłą rzeczy spełnia warunki interfejsu Writer. Zanim przejdziemy dalej, powinniśmy wyjaśnić kwestię tego, co znaczy, że jakiś typ ma metodę. Przypomnijmy z punktu 6.2, że dla każdego nazwanego typu konkretnego T niektóre jego metody same posiadają odbiornik typu T, podczas gdy inne wymagają wskaźnika *T. Przypomnijmy rów- nież, że prawidłowe jest wywołanie metody *T na argumencie typu T, pod warunkiem że ten ar- gument jest zmienną. Kompilator pośrednio pobiera jej adres. Ale to jest zwykły lukier skła- dniowy: wartość typu T nie posiada wszystkich metod, które ma wskaźnik *T, w wyniku czego może spełniać wymagania mniejszej liczby interfejsów. Wyjaśnimy to na przykładzie. Metoda String typu IntSet z podrozdziału 6.5 wymaga odbiornika wskaźnikowego, więc nie możemy wywołać tej metody na nieadresowalnej wartości IntSet: type IntSet struct { /* ... */ } func (*IntSet) String() string var _ = IntSet{}.String() // b(cid:225)(cid:261)d kompilacji: String wymaga odbiornika *IntSet Poleć książkęKup książkę 178 ROZDZIA(cid:227) 7. INTERFEJSY Możemy jednak wywołać ją na zmiennej IntSet: var s IntSet var _ = s.String() // OK: s jest zmienn(cid:261), a s ma metod(cid:266) String Ponieważ jednak tylko *IntSet ma metodę String, tylko *IntSet spełnia warunki interfejsu fmt.Stringer: var _ fmt.Stringer = s // OK var _ fmt.Stringer = s // b(cid:225)(cid:261)d kompilacji: IntSet nie ma metody String Podrozdział 12.8 zawiera program wyświetlający metody dowolnej wartości, a narzędzie godoc -analysis=type (zob. punkt 10.7.4) wyświetla metody każdego typu i relacje pomiędzy interfejsami i typami konkretnymi. Tak jak koperta opakowuje i ukrywa przechowywany list, tak interfejs opakowuje i ukrywa typ konkretny i przechowywaną przez niego wartość. Wywoływane mogą być tylko metody ujawnione przez typ interfejsowy, nawet jeśli dany typ konkretny ma jeszcze inne: os.Stdout.Write([]byte( witaj )) // OK: *os.File ma metod(cid:266) Write os.Stdout.Close() // OK: *os.File ma metod(cid:266) Close var w io.Writer w = os.Stdout w.Write([]byte( witaj )) // OK: io.Writer ma metod(cid:266) Write w.Close() // b(cid:225)(cid:261)d kompilacji: io.Writer nie ma metody Close Interfejs z większą liczbą metod (taki jak io.ReadWriter) daje nam więcej informacji o wartościach, jakie zawiera, i stawia większe wymagania dotyczące typów, które go implementują, niż interfejs z mniejszą liczbą metod (taki jak io.Reader). Jakie więc informacje daje nam typ interface{}, który w ogóle nie ma żadnych metod, na temat typów konkretnych spełniających jego warunki? Zgadza się: nie daje żadnych. Może się to wydawać bezużyteczne, ale w rzeczywistości typ interface{}, zwany pustym typem interfejsowym, jest nieodzowny. Ponieważ pusty typ interfejsowy nie stawia żadnych wymagań dotyczących typów spełniających jego warunki, możemy do niego przypisać dowolną wartość. var any interface{} any = true any = 12.34 any = witaj any = map[string]int{ jeden : 1} any = new(bytes.Buffer) Chociaż nie było oczywiste, używaliśmy typu pustego interfejsu już od pierwszego przykładu przed- stawionego w tej książce, ponieważ pozwala on funkcjom takim jak fmt.Println lub errorf z pod- rozdziału 5.7 akceptować argumenty dowolnego typu. Oczywiście gdy utworzymy wartość interface{} zawierającą wartość logiczną, liczbę zmienno- przecinkową, łańcuch znaków, mapę, wskaźnik lub dowolny inny typ, nie możemy bezpośrednio nic zrobić z tą przechowywaną przez niego wartością, ponieważ ten interfejs nie ma metod. Musi- my znaleźć sposób, aby ponownie wydobyć tę wartość. W podrozdziale 7.10 zobaczymy, jak to zrobić przy użyciu asercji typów. Ponieważ spełnienie warunków interfejsu zależy tylko od metod dwóch zaangażowanych w to ty- pów, nie ma potrzeby deklarowania relacji między typem konkretnym a interfejsem, którego wa- runki on spełnia. Mimo to czasami przydatne jest udokumentowanie i założenie danej relacji, gdy jest ona zamierzona, ale w żaden inny sposób nie jest egzekwowana przez program. Poniższa Poleć książkęKup książkę 7.3. SPE(cid:227)NIANIE WARUNKÓW INTERFEJSU 179 deklaracja stwierdza w czasie kompilacji, że wartość typu *bytes.Buffer spełnia warunki interfejsu io.Writer: // Typ *bytes.Buffer musi spe(cid:225)nia(cid:252) warunki interfejsu io.Writer. var w io.Writer = new(bytes.Buffer) Nie musimy alokować nowej zmiennej, ponieważ nada się każda wartość typu *bytes.Buffer, nawet nil, którą zapisujemy jako (*bytes.Buffer)(nil), używając konwersji bezpośredniej. A ponieważ nigdy nie zamierzamy odwoływać się do zmiennej w, możemy zastąpić ją pustym iden- tyfikatorem. Wszystkie te zmiany zebrane razem dają nam ten bardziej oszczędny wariant: // Typ *bytes.Buffer musi spe(cid:225)nia(cid:252) warunki interfejsu io.Writer. var _ io.Writer = (*bytes.Buffer)(nil) Warunki niepustych typów interfejsowych, takich jak io.Writer, są najczęściej spełniane przez typ wskaźnika, w szczególności gdy jedna z metod tego interfejsu lub kilka z nich zakłada pewien rodzaj mutacji odbiornika, tak jak metoda Write. Wskaźnik do struktury jest szczególnie popu- larnym typem przenoszącym metody. Jednak typy wskaźników wcale nie są jedynymi typami, które spełniają warunki interfejsów, i nawet warunki interfejsów z metodami modyfikującymi mogą być spełnione przez jeden z innych typów referencyjnych języka Go. Widzieliśmy przykłady typów wycinka z metodami (geometry.Path, podrozdział 6.1) i typów map z metodami (url.Values, punkt 6.2.1), a później zobaczymy typ funkcji z metodami (http.HandlerFunc, podrozdział 7.7). Nawet podstawowe typy mogą spełniać warunki interfejsów. Jak zobaczymy w podrozdziale 7.4, typ time.Duration spełnia warunki interfejsu fmt.Stringer. Typ konkretny może spełniać warunki wielu niepowiązanych interfejsów. Rozważmy program, który organizuje lub sprzedaje cyfrowe artefakty kulturowe, takie jak muzyka, filmy i książki. Może on definiować następujący zestaw typów konkretnych: Album Book Movie Magazine Podcast TVEpisode Track Każdą interesującą nas abstrakcję możemy wyrazić jako interfejs. Niektóre właściwości są wspólne dla wszystkich artefaktów, np.: tytuł, data utworzenia oraz lista twórców (autorów lub artystów). type Artifact interface { Title() string Creators() []string Created() time.Time } Inne właściwości są ograniczone do określonych typów artefaktów. Właściwości słowa drukowanego dotyczą tylko książek i czasopism, podczas gdy rozdzielczość mają tylko filmy i seriale telewizyjne. type Text interface { Pages() int Words() int PageSize() int } type Audio interface { Stream() (io.ReadCloser, error) Poleć książkęKup książkę 180 ROZDZIA(cid:227) 7. INTERFEJSY RunningTime() time.Duration Format() string // np. MP3 , WAV } type Video interface { Stream() (io.ReadCloser, error) RunningTime() time.Duration Format() string // np. MP4 , WMV Resolution() (x, y int) } Interfejsy są tylko jednym z wielu sposobów grupowania powiązanych typów konkretnych i wyra- żania ich wspólnych aspektów. Inne sposoby grupowania poznamy później. Jeśli okazałoby się np., że potrzebujemy obsługiwać elementy Audio i Video w taki sam sposób, moglibyśmy zdefinio- wać interfejs Streamer służący do reprezentowania ich wspólnych aspektów bez zmiany istniejących deklaracji typów. type Streamer interface { Stream() (io.ReadCloser, error) RunningTime() time.Duration Format() string } Każde grupowanie typów konkretnych oparte na ich wspólnych zachowaniach może być wyrażone jako typ interfejsowy. W przeciwieństwie do języków opartych na klasach, w których zestaw interfej- sów o warunkach spełnianych przez jakąś klasę jest wyraźnie określony, w języku Go możemy definiować nowe abstrakcje lub grupy interesów w razie potrzeby, bez modyfikacji deklaracji typu konkretnego. Jest to szczególnie przydatne, gdy dany typ konkretny pochodzi z pakietu napisanego przez innego autora. Oczywiście muszą istnieć bazowe podobieństwa w tych typach konkretnych. 7.4. Parsowanie flag za pomoc(cid:241) interfejsu flag.Value W tym podrozdziale zobaczymy, jak inny standardowy interfejs, flag.Value, pomaga nam defi- niować nowe notacje dla flag wiersza poleceń. Rozważmy poniższy program, który zostaje uśpiony na określony czas. code/r07/sleep var period = flag.Duration( period , 1*time.Second, sleep period ) func main() { flag.Parse() fmt.Printf( (cid:165)pi przez v... , *period) time.Sleep(*period) fmt.Println() } Zanim program zostanie uśpiony, wyświetla przedział czasu. Pakiet fmt wywołuje metodę String typu time.Duration, żeby wyświetlić przedział czasu, ale nie w nanosekundach, tylko w sposób przyjazny dla użytkownika: $ go build code/r07/sleep $ ./sleep (cid:165)pi przez 1s... Domyślnym okresem uśpienia jest jedna sekunda, ale można go kontrolować za pomocą flagi wiersza poleceń -period. Funkcja flag.Duration tworzy zmienną flagi o typie time.Duration i pozwala użytkownikowi określać czas trwania uśpienia w różnych przyjaznych dla użytkownika Poleć książkęKup książkę 7.4. PARSOWANIE FLAG ZA POMOC(cid:240) INTERFEJSU FLAG.VALUE 181 formatach, również w tej samej notacji, która jest wyświetlana przez metodę String. Ta symetria rozwiązania pozwala uzyskać miły interfejs użytkownika. $ ./sleep -period 50ms (cid:165)pi przez 50ms... $ ./sleep -period 2m30s (cid:165)pi przez 2m30s... $ ./sleep -period 1.5h (cid:165)pi przez 1h30m0s... $ ./sleep -period 1 dzie(cid:241) invalid value 1 dzie(cid:241) for flag -period: time: unknown unit dzie(cid:241) in duration 1 dzie(cid:241) Ponieważ flagi czasu trwania są tak przydatne, ta funkcja jest wbudowana w pakiet flag, ale łatwo jest zdefiniować nową notację flag dla własnych typów danych. Trzeba tylko zdefiniować typ spełniający warunki interfejsu flag.Value, którego deklaracja została przedstawiona poniżej: package flag // Value jest interfejsem dla warto(cid:286)ci przechowywanej we fladze. type Value interface { String() string Set(string) error } Metoda String formatuje wartość flagi do wykorzystywania w komunikatach pomocy wiersza poleceń. Zatem każdy interfejs flag.Value jest również interfejsem fmt.Stringer. Metoda Set parsuje swój argument w postaci łańcucha znaków i aktualizuje wartość flagi. W efekcie metoda Set stanowi odwrotność metody String i jest dobrą praktyką, aby obie te metody korzystały z tej samej notacji. Zdefiniujmy typ celsiusFlag, który pozwala określać temperaturę w stopniach Celsjusza lub Fahren- heita z odpowiednią konwersją. Należy zwrócić uwagę, że celsiusFlag osadza typ Celsius (zob. podrozdział 2.5), a tym samym uzyskuje za darmo metodę String. Aby spełnić warunki inter- fejsu flag.Value, trzeba tylko zadeklarować metodę Set: code/r07/tempconv // Typ *celsiusFlag spe(cid:225)nia warunki interfejsu flag.Value. type celsiusFlag struct{ Celsius } func (f *celsiusFlag) Set(s string) error { var unit string var value float64 fmt.Sscanf(s, f s , value, unit) // nie potrzeba kontroli b(cid:225)(cid:266)dów switch unit { case C , °C : f.Celsius = Celsius(value) return nil case F , °F : f.Celsius = FToC(Fahrenheit(value)) return nil } return fmt.Errorf( nieprawid(cid:239)owa temperatura q , s) } Wywołanie funkcji fmt.Sscanf parsuje liczbę zmiennoprzecinkową (value) i łańcuch znaków (unit) z danych wejściowych s. Chociaż zwykle trzeba sprawdzać wynik błędu funkcji Sscanf, w tym przypadku nie musimy tego robić, ponieważ jeśli wystąpiłby problem, nie zostałby dopasowa- ny żaden przypadek instrukcji switch. Poleć książkęKup książkę 182 ROZDZIA(cid:227) 7. INTERFEJSY To wszystko opakowuje przedstawiona poniżej funkcja CelsiusFlag. Zwraca ona podmiotowi wywołującemu wskaźnik do pola Celsius osadzonego w zmiennej f typu celsiusFlag. Pole Celsius jest zmienną, która będzie aktualizowana przez metodę Set w trakcie przetwarzania flag. Wywołanie Var dodaje daną flagę do zestawu flag wiersza poleceń aplikacji, czyli zmiennej globalnej flag.CommandLine. Programy z niezwykle skomplikowanymi interfejsami wiersza poleceń mogą mieć kilka zmiennych tego typu. Wywołanie Var przypisuje argument *celsiusFlag do parametru flag.Value, powodując, że kompilator sprawdza, czy *celsiusFlag posiada niezbędne metody. // CelsiusFlag definiuje flag(cid:266) Celsius z okre(cid:286)lon(cid:261) nazw(cid:261), domy(cid:286)ln(cid:261) warto(cid:286)ci(cid:261) // oraz informacj(cid:261) o sposobie wykorzystania i zwraca adres zmiennej flagi. // Argument flagi musi podawa(cid:252) ilo(cid:286)(cid:252) i jednostk(cid:266), np. 100C . func CelsiusFlag(name string, value Celsius, usage string) *Celsius { f := celsiusFlag{value} flag.CommandLine.Var( f, name, usage) return f.Celsius } Teraz możemy zacząć używać tej nowej flagi w naszych programach: code/r07/tempflag var temp = tempconv.CelsiusFlag( temp , 20.0, temperatura ) func main() { flag.Parse() fmt.Println(*temp) } Oto typowa sesja: $ go build code/r07/tempflag $ ./tempflag 20°C $ ./tempflag -temp -18C -18°C $ ./tempflag -temp 212°F 100°C $ ./tempflag -temp 273.15K invalid value 273.15K for flag -temp: nieprawid(cid:239)owa temperatura 273.15K Usage of ./tempflag: -temp value temperatura (default 20°C) $ ./tempflag -help Usage of ./tempflag: -temp value temperatura (default 20°C) Ćwiczenie 7.6. Dodaj do programu tempflag wsparcie dla temperatury w skali Kelvina. Ćwiczenie 7.7. Wyjaśnij, dlaczego komunikat pomocy zawiera symbol °C, podczas gdy domyślna wartość 20.0 go nie zawiera. 7.5. Warto(cid:264)ci interfejsów Koncepcyjnie wartość typu interfejsowego, czyli inaczej wartość interfejsu, obejmuje dwa kompo- nenty: typ konkretny i wartość tego typu. Są one nazywane dynamicznym typem i dynamiczną wartością interfejsu. Poleć książkęKup książkę 7.5. WARTO(cid:263)CI INTERFEJSÓW 183 W przypadku języka typowanego statycznie, takiego jak Go, typy są pojęciem czasu kompilacji, więc typ nie jest wartością. W naszym modelu koncepcyjnym zestaw wartości, zwanych deskryp- torami typów, dostarcza na temat każdego typu informacji takich jak jego nazwa i metody. W warto- ści interfejsu komponent typu jest reprezentowany przez odpowiedni deskryptor typu. W czterech poniższych instrukcjach zmienna w przyjmuje trzy różne wartości. (Wartość początkowa jest taka sama jak końcowa). var w io.Writer w = os.Stdout w = new(bytes.Buffer) w = nil Przyjrzyjmy się bliżej wartości i dynamicznemu zachowaniu zmiennej w po każdej instrukcji. Pierwsza instrukcja deklaruje w: var w io.Writer W języku Go zmienne są zawsze inicjowane do wyraźnie określonej wartości, a interfejsy nie są wy- jątkiem. Wartość zerowa dla interfejsu ma ustawione na nil oba jego komponenty: typ i wartość (rysunek 7.1). Rysunek 7.1. Wartość nil interfejsu Wartość interfejsu jest opisywana jako nil lub różna od nil na podstawie jego dynamicznego typu, więc to jest wartość nil interfejsu. Możesz sprawdzić, czy wartością interfejsu jest nil, za pomocą w == nil lub w != nil. Wywołanie jakiejkolwiek metody wartości nil interfejsu wywołuje procedurę panic: w.Write([]byte( witaj )) // panic: wy(cid:225)uskanie wska(cid:296)nika nil Druga instrukcja przypisuje do zmiennej w wartość typu *os.File: w = os.Stdout To przypisanie obejmuje pośrednią konwersję z typu konkretnego na typ interfejsowy i jest równo- ważne z bezpośrednią konwersją io.Writer(os.Stdout). Konwersja tego rodzaju, pośrednia lub bezpośrednia, przechwytuje typ i wartość jego operandu. Typ dynamiczny interfejsu jest ustawiany na deskryptor typu dla typu wskaźnika *os.File, a jego wartość dynamiczna przechowuje kopię os.Stdout, która jest wskaźnikiem do zmiennej os.File reprezentującej standardowy strumień wyjściowy procesu (rysunek 7.2). Rysunek 7.2. Wartość interfejsu zawierająca wskaźnik *os.File Poleć książkęKup książkę 184 ROZDZIA(cid:227) 7. INTERFEJSY Wywołanie metody Write na wartości interfejsu zawierającej wskaźnik *os.File powoduje wywoła- nie metody (*os.File).Write. To wywołanie wyświetla witaj . w.Write([]byte( witaj )) // witaj Zasadniczo podczas kompilacji nie możemy wiedzieć, jaki będzie dynamiczny typ wartości inter- fejsu, więc wywołanie poprzez interfejs musi używać dynamicznego rozdzielania (ang. dynamic dispatch). Zamiast bezpośredniego wywołania kompilator musi wygenerować kod, aby uzyskać adres metody o nazwie Write z deskryptora typu, a następnie wykonać pośrednie wywołanie tego adre- su. Argumentem odbiornika dla tego wywołania jest kopia wartości dynamicznej interfejsu — os.Stdout. Efekt jest taki, jakbyśmy wykonali to wywołanie bezpośrednio: os.Stdout.Write([]byte( witaj )) // witaj Trzecia instrukcja przypisuje wartość typu *bytes.Buffer do wartości interfejsu: w = new(bytes.Buffer) Typem dynamicznym jest teraz *bytes.Buffer, a wartością dynamiczną jest wskaźnik do nowo alo- kowanego bufora (rysunek 7.3). Rysunek 7.3. Wartość interfejsu zawierająca wskaźnik *bytes.Buffer Wywołanie metody Write wykorzystuje ten sam mechanizm co poprzednio: w.Write([]byte( witaj )) // zapisuje witaj w bytes.Buffer Tym razem deskryptorem typu jest *bytes.Buffer, więc wywoływana jest metoda (*bytes. Buffer).Write z adresem bufora jako wartością parametru odbiornika. To wywołanie dołącza witaj do bufora. Na koniec czwarta instrukcja przypisuje nil do wartości interfejsu: w = nil To przypisanie resetuje oba jego komponenty do wartości nil, przywracając zmienną w do tego same- go stanu, w jakim była po zadeklarowaniu, czyli do stanu, który został przedstawiony na rysunku 7.1. Wartość interfejsu może przechowywać dowolnie duże wartości dynamiczne. Przykładowo: typ time.Time, który reprezentuje moment w czasie, jest typem struct z kilkoma niewyeksporto- wanymi polami. Jeśli utworzymy z niego wartość interfejsu var x interface{} = time.Now() wynik może wyglądać tak, jak pokazano na rysunku 7.4. Koncepcyjnie wartość dynamiczna zawsze mieści się wewnątrz wartości interfejsu, bez względu na to, jak duży jest jego typ. (To tylko model koncepcyjny. Rzeczywista implementacja jest zupełnie inna) Poleć książkęKup książkę 7.5. WARTO(cid:263)CI INTERFEJSÓW 185 Rysunek 7.4. Wartość interfejsu przechowuje strukturę time.Time Wartości interfejsów mogą być porównywane za pomocą operatorów == i !=. Dwie wartości in- terfejsów są równe, jeśli obie są nil lub jeśli ich typy dynamiczne są identyczne, a ich wartości dynamiczne są równe zgodnie ze standardowym zachowaniem operatora == dla danego typu. Ponieważ wartości interfejsów są porównywalne, mogą być stosowane jako klucze map lub jako operandy instrukcji switch. Jeśli jednak porównywane są dwie wartości interfejsów i mają one ten sam typ dynamiczny, który jednak nie jest porównywalny (np. wycinek), porównanie nie powiedzie się i wywoła panikę: var x interface{} = []int{1, 2, 3} fmt.Println(x == x) // panic: porównywanie nieporównywalnego typu []int Pod tym względem typy interfejsów są ewenementem. Inne typy są bezpiecznie porównywalne (typy podstawowe i wskaźniki) albo w ogóle nieporównywalne (wycinki, mapy i funkcje), ale kie- dy porównujemy wartości interfejsów lub typy złożone, które zawierają wartości interfejsów, musimy być świadomi możliwości uruchomienia procedury panic. Podobne ryzyko istnieje, gdy używamy interfejsów jako kluczy map lub operandów instrukcji switch. Wartości interfejsów na- leży porównywać tylko wtedy, kiedy ma się pewność, że zawierają one dynamiczne wartości po- równywalnych typów. Podczas obsługi błędów lub debugowania często pomocne jest raportowanie dynamicznego typu wartości interfejsu. W tym celu używamy czasownika T pakietu fmt: var w io.Writer fmt.Printf( T\n , w) // nil w = os.Stdout fmt.Printf( T\n , w) // *os.File w = new(bytes.Buffer) fmt.Printf( T\n , w) // *bytes.Buffer Wewnętrznie pakiet fmt do uzyskania nazwy dynamicznego typu interfejsu wykorzystuje refleksję. Refleksji przyjrzymy się w rozdziale 12. 7.5.1. Zastrzeżenie: interfejs zawierający wskaźnik nil jest różny od nil Wartość nil interfejsu, który nie zawiera w ogóle żadnej wartości, nie jest tym samym co wartość interfejsu zawierającego wskaźnik, który akurat jest nil. Ta subtelna różnica tworzy pułapkę, w którą wpadł chyba każdy programista Go. Rozważmy poniższy program. Przy stałej debug ustawionej na wartość true funkcja main gromadzi dane wyjściowe z funkcji f w typie bytes.Buffer. Poleć książkęKup książkę 186 ROZDZIA(cid:227) 7. INTERFEJSY const debug = true func main() { var buf *bytes.Buffer if debug { buf = new(bytes.Buffer) // w(cid:225)(cid:261)czenie gromadzenia danych wyj(cid:286)ciowych } f(buf) // Uwaga: subtelnie nieprawid(cid:225)owe! if debug { // …u(cid:298)ycie buf… } } // Je(cid:286)li parametr out jest ró(cid:298)ny od nil, dane wyj(cid:286)ciowe b(cid:266)d(cid:261) w nim zapisywane. func f(out io.Writer) { // …co(cid:286) do zrobienia… if out != nil { out.Write([]byte( zrobione!\n )) } } Można by się spodziewać, że zmiana debug na false wyłączy gromadzenie danych wyjścio- wych, ale w rzeczywistości powoduje, że program uruchamia procedurę panic podczas wywołania out.Write: if out != nil { out.Write([]byte( zrobione!\n )) // panic: wy(cid:225)uskanie wska(cid:296)nika nil } Gdy funkcja main wywołuje funkcję f, przypisuje do parametru out wskaźnik nil typu *bytes. (cid:180)Buffer, więc wartością dynamiczną parametru out jest nil. Jego typem dynamicznym jest jednak *bytes.Buffer, co oznacza, że out jest interfejsem różnym od nil, zawierającym wartość wskaź- nika nil (rysunek 7.5), więc sprawdzenie defensywne out != nil jest nadal prawdziwe. Rysunek 7.5. Różny od nil interfejs zawierający wskaźnik nil Tak jak wcześniej dynamiczny mechanizm rozdzielania określa, że wywołana musi być metoda (*bytes.Buffer).Write, ale tym razem z wartością odbiornika, którą jest nil. Dla niektórych typów, takich jak *os.File, nil jest prawidłowym odbiornikiem (zob. punkt 6.2.1), ale *bytes.Buffer do nich nie należy. Ta metoda jest wywoływana, ale uruchamia procedurę panic, ponieważ próbuje uzyskać dostęp do bufora. Problem polega na tym, że chociaż wskaźnik nil typu *bytes.Buffer ma metody niezbędne do spełnienia warunków tego interfejsu, to nie spełnia jego wymagań behawioralnych. Wywołanie to narusza w szczególności dorozumiany warunek wstępny metody (*bytes.Buffer).Write, który zakłada, że jej odbiornik nie będzie nil, więc przypisanie wskaźnika nil do tego interfejsu było błędem. Rozwiązaniem jest zmiana typu zmiennej buf w funkcji main na io.Writer, co po- zwala przede wszystkim uniknąć przypisania do interfejsu dysfunkcyjnej wartości: Poleć książkęKup książkę 7.6. SORTOWANIE ZA POMOC(cid:240) INTERFEJSU SORT.INTERFACE 187 var buf io.Writer if debug { buf = new(bytes.Buffer) // w(cid:225)(cid:261)czenie gromadzenia danych wyj(cid:286)ciowych } f(buf) // OK Omówiliśmy mechanizmy wartości interfejsu, więc przyjrzyjmy się teraz kilku ważniejszym inter- fejsom ze standardowej biblioteki języka Go. W trzech kolejnych podrozdziałach zobaczymy, w jaki sposób interfejsy są używane do sortowania, serwowania zawartości WWW i obsługi błędów. 7.6. Sortowanie za pomoc(cid:241) interfejsu sort.Interface Tak jak formatowanie łańcuchów znaków sortowanie jest często używaną operacją w wielu pro- gramach. Chociaż minimalny algorytm sortowania szybkiego (ang. quicksort) można zmieścić w ja- kichś 15 liniach kodu, solidna implementacja jest znacznie dłuższa i nie jest to rodzaj kodu, który za każdym razem chcielibyśmy pisać od nowa lub kopiować, gdy jest on potrzebny. Na szczęście pakiet sort zapewnia sortowanie in situ dowolnej sekwencji według którejkolwiek funkcji porządkującej. Jego konstrukcja jest dość niezwykła. W wielu językach algorytm sortowa- nia jest powiązany z sekwencyjnym typem danych, podczas gdy funkcja porządkująca jest powią- zana z typem elementów. Natomiast funkcja sort.Sort języka Go nie zakłada niczego na temat reprezentacji sekwencji lub jej elementów. Zamiast tego wykorzystuje interfejs sort.Interface, służący do określania kontraktu pomiędzy ogólnym algorytmem sortowania i każdym typem se- kwencyjnym, który może być sortowany. Implementacja tego interfejsu określa zarówno konkretną reprezentację sekwencji (którą często jest wycinek), jak i wymagany porządek jej elementów. Algorytm sortowania in situ potrzebuje trzech rzeczy (długości sekwencji, zasad porównywania dwóch elementów i sposobu zamieniania dwóch elementów), więc interfejs sort.Interface ma trzy metody: package sort type Interface interface { Len() int Less(i, j int) bool // i, j to indeksy elementów sekwencji Swap(i, j int) } Aby posortować dowolną sekwencję, musimy zdefiniować typ implementujący te trzy metody, a na- stępnie zastosować funkcję sort.Sort do instancji tego typu. Rozważmy sortowanie wycinka łań- cuchów znaków jako być może najprostszy przykład. Poniżej pokazano nowy typ StringSlice i jego metody Len, Less i Swap. type StringSlice []string func (p StringSlice) Len() int { return len(p) } func (p StringSlice) Less(i, j int) bool { return p[i] p[j] } func (p StringSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } Teraz możemy posortować wycinek łańcuchów znaków (names) poprzez przekonwertowanie go na typ StringSlice w taki sposób: sort.Sort(StringSlice(names)) Ta konwersja daje wartość wycinka o tej samej długości, pojemności i tablicy bazowej co names, ale z typem, który ma trzy metody wymagane do sortowania. Poleć książkęKup książkę 188 ROZDZIA(cid:227) 7. INTERFEJSY Sortowanie wycinka łańcuchów znaków jest tak powszechne, że pakiet sort zapewnia typ StringSlice oraz funkcję o nazwie Strings, więc powyższe wywołanie można uprościć do postaci sort.Strings(names). Przedstawioną tu technikę można łatwo dostosować do innych porządków sortowania, np. do ignorowania wielkich liter lub znaków specjalnych. (Program Go sortujący tematy skorowidza i numery stron dla tej książki robi to przy dodatkowej logice dla cyfr rzymskich). Do skompliko- wanego sortowania używamy tej samej koncepcji, ale z bardziej skomplikowanymi strukturami danych lub z bardziej skomplikowanymi implementacjami metod interfejsu sort.Interface. Naszym działającym przykładem sortowania będzie lista odtwarzania utworów muzycznych wy- świetlona w postaci tabeli. Każdy utwór będzie pojedynczym wierszem, a każda kolumna będzie atrybutem tego utworu, takim jak: artysta, tytuł i czas odtwarzania. Wyobraź sobie, że graficzny interfejs użytkownika prezentuje tabelę, a kliknięcie nagłówka wybranej kolumny powoduje po- sortowanie listy odtwarzania według tego atrybutu. Ponowne kliknięcie nagłówka tej samej ko- lumny odwraca kolejność. Zobaczmy, co się może zdarzyć w reakcji na każde kliknięcie. Użyta poniżej zmienna tracks zawiera listę odtwarzania. (Jeden z autorów przeprasza za gust mu- zyczny drugiego). Każdy element jest pośredni — jest wskaźnikiem do typu Track. Chociaż poniższy kod działałby, gdybyśmy przechowywali elementy Track bezpośrednio, funkcja sortowania za- mieni wiele par elementów, więc będzie działać szybciej, jeżeli każdy element będzie wskaźnikiem, czyli pojedynczym słowem maszynowym, a nie całym typem Track, który może mieć osiem słów lub więcej. code/r07/sorting type Track struct { Title string Artist string Album string Year int Length time.Duration } var tracks = []*Track{ { Go , Delilah , From the Roots Up , 2012, length( 3m38s )}, { Go , Moby , Moby , 1992, length( 3m37s )}, { Go Ahead , Alicia Keys , As I Am , 2007, length( 4m36s )}, { Ready 2 Go , Martin Solveig , Smash , 2011, length( 4m24s )}, } func length(s string) time.Duration { d, err := time.ParseDuration(s) if err != nil { panic(s) } return d } Funkcja printTracks wyświetla listę odtwarzania w postaci tabeli. Wyświetlenie graficzne było- by ładniejsze, ale ta prosta procedura wykorzystuje pakiet text/tabwriter do wygenerowania tabeli, której kolumny są starannie wyrównane i dopełnione, tak jak pokazano nieco niżej. Zauważmy, że *tabwriter.Writer spełnia warunki interfejsu io.Writer. Gromadzi każdy zapisany w nim fragment danych. Jego metoda Flush formatuje całą tabelę i zapisuje ją do os.Stdout. func printTracks(tracks []*Track) { const format = v\t v\t v\t v\t v\t\n Poleć książkęKup książkę 7.6. SORTOWANIE ZA POMOC(cid:240) INTERFEJSU SORT.INTERFACE 189 tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, , 0) fmt.Fprintf(tw, format, Tytu(cid:239) , Artysta , Album , Rok , D(cid:239)ugo(cid:258)(cid:202) ) fmt.Fprintf(tw, format, ----- , ------- , ----- , --- , ------- ) for _, t := range tracks { fmt.Fprintf(tw, format, t.Title, t.Artist, t.Album, t.Year, t.Length) } tw.Flush() // oblicza szeroko(cid:286)ci kolumn i wy(cid:286)wietla tabel(cid:266) } Aby posortować listę utworów według pola Artist, definiujemy nowy typ wycinka z niezbędnymi metodami Len, Less i Swap, analogicznie do tego, co zrobiliśmy dla StringSlice. type byArtist []*Track func (x byArtist) Len() int { return len(x) } func (x byArtist) Less(i, j int) bool { return x[i].Artist x[j].Artist } func (x byArtist) Swap(i, j int) { x[i], x[j] = x[j], x[i] } Aby wywołać ogólną procedurę sortowania, musimy najpierw przekonwertować tracks na no- wy typ byArtist, który definiuje porządek: sort.Sort(byArtist(tracks)) Po posortowaniu wycinka według artysty dane wyjściowe z funkcji printTracks są następujące: Tytu(cid:239) Artysta Album Rok D(cid:239)ugo(cid:258)(cid:202) ----- ------- ----- --- ------- Go Ahead Alicia Keys As I Am 2007 4m36s Go Delilah From the Roots Up 2012 3m38s Ready 2 Go Martin Solveig Smash 2011 4m24s Go Moby Moby 1992 3m37s Jeśli użytkownik po raz drugi zażąda „sortuj według artysty”, utwory zostaną posortowane w od- wrotnej kolejności. Nie musimy jednak definiować nowego typu byReverseArtist z odwróconą metodą Less, ponieważ pakiet sort zapewnia funkcję Reverse, która przekształca dowolny porzą- dek sortowania na jego odwrotność: sort.Sort(sort.Reverse(byArtist(tracks))) Po odwrotnym posortowaniu wycinka według artysty dane wyjściowe z funkcji printTracks są następujące: Tytu(cid:239) Artysta Album Rok D(cid:239)ugo(cid:258)(cid:202) ----- ------- ----- --- ------- Go Moby Moby 1992 3m37s Ready 2 Go Martin Solveig Smash 2011 4m24s Go Delilah From the Roots Up 2012 3m38s Go Ahead Alicia Keys As I Am 2007 4m36s Funkcja sort.Reverse zasługuje na uwagę, ponieważ wykorzystuje kompozycję (zob. podrozdział 6.3), która jest ważną koncepcją. Pakiet sort definiuje niewyeksportowany typ reverse, który jest strukturą osadzającą sort.Interface. Metoda Less dla typu reverse wywołuje metodę Less osa- dzonej wartości sort.Interface, ale z odwróconymi indeksami, co odwraca kolejność wyników sortowania. package sort type reverse struct{ Interface } // czyli sort.Interface func (r reverse) Less(i, j int) bool { return r.Interface.Less(j, i) } func Reverse(data Interface) Interface { return reverse{data} } Poleć książkęKup książkę 190 ROZDZIA(cid:227) 7. INTERFEJSY Pozostałe dwie metody typu reverse, czyli Len i Swap, są pośrednio dostarczane przez oryginal- ną wartość sort.Interface, ponieważ jest to osadzone pole. Wyeksportowana funkcja Reverse zwraca instancję typu reverse, która zawiera oryginalną wartość sort.Interface. Aby posortować według innej kolumny, musimy zdefiniować nowy typ, np. byYear (według roku): type byYear []*Track func (x byYear) Len() int { return len(x) } func (x byYear) Less(i, j int) bool { return x[i].Year x[j].Year } func (x byYear) Swap(i, j int) { x[i], x[j] = x[j], x[i] } Po posortowaniu trakcs według roku za pomocą sort.Sort(byYear(tracks)) funkcja printTracks wyświetla chronologiczny wykaz: Tytu(cid:239) Artysta Album Rok D(cid:239)ugo(cid:258)(cid:202) ----- ------- ----- --- ------- Go Moby Moby 1992 3m37s Go Ahead Alicia Keys As I Am 2007 4m36s Ready 2 Go Martin Solveig Smash 2011 4m24s Go Delilah From the Roots Up 2012 3m38s Dla każdego typu elementu wycinka i każdej funkcji porządkowania, której potrzebujemy, dekla- rujemy nową implementację sort.Interface. Jak widać, metody Len i Swap mają identyczne definicje dla wszystkich typów wycinka. W następnym przykładzie typ konkretny customSort łączy wycinek z funkcją, pozwalając nam zdefiniować nowy porządek poprzez napisanie jedynie funkcji porównania. Nawiasem mówiąc, konkretne typy implementujące sort.Interface nie zawsze są wycinkami. Typ customSort jest typem struct. type customSort struct { t [ ]*Track less func(x, y *Track) bool } func (x customSort) Len() int { return len(x.t) } func (x customSort) Less(i, j int) bool { return x.less(x.t[i], x.t[j]) } func (x customSort) Swap(i, j int) { x.t[i], x.t[j] = x.t[j], x.t[i] } Zdefiniujmy wielopoziomową funkcję porządkowania, której głównym kluczem sortowania jest Title (tytuł), kluczem wtórnym jest Year (rok), a kluczem trzeciorzędowym jest Length (długość utworu). Oto wywołanie funkcji Sort wykorzystujące anonimową funkcję porządkowania: sort.Sort(customSort{tracks, func(x, y *Track) bool { if x.Title != y.Title { return x.Title y.Title } if x.Year != y.Year { return x.Year y.Year } if x.Length != y.Length { return x.Length y.Length } return false }}) A oto wynik. Należy zwrócić uwagę, że powiązanie między dwoma utworami zatytułowanymi Go zostało rozerwane na korzyść utworu starszego
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

Język Go. Poznaj i programuj
Autor:
,

Opinie na temat publikacji:


Inne popularne pozycje z tej kategorii:


Czytaj również:


Prowadzisz stronę lub blog? Wstaw link do fragmentu tej książki i współpracuj z Cyfroteką: