Go package design: when should I define methods on types?
In some situations, when you use methods:
- Recipient mutation: Things that change the fields of objects are often methods. It is less surprising to your users what
x.Foo
X will change thanFoo(x)
. - Side Effects via Receiver: Things are often type methods if they have side effects on / via an object in more subtle ways, such as writing to a network connection that part
struct
, or writing via pointers or fragments or so on in a structure. - Private Field Access: In theory, anything inside the same package can see the non-exported fields of an object, but more often than not, it's just the object's constructor and methods. Other views on unfulfilled fields are similar to C ++
friend
s. - Required to satisfy an interface: Only methods can be part of interfaces, so you might need to do something like a method to just satisfy the interface. For example, Peter Bourgon Go's intro defines
type openWeatherMap
as an empty structure with a method rather than a function, just to satisfy the sameweatherProvider
as other implementations that are not empty structures.- Testing: . As a special case above, sometimes interfaces help in disposing of objects for testing , so your stub implementations can be methods even if they are stateless.
Some where you usually use functions:
- Constructors:
func NewFoo(...) (*Foo)
is a function, not a method. Go has no idea about a constructor, as it should be. - Working with interfaces or base types: You cannot add methods on
interface
or base types (unless you usetype
to make them a new type). So,strings.Split
andreflect.DeepEqual
should be functions. Also, itio.Copy
must be a function, because it cannot simply define a method onReader
orWriter
. Note that they do not declare a new type (for examplestrings.MyString
) to get around the impossibility of making methods on basic types. - Moving functionality out of oversized types or packages: Sometimes one type (think,
User
orPage
in some web applications) accumulates a lot of functionality, which degrades readability or organization, or even causes structural problems (for example, if it becomes more difficult to avoid circular imports). Creating a non-method from a method that doesn't mutate the receiver, accessing unallocated fields, etc. It can be a refactoring step to move your code "up" to a higher level of the application, or "above" to another type / package, or a stand-alone function is the most natural long-term place for it. (Hat tip Steve Francia for including an example of this from hugo when talking about his Go bugs.) - Convenience "just use the default function": . If your users need a quick way to use default object values without explicitly creating an object, you can expose functions that do this, often with the same name as the object's method. For example
http.ListenAndServe()
is a package level function that does trivialhttp.Server
and calls on itListenAndServe
. - Functions for the passage of the behavior around: . Sometimes you don't need to define a type and interface to skip functionality, and a simple function like in
http.HandleFunc()
ortemplate.Funcs()
or for logging checksgo vet
, etc. is enough . Do not force it. - Functions if object orientation will be forced: Say yours
main()
orinit()
cleaner if they call some helpers or you have private functions that don't look at any objects and never will. Again, don't feel like you have to force OO (à latype Application struct{...}
) if you don't get anything out of it in your situation.
When in doubt if something is part of your exported API and there is a natural choice about what type to attach to, make it a method. However, don't warp your design (by pulling problems into your type or package, which may be separate) so that something might be a method. Writer
not WriteJSON
; it would be difficult to implement if they did. Instead, you have the JSON function added in Writer
terms of the function elsewhere json.NewEncoder(w io.Writer)
.
If you're still not sure, first write so that the documentation reads clearly and then so that the code reads naturally ( o.Verb()
or o.Attrib()
), then move on to what seems to be correct without sweating too much over it, because often you can change it later.
source to share
if you want to change object T then use
func (t *T) someLogic() {
// ...
}
if you are not modifying the T object and want the original object path to be used then use
func (t T) someLogic() {
// ...
}
but remember that this will create a temporary object T to call someLogic
if you like c language use
func somelogic(t T) {
t.DoThis()
t.DoThat()
}
or
func somelogic(t T) {
t.DoThis()
t.DoThat()
}
one more thing, type works with var in golang.
source to share