Interactive Go Cheatsheet

An interactive guide to Go concepts, data structures, and concurrency patterns.

0. Go Fundamentals

This section covers the foundational concepts of Go programming, essential for building any application.

Packages & Imports

Go programs are organized into packages. The main package is the entry point. Use import to bring in other packages.

package main

import (
	"fmt" // Format package for I/O
	"math" // Math functions
	"strconv" // String conversion
)

func main() {
	fmt.Println("Hello, Go!")
	fmt.Println("Square root of 16:", math.Sqrt(16))
	num, _ := strconv.Atoi("123") // Convert string to int
	fmt.Println("Converted number:", num)
}

Basic Data Types

Go has several built-in types.

  • bool: true or false.
  • Numeric Types:
    • Integers: int, int8, int16, int32, int64, uint, uint8, etc.
    • Floating-point: float32, float64.
    • Complex: complex64, complex128.
  • string: Immutable sequence of bytes (UTF-8 encoded).
  • rune: An alias for int32, represents a Unicode code point.
  • byte: An alias for uint8.

Variables

Variables can be declared with var or using short declaration :=.

package main

import "fmt"

func main() {
	var i int // Declaration
	i = 10    // Assignment
	fmt.Println("i:", i)

	var j int = 20 // Declaration and initialization
	fmt.Println("j:", j)

	k := 30 // Short declaration (type inferred)
	fmt.Println("k:", k)

	var message string = "Hello"
	fmt.Println("message:", message)

	const PI float64 = 3.14159 // Constants
	fmt.Println("PI:", PI)
}

Input/Output

Using fmt package for formatted I/O.

package main

import "fmt"

func main() {
	var name string
	var age int

	fmt.Print("Enter your name: ")
	fmt.Scanln(&name) // Read string

	fmt.Print("Enter your age: ")
	fmt.Scanln(&age) // Read int

	fmt.Printf("Hello, %s! You are %d years old.\n", name, age)
}

Operators

Go supports standard arithmetic, comparison, logical, and bitwise operators.

  • **Arithmetic**: +, -, *, /, %
  • **Comparison**: ==, !=, <, >, <=, >=
  • **Logical**: && (AND), || (OR), ! (NOT)
  • **Bitwise**: &, |, ^, <<, >>, &^ (bit clear)
  • **Assignment**: =, +=, -=, etc.

Control Flow

Statements that control the order of execution.

If-Else

package main

import "fmt"

func main() {
	score := 85
	if score >= 90 {
		fmt.Println("Grade A")
	} else if score >= 80 {
		fmt.Println("Grade B")
	} else {
		fmt.Println("Grade C")
	}

	// If with a short statement
	if num := 10; num%2 == 0 {
		fmt.Println(num, "is even")
	} else {
		fmt.Println(num, "is odd")
	}
}

Switch

package main

import "fmt"

func main() {
	day := "Wednesday"
	switch day {
	case "Monday", "Tuesday":
		fmt.Println("Start of week")
	case "Wednesday":
		fmt.Println("Midweek")
	case "Saturday", "Sunday":
		fmt.Println("Weekend")
	default:
		fmt.Println("Invalid day")
	}

	// Switch without a condition (acts like if-else if)
	age := 25
	switch {
	case age < 18:
		fmt.Println("Minor")
	case age >= 18 && age < 65:
		fmt.Println("Adult")
	default:
		fmt.Println("Senior")
	}
}

For Loop

Go only has one looping construct: for.

package main

import "fmt"

func main() {
	// Traditional for loop
	for i := 0; i < 5; i++ {
		fmt.Println("Iteration:", i)
	}

	// While-like for loop
	sum := 1
	for sum < 1000 {
		sum += sum
	}
	fmt.Println("Sum:", sum)

	// Infinite loop
	// for {
	//     fmt.Println("Looping forever!")
	// }

	// For-each (range) loop for slices, arrays, maps, strings, channels
	numbers := []int{10, 20, 30}
	for index, value := range numbers {
		fmt.Printf("Index: %d, Value: %d\n", index, value)
	}

	// Iterate over map
	kvs := map[string]string{"a": "apple", "b": "banana"}
	for k, v := range kvs {
		fmt.Printf("%s -> %s\n", k, v)
	}
}

