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:trueorfalse.- Numeric Types:
- Integers:
int,int8,int16,int32,int64,uint,uint8, etc. - Floating-point:
float32,float64. - Complex:
complex64,complex128.
- Integers:
string: Immutable sequence of bytes (UTF-8 encoded).rune: An alias forint32, represents a Unicode code point.byte: An alias foruint8.
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.) |
|---|---|---|---|---|---|---|---|---|
Array | Fixed-size sequence of elements | Fixed | $O(1)$ | N/A | N/A | Yes (index order) | No | No (requires external sync) |
Slice | Dynamic-size view into an array | Dynamic | $O(1)$ | $O(1)$ amortized (append), $O(N)$ (insert middle) | $O(N)$ | Yes (insertion order) | No | No (requires external sync) |
Map | Unordered key-value pairs (Hash Table) | Dynamic | $O(1)$ (avg), $O(N)$ (worst) | $O(1)$ (avg), $O(N)$ (worst) | $O(1)$ (avg), $O(N)$ (worst) | No | Keys: Yes | No (requires external sync) |
Struct | Typed collection of fields | Fixed (fields) | $O(1)$ (field access) | N/A | N/A | Yes (field declaration order) | N/A | Fields 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
newor 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)
}
}