Delegation Pattern
Concepts
Delegation
means: "I'm not implementing this method myself, but instead I'm passing the call to another object that knows how."
In Go, delegation is commonly implemented through:
Struct embedding
(automatic delegation)- Explicit forwarding (manual delegation)
But with interface
, we may have better flexible and extensible
An Example
Let's say that, we have an set
of integers, we can add or remove number from it.
type IntSet struct {
data map[int]struct{}
}
func NewIntSet() IntSet {
return IntSet{make(map[int]struct{})}
}
func (set *IntSet) Add(x int) {
set.data[x] = struct{}{}
}
func (set *IntSet) Delete(x int) {
delete(set.data, x)
}
And now, we need an Undo
feature to IntSet
. That's to say, remove the added number with Undo
, or add the removed number with it. We may do that like this
Define the Undoer
interface and implement it
type Undoer interface {
Trace(func())
Undo() error
}
type DefaultUndoer []func()
func (du *DefaultUndoer) Trace(f func()) {
*du = append(*du, f)
}
func (du *DefaultUndoer) Undo() error {
if len(*du) == 0 {
return errors.New("no function traced")
}
f := (*du)[len(*du)-1]
if f != nil {
f()
}
*du = (*du)[:len(*du)-1]
return nil
}
Utilize the new interface
type IntSetWithUndo struct {
IntSet
Undoer
}
func NewIntSetWithUndo() IntSetWithUndo {
return IntSetWithUndo{
NewIntSet(),
new(DefaultUndoer),
}
}
func (set *IntSetWithUndo) Add(x int) {
set.IntSet.Add(x)
set.Undoer.Trace(func() {
set.IntSet.Delete(x)
})
}
func (set *IntSetWithUndo) Delete(x int) {
set.IntSet.Delete(x)
set.Undoer.Trace(func() {
set.IntSet.Add(x)
})
}
func (set *IntSetWithUndo) Undo() error {
return set.Undoer.Undo()
}
With this we can gain the benefits,
- No modification to the
IntSet
. Closed to the modification - Implement the feature with
IntSetWithUndo
. Open to extension. Undoer
can ALSO be used by other components, withDelegation Pattern
.