Interfejsy - Dziennik pokładowy #4

Kamil Powroźnik

2019-06-23T22:00+02:00 | 6 min

Hej, dzisiaj o tym, czym są interfejsy w języku Go. W drugim wpisie z serii o tym języku opisywałem między innymi typ struct, który jest kolekcją pól o określonym typie.

type User struct {
	name     string
	age      int
	email    string
	password string
}

Interfejsy natomiast możemy porównać do zbioru metod.

Pierwszy interfejs

Zobacz poniższy przykład:

package main

import "fmt"

type geometry interface {
area() float64
}

type rect struct {
width, height float64
}

func (r rect) area() float64 {
return r.width * r.height
}

func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
}

func main() {
element := rect{width: 3, height: 4}
measure(element)
}

Przeanalizujmy po kolei co się tutaj stało. Po pierwsze, utworzyłem interfejs o nazwie geometry, który jak na razie zawiera w sobie tylko metodę area.

Kolejnym krokiem było stworzenie kolekcji rect, która zawiera dwie właściwości o typie float64 - width oraz height, będzie on podstawą dla naszych obliczeń.

Niżej znalazła się funkcja przyjmująca jako argument kolekcję z powstałym wcześniej typem rect, zwraca ona powierzchnię naszego prostokąta.

Funkcja measure w definicji przyjmuje interface geometry, jednak jak to jest, że w ciele main() do tej funkcji przekazuje element, który jest kolekcją struct?

Tak właśnie działają interfejsy - jeśli zmienna (w tym przypadku element) posiada typ, który spełnia interfejs (w tym przypadku rect) to możemy wywołać na niej metody w nim zawarte.

Czy nie przypomina Ci to klas i ich metod?

Wzbogacamy interfejs

Gdybyśmy chcieli zaimplementować obliczenie pola dla trójkąta kod wyglądałby tak:

package main

import "fmt"

type geometry interface {
area() float64
}

type rect struct {
width, height float64
}

type triangle struct {
base, height float64
}

func (r rect) area() float64 {
return r.width * r.height
}

func (t triangle) area() float64 {
return (t.base * t.height) / 2
}

func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
}

func main() {
element := rect{width: 3, height: 4}
measure(element)

triangle := triangle{base: 10, height: 2}
measure(triangle)
}

Możesz zauważyć, że zarówno kolekcja rect jak i triangle dalej spełnia założenia interfejsu - dla obydwu mamy zdefiniowaną funkcję area()

Zdaje sobie sprawę, że to co wyżej napisałem może wyglądać na masło maślane, spróbujmy jednak dojść do sensu interfejsu poprzez poniższy błędny kod:

package main

import "fmt"

type geometry interface {
area() float64
printName() string
}

type rect struct {
width, height float64
name          string
}

type triangle struct {
base, height float64
name         string
}

type shape struct {
name string
}

func (r rect) area() float64 {
return r.width * r.height
}

func (t triangle) area() float64 {
return (t.base * t.height) / 2
}

func (r rect) printName() string {
return r.name
}

func (t triangle) printName() string {
return t.name
}

func (s shape) printName() string {
return s.name
}

func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.printName())
}

func main() {
element := rect{width: 3, height: 4, name: „Prostokąt”}
measure(element)

triangle := triangle{base: 10, height: 2, name: „Trójkąt”}
measure(triangle)

circle := shape{name: „Koło”}
measure(circle)
}

Dodałem tutaj funkcję, która zwraca nazwę naszego elementu oraz kolejną kolekcje o nazwie circle. Dla każdego typu (prócz circle) zdefiniowana jest metoda area() oraz printName(). Powyższy kod jest błędny z jednego powodu - shape nie spełnia założeń interfejsu geometry (z powodu braku metody area dla tego typu) o czym poinformuje nas kompilator:

cannot use circle (type shape) as type geometry in argument to measure:
        shape does not implement geometry (missing area method)

Jednak możemy to naprawić w szybki sposób, uzupełniając nasz typ shape o wartość radius:

type shape struct {
radius float64
name   string
}

Oraz metodę area():

func (s shape) area() float64 {
return 3.14 * (s.radius * s.radius)
}

W tym momencie kolekcja shape zaczęła spełniać założenia interfejsu geometry, a naszym oczom ukaże się wynik:

❯ go run hello.go
{3 4 Prostokąt}
12
Prostokąt
{10 2 Trójkąt}
10
Trójkąt
{5 Koło}
78.5
Koło

Podsumowanie

Jak wspomniałem wyżej interfejsy w połączeniu z typem struct przypominają klasy i ich metody w innych językach programowania. Aby typ struct spełniał założenia naszego interfejsu, musi posiadać odpowiednio zdefiniowane funkcje przyjmujące dany typ.


Hej, ta strona wykorzysuje pliki cookies, localStorage, sessionStorage oraz dane osobowe do celów analitycznych.