Design Patterns in Go
repository·main·Indexed 18 days ago
https://github.com/refactoringguru/design-patterns-goA collection of Go implementations for all classic Gang of Four (GoF) design patterns, providing a practical reference for implementing these patterns using idiomatic Go code. Requires Go v1.19 or higher.
What's inside refactoringguru-design-patterns-go
- The design pattern examples in this repository require Go v1.19 or higher to function correctly.
Run design pattern examples in Go
mainTo run any of the design pattern examples, navigate to the specific pattern's directory and use the
go run .command. For example, to run thebuilderpattern example, use the following commands:cd <examples-dir>/builder; go run .Run the SyncOnce singleton example
mainThe
mainpackage insingleton/syncOnce/main.godemonstrates a thread-safe Singleton pattern implementation by launching 30 concurrent goroutines that all attempt to callgetInstance(). This simulates a high-concurrency environment to verify that the singleton instance is initialized only once.To run this specific example, navigate to the directory and use the standard Go run command.
go run singleton/syncOnce/main.goUse the Iterator pattern to traverse a UserCollection
mainThe Iterator pattern allows you to traverse elements of a collection without exposing its underlying representation. In this implementation, you can create an iterator from a
UserCollectionand usehasNext()andgetNext()to loop through the users.To use it:
- Initialize a
UserCollectionwith a slice of*Userpointers. - Call
createIterator()on the collection to get an iterator instance. - Use a
forloop withiterator.hasNext()to check for more elements anditerator.getNext()to retrieve the current element.
user1 := &User{ name: "a", age: 30, } user2 := &User{ name: "b", age: 20, } userCollection := &UserCollection{ users: []*User{user1, user2}, } iterator := userCollection.createIterator() for iterator.hasNext() { user := iterator.getNext() fmt.Printf("User is %+v\n", user) }- Initialize a
Implement the Memento pattern in Go
mainThe Memento pattern allows you to capture and restore an object's internal state without violating encapsulation. This implementation uses three main components:
- Originator: The object whose state you want to save. It provides methods to create a
Mementocontaining its current state and to restore its state from aMemento. - Memento: A value object that stores the internal state of the Originator.
- Caretaker: Responsible for keeping track of multiple Mementos (e.g., in a history list) but never operates on or examines the contents of a Memento.
To use this pattern, initialize a
Caretakerto manage history, anOriginatorwith an initial state, and useoriginator.createMemento()to save snapshots andoriginator.restoreMemento(memento)to roll back.package main import "fmt" func main() { // Initialize the Caretaker to manage history caretaker := &Caretaker{ mementoArray: make([]*Memento, 0), } // Initialize the Originator with an initial state originator := &Originator{ state: "A", } fmt.Printf("Originator Current State: %s\n", originator.getState()) // Save current state caretaker.addMemento(originator.createMemento()) originator.setState("B") fmt.Printf("Originator Current State: %s\n", originator.getState()) caretaker.addMemento(originator.createMemento()) originator.setState("C") fmt.Printf("Originator Current State: %s\n", originator.getState()) caretaker.addMemento(originator.createMemento()) // Restore to a previous state using the Caretaker's history originator.restoreMemento(caretaker.getMemento(1)) fmt.Printf("Restored to State: %s\n", originator.getState()) originator.restoreMemento(caretaker.getMemento(0)) fmt.Printf("Restored to State: %s\n", originator.getState()) }- Originator: The object whose state you want to save. It provides methods to create a
Implement the Chain of Responsibility pattern
mainThe Chain of Responsibility pattern allows you to pass a request along a chain of handlers. Each handler decides either to process the request or to pass it to the next handler in the chain.
In this implementation, a
Patientrequest is passed through a chain of departments:Reception->Doctor->Medical->Cashier. Each department implements anexecutemethod to handle the patient.package main func main() { cashier := &Cashier{} // Set next for medical department medical := &Medical{} medical.setNext(cashier) // Set next for doctor department doctor := &Doctor{} doctor.setNext(medical) // Set next for reception department reception := &Reception{} reception.setNext(doctor) patient := &Patient{name: "abc"} // Patient visiting reception.execute(patient) }Run the Mediator pattern example
mainThe
mainpackage provides a demonstration of the Mediator pattern. It simulates a train station environment where astationManager(the mediator) coordinates the movement ofPassengerTrainandFreightTrainobjects. This prevents the trains from communicating directly with each other, instead routing all interactions through the mediator.package main func main() { stationManager := newStationManger() passengerTrain := &PassengerTrain{ mediator: stationManager, } freightTrain := &FreightTrain{ mediator: stationManager, } passengerTrain.arrive() freightTrain.arrive() passengerTrain.depart() }Use the Client in the Adapter pattern
mainIn the Adapter pattern implementation, the
Clientrepresents the high-level component that interacts with theComputerinterface. The client uses theInsertLightningConnectorIntoComputermethod to perform operations on aComputerinstance, which abstracts away the specific connector/port compatibility logic.type Client struct { } func (c *Client) InsertLightningConnectorIntoComputer(com Computer) { fmt.Println("Client inserts Lightning connector into computer.") com.InsertIntoLightningPort() }Run the Observer pattern example
mainThe
observer/main.gofile serves as an entrypoint to demonstrate the Observer design pattern. It simulates a scenario where ashirtItem(the Subject) notifies multipleCustomerinstances (the Observers) when its availability is updated.package main func main() { shirtItem := newItem("Nike Shirt") observerFirst := &Customer{id: "abc@gmail.com"} observerSecond := &Customer{id: "xyz@gmail.com"} shirtItem.register(observerFirst) shirtItem.register(observerSecond) shirtItem.updateAvailability() }Run the Proxy pattern example
mainThe
proxy/main.gofile serves as a demonstration of the Proxy design pattern. It simulates an Nginx server acting as a proxy that handles HTTP requests for specific URLs like/app/statusand/create/user.To run this specific implementation, you can execute the main package. The example demonstrates how the proxy intercepts requests, handles different HTTP methods (GET, POST), and returns an HTTP status code along with a response body.
package main import "fmt" func main() { nginxServer := newNginxServer() appStatusURL := "/app/status" createuserURL := "/create/user" httpCode, body := nginxServer.handleRequest(appStatusURL, "GET") fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body) httpCode, body = nginxServer.handleRequest(appStatusURL, "GET") fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body) httpCode, body = nginxServer.handleRequest(appStatusURL, "GET") fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body) httpCode, body = nginxServer.handleRequest(createuserURL, "POST") fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body) httpCode, body = nginxServer.handleRequest(createuserURL, "GET") fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body) }Use the Strategy pattern to switch cache eviction algorithms
mainThe Strategy pattern implementation allows you to dynamically change the eviction algorithm used by a cache at runtime.
To use it:
- Initialize a cache with a starting strategy (e.g.,
Lfu,Lru, orFifo) usinginitCache(strategy). - Add items to the cache using
cache.add(key, value). - Switch the eviction algorithm at any time using
cache.setEvictionAlgo(newStrategy).
Available strategies demonstrated in this implementation include:
Lfu(Least Frequently Used)Lru(Least Recently Used)Fifo(First In, First Out)
// Initialize cache with LFU strategy lfu := &Lfu{} cache := initCache(lfu) cache.add("a", "1") // Switch to LRU strategy at runtime lru := &Lru{} cache.setEvictionAlgo(lru) cache.add("d", "4") // Switch to FIFO strategy at runtime fifo := &Fifo{} cache.setEvictionAlgo(fifo) cache.add("e", "5")- Initialize a cache with a starting strategy (e.g.,
Use the Adapter pattern to connect incompatible interfaces
mainThe Adapter pattern allows incompatible interfaces to work together. In this implementation, a
Clientexpects aLightningConnectorcompatible interface (represented by theMactype), but can interact with aWindowsmachine by using aWindowsAdapter. The adapter wraps the incompatibleWindowsobject and exposes the methods required by theClient.package main func main() { // Scenario 1: Direct compatibility client := &Client{} mac := &Mac{} client.InsertLightningConnectorIntoComputer(mac) // Scenario 2: Using an Adapter for incompatible types windowsMachine := &Windows{} windowsMachineAdapter := &WindowsAdapter{ windowMachine: windowsMachine, } client.InsertLightningConnectorIntoComputer(windowsMachineAdapter) }