From 54886195c601210187a9be2ec6677f2d9c4f6685 Mon Sep 17 00:00:00 2001 From: Sebastian Steger Date: Wed, 20 Aug 2025 11:31:53 +0000 Subject: [PATCH 1/2] observer design pattern --- go/04-design-pattern/go.mod | 3 + go/04-design-pattern/main.go | 85 +++++++++++++++++++ go/04-design-pattern/patterns/observer.go | 35 ++++++++ .../patterns/observer_test.go | 66 ++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 go/04-design-pattern/go.mod create mode 100644 go/04-design-pattern/main.go create mode 100644 go/04-design-pattern/patterns/observer.go create mode 100644 go/04-design-pattern/patterns/observer_test.go diff --git a/go/04-design-pattern/go.mod b/go/04-design-pattern/go.mod new file mode 100644 index 0000000..40b6024 --- /dev/null +++ b/go/04-design-pattern/go.mod @@ -0,0 +1,3 @@ +module gitty.informatik.hs-mannheim.de/steger/pr3-code/go/04-design-pattern + +go 1.25.0 diff --git a/go/04-design-pattern/main.go b/go/04-design-pattern/main.go new file mode 100644 index 0000000..777d855 --- /dev/null +++ b/go/04-design-pattern/main.go @@ -0,0 +1,85 @@ +package main + +import ( + "fmt" + + "gitty.informatik.hs-mannheim.de/steger/pr3-code/go/04-design-pattern/patterns" +) + +type Counter struct { + patterns.Subject + Count int +} + +func NewCounter() Counter { + return Counter{patterns.NewSubject(), 0} +} + +func (c *Counter) Inc() { + c.Count++ + c.Notify() +} + +func (c *Counter) Dec() { + c.Count-- + c.Notify() +} + +type CountPrinter struct { + counter *Counter +} + +func (cp *CountPrinter) Update() { + fmt.Println("count updated to ", cp.counter.Count) +} + +type CounterHistory struct { + counter *Counter + history []int +} + +func (ch *CounterHistory) Update() { + ch.history = append(ch.history, ch.counter.Count) +} + +func main() { + counter := NewCounter() + + history := CounterHistory{&counter, []int{}} + counter.Register(&history) + + printer := CountPrinter{&counter} + counter.Register(&printer) + printerRegistered := true + + var input string + for { + fmt.Print("Enter a command (+: increment, -: decrement, q: quit): ") + fmt.Scanln(&input) + + if len(input) != 1 { + fmt.Println("Please enter a single character.") + continue + } + + switch input[0] { + case '+': + counter.Inc() + case '-': + counter.Dec() + case 'p': + if printerRegistered { + counter.Unregister(&printer) + } else { + counter.Register(&printer) + } + printerRegistered = !printerRegistered + case 'q': + fmt.Println("Exiting...") + fmt.Println(history.history) + return + default: + fmt.Println("Unknown command. Use '+', '-', or 'q'.") + } + } +} diff --git a/go/04-design-pattern/patterns/observer.go b/go/04-design-pattern/patterns/observer.go new file mode 100644 index 0000000..8e674ff --- /dev/null +++ b/go/04-design-pattern/patterns/observer.go @@ -0,0 +1,35 @@ +package patterns + +type Observer interface { + Update() +} + +type Subject interface { + Register(o Observer) + Unregister(o Observer) + Notify() +} + +type subject struct { + observers map[Observer]bool +} + +func NewSubject() Subject { + return &subject{ + observers: make(map[Observer]bool), + } +} + +func (s *subject) Register(o Observer) { + s.observers[o] = true +} + +func (s *subject) Unregister(o Observer) { + delete(s.observers, o) +} + +func (s *subject) Notify() { + for o := range s.observers { + o.Update() + } +} diff --git a/go/04-design-pattern/patterns/observer_test.go b/go/04-design-pattern/patterns/observer_test.go new file mode 100644 index 0000000..d4582f0 --- /dev/null +++ b/go/04-design-pattern/patterns/observer_test.go @@ -0,0 +1,66 @@ +package patterns_test + +import ( + "testing" + + "gitty.informatik.hs-mannheim.de/steger/pr3-code/go/04-design-pattern/patterns" +) + +type MockObserver struct { + callCount int +} + +func (mo *MockObserver) Update() { + mo.callCount++ +} + +func TestSubject_Notify(t *testing.T) { + type observer struct { + MockObserver + toRegister bool + expectedCallcount int + } + tests := []struct { + name string + observers []*observer + }{ + {"No observers", []*observer{}}, + {"Single unregistered observer", []*observer{&observer{MockObserver{0}, false, 0}}}, + {"Single registered observer", []*observer{&observer{MockObserver{0}, true, 1}}}, + {"Two registered observers", []*observer{&observer{MockObserver{0}, true, 1}, &observer{MockObserver{0}, true, 1}}}, + {"One registered and one unregistered observers", []*observer{&observer{MockObserver{0}, true, 1}, &observer{MockObserver{0}, false, 0}}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := patterns.NewSubject() + + for _, o := range tt.observers { + if o.toRegister { + s.Register(o) + } + } + + s.Notify() + + for _, o := range tt.observers { + if o.callCount != o.expectedCallcount { + t.Errorf("%s: expected a callcount of %v, actual: %v", tt.name, o.expectedCallcount, o.callCount) + } + + //reset callcount & unregister + o.callCount = 0 + s.Unregister(o) + } + + //call notify when all observers are unregistered + s.Notify() + + //expect a callcount of 0 + for _, o := range tt.observers { + if o.callCount != 0 { + t.Errorf("%s: expected a callcount of 0, actual: %v", tt.name, o.callCount) + } + } + }) + } +} From fdacec5fe65b4e5fe8a74d3a8499aa262bd905ab Mon Sep 17 00:00:00 2001 From: Sebastian Steger Date: Tue, 14 Apr 2026 14:33:22 +0000 Subject: [PATCH 2/2] added comments --- go/04-design-pattern/main.go | 21 +++++++++++++++++-- go/04-design-pattern/patterns/observer.go | 12 ++++++++++- .../patterns/observer_test.go | 3 +++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/go/04-design-pattern/main.go b/go/04-design-pattern/main.go index 777d855..ccbc7c0 100644 --- a/go/04-design-pattern/main.go +++ b/go/04-design-pattern/main.go @@ -6,25 +6,30 @@ import ( "gitty.informatik.hs-mannheim.de/steger/pr3-code/go/04-design-pattern/patterns" ) +// Counter is a concrete implementation of the Subject interface, which maintains a count and notifies observers whenever the count changes type Counter struct { - patterns.Subject - Count int + patterns.Subject // Embedding the Subject interface allows Counter to inherit its methods and be treated as a Subject, enabling it to register observers and notify them of changes + Count int } +// NewCounter creates and returns a new instance of the Counter struct, initializing the embedded Subject using the NewSubject function from the patterns package func NewCounter() Counter { return Counter{patterns.NewSubject(), 0} } +// Inc increments the counter's count and notifies all registered observers of the change func (c *Counter) Inc() { c.Count++ c.Notify() } +// Dec decrements the counter's count and notifies all registered observers of the change func (c *Counter) Dec() { c.Count-- c.Notify() } +// CountPrinter is a concrete implementation of the Observer interface, which prints the current count whenever it receives an update notification from the Counter type CountPrinter struct { counter *Counter } @@ -33,21 +38,32 @@ func (cp *CountPrinter) Update() { fmt.Println("count updated to ", cp.counter.Count) } +var _ patterns.Observer = (*CountPrinter)(nil) // Compile-time assertion to ensure CountPrinter implements the Observer interface + +// CounterHistory is a concrete implementation of the Observer interface, which maintains a history of the counter's count values whenever it receives an update notification from the Counter type CounterHistory struct { counter *Counter history []int } +// Update appends the current count value to the history slice whenever it receives an update notification from the Counter func (ch *CounterHistory) Update() { ch.history = append(ch.history, ch.counter.Count) } +var _ patterns.Observer = (*CounterHistory)(nil) // Compile-time assertion to ensure CounterHistory implements the Observer interface + +// The main function demonstrates the usage of the Counter, CountPrinter, and CounterHistory in an interactive command-line application. +// It allows the user to increment or decrement the counter and see the updates printed, while also maintaining a history of count values. func main() { + // Create a new Counter instance, which will serve as the Subject in the Observer pattern counter := NewCounter() + // Create a new CounterHistory instance and register it as an observer of the counter to track the history of count values history := CounterHistory{&counter, []int{}} counter.Register(&history) + // Create a new CountPrinter instance and register it as an observer of the counter to print the current count whenever it changes printer := CountPrinter{&counter} counter.Register(&printer) printerRegistered := true @@ -68,6 +84,7 @@ func main() { case '-': counter.Dec() case 'p': + // Toggle the registration of the CountPrinter observer. If it's currently registered, unregister it; if it's currently unregistered, register it. if printerRegistered { counter.Unregister(&printer) } else { diff --git a/go/04-design-pattern/patterns/observer.go b/go/04-design-pattern/patterns/observer.go index 8e674ff..4474ee0 100644 --- a/go/04-design-pattern/patterns/observer.go +++ b/go/04-design-pattern/patterns/observer.go @@ -1,33 +1,43 @@ package patterns +// Observer pattern implementation in Go type Observer interface { Update() } +// Subject interface defines methods for registering, unregistering, and notifying observers type Subject interface { Register(o Observer) Unregister(o Observer) Notify() } +// Concrete implementation of the Subject interface. Lowercase 'subject' to make it unexported, as it will be created through the NewSubject function. type subject struct { - observers map[Observer]bool + observers map[Observer]bool // Using a map to store observers for efficient add/remove operations } +// NewSubject creates and returns a new instance of the subject func NewSubject() Subject { + // Needs to return a pointer to the subject struct to ensure that the same instance is modified when registering/unregistering observers return &subject{ observers: make(map[Observer]bool), } } +// Register adds an observer to the subject's set of observers +// Pointer receiver is used to modify the subject's state (the observers map) when registering an observer func (s *subject) Register(o Observer) { s.observers[o] = true } +// Unregister removes an observer from the subject's set of observers +// Pointer receiver is used to modify the subject's state (the observers map) when unregistering an observer func (s *subject) Unregister(o Observer) { delete(s.observers, o) } +// Notify calls the Update method on all registered observers to notify them of a change func (s *subject) Notify() { for o := range s.observers { o.Update() diff --git a/go/04-design-pattern/patterns/observer_test.go b/go/04-design-pattern/patterns/observer_test.go index d4582f0..cb9cd62 100644 --- a/go/04-design-pattern/patterns/observer_test.go +++ b/go/04-design-pattern/patterns/observer_test.go @@ -6,14 +6,17 @@ import ( "gitty.informatik.hs-mannheim.de/steger/pr3-code/go/04-design-pattern/patterns" ) +// MockObserver is a test implementation of the Observer interface, used to verify that the Notify method correctly calls the Update method on registered observers type MockObserver struct { callCount int } +// Update increments the callCount to track how many times the Update method has been called on this observer func (mo *MockObserver) Update() { mo.callCount++ } +// Table driven tests for the Subject's Notify method, covering various scenarios of registered and unregistered observers func TestSubject_Notify(t *testing.T) { type observer struct { MockObserver