Object Oriented Programming II
CIS 193 – Go Programming
Prakhar Bhandari, Adel Qalieh
CIS 193
Prakhar Bhandari, Adel Qalieh
CIS 193
All of the types we've seen so far are concrete types
Interface types are abstract types
- Doesn't expose the internal structure of its values
- Only exposes some methods
Don't necessarily know what it is, just what it can do
More concretely, an interface is a named set containing method headers
An interface looks like this
type Runner interface { Run(feet int) }
Runner is an interface type
We can define interfaces using interfaces and methods
type SprintRunner interface { Runner Sprint(feet int) (distance int) }
Typically end in "er"
Order doesn't matter for the methods inside
Here's an interface we've seen before
package fmt type Stringer interface { String() string }
The fmt
package looks for types that satisfy Stringer and uses their String()
method to print them
A type satisfies an interface if it has all of the methods the interface requires
func (c Celsius) String() string { return fmt.Sprintf("%f°C", c) }
Type Celsius satisfies Stringer
We say that Celsius is a Stringer
With interfaces, Type vs *Type matters when considering if a type has a method
func (c *Celsius) String() string { return fmt.Sprintf("%f°C", *c) } var c Celsius = 4.5
Celsius does not satisfy Stringer here - what happens when we do fmt.Println(c)
?
Only the methods revealed by the interface type may be called, even if the underlying concrete type has more
type Runner interface { Run(feet int) } type Gopher struct { name string } func (d Gopher) Bark(s string) { fmt.Println(s) } func (d Gopher) Run(feet int) { fmt.Printf("%s ran %d feet!\n", d.name, feet) } var d Runner d = Gopher{"Henry"} d.Run(23) // "Henry ran 23 feet!" d.Bark("woof woof") // d.Bark undefined (type Runner has no field or method Bark)
var anything interface{} anything = 6 anything = "hello" anything = false
This is how fmt.Println()
(and other printing functions) can take any arguments
Non-empty interfaces are usually satisfied by a pointer type, especially with structs
How is an interface actually stored?
An interface has two components, type and value
var w Runner // value = nil, type = nil w = Gopher{"Henry"} // value = Gopher{"Henry"}, type = Gopher
The type of an interface is the underlying dynamic type
Interface values can be compared using ==
- If both are nil, this returns true
- If both dynamic types are equal and both dynamic values are equal, this returns true
Why doesn't this work?
var x interface{} = []int{1, 2} fmt.Println(x == x) // panic: comparing uncomparable types
The sort
package provides in-place sorting of any ordered sequence
package sort type Interface interface { Len() int Less(i, j int) bool // i, j are indices of sequence elements Swap(i, j int) }
Syntax
x.(T)
Type assertions are operations applied to an interface value
If T is a concrete type:
- The type assertion checks if x's dynamic type is equal to T, panics otherwise
- Extracts the concrete value from x
For example:
var r Runner r = Gopher{"Harry"} a1 := r.(Gopher) // successful, a is now Gopher{"Harry"} a2 := r.(Cat) // panic
If T is an interface type:
- The type assertion checks whether x's dynamic type satisfies T - if yes, the result still has the same dynamic type and value, but the interface type is changed to T
- Commonly used to make a different (usually larger) set of methods available
// Gopher has two methods, Run and Bark type Runner interface { Run(feet int) } type Barker interface { Bark(s string) } var r Runner r = Gopher{"Harry"} rb := r.(Barker) rb.Bark("meow") // prints "meow" rb.Run(23) // fails, rb is a Barker - no Run method
Checking the type without having a panic
var r Runner r = Gopher{"Harry"} a1, ok := r.(Gopher) // a is now Gopher{"Harry"}, ok = true a2, ok := r.(Cat) // a2 = nil, ok = false
How do we extract the value from an empty interface?
Bad way:
func HandleUnknown(x interface{}) { if _, ok := x.(int); ok { fmt.Printf("%d\n", x) } else if _, ok := x.(string); ok { fmt.Printf("%s\n", x) } }
Good way:
func HandleUnknown(x interface{}) { switch x := x.(type) { case int: fmt.Printf("%d\n", x) case string: fmt.Printf("%s\n", x) }
Simple I/O with ioutil
import "io/ioutil" func main() { // Read entire file b, err := ioutil.ReadFile("input.txt") if err != nil { panic(err) } // Write entire file err = ioutil.WriteFile("output.txt", b, 0644) if err != nil { panic(err) } }
What's bad about loading the entire file into memory?
Provides buffered I/O
Creating a read buffer
import ( "bufio" "io" "os" ) func main() { // Open intput file fi, err := os.Open("input.txt") if err != nil { panic(err) } // Read buffer r := bufio.NewReader(fi) ... }
Creating a write buffer
func main() { ... // Open output file fo, err := os.Create("output.txt") if err != nil { panic(err) } // Write buffer w := bufio.NewWriter(fo) ... }
Copying a file
func main() { // []byte buffer for each chunk buf := make([]byte, 1024) for { n, err := r.Read(buf) // Read a chunk if err != nil && err != io.EOF { panic(err) } if n == 0 { break } // Write chunk if _, err := w.Write(buf[:n]); err != nil { panic(err) } } if err = w.Flush(); err != nil { panic(err) } }
A function that can be called with varying numbers of arguments (eg: fmt.Println()
)
func product(vals ...int) int { prod := 1 for _, val := range vals { prod *= val } return prod }
vals
is a slice here
If the arguments are already in a slice, place "..." after them
values := []int{1, 3, 4} product(values...) // 12
An ordinary function or method call prefixed by the defer keyword
Evaluated when the statement is executed, but the actual call is deferred until the parent function is done
Multiple defers are executed in the reverse of the order in which they were deferred
Usually done with paired operations (eg: open/close) to make sure resources are released or some final action is always done
f, err := os.Open(filename) if err != nil { return nil, err } defer f.Close()
Normal execution stops, all deferred functions are executed, and the program crashes with a log message
panic()
accepts any value as an argument