Functions

Functions are declared with the func keyword. They can return multiple values.

package main

import "fmt"

// Function with two int parameters and one int return
func add(a, b int) int {
	return a + b
}

// Function with multiple return values
func swap(x, y string) (string, string) {
	return y, x
}

// Variadic function (takes variable number of arguments)
func sumAll(nums ...int) int {
	total := 0
	for _, num := range nums {
		total += num
	}
	return total
}

func main() {
	result := add(5, 7)
	fmt.Println("Sum:", result)

	a, b := swap("hello", "world")
	fmt.Println("Swapped:", a, b)

	fmt.Println("Sum all:", sumAll(1, 2, 3, 4, 5))
}

Pointers (Basic)

Go has pointers, but no pointer arithmetic. Used for passing values by reference.

package main

import "fmt"

func main() {
	i := 10
	p := &i // p points to i

	fmt.Println("Value of i:", i)
	fmt.Println("Address of i:", p)
	fmt.Println("Value pointed to by p:", *p) // Dereference

	*p = 20 // Change value through pointer
	fmt.Println("New value of i:", i)
}

1. Data Structures

Go's built-in data structures are flexible and powerful.

Arrays

  • Description: Fixed-size sequence of elements of the same type.
  • Performance: Access by index: $O(1)$.
  • Thread Safety: Not inherently thread-safe for concurrent writes.
package main

import "fmt"

func main() {
	var a [5]int // Declares an array of 5 integers, initialized to zeros
	a[2] = 99    // Set element
	fmt.Println("Array:", a)
	fmt.Println("First element:", a[0])
}

Slices

  • Description: Dynamic-size, flexible view into an array. More common than raw arrays.
  • Performance: Append (amortized): $O(1)$, Index access: $O(1)$, Resizing: $O(N)$.
  • Thread Safety: Not inherently thread-safe for concurrent writes.
package main

import "fmt"

func main() {
	s := []int{1, 2, 3} // Slice literal
	fmt.Println("Slice:", s)

	s = append(s, 4, 5) // Append elements
	fmt.Println("Appended slice:", s)

	// Slicing an existing slice/array
	subSlice := s[1:4] // Elements from index 1 (inclusive) to 4 (exclusive)
	fmt.Println("Sub-slice:", subSlice)

	// Make function for slices: make([]T, length, capacity)
	vec := make([]int, 3, 5) // len=3, cap=5
	fmt.Println("Made slice:", vec, "Len:", len(vec), "Cap:", cap(vec))
}

Maps

  • Description: Unordered collection of key-value pairs. Keys must be unique.
  • Performance: Insert/Delete/Lookup: $O(1)$ on average, $O(N)$ worst case (hash collisions).
  • Thread Safety: Not thread-safe for concurrent access (reads or writes). Use `sync.RWMutex` or `sync.Map`.
package main

import "fmt"

func main() {
	// Declare and initialize a map
	m := map[string]int{"apple": 1, "banana": 2}
	fmt.Println("Map:", m)

	// Add/Update element
	m["orange"] = 3
	fmt.Println("Updated map:", m)

	// Access element
	fmt.Println("Value of apple:", m["apple"])

	// Check if key exists
	val, ok := m["grape"]
	fmt.Println("Value of grape:", val, "Exists:", ok)

	// Delete element
	delete(m, "banana")
	fmt.Println("Map after deletion:", m)

	// Iterate over map
	for key, value := range m {
		fmt.Printf("Key: %s, Value: %d\n", key, value)
	}
}

Structs

  • Description: Typed collection of fields. Similar to classes in other languages, but without methods directly on the struct (methods are associated with types).
  • Performance: Accessing fields: $O(1)$.
  • Thread Safety: Fields are not inherently thread-safe.
package main

import "fmt"

// Define a struct
type Person struct {
	Name string
	Age  int
}

// Method associated with the Person type
func (p Person) Greet() {
	fmt.Printf("Hello, my name is %s and I am %d years old.\n", p.Name, p.Age)
}

