一个功能开启类型vs许多类型的功能

Given the following struct Foo and goal of handling multiple types (where Handle could be Read, Write, etc.). I understand that we lose compile-time type checks when we use the empty interface, though aside from this, what are the pros and cons of each? Finally, what is the most idiomatic way to achieve this?

package main

type Foo struct {
    A int
    B string
}

//Handle all types with switch
func (f *Foo) Handle(obj interface{}) {

    switch obj := obj.(type) {
    case int:
        //do int stuff...
        f.A + obj
    case string:
        //do string stuff...
        f.B + obj
    default:
        panic("Unknown type")
    }
}

//Handle each type individually
func (f *Foo) HandleInt(i int) {
    //do int stuff...
    f.A + i
}
func (f *Foo) HandleString(s string) {
    //do string stuff...
    f.B + s
}

The empty interface is necessary if you're going to handle user-defined types via reflect. That's fmt.Printf, json.Encode, and binary.Write's reason for accepting it. In terms of the Merkle tree scenario you posted about earlier, where you're hashing things, you'd use an empty interface if your Hash() had a reflect-based fallback for hashing user-created structs.

Specific methods probably make more sense if you're only going to provide methods for a few key types ([]byte, string, whatever). Besides compile-time checking, the function list serves as documentation of what you can hash. On the other hand, if there are, like, a dozen-plus types you want to hash--think all the (u)int types and slices of them--I think I'd use an interface{} just for the sake of an uncluttered API unless you absolutely needed the very best performance, but I don't think there's a clear consensus one way or the other.

One good example would be the package sort (sources here), which:

  • does implement one method for basic type (as in your second approach)
  • proposes an interface that the type to be sorted has to implement.
  • can sort slices and user-defined collection

If you do the same for Handle(obj interface{}) => Handle(obj Interface), that would help you handler (for more complex type) to be defined once, using the methods declared in 'Interface'.
Any type satisfying said Interface would be passed to the generic Handle() function.
And you could Handle slice (similar to this example for sort) or used-defined collection as well.