3

In Go we can create functions that implement an interface, like http.Handler interface and concrete type http.HandlerFunc. I created another simple example of this kind of pattern to calculate bonus for different employees.


type BonusCalculator interface {
    Calculate(salary float64) float64
}

type BonusFunc func(salary float64) float64

func (bonus BonusFunc) Calculate(salary float64) float64 {
    return bonus(salary)
}

var (
    DeveloperBonus BonusFunc = func(salary float64) float64 { return salary*1.5 + 2500.00 }
    ManagerBonus   BonusFunc = func(salary float64) float64 { return salary * 2.0 }
    DirectorBonus  BonusFunc = func(salary float64) float64 { return salary * 3.3 }
)

func CalculateBonus (bonus BonusCalculator, salary float64) float64 {
  return bonus.Calculate(salary)
}

func main() {
    bonus := CalculateBonus(DirectorBonus, 35000.00)
    fmt.Printf("bonus %.2f", bonus)
}


So above we have simple BonusFuncs implementing interface BonusCalculator instead of use structs to do the same.

Is there a name to this pattern? I see it in many places and never found a name for it.

Lawrence
  • 172
  • 1
  • 9
  • 3
    The term _adapter_ is mentioned in [the documentation of the `HandlerFunc` type](https://pkg.go.dev/net/http#HandlerFunc). – jub0bs Dec 26 '22 at 22:26
  • 1
    I see the point in calling `http.HandlerFunc` an adapter as it structurally adapts the interface of the function by adding the `ServeHTTP()` method. For the example above, it's also worth looking at the *strategy pattern* as you encapsulate different behaviors in different interchangeable values satisfying the same interface. See how the strategy pattern relates to the adapter pattern: https://stackoverflow.com/q/46023431/2102916. – Ricardo Erikson Dec 27 '22 at 00:48

1 Answers1

4

Is there a name to this pattern? I see it in many places and never found a name for it.

Yes, this pattern is called Adapter because it allows some sort of "interface" (closures in this case) to be used as another kind of interface (a type that satisfies BonusCalculator).

In your example, you have an interface BonusCalculator:

type BonusCalculator interface {
    Calculate(salary float64) float64
}

However, you have closures of type func(salary float64) float64, and you want to be able to pass them when a type that satisfies BonusFunc is required, i.e., a type that has the method Calculate(salary float64) float64 (the closures don't have a method with such name, so they don't satisfy BonusCalculator).

What you want is to adapt a func(salary float64) float64 to a BonusCalculator. So, you define a new type, BonusFunc, which is the adapter. It will derive from the closure type you want to adapt and, in turn, will satisfy BonusCalculator by defining a method Calculate(salary float64) float64 on it that simply calls the underlying closure:

type BonusFunc func(salary float64) float64

func (bonus BonusFunc) Calculate(salary float64) float64 {
    return bonus(salary)
}

The BonusFunc is the Adapter; it exists only for adapting a func(salary float64) float64 so that the interface BonusCalculator can be satisfied. Now, whenever you assign a func(salary float64) float64 to a BonusFunc, you obtain a value that satisfies Bonuscalculator.

JFMR
  • 23,265
  • 4
  • 52
  • 76