func main() {
	// Create a struct instance
	p1 := Person{Name: "Alice", Age: 30}
	fmt.Println("Person 1:", p1)

	// Access fields
	fmt.Println("Person 1 Name:", p1.Name)

	// Call method
	p1.Greet()

	// Create a struct pointer
	p2 := &Person{Name: "Bob", Age: 25}
	fmt.Println("Person 2 (pointer):", p2.Name) // Access fields directly through pointer
	p2.Greet()
}

Comparison of Go Built-in Data Structures

Data Structure Description Fixed/Dynamic Size Access (Index/Key) Insertion (Avg) Deletion (Avg) Ordered Unique Elements Thread Safety (Concurrent Mod.)
ArrayFixed-size sequence of elementsFixed$O(1)$N/AN/AYes (index order)NoNo (requires external sync)
SliceDynamic-size view into an arrayDynamic$O(1)$$O(1)$ amortized (append), $O(N)$ (insert middle)$O(N)$Yes (insertion order)NoNo (requires external sync)
MapUnordered key-value pairs (Hash Table)Dynamic$O(1)$ (avg), $O(N)$ (worst)$O(1)$ (avg), $O(N)$ (worst)$O(1)$ (avg), $O(N)$ (worst)NoKeys: YesNo (requires external sync)
StructTyped collection of fieldsFixed (fields)$O(1)$ (field access)N/AN/AYes (field declaration order)N/AFields not inherently safe (requires external sync)

2. Concurrency (Goroutines & Channels)

Go's concurrency model is based on communicating sequential processes (CSP), using goroutines and channels.

Goroutines

Lightweight threads managed by the Go runtime. Start a goroutine by prefixing a function call with go.

package main

import (
	"fmt"
	"time"
)

func sayHello() {
	for i := 0; i < 3; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println("Hello from goroutine!")
	}
}

func main() {
	go sayHello() // Start a goroutine
	fmt.Println("Main goroutine continues...")
	time.Sleep(500 * time.Millisecond) // Give time for sayHello to run
	fmt.Println("Main goroutine finished.")
}

Channels

Typed conduits through which you can send and receive values with a goroutine. Used for communication and synchronization.

  • make(chan Type): Unbuffered channel.
  • make(chan Type, capacity): Buffered channel.
package main

import "fmt"

func sum(s []int, c chan int) {
	total := 0
	for _, v := range s {
		total += v
	}
	c <- total // Send total to channel c
}

func main() {
	s := []int{7, 2, 8, -9, 4, 0}

	c := make(chan int) // Unbuffered channel
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)

	x, y := <-c, <-c // Receive from c
	fmt.Println(x, y, x+y) // Output: -5 17 12
}

Buffered Channels

Channels that have a fixed capacity. Sends to a buffered channel block only when the buffer is full. Receives block when the buffer is empty.

package main

import "fmt"

func main() {
	ch := make(chan int, 2) // Buffered channel with capacity 2
	ch <- 1                 // Send 1 (buffer has 1 element)
	ch <- 2                 // Send 2 (buffer has 2 elements)
	// ch <- 3 // This would block because buffer is full

	fmt.Println(<-ch) // Receive 1
	fmt.Println(<-ch) // Receive 2
	// fmt.Println(<-ch) // This would block because buffer is empty
}

Select Statement

Used to wait on multiple channel operations. It blocks until one of its cases can run.

package main

import (
	"fmt"
	"time"
)

func producer(ch chan int, name string) {
	for i := 0; i < 3; i++ {
		time.Sleep(100 * time.Millisecond)
		ch <- i
		fmt.Printf("%s sent %d\n", name, i)
	}
	close(ch)
}

