AmitKumarDas / Decisions

Apache License 2.0
10 stars 3 forks source link

Go: Interface whose impl is a func adaptor #192

Open AmitKumarDas opened 5 years ago

AmitKumarDas commented 5 years ago

refer - k8s.io/client-go

:nerd_face: Your function can be an implementation of an interface

type ResourceEventHandler interface {
  OnHandle(obj interface{})
}

type ResourceEventHandlerFunc func(obj interface{}) 

func (f ResourceEventHandlerFunc) OnHandle(obj interface{}) {
  f(obj)
}

func Get(obj interface{}) {
  // logic
}
func Update(obj interface{}) {
  // logic
}
func Delete(obj interface{}) {
  // logic
}

func main() {
  obj := map[string]string{}

  handlers := []ResourceEventHandler {
    // we are type casting the `typed func` into specific implementations
    // note: we cannot use the specific implementations directly
    ResourceEventHandlerFunc(Get),
    ResourceEventHandlerFunc(Update),
    ResourceEventHandlerFunc(Delete),
  }

  for h := range handlers {
    h.OnHandle(obj)
  }

  funcs := []ResourceEventHandlerFunc {
    // no need to cast here
    Get,
    Update,
    Delete,
  }

  for fn := range funcs {
    fn(obj)
  }
}

:worried: What if there are multiple contracts exposed from the interface :bulb: Define a function per contract & compose all of them within a struct

type ResourceEventHandler interface {
    OnAdd(obj interface{})
    OnUpdate(oldObj, newObj interface{})
    OnDelete(obj interface{})
}

:nerd_face: specify as many or as few via below struct

// ResourceEventHandlerFuncs is an adaptor to let you easily specify as many or
// as few of the notification functions as you want while still implementing
// ResourceEventHandler.
type ResourceEventHandlerFuncs struct {
    AddFunc    func(obj interface{})
    UpdateFunc func(oldObj, newObj interface{})
    DeleteFunc func(obj interface{})
}

// OnAdd calls AddFunc if it's not nil.
func (r ResourceEventHandlerFuncs) OnAdd(obj interface{}) {
    if r.AddFunc != nil {
        r.AddFunc(obj)
    }
}

// OnUpdate calls UpdateFunc if it's not nil.
func (r ResourceEventHandlerFuncs) OnUpdate(oldObj, newObj interface{}) {
    if r.UpdateFunc != nil {
        r.UpdateFunc(oldObj, newObj)
    }
}

// OnDelete calls DeleteFunc if it's not nil.
func (r ResourceEventHandlerFuncs) OnDelete(obj interface{}) {
    if r.DeleteFunc != nil {
        r.DeleteFunc(obj)
    }
}

:bulb: Above solution also solves the problem of an interface that exposes multiple contracts. In other words, a struct that embeds above struct need not implement all the contracts since the embedded struct has implemented all the contracts.

AmitKumarDas commented 5 years ago

:bulb: Let us extend above scenario further

type FilteringResourceEventHandler struct {
    FilterFunc func(obj interface{}) bool

        // uses interface & not struct ... which is good
        // let the struct be utilised by callers
        // remember the go proverb `accept interface & return struct`
    Handler    ResourceEventHandler
}

// OnAdd calls the nested handler only if the filter succeeds
func (r FilteringResourceEventHandler) OnAdd(obj interface{}) {
    if !r.FilterFunc(obj) {
        return
    }
    r.Handler.OnAdd(obj)
}

// OnUpdate ensures the proper handler is called depending on 
// whether the filter matches
func (r FilteringResourceEventHandler) OnUpdate(oldObj, newObj interface{}) {
    newer := r.FilterFunc(newObj)
    older := r.FilterFunc(oldObj)
    switch {
    case newer && older:
        r.Handler.OnUpdate(oldObj, newObj)
    case newer && !older:
        r.Handler.OnAdd(newObj)
    case !newer && older:
        r.Handler.OnDelete(oldObj)
    default:
        // do nothing
    }
}

// OnDelete calls the nested handler only if the filter succeeds
func (r FilteringResourceEventHandler) OnDelete(obj interface{}) {
    if !r.FilterFunc(obj) {
        return
    }
    r.Handler.OnDelete(obj)
}