Lesson 5
Design Patterns in Go: Encapsulation, Polymorphism, Composition, and Abstraction
Lesson Introduction

Hello! Today, we'll delve into design patterns in Go with practical exercises that apply these fundamental principles to solve problems. Mastering these concepts will enhance your coding skills and understanding.

Our focus today is to strengthen your understanding of how and when to apply specific Go design concepts using structs, interfaces, and composition. These include Encapsulation, Abstraction, Polymorphism, and Composition through Go’s distinct approach.

We'll explore four real-life scenarios, examining which pattern is applicable and why, using Go’s unique features. Let's dive in!

Real-life Example 1: Database Management System (Encapsulation)

In Go, Encapsulation is achieved using structs and package-level visibility. We use unexported fields and methods to control data access and promote data integrity.

Go
1package main 2 3import ( 4 "fmt" 5) 6 7type Employees struct { 8 employees map[int]string // unexported field 9} 10 11func NewEmployees() *Employees { 12 return &Employees{employees: make(map[int]string)} 13} 14 15func (e *Employees) AddEmployee(id int, name string) { // method to operate on unexported data 16 e.employees[id] = name 17} 18 19func (e *Employees) UpdateEmployee(id int, newName string) { // method to operate on unexported data 20 if _, exists := e.employees[id]; exists { 21 e.employees[id] = newName 22 } 23} 24 25func (e *Employees) GetEmployee(id int) string { // method to get unexported data 26 if name, exists := e.employees[id]; exists { 27 return name 28 } 29 return "" 30} 31 32func main() { 33 employees := NewEmployees() 34 employees.AddEmployee(1, "John") 35 employees.AddEmployee(2, "Mark") 36 37 employees.UpdateEmployee(2, "Jake") 38 39 fmt.Println(employees.GetEmployee(1)) // Outputs: John 40 fmt.Println(employees.GetEmployee(2)) // Outputs: Jake 41}

Here, Encapsulation restricts direct access to the employee data, using methods to interact with the stored information securely.

Real-life Example 2: Graphic User Interface (GUI) Development (Polymorphism)

In Go, Polymorphism is achieved through interfaces, allowing different structs to define specific behaviors for the same method.

Go
1package main 2 3import ( 4 "fmt" 5) 6 7type Clickable interface { 8 Click() 9} 10 11type Button struct{} 12 13func (b Button) Click() { 14 fmt.Println("Button Clicked!") 15} 16 17type CheckBox struct{} 18 19func (c CheckBox) Click() { 20 fmt.Println("CheckBox Clicked!") 21} 22 23func main() { 24 var b Clickable = Button{} 25 var c Clickable = CheckBox{} 26 27 // Click Controls 28 b.Click() // Outputs: Button Clicked! 29 c.Click() // Outputs: CheckBox Clicked! 30}

Interfaces in Go allow Polymorphism, enabling different structs to share behavior provided by the Clickable interface.

Real-life Example 3: Creating a Web Page Structure (Composition)

The Composition design pattern in Go uses struct embedding to create complex types by combining simpler structs.

Go
1package main 2 3import ( 4 "fmt" 5 "strings" 6) 7 8type Header struct { 9 Text string 10} 11 12func (h Header) Render() string { 13 return "<h1>" + h.Text + "</h1>\n" 14} 15 16type Paragraph struct { 17 Text string 18} 19 20func (p Paragraph) Render() string { 21 return "<p>" + p.Text + "</p>\n" 22} 23 24type ListElement struct { 25 Items []string 26} 27 28func (l ListElement) Render() string { 29 var sb strings.Builder 30 sb.WriteString("<ul>") 31 for _, item := range l.Items { 32 sb.WriteString("<li>" + item + "</li>") 33 } 34 sb.WriteString("</ul>\n") 35 return sb.String() 36} 37 38type WebPage struct { 39 Title string 40 Elements []interface{ Render() string } 41} 42 43func (wp *WebPage) AddElement(e interface{ Render() string }) { 44 wp.Elements = append(wp.Elements, e) 45} 46 47func (wp WebPage) Display() string { 48 var sb strings.Builder 49 sb.WriteString("<html><head><title>") 50 sb.WriteString(wp.Title) 51 sb.WriteString("</title></head><body>\n") 52 for _, e := range wp.Elements { 53 sb.WriteString(e.Render()) 54 } 55 sb.WriteString("</body></html>\n") 56 return sb.String() 57} 58 59func main() { 60 page := WebPage{Title: "My Web Page"} 61 page.AddElement(Header{Text: "Welcome to My Web Page"}) 62 page.AddElement(Paragraph{Text: "This is a paragraph of text on the web page."}) 63 64 items := []string{"Item 1", "Item 2", "Item 3"} 65 page.AddElement(ListElement{Items: items}) 66 67 fmt.Println(page.Display()) 68 /* 69 Outputs: 70 <html><head><title>My Web Page</title></head><body> 71 <h1>Welcome to My Web Page</h1> 72 <p>This is a paragraph of text on the web page.</p> 73 <ul><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul> 74 </body></html> 75 */ 76}

In this code, we construct a webpage structure using the Composition design pattern, leveraging struct embedding to model composite types.

Real-life Example 4: Creating a Vehicle (Abstraction)

In Go, Abstraction is facilitated through interfaces, allowing you to provide specific functionality and abstract away particular behaviors.

Go
1package main 2 3import ( 4 "fmt" 5) 6 7type Vehicle interface { 8 StartEngine() 9 StopEngine() 10 Drive() 11} 12 13type Car struct { 14 Color string 15 EngineType string 16 EngineRunning bool 17} 18 19func (c *Car) StartEngine() { 20 c.EngineRunning = true 21 fmt.Println("Car engine started!") 22} 23 24func (c *Car) StopEngine() { 25 c.EngineRunning = false 26 fmt.Println("Car engine stopped!") 27} 28 29func (c *Car) Drive() { 30 if c.EngineRunning { 31 fmt.Printf("%s car is driving on the %s engine type!\n", c.Color, c.EngineType) 32 } else { 33 fmt.Println("Start the engine first!") 34 } 35} 36 37func main() { 38 car := &Car{Color: "red", EngineType: "gasoline"} 39 car.StartEngine() 40 car.Drive() 41 /* 42 Output: 43 Car engine started! 44 red car is driving on the gasoline engine type! 45 */ 46}

Here, the Vehicle interface specifies essential methods (StartEngine, StopEngine, Drive), and the Car struct implements these, providing concrete behavior while concealing complexity.

Design Pattern Identification

Let's recap the major Go patterns:

  • Encapsulation: Structs and package-level visibility control access and protect data integrity.
  • Abstraction: Interfaces hide complexity, exposing only necessary behavior.
  • Polymorphism: Interfaces enable shared behavior across distinct types.
  • Composition: Struct embedding creates more complex systems by combining simpler components.

Understanding these principles helps you recognize and apply Go’s design approaches in real-world scenarios.

Lesson Summary

Great job! We've explored the practical applications of Go’s design patterns. We've seen how encapsulation is implemented through structs and methods, the role of polymorphism in sharing behavior using interfaces, the power of composition through struct embedding, and how abstraction is achieved with interfaces.

Now, it's time for hands-on exercises to solidify these concepts. Remember, continued practice is key to mastering these techniques. So keep coding!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.