func main() {
	c1 := make(chan int)
	c2 := make(chan int)

	go producer(c1, "Producer 1")
	go producer(c2, "Producer 2")

	// Consume from both channels using select
	for i := 0; i < 6; i++ {
		select {
		case msg1, ok := <-c1:
			if ok {
				fmt.Println("Received from c1:", msg1)
			} else {
				fmt.Println("c1 closed")
				c1 = nil // Prevents further reads from closed channel
			}
		case msg2, ok := <-c2:
			if ok {
				fmt.Println("Received from c2:", msg2)
			} else {
				fmt.Println("c2 closed")
				c2 = nil // Prevents further reads from closed channel
			}
		default: // Optional: runs if no other case is ready
			// fmt.Println("No channel ready, waiting...")
			time.Sleep(50 * time.Millisecond)
		}
		if c1 == nil && c2 == nil {
			break // Both channels closed
		}
	}
	fmt.Println("Finished receiving.")
}

Mutexes (sync.Mutex)

Used for mutual exclusion to protect shared resources from concurrent access. Less idiomatic than channels for communication, but useful for shared state.

package main

import (
	"fmt"
	"sync"
	"time"
)

var (
	counter int
	mutex   sync.Mutex // Mutex to protect counter
)

func increment() {
	mutex.Lock() // Acquire lock
	counter++
	mutex.Unlock() // Release lock
}

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			increment()
		}()
	}

	wg.Wait() // Wait for all goroutines to finish
	fmt.Println("Final counter:", counter) // Should be 1000
}

3. Interfaces

Go interfaces are implicitly implemented. A type implements an interface by simply having all the methods declared in the interface.

package main

import "fmt"

// Define an interface
type Greeter interface {
	SayHello() string
}

// Define a struct
type Person struct {
	Name string
}

// Person implements Greeter because it has SayHello()
func (p Person) SayHello() string {
	return "Hello, my name is " + p.Name
}

// Another struct
type Robot struct {
	Model string
}

// Robot also implements Greeter
func (r Robot) SayHello() string {
	return "Beep boop, I am " + r.Model
}

func greet(g Greeter) {
	fmt.Println(g.SayHello())
}

func main() {
	p := Person{Name: "Alice"}
	r := Robot{Model: "C3PO"}

	greet(p) // Person implements Greeter
	greet(r) // Robot implements Greeter
}

4. Error Handling

Go handles errors by returning an error type as the last return value. No exceptions (try-catch).

Returning Errors

package main

import (
	"errors"
	"fmt"
)

func divide(a, b float64) (float64, error) {
	if b == 0 {
		return 0, errors.New("division by zero") // Return an error
	}
	return a / b, nil // Return result and nil (no error)
}

func main() {
	result, err := divide(10, 2)
	if err != nil {
		fmt.Println("Error:", err)
	} else {
		fmt.Println("Result:", result)
	}

	result, err = divide(10, 0)
	if err != nil {
		fmt.Println("Error:", err) // Output: Error: division by zero
	} else {
		fmt.Println("Result:", result)
	}
}

Panic and Recover

panic is used for unrecoverable errors (e.g., programming bugs). recover can catch a panic in a defered function.

package main

import "fmt"

func safeDivide(a, b int) {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered from panic:", r)
		}
	}()

	if b == 0 {
		panic("cannot divide by zero") // Panic!
	}
	fmt.Println("Result of division:", a/b)
}

func main() {
	fmt.Println("Calling safeDivide(10, 2)")
	safeDivide(10, 2)
	fmt.Println("Calling safeDivide(10, 0)")
	safeDivide(10, 0) // This will cause a panic, but it's recovered
	fmt.Println("Program continues after panic recovery.")
}

5. Pointers

Go has pointers, but they are more restricted than in C/C++. No pointer arithmetic. Used for passing values by reference and working with structs.

package main

import "fmt"

func modifyValue(ptr *int) {
	*ptr = 100 // Dereference and modify the value at the address
}

func main() {
	value := 50
	fmt.Println("Original value:", value) // Output: 50

	modifyValue(&value) // Pass the address of 'value'
	fmt.Println("Modified value:", value) // Output: 100

	// Pointers to structs
	type Point struct {
		X, Y int
	}
	p := &Point{1, 2} // p is a pointer to a Point struct
	fmt.Println("Point X:", p.X) // Access fields directly using . (Go automatically dereferences)
	p.Y = 5
	fmt.Println("Modified Point:", *p) // Output: {1 5}
}

