dongya6381 2017-03-31 09:14
浏览 25
已采纳

代码行为取决于开关操作符中的类型顺序,如何摆脱呢?

Recently I started to learn the Go language.
I am trying to understand interface principles in Go and was completely puzzled by one thing.
The duck principle says: if something quacks like a duck and walks like a duck, then it's a duck.
But I wondered how Go will behave if we have three interfaces like this:

// Interface A
type InterfaceA interface {
    ActionA() string
}

// Interface B
type InterfaceB interface {
    ActionB() string
}

And interface C, which does something different but has functions which are similar to interfaces A and B functions:

// Interface C with methods A and B interfaces
type InterfaceC interface {
    ActionA() string
    ActionB() string
}

Then we have three structures which implement the interfaces above:

type StructA struct{}

// If it does ActionA then it's interface A
func (a StructA) ActionA() string {
    return "Interface A implementation"
}

type StructB struct{}

// If it does ActionB then it's interface B
func (b StructB) ActionB() string {
    return "Interface B implementation"
}

type StructC struct{}

// If it does ActionA and ActionB, it's an Interface C
func (c StructC) ActionA() string {
    return "Interface C implementation"
}

func (c StructC) ActionB() string {
    return "Interface C implementation"
}

And a function that identifies which type it gets:

func getType(data interface{}) string {
    switch data.(type) {
    default:
        return "Unknown"
    case InterfaceA:
        return "Interface A"
    case InterfaceB:
        return "Interface B"
    case InterfaceC:
        return "Interface C"
    }
}

Code inside main function:

func main() {
    a := StructA{}
    fmt.Println(a.ActionA())
    fmt.Println(getType(a)) // should return InterfaceA
    fmt.Println("")
    b := StructB{}
    fmt.Println(b.ActionB())
    fmt.Println(getType(b)) // should return InterfaceB
    fmt.Println("")
    c := StructC{}
    fmt.Println(c.ActionA())
    fmt.Println(c.ActionB())
    fmt.Println(getType(c)) // should return InterfaceC
}

Output:

Interface A implementation
Interface A

Interface B implementation
Interface B

Interface C implementation
Interface C implementation
Interface A

After some experiments I found out if we change the case order inside switch then the function identifies the type correctly:

func getType(data interface{}) string {
    switch data.(type) {
    default:
        return "Unknown"
    case InterfaceC:
        return "Interface C"
    case InterfaceB:
        return "Interface B"
    case InterfaceA:
        return "Interface A"
    }
}

Output:

Interface A implementation
Interface A

Interface B implementation
Interface B

Interface C implementation
Interface C implementation
Interface C

See also full code on play.golang.org

My question: Is it a bug or a feature? And if it's a feature, how should I change getType so that the function doesn't depend on case order?

  • 写回答

1条回答 默认 最新

  • dongqiao0953 2017-03-31 09:25
    关注

    This is the intended working, as defined by the language spec.

    There are 2 types of switch statement, Expression switches and Type switches, and this behavior is documented at the Expression switches:

    In an expression switch, the switch expression is evaluated and the case expressions, which need not be constants, are evaluated left-to-right and top-to-bottom; the first one that equals the switch expression triggers execution of the statements of the associated case; the other cases are skipped. If no case matches and there is a "default" case, its statements are executed. There can be at most one default case and it may appear anywhere in the "switch" statement.

    [...]

    A type switch compares types rather than values. It is otherwise similar to an expression switch.

    In Go a type implicitly implements an interface if its method set is a superset of the interface. There is no declaration of the intent. So in Go it doesn't matter which interface defines the methods, only thing that matters is the method signatures: if a type has all the methods an interface "prescribes", then that type implicitly implements the said interface.

    The problem is that you want to use the Type switch to something it was not designed for. You want to find the "widest" type (with the most methods) that is still implemented by the value. It will only do this if you enumerate the cases (the different types) in this intended order.

    That being said, in your case there's no such thing that a value is only InterfaceC implementation. Your code doesn't lie: all values that implement InterfaceC will also implement InterfaceA and InterfaceB too, because the method sets of both InterfaceA and InterfaceB are subsets of the method set of InterfaceC.

    If you want to be able to "differentiate" InterfaceC implementations, you have to "alter" the method sets so that the above mentioned relation will not hold (method set of InterfaceC will not be a superset of the method set of InterfaceA and InterfaceB). If you want StructC to not be an InterfaceA implementation, you must change the method signature of ActionA() (either in InterfaceA or in InterfaceC), and similarly ActionB() to not be an InterfaceB implementation.

    You could also add a method to InterfaceA (and to InterfaceB) which is missing from InterfaceC:

    type InterfaceA interface {
        ActionA() string
        implementsA()
    }
    
    type InteraceB interface {
        ActionB() string
        implementsB()
    }
    

    And you have to add them to StructA and StructB of course:

    func (a StructA) implementsA() {}
    
    func (b StructB) implementsB() {}
    

    This way you get the desired output. Try it on the Go Playground.

    If you can't or don't want to do this, your only option is to enumerate the cases in the right order. Or don't use type switch for this.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 如何在炒股软件中,爬到我想看的日k线
  • ¥15 51单片机中C语言怎么做到下面类似的功能的函数(相关搜索:c语言)
  • ¥15 seatunnel 怎么配置Elasticsearch
  • ¥15 PSCAD安装问题 ERROR: Visual Studio 2013, 2015, 2017 or 2019 is not found in the system.
  • ¥15 (标签-MATLAB|关键词-多址)
  • ¥15 关于#MATLAB#的问题,如何解决?(相关搜索:信噪比,系统容量)
  • ¥500 52810做蓝牙接受端
  • ¥15 基于PLC的三轴机械手程序
  • ¥15 多址通信方式的抗噪声性能和系统容量对比
  • ¥15 winform的chart曲线生成时有凸起