Типове и интерфейси

31.10.2018

Но преди това...

Въпрос за мъфин #1

Как увеличаваме броя елементи в един масив? Можем ли да сравняваме масиви с оператора "==" ?

Въпрос за мъфин #2

arr := [6]float64{1, 2, 3, 4, 5, 6}
x := arr[1:]
y := append(x, 4)

Какви типове имат arr, x, y?
Какво ще върнат len(x), cap(x), len(y), cap(y)?

Въпрос за мъфин #3

Кои от следните типове са допустими ключове на map?

Въпрос за мъфин #3

Отговор:

func main() {
    slice := []int{1, 2, 3}
    fmt.Printf("%+v\n", map[*[]int]int{&slice: 5})
}

Въпрос за мъфин #4

Каква е разликата между new() и make(), кога се ползва едното и кога другото и какво връщат?

Въпрос за мъфин #5

Как инициализираме thread-safe slice или map?

Типове и интерфейси

Собствени типове

type integer int
type float float64
type chars string

Нека разгледаме функцията Abs

func Abs(i integer) integer {
    switch {
    case i < 0:
        return -i
    case i == 0:
        return 0
    default:
        return i
    }
}

var number integer = -42
positiveInteger := Abs(number)

"Обектно-ориентираният" начин да се направи подобно нещо

func (i integer) Abs() integer {
    switch {
    case i < 0:
        return -i
    case i == 0:
        return 0
    default:
        return i
    }
}

var number integer = -42
number.Abs()

Какво точно е метод?

Що е то receiver-а?

По стойност

Като указател

Няма различен синтаксис за използването на двата вида receiver-и.

Пример

type integer int

func (i integer) Abs() integer {
    switch {
    case i < 0:
        return -i
    case i == 0:
        return 0
    default:
        return i
    }
}

func (i *integer) Increment() {
    *i++
}

func main() {
    var number integer
    number = -42

    number.Increment()
    fmt.Println(number.Abs())
}

nil обекти

Извикването на методи работи дори с nil pointers към обекти:

type integer int

func (i *integer) Increment() {
    fmt.Println("Incrementing...")
    *i++
}

func main() {
    var number *integer
    number.Increment()
    fmt.Println(number)
}

Внимавайте с тях :)

struct

type Rectangle struct {
    X, Y int
}

type Triangle struct {
    X, Y, Z int
}

Методи за тези типове

func (r *Rectangle) Area() float64 {
    return float64(r.X * r.Y)
}

func (r *Rectangle) Circumference() int {
    return 2 * (r.X + r.Y)
}

func (t *Triangle) Area() float64 {
    p := float64(t.Circumference() / 2)
    return math.Sqrt(p * (p - float64(t.X)) * (p - float64(t.Y)) * (p - float64(t.Z)))
}

func (t *Triangle) Circumference() int {
    return t.X + t.Y + t.Z
}

"Завързани" методи

type greeter struct {
    Name string
}

func (g *greeter) SayHi(name string) {
    fmt.Printf("Hi, %s! I am %s (%p)\n", name, g.Name, g)
}

func main() {
    var p = &greeter{Name: "p"}
    pGreeter := p.SayHi

    fmt.Printf("Calling p.SayHi:\n")
    p.SayHi("Pesho")

    fmt.Printf("Calling pGreeter:\n")
    pGreeter("Pesho") // The same as p.SayHi("Pesho")

    unboundGreeter := (*greeter).SayHi
    fmt.Printf("Calling unboundGreeter:\n")
    unboundGreeter(p, "Gosho")

    unboundGreeter(&greeter{Name: "other"}, "Maria")
}

Интерфейси

Stringer

type Stringer interface {
    String() string
}

Това е интерфейс от стандартната библиотека, дефиниран в пакета fmt.

Ако имплементираме този интефейс за наш тип, може да променим начина, по който fmt.Printf() го принтира чрез %s.

type myint uint64

func (i myint) String() string {
    return "myint in binary is " + strconv.FormatUint(uint64(i), 2)
}

func main() {
    var i myint
    i = 5
    fmt.Printf("Value: %s\n", i)
}

Структура

type Binary uint64

По-подробно обяснение може да намерите тук: research.swtch.com/interfaces

Дефиниция на интерфейс

type Shape interface {
    Area() float64
    Circumference() int
}

Пример

func sumOfCircumferences(shapes ...Shape) int {
    sum := 0
    for _, shape := range shapes {
        sum += shape.Circumference()
    }
    return sum
}