6. Memory Management (Garbage Collection)

Go features automatic memory management through its garbage collector. Developers do not manually allocate or deallocate memory.

  • **Heap**: Memory for dynamically allocated objects (e.g., using new or composite literals like slices, maps, structs). Managed by the GC.
  • **Stack**: Memory for local variables and function call frames. Managed automatically by the runtime.
  • **Garbage Collector (GC)**: Identifies and reclaims memory that is no longer reachable by the program. Go's GC is concurrent and low-latency.
  • **Escape Analysis**: The compiler determines if a variable should be allocated on the stack or the heap. If a local variable's address escapes the function's scope, it must be allocated on the heap.
package main

import (
	"fmt"
	"runtime"
	"time"
)

// This function creates a large slice, which will likely be allocated on the heap.
func createLargeSlice() []int {
	return make([]int, 1000000) // 1 million integers
}

func main() {
	fmt.Println("Starting memory management example.")

	// Get initial memory stats
	var m runtime.MemStats
	runtime.ReadMemStats(&m)
	fmt.Printf("Initial heap alloc: %v bytes\n", m.HeapAlloc)

	// Create some objects
	_ = createLargeSlice() // Assign to _ to prevent compiler optimizing it away
	_ = createLargeSlice()

	runtime.ReadMemStats(&m)
	fmt.Printf("After creating slices, heap alloc: %v bytes\n", m.HeapAlloc)

	// Hint to the garbage collector to run (not guaranteed to run immediately)
	runtime.GC()
	time.Sleep(100 * time.Millisecond) // Give GC a moment

	runtime.ReadMemStats(&m)
	fmt.Printf("After explicit GC, heap alloc: %v bytes\n", m.HeapAlloc)

	fmt.Println("Memory management example finished.")
}

7. Modules & Packages

Go uses modules to manage dependencies and packages for code organization.

  • **Packages**: A collection of source files in the same directory that are compiled together. Every Go program is made of packages.
  • **Modules**: A collection of related Go packages that are versioned together. Modules are the unit of source code interchange and versioning.

Creating a Module

go mod init example.com/mymodule

Adding a Dependency

go get github.com/gorilla/mux

Using a Package

package main

import (
	"fmt"
	"example.com/mymodule/mypackage" // Assuming mypackage is in mymodule
)

func main() {
	fmt.Println("Value from mypackage:", mypackage.GetValue())
}

// In mypackage/mypackage.go:
// package mypackage
// func GetValue() string {
//     return "Hello from mypackage!"
// }

8. Testing

Go has a built-in testing framework. Test files end with _test.go and contain functions starting with Test.

// mymath.go
package mymath

func Add(a, b int) int {
	return a + b
}

func Subtract(a, b int) int {
	return a - b
}

// mymath_test.go
package mymath

import "testing"

func TestAdd(t *testing.T) {
	result := Add(2, 3)
	expected := 5
	if result != expected {
		t.Errorf("Add(2, 3) = %d; want %d", result, expected)
	}
}

func TestSubtract(t *testing.T) {
	tests := []struct {
		a, b, expected int
	}{
		{5, 2, 3},
		{10, 7, 3},
		{1, 1, 0},
	}

	for _, test := range tests {
		result := Subtract(test.a, test.b)
		if result != test.expected {
			t.Errorf("Subtract(%d, %d) = %d; want %d", test.a, test.b, result, test.expected)
		}
	}
}

Run tests from the terminal:

go test ./mymath

9. Design Patterns

Common solutions to recurring problems in software design, adapted for Go idioms.

Singleton

Ensures a class has only one instance and provides a global point of access to it. In Go, often achieved using sync.Once.

package main

import (
	"fmt"
	"sync"
)

// singleton represents the single instance of our object
type singleton struct {
	data string
}

var (
	instance *singleton
	once     sync.Once // Ensures a function is called only once
)

// GetInstance returns the singleton instance
func GetInstance() *singleton {
	once.Do(func() {
		instance = &singleton{data: "I am the one and only instance!"}
		fmt.Println("Singleton instance created.")
	})
	return instance
}

