CSE224 - Go OOP
Class
Named Type
I can create a new named type MyInt that is based on int, but is treated as a distinct type by the compiler.
type MyInt int
struct + methods
Go doesn’t have traditional classes, instead, it uses Custom types + attached methods. As a result, structs in go can hold both data and associated methods. Example:
type Celsius float64
// This defines a method on the Celsius type.
func (c Celsius) ToFahrenheit() float64 {
return float64(c)*9/5 + 32 // need type convertion
}
func main() {
temp := Celsius(25)
fmt.Println("In Fahrenheit:", temp.ToFahrenheit()) // 77
}
Another example: User behaves just like a class
type User struct {
Name string
Age int
}
// This function is like a method defined in a class
func (u User) Greet() string {
return "Hello, " + u.Name
}
Constructors
Go does not have built-in constructors. We define a function by ourself as a constructor:
func NewUser(name string, age int) User {
return User{Name: name, Age: age}
}
Encapsulation
Go doesn’t use public or private keywords like Java or C++. Instead, it uses capitalization to control visibility.
type Account struct {
Owner string // Exported, other packages can read/write it.
balance float64 // Unexported, only code inside this package can access it.
}
Composition
Composition over Inheritance: Go doesn’t have classes or inheritance, but it has composition using embedding.
type Animal struct {
Name string
}
func (a Animal) Speak() {
fmt.Println(a.Name, "makes a sound")
}
type Dog struct {
Animal // embedded
Breed string
}
// Now Dog inherits the Speak() method from Animal.
d := Dog{Animal: Animal{Name: "Buddy"}, Breed: "Labrador"}
d.Speak() // Buddy makes a sound
By composition, I can resue methods.
Override
// Define a method with the same name on the outer struct, and it will shadow the embedded (override)
func (d Dog) Speak() {
fmt.Println(d.Name, "barks!")
}
Interface (Polymorphism and Abstraction)
An interface in Go is a set of method signatures. If a type has those methods, it automatically implements the interface.
type Greeter interface {
Greet() string
}
type Person struct{}
// Person has a Greet() method, so it automatically satisfies the Greeter interface.
func (p Person) Greet() string {
return "Hello!"
}
func sayHello(g Greeter) {
// any type that has Greet() can be passed as a parameter (Polymorphism)
fmt.Println(g.Greet())
}
func main() {
p := Person{}
sayHello(p) // Hello!
}
If the method is defined on a pointer receiver, only the pointer type implements the interface.
type Greeter interface {
Greet()
}
type Person struct {
Name string
}
// This means only *Person (pointer) has the method Greet()
func (p *Person) Greet() {
fmt.Println("Hello, my name is", p.Name)
}
func main() {
var g Greeter
p := Person{Name: "Alice"}
// g = p // ❌ error (p is a Person (value))
g = &p // ✅ &p is a *Person
g.Greet()
}In this example, Person doesn’t implement the interface, but *Person does, so assigning a Person to a Greeter causes an error. (g = &p works)