func biggestArea(shapes ...Shape) (result float64) {
    for _, shape := range shapes {
        area := shape.Area()
        if area > result {
            result = area
        }
    }
    return result
}

func main() {
    rect := &Rectangle{X: 12, Y: 64}
    tr := &Triangle{X: 12, Y: 64, Z: 50}
    fmt.Println(sumOfCircumferences(rect, tr))
    fmt.Println(biggestArea(rect, tr))
}

Вложени типове

Композиция

Конструираме един тип, комбинирайки няколко прости други типa.

* Пример:
Искаме да си направим smartphone. Не откриваме топлата вода, а просто го наблъскваме с каквито джаджи се сетим.

type Smartphone struct {
    phone     BasicPhone
    camera    CameraModule
    wifi      WiFiModule
    screen    MultiTouchScreen
    battery   DamnHugeBattery
    gyroscope SmallGyroscope
    gps       GPSModule
    secret    CanOpener
}

Всеки един от тези типове отговаря за точно едно нещо и може да бъде използвано самостоятелно.

Квази-Наследяване

Вярваме, че знаете как работи то. Дори сме сигурни, че сте правили хора и студенти:

type Person struct {
    firstName, lastName string
}

func (p Person) Name() string {
    return p.firstName + " " + p.lastName
}

type Student struct {
    Person
    facultyNumber int16
}

func main() {
    s := Student{Person{"Иван", "Иванов"}, 123}
    fmt.Printf("We have a student with name %s and FN %d", s.Name(), s.facultyNumber)
}

Вложеният тип Person е анонимен, което присвоява всичките му методи и атрибути на базовия тип.

Множествено "наследяване"

Да, имате право на много анонимни вложени типа.

Не, това не е яко.

Да, не очакваме да го ползвате често.

Duck typing

Всеки обект имплементира празния интерфейс:

interface{}

Съответно на променлива от такъв тип може да присвоим всякаква стойност!

Но с нея не можем да правим абсолютно нищо :)

Това може да звучи безполезно, но не е, тъй като имаме...

Type Assertion

package main

import "fmt"

func main() {
    var value interface{}
    value = 20
    value = "asd"

    if str, ok := value.(string); ok {
        fmt.Printf("We have a %T: %v\n", str, str)
    } else {
        fmt.Println("Oops, not a string")
    }
}

В str ще имаме стойността на value, ако тя наистина е била от тип string.

Type Switch

type mystruct struct{ a, b int }

func (m mystruct) String() string {
    return fmt.Sprintf("mystruct(%d, %d) yo!", m.a, m.b)
}

func stringify(value interface{}) string {
    switch s := value.(type) {
    case string:
        return s
    case fmt.Stringer:
        return s.String()
    case int, uint:
        return fmt.Sprintf("%06d", s) // leftpad
    default:
        return "No clue, mate :)"
    }
}

func main() {
    fmt.Printf("%#v\n", stringify(mystruct{1, 2}))
}

Така може да правим различни неща въз основа на типа на променлива.

JSON

type Rectangle struct {
    X, Y int
}

func main() {
    var empty interface{}
    emptyRect := new(Rectangle)

    rect := &Rectangle{X: 12, Y: 64}

    marshalledRect, _ := json.Marshal(rect)
    fmt.Printf("%s\n", marshalledRect)

    json.Unmarshal(marshalledRect, emptyRect)
    fmt.Printf("%#v\n", emptyRect)

    json.Unmarshal(marshalledRect, &empty)
    fmt.Printf("%#v\n", empty)
}

JSON

type Marshaler interface {
    MarshalJSON() ([]byte, error)
}

type Unmarshaler interface {
    UnmarshalJSON([]byte) error
}

JSON

func (t *Triangle) MarshalJSON() ([]byte, error) {
    return json.Marshal(struct {
        X, Y, Z int
        Shape   string
    }{
        X:     t.X,
        Y:     t.Y,
        Z:     t.Z,
        Shape: "Триъгълник",
    })
}

func main() {
    tr := &Triangle{X: 12, Y: 64, Z: 50}
    marshalledTr, _ := json.Marshal(tr)
    fmt.Printf("%s\n", marshalledTr)
}

Type Aliases

type integerAlias = int

Не създава нов тип, a псевдоним на вече съществуващ тип

type integer int

func isPositive(a integer) bool {
    return a > 0
}

type integerAlias = int

func isPositive1(a integerAlias) bool {
    return a > 0
}

func main() {
    a := 5
    isPositive(a) // cannot use a (type int) as type integer in argument to isPositive
    isPositive1(a)
}

Въпроси?