func main() {
	s1 := GetInstance()
	fmt.Println(s1.data)

	s2 := GetInstance() // This will return the same instance
	fmt.Println(s2.data)

	fmt.Println("Are s1 and s2 the same instance?", s1 == s2) // Output: true
}

Factory Method

Provides an interface for creating objects, allowing subclasses (or different implementations) to decide which class to instantiate.

package main

import "fmt"

// Product interface
type Product interface {
	GetName() string
}

// Concrete Product A
type ConcreteProductA struct{}

func (p *ConcreteProductA) GetName() string {
	return "Product A"
}

// Concrete Product B
type ConcreteProductB struct{}

func (p *ConcreteProductB) GetName() string {
	return "Product B"
}

// Creator interface (Factory)
type Creator interface {
	CreateProduct() Product
}

// Concrete Creator A
type ConcreteCreatorA struct{}

func (c *ConcreteCreatorA) CreateProduct() Product {
	return &ConcreteProductA{}
}

// Concrete Creator B
type ConcreteCreatorB struct{}

func (c *ConcreteCreatorB) CreateProduct() Product {
	return &ConcreteProductB{}
}

func main() {
	creatorA := &ConcreteCreatorA{}
	productA := creatorA.CreateProduct()
	fmt.Println("Created:", productA.GetName())

	creatorB := &ConcreteCreatorB{}
	productB := creatorB.CreateProduct()
	fmt.Println("Created:", productB.GetName())
}

Observer

Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. Often implemented with channels in Go.

package main

import (
	"fmt"
	"sync"
)

// Observer interface
type Observer interface {
	Update(message string)
}

// Subject struct
type Subject struct {
	observers []Observer
	mu        sync.Mutex // Protects observers list
}

func (s *Subject) Attach(o Observer) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.observers = append(s.observers, o)
}

func (s *Subject) Detach(o Observer) {
	s.mu.Lock()
	defer s.mu.Unlock()
	for i, obs := range s.observers {
		if obs == o {
			s.observers = append(s.observers[:i], s.observers[i+1:]...)
			break
		}
	}
}

func (s *Subject) Notify(message string) {
	s.mu.Lock()
	defer s.mu.Unlock()
	for _, obs := range s.observers {
		obs.Update(message)
	}
}

// Concrete Observer
type ConcreteObserver struct {
	Name string
}

func (o *ConcreteObserver) Update(message string) {
	fmt.Printf("%s received update: %s\n", o.Name, message)
}

func main() {
	subject := &Subject{}
	obs1 := &ConcreteObserver{Name: "Observer 1"}
	obs2 := &ConcreteObserver{Name: "Observer 2"}

	subject.Attach(obs1)
	subject.Attach(obs2)

	subject.Notify("A new event occurred!")
	subject.Detach(obs1)
	subject.Notify("Another event!")
}

Strategy

Defines a family of algorithms, encapsulates each one, and makes them interchangeable.

package main

import "fmt"

// Strategy interface
type PaymentStrategy interface {
	Pay(amount int)
}

// Concrete Strategy: Credit Card Payment
type CreditCardPayment struct{}

func (c *CreditCardPayment) Pay(amount int) {
	fmt.Printf("Paying %d using Credit Card.\n", amount)
}

// Concrete Strategy: PayPal Payment
type PayPalPayment struct{}

func (p *PayPalPayment) Pay(amount int) {
	fmt.Printf("Paying %d using PayPal.\n", amount)
}

// Context
type ShoppingCart struct {
	strategy PaymentStrategy
}

func (s *ShoppingCart) SetPaymentStrategy(strategy PaymentStrategy) {
	s.strategy = strategy
}

func (s *ShoppingCart) Checkout(amount int) {
	if s.strategy != nil {
		s.strategy.Pay(amount)
	} else {
		fmt.Println("No payment strategy set.")
	}
}

func main() {
	cart := &ShoppingCart{}

	cart.SetPaymentStrategy(&CreditCardPayment{})
	cart.Checkout(100)

	cart.SetPaymentStrategy(&PayPalPayment{})
	cart.Checkout(50)
}

