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

16.10.2013

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

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

Какво са масивите в Go?
Могат ли да имат променлива дължина?
Какво е range?

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

Как са имплементирани слайсовете в Go?

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

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

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

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

Какво прави този код:

var x map[string]int
x["key"] = 10
if name, ok := x["key"]; ok {
    fmt.Println(name, ok)
}

Ами ако първият ред беше x := make(map[string]int)?

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

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

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

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-а?

* По стойност
- Работи се върху копие на обекта
- Това може да е скъпа операция за големи обекти

* Като указател
- Работи се върху самия обект
- Всяка промяна в метода се отразява на оригиналния обект

Пример

package main

import "fmt"

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())
}

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
}

Интерфейси

Stringer

type Stringer interface {
    String() string
}

Всеки тип, който имплементира този интерфейс, може да бъде принтиран в Printf например с %s.

Printf просто ще извиква String() и ще вземе стойността.

Структура

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

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

Пример

package main

import (
	"fmt"
	"math"
)

// start interface OMIT
type Shape interface {
	Area() float64
	Circumference() int
}

// end interface OMIT

// start types OMIT
type Rectangle struct {
	X, Y int
}

type Triangle struct {
	X, Y, Z int
}

// end types OMIT

// start methods OMIT
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
}

// end methods OMIT

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 Student struct {
    Person
    facultyNumber int16
}

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

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

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

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

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

Duck typing

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

interface{}

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

Type Assertions

var value interface{}
value = 20
value = "asd"
str := value.(string)

На последния ред или ще се паникьосаме, или в str ще имаме стойността на value, ако тя наистина е била от тип string.

Можем и да сме културни

var value interface{}
value = 20
value = "asd"
str, ok := value.(string)
if !ok {
        fmt.Println("Oops")
}

Interface Conversions

var value interface{}
switch str := value.(type) {
case string:
    return str
case Stringer:
    return str.String()

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

JSON

package main

import (
	"encoding/json"
	"fmt"
)

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

package main

import (
	"encoding/json"
	"fmt"
)

type Triangle struct {
	X, Y, Z int
}

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)
}

JSON

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

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

Въпроси?