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

10.11.2015

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

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

Как постигаме паралелизъм в Go?

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

Какво е отношението между go рутини и нишки?

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

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

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

Как разбираме, че един канал е затворен?

if val, ok := <-ch; !ok {
    // Channel is closed
}
for val := range ch {
    // Do something
}
// Channel is closed

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

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

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-и.

Пример

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

nil обекти

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

package main

import "fmt"

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
}

Интерфейси

Stringer

type Stringer interface {
    String() string
}

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

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

package main

import (
	"fmt"
	"strconv"
)

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
}

Пример

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
}

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

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

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

package main

import "fmt"

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 Assertions

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

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

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

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

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

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

Въпроси?