Методы и Интерфейсы
Лекция 3
Максим Иванов
Максим Иванов
Пример вызова метода:
const day = 24 * time.Hour fmt.Println(day.Seconds()) // "86400"
Пример определения метода:
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }package geometry import "math" type Point struct{ X, Y float64 } // traditional function func Distance(p, q Point) float64 { return math.Hypot(q.X-p.X, q.Y-p.Y) } // same thing, but as a method of the Point type func (p Point) Distance(q Point) float64 { return math.Hypot(q.X-p.X, q.Y-p.Y) }
this или selfpackage geometry
import (
"fmt"
"testing"
)
func TestPoint(t *testing.T) { p := Point{1, 2} q := Point{4, 6} fmt.Println(Distance(p, q)) // "5", function call fmt.Println(p.Distance(q)) // "5", method call }
// A Path is a journey connecting the points with straight lines.
type Path []Point
// Distance returns the distance traveled along the path.
func (path Path) Distance() float64 {
sum := 0.0
for i := range path {
if i > 0 {
sum += path[i-1].Distance(path[i])
}
}
return sum
}perim := Path{
{1, 1},
{5, 1},
{5, 4},
{1, 1},
}
fmt.Println(perim.Distance()) // "12"func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}type IntList struct {
Value int
Tail *IntList
}
func (list *IntList) Sum() int {
if list == nil {
return 0
}
return list.Value + list.Tail.Sum()
}type Point struct{ X, Y float64 }
type ColoredPoint struct {
Point
Color color.RGBA
}Methods
red := color.RGBA{255, 0, 0, 255}
blue := color.RGBA{0, 0, 255, 255}
var p = ColoredPoint{Point{1, 1}, red}
var q = ColoredPoint{Point{5, 4}, blue}
fmt.Println(p.Distance(q.Point)) // "5"
p.ScaleBy(2)
q.ScaleBy(2)
fmt.Println(p.Distance(q.Point)) // "10"var p = ColoredPoint{Point{1, 1}, red}
var q = ColoredPoint{Point{5, 4}, blue}
p.Distance(q) // compile error: cannot use q (ColoredPoint) as Point// Одинаковые методы у разных встраиваемых типов → конфликт (надо уточнять явно)
type LoggerA struct{}
func (LoggerA) Log(s string) { fmt.Println("A:", s) }
type LoggerB struct{}
func (LoggerB) Log(s string) { fmt.Println("B:", s) }
type S struct {
LoggerA
LoggerB
}
var s S
// s.Log("hi") // compile error: ambiguous selector s.Log
s.LoggerA.Log("hi") // A: hi
s.LoggerB.Log("hi") // B: hi// Если у внешнего типа и у встраиваемого типа есть методы с одинаковым именем,
// вызов имени без уточнения берётся у внешнего типа.
type Base struct{}
func (Base) Show() { fmt.Println("base") }
type T struct {
Base
}
func (T) Show() { fmt.Println("outer") }
var t T
t.Show() // "outer" — берётся метод T.Show
t.Base.Show() // "base" — явное обращение к методу Base// Многоуровневое встраивание: выбирается ближайший по уровню метод
type L3 struct{}
func (L3) Show() { fmt.Println("L3") }
type L2 struct{ L3 }
func (L2) Show() { fmt.Println("L2") }
type L1 struct{ L2 }
var x L1
x.Show() // "L2" — ближе (глубина 1)
x.L2.Show() // "L2"
x.L2.L3.Show() // "L3"
// Если убрать L2.Show, x.Show() вызовет L3.Show (глубина 2)// Указательные методы (с получателем *T) становятся доступны через переменную-структуру
// только если поле встраивается как указатель или если поле адресуемо.
type Counter struct{ n int }
func (c *Counter) Inc() { c.n++ }
type WrapByValue struct{ Counter } // встраивание значением
type WrapByPointer struct{ *Counter } // встраивание указателем
var wv WrapByValue
fmt.Println(wv.Counter.n) // 0
// wv.Inc() // compile error: метод с указательным получателем не виден напрямую
wv.Counter.Inc() // OK: у поля есть адрес, метод вызовется на &wv.Counter
fmt.Println(wv.Counter.n) // 1 — значение изменилось внутри wv.Counter
(&wv).Counter.Inc() // эквивалентно; подчёркивает, что нужен указатель
fmt.Println(wv.Counter.n) // 2
var wp WrapByPointer
wp.Counter = &Counter{}
fmt.Println(wp.Counter.n) // 0
wp.Inc() // OK: указательное встраивание делает метод доступным напрямую
fmt.Println(wp.Counter.n) // 1 — изменилось значение по указателюvar cache = struct {
sync.Mutex
mapping map[string]string
} {
mapping: make(map[string]string),
}
func Lookup(key string) string {
cache.Lock()
v := cache.mapping[key]
cache.Unlock()
return v
}p := Point{1, 2}
q := Point{4, 6}
distanceFromP := p.Distance
fmt.Println(distanceFromP(q))
var origin Point
fmt.Println(distanceFromP(origin))
scaleP := p.ScaleBy
scaleP(2)
scaleP(3)
scaleP(10)type Rocket struct { /* ... */ }
func (r *Rocket) Launch() { /* ... */ }
r := new(Rocket)
time.AfterFunc(10 * time.Second, func() { r.Launch() })
time.AfterFunc(10 * time.Second, r.Launch)p := Point{1, 2}
q := Point{4, 6}
distance := Point.Distance // method expression
fmt.Println(distance(p, q)) // "5"
fmt.Printf("%T\n", distance) // "func(Point, Point) float64"
scale := (*Point).ScaleBy
scale(&p, 2)
fmt.Println(p) // "{2 4}"
fmt.Printf("%T\n", scale) // "func(*Point, float64)"type Point struct{ X, Y float64 }
func (p *Point) MoveBy(dx, dy float64) {
p.X += dx
p.Y += dy
}
p := &Point{1, 2}
move := p.MoveBy // method value; receiver привязан, аргументы остаются
move(3, 4) // вызывает p.MoveBy(3, 4)
fmt.Println(*p) // {4 6}
offsetFrom := (*Point).MoveBy // method expression; receiver не привязан
offsetFrom(p, -1, -1) // явная передача получателя первым аргументом
fmt.Println(*p) // {3 5}type Counter struct { n int }
func (c *Counter) N() int { return c.n }
func (c *Counter) Increment() { c.n++ }
func (c *Counter) Reset() { c.n = 0 }Сеттеры/геттеры для простых типов не приветствуются.
type Point struct { x, y int }
func (p *Point) X() int { return p.x }
func (p *Point) Y() int { return p.y }
func (p *Point) SetX(x int) { p.x = x }
func (p *Point) SetY(y int) { p.y = y }func Fprintf(w io.Writer, format string, args ...any) (int, error)
func Printf(format string, args ...any) (int, error) {
return Fprintf(os.Stdout, format, args...)
}
func Sprintf(format string, args ...any) string {
var buf bytes.Buffer
Fprintf(&buf, format, args...)
return buf.String()
}// Writer is the interface that wraps the basic Write method.
type Writer interface {
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
// Write must not modify the slice data, even temporarily.
//
// Implementations must not retain p.
Write(p []byte) (n int, err error)
}type ByteCounter int
func (c *ByteCounter) Write(p []byte) (int, error) {
*c += ByteCounter(len(p)) // convert int to ByteCounter
return len(p), nil
}
var c ByteCounter
c.Write([]byte("hello"))
fmt.Println(c) // "5", = len("hello")package fmt
// The String method is used to print values passed
// as an operand to any format that accepts a string
// or to an unformatted printer such as Print.
type Stringer interface {
String() string
}// Если тип реализует fmt.Stringer, форматирование %v и %s используют String().
type User struct {
ID int
Name string
}
func (u User) String() string {
return fmt.Sprintf("User(%d:%q)", u.ID, u.Name)
}
u := User{ID: 42, Name: "Ada"}
fmt.Printf("%v\n", u) // User(42:"Ada") — вызван u.String()
fmt.Printf("%s\n", u) // User(42:"Ada")
fmt.Printf("%#v\n", u) // main.User{ID:42, Name:"Ada"} — структурный вывод
// Если реализован fmt.GoStringer (метод GoString() string), %#v использует его.package io
type Reader interface {
Read(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
type ReadWriter interface {
Reader
Writer
}
type ReadWriteCloser interface {
Reader
Writer
Closer
}package sort type Interface interface { Len() int Less(i, j int) bool Swap(i, j int) } func Reverse(data Interface) Interface { return &reverse{data} } type reverse struct { // This embedded Interface permits Reverse to use the methods of // another Interface implementation. Interface } func (r reverse) Less(i, j int) bool { return r.Interface.Less(j, i) }
var w io.Writer w = os.Stdout // OK: *os.File has Write method w = new(bytes.Buffer) // OK: *bytes.Buffer has Write method w = time.Second // compile error: time.Duration lacks Write method
type IntSet struct { /* ... */ }
func (*IntSet) String() string
var s IntSet
var _ = s.String() // OK: s is a variable and &s has a String method
var _ fmt.Stringer = &s // OK
var _ fmt.Stringer = s // compile error: IntSet lacks String methodtype any = interface{}any - это alias для interface{}var v any
v = true
v = 12.34
v = "hello"
v = map[string]int{"one": 1}
v = new(bytes.Buffer)// *bytes.Buffer must satisfy io.Writer var _ io.Writer = (*bytes.Buffer)(nil)
var w io.Writer w = os.Stdout w = new(bytes.Buffer) w = nil var x any = time.Now()
var buf *bytes.Buffer
if debug {
buf = new(bytes.Buffer) // enable collection of output
}
f(buf) // NOTE: subtly incorrect!
// If out is non-nil, output will be written to it.
func f(out io.Writer) {
// ...do something...
if out != nil {
out.Write([]byte("done!\n"))
}
}var buf io.Writer
if debug {
buf = new(bytes.Buffer) // enable collection of output
}
f(buf) // OKConcrete type:
var w io.Writer w = os.Stdout f := w.(*os.File) // success: f == os.Stdout c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer
Interface type:
var w io.Writer w = os.Stdout rw := w.(io.ReadWriter) // success: *os.File has both Read and Write w = new(ByteCounter) rw = w.(io.ReadWriter) // panic: *ByteCounter has no Read method
var w io.Writer var rw io.ReadWriter w = rw // io.ReadWriter is assignable to io.Writer w = rw.(io.Writer) // fails only if rw == nil
Check type
var w io.Writer = os.Stdout f, ok := w.(*os.File) // success: ok, f == os.Stdout b, ok := w.(*bytes.Buffer) // failure: !ok, b == nil
func sqlQuote(x any) string {
if x == nil {
return "NULL"
} else if _, ok := x.(int); ok {
return fmt.Sprintf("%d", x)
} else if _, ok := x.(uint); ok {
return fmt.Sprintf("%d", x)
} else if b, ok := x.(bool); ok {
if b {
return "TRUE"
}
return "FALSE"
} else if s, ok := x.(string); ok {
return sqlQuoteString(s) // (not shown)
} else {
panic(fmt.Sprintf("unexpected type %T: %v", x, x))
}
}switch x.(type) {
case nil: // ...
case int, uint: // ...
case bool: // ...
case string: // ...
default: // ...
}
switch x := x.(type) { /* ... */ }func sqlQuote(x any) string {
switch v := x.(type) {
case nil:
return "NULL"
case int, uint:
return fmt.Sprintf("%d", v) // v has type any here.
case bool:
if v {
return "TRUE"
}
return "FALSE"
case string:
return sqlQuoteString(v) // (not shown)
default:
panic(fmt.Sprintf("unexpected type %T: %v", v, v))
}
}switch s := suit(drawCard()); s {
case "Spades": // ...
case "Hearts": // ...
case "Diamonds": // ...
case "Clubs": // ...
default:
panic(fmt.Sprintf("invalid suit %q", s)) // Joker?
}
func Reset(x *Buffer) {
if x == nil {
panic("x is nil") // unnecessary!
}
x.elements = nil
}package copyfile
import (
"io"
"os"
)
func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { return } dst, err := os.Create(dstName) if err != nil { return } written, err = io.Copy(dst, src) dst.Close() src.Close() return }
package copyfile2
import (
"io"
"os"
)
func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { return } defer src.Close() dst, err := os.Create(dstName) if err != nil { return } defer dst.Close() return io.Copy(dst, src) }
func a() {
i := 0
defer fmt.Println(i)
i++
return
}
a()Output:
0
func b() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
}
fmt.Println(b())Output:
3210
func c() (i int) {
defer func() { i++ }()
return 1
}
fmt.Println(c())Output:
2
func main() {
f(3)
}
func f(x int) {
fmt.Printf("f(%d)\n", x+0/x) // panics if x == 0
defer fmt.Printf("defer %d\n", x)
f(x - 1)
}Output:
f(3) f(2) f(1) defer 1 defer 2 defer 3
func Parse(input string) (s *Syntax, err error) {
defer func() {
if p := recover(); p != nil {
err = fmt.Errorf("internal error: %v", p)
}
}()
// ...parser...
}func main() {
defer func() {
recover()
fmt.Println("Checkpoint 1")
panic(1)
}()
defer func() {
recover()
fmt.Println("Checkpoint 2")
panic(2)
}()
panic(999)
}Output:
Checkpoint 2
Checkpoint 1
panic: 999 [recovered]
panic: 2 [recovered]
panic: 1type error interface {
Error() string
}
type Unwrapper interface {
Unwrap() error
}Unwrap
errors.Unwrap(fmt.Errorf("... %w ...", ..., err, ...)) == errIs
if errors.Is(err, os.ErrExist) if err == os.ErrExist
As
var perr *os.PathError
if errors.As(err, &perr) {
fmt.Println(perr.Path)
}
if perr, ok := err.(*os.PathError); ok {
fmt.Println(perr.Path)
}package main import "errors" type Temporary interface { IsTemporary() bool } func do() error { return nil } func main() { err := do() var terr Temporary if errors.As(err, &terr) && terr.IsTemporary() { //... } }
Максим Иванов