设计Go包:什么时候我应该在类型上定义方法?

tar*_*lah 3 go

假设我有一个类型type T int,我想定义一个逻辑来操作这种类型.

我应该使用什么抽象和什么时候?

two*_*two 5

在某些情况下,您倾向于使用方法:

  • 改变接收器:修改对象字段的东西通常是方法.对于x.Foo修改X的用户而言,这并不令人惊讶Foo(x).
  • 通过接收器的副作用:如果它们以较微妙的方式对对象/通过对象产生副作用,通常是类型上的方法,例如写入网络连接struct,或通过指针或切片等在结构中写入.
  • 访问私有字段:理论上,同一个包中的任何内容都可以看到对象的未导出字段,但更常见的是,只是对象的构造函数和方法.让其他东西看待未导出的字段有点像使用C++ friend.
  • 必须满足接口:只有方法可以成为接口的一部分,因此您可能需要创建一个方法来满足接口.例如,Peter Bourgon的Go intro定义type openWeatherMap为带有方法而不是函数的空结构,只是为了满足与非空结构的weatherProvider其他实现相同的接口.
    • 测试存根:作为上述情况的一个特例,有时接口会帮助存根对象进行测试,因此即使没有状态,存根实现也可能必须是方法.

您倾向于使用函数的地方:

  • 构造函数: func NewFoo(...) (*Foo)是一个函数,而不是一个方法.Go没有构造函数的概念,因此它必须如此.
  • 在接口或基本类型上运行:您不能在interfaces或基本类型上添加方法(除非您使用type它们使它们成为新类型).所以,strings.Split和reflect.DeepEqual必须的功能.此外,io.Copy必须是一个函数,因为它不能只定义一个方法Reader或Writer.请注意,这些不会声明一个新类型(例如strings.MyString),以解决无法对基本类型执行方法的问题.
  • 从超大类型或包中移出功能:有时单个类型(想想User或Page在某些Web应用程序中)会累积大量功能,这会损害可读性或组织甚至导致结构性问题(例如,如果避免循环导入变得更加困难).从不改变接收器,访问未导出的字段等的方法中制作非方法可能是将其代码"向上"移动到应用程序的更高层或"超过"到另一种类型的重构步骤/包,或独立功能只是它最自然的长期地方.(帽子提示史蒂夫弗兰西亚在谈论他的Go错误时,从hugo中包含了一个这样的例子.)
  • 方便"只使用默认值"功能:如果用户可能想要快速使用"默认"对象值而不显式创建对象,则可以公开执行此操作的函数,通常使用与对象方法相同的名称.例如,http.ListenAndServe()是一个包级别的函数,它使得它变得微不足道http.Server并调用ListenAndServe它.
  • :周边的传递行为的功能有时候,你并不需要定义一个类型和接口仅仅是传递功能和裸露的功能就足够了,如在http.HandleFunc()或template.Funcs()或登记go vet检查等.不要强迫它.
  • 如果面向对象将被迫功能:说出你的main()或init()更清洁,如果他们召唤出一些助手,或者你有私人活动不看任何物体领域,永远不会.再说一遍,type Application struct{...}如果在你的情况下你没有获得任何东西,你不要觉得你必须强迫OO(àla ).

如果有疑问,如果某些内容是您导出的API的一部分,并且可以自然选择将其附加到哪种类型,请将其作为方法.但是,不要扭曲您的设计(将问题拉入您的类型或包装中可能是分开的),这样就可以成为一种方法.Writer不要WriteJSON; 如果他们这样做的话,很难实现.而是Writer通过其他地方的函数将sSON功能添加到s中json.NewEncoder(w io.Writer).

如果您仍然不确定,请首先编写以便文档清晰地读取,然后使代码自然地(o.Verb()或o.Attrib())读取,然后使用感觉正确而不会过多地冒汗,因为通常您可以稍后重新排列它.