Decorator

Attaches additional responsibilities to an object dynamically.

package main

import "fmt"

// Component interface
type Coffee interface {
	GetCost() float64
	GetIngredients() string
}

// Concrete Component
type SimpleCoffee struct{}

func (c *SimpleCoffee) GetCost() float64 {
	return 5.0
}

func (c *SimpleCoffee) GetIngredients() string {
	return "Coffee"
}

// Decorator Base struct (embeds Coffee interface)
type CoffeeDecorator struct {
	Coffee
}

// Concrete Decorators
type MilkDecorator struct {
	CoffeeDecorator
}

func NewMilkDecorator(c Coffee) Coffee {
	return &MilkDecorator{CoffeeDecorator{c}}
}

func (d *MilkDecorator) GetCost() float64 {
	return d.Coffee.GetCost() + 1.5
}

func (d *MilkDecorator) GetIngredients() string {
	return d.Coffee.GetIngredients() + ", Milk"
}

type SugarDecorator struct {
	CoffeeDecorator
}

func NewSugarDecorator(c Coffee) Coffee {
	return &SugarDecorator{CoffeeDecorator{c}}
}

func (d *SugarDecorator) GetCost() float64 {
	return d.Coffee.GetCost() + 0.5
}

func (d *SugarDecorator) GetIngredients() string {
	return d.Coffee.GetIngredients() + ", Sugar"
}

func main() {
	var myCoffee Coffee = &SimpleCoffee{}
	fmt.Printf("Cost: %.2f, Ingredients: %s\n", myCoffee.GetCost(), myCoffee.GetIngredients())

	myCoffee = NewMilkDecorator(myCoffee) // Add milk
	fmt.Printf("Cost: %.2f, Ingredients: %s\n", myCoffee.GetCost(), myCoffee.GetIngredients())

	myCoffee = NewSugarDecorator(myCoffee) // Add sugar
	fmt.Printf("Cost: %.2f, Ingredients: %s\n", myCoffee.GetCost(), myCoffee.GetIngredients())
}

Visitor

Represents an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

package main

import "fmt"

// Visitor interface
type Visitor interface {
	VisitConcreteElementA(element *ConcreteElementA)
	VisitConcreteElementB(element *ConcreteElementB)
}

// Element interface
type Element interface {
	Accept(visitor Visitor)
}

// Concrete Element A
type ConcreteElementA struct{}

func (e *ConcreteElementA) OperationA() string { return "ConcreteElementA" }
func (e *ConcreteElementA) Accept(visitor Visitor) { visitor.VisitConcreteElementA(e) }

// Concrete Element B
type ConcreteElementB struct{}

func (e *ConcreteElementB) OperationB() string { return "ConcreteElementB" }
func (e *ConcreteElementB) Accept(visitor Visitor) { visitor.VisitConcreteElementB(e) }

// Concrete Visitor 1
type ConcreteVisitor1 struct{}

func (v *ConcreteVisitor1) VisitConcreteElementA(element *ConcreteElementA) {
	fmt.Println("Visitor 1 processing " + element.OperationA())
}
func (v *ConcreteVisitor1) VisitConcreteElementB(element *ConcreteElementB) {
	fmt.Println("Visitor 1 processing " + element.OperationB())
}

// Concrete Visitor 2
type ConcreteVisitor2 struct{}

func (v *ConcreteVisitor2) VisitConcreteElementA(element *ConcreteElementA) {
	fmt.Println("Visitor 2 processing " + element.OperationA() + " differently.")
}
func (v *ConcreteVisitor2) VisitConcreteElementB(element *ConcreteElementB) {
	fmt.Println("Visitor 2 processing " + element.OperationB() + " differently.")
}

func main() {
	elements := []Element{
		&ConcreteElementA{},
		&ConcreteElementB{},
	}

	visitor1 := &ConcreteVisitor1{}
	for _, elem := range elements {
		elem.Accept(visitor1)
	}

	visitor2 := &ConcreteVisitor2{}
	for _, elem := range elements {
		elem.Accept(visitor2)
	}
}