3

Suppose that I have a type type T intand I want to define a logic to operate on this type.

What abstraction should I use and When ?

  • Defining a method on that type:

     func (T t) someLogic() {
     // ...
     } 
    
  • Defining a function:

     func somelogic(T t) {
     // ...
     }
    
Salah Eddine Taouririt
  • 24,925
  • 20
  • 60
  • 96
  • The second version (function) is basically nonsensical without a return value. Otherwise: It just depends. – Volker Sep 02 '14 at 21:15
  • @Volker: it's not common, but that form is sometimes useful with pointer members, slices, or maps; e.g. `func (p IntSlice) Sort()` in the sort package – JimB Sep 02 '14 at 21:45

3 Answers3

5

Some situations where you tend to use methods:

  • Mutating the receiver: Things that modify fields of the objects are often methods. It's less surprising to your users that x.Foo will modify X than that Foo(x) will.
  • Side effects through the receiver: Things are often methods on a type if they have side effects on/through the object in subtler ways, like writing to a network connection that's part of the struct, or writing via pointers or slices or so on in the struct.
  • Accessing private fields: In theory, anything within the same package can see unexported fields of an object, but more commonly, just the object's constructor and methods do. Having other things look at unexported fields is sort of like having C++ friends.
  • Necessary to satisfy an interface: Only methods can be part of interfaces, so you may need to make something a method to just satisfy an interface. For example, Peter Bourgon's Go intro defines type openWeatherMap as an empty struct with a method, rather than a function, just to satisfy the same weatherProvider interface as other implementations that aren't empty structs.
    • Test stubbing: As a special case of the above, sometimes interfaces help stub out objects for testing, so your stub implementations might have to be methods even if they have no state.

Some where you tend to use functions:

  • Constructors: func NewFoo(...) (*Foo) is a function, not a method. Go has no notion of a constructor, so that's how it has to be.
  • Running on interfaces or basic types: You can't add methods on interfaces or basic types (unless you use type to make them a new type). So, strings.Split and reflect.DeepEqual must be functions. Also, io.Copy has to be a function because it can't just define a method on Reader or Writer. Note that these don't declare a new type (e.g., strings.MyString) to get around the inability to do methods on basic types.
  • Moving functionality out of oversized types or packages: Sometimes a single type (think User or Page in some Web apps) accumulates a lot of functionality, and that hurts readability or organization or even causes structural problems (like if it becomes harder to avoid cyclic imports). Making a non-method out of a method that isn't mutating the receiver, accessing unexported fields, etc. might be a refactoring step towards moving its code "up" to a higher layer of the app or "over" to another type/package, or the standalone function is just the most natural long-term place for it. (Hat tip Steve Francia for including an example of this from hugo in a talk about his Go mistakes.)
  • Convenience "just use the defaults" functions: If your users might want a quick way to use "default" object values without explicitly creating an object, you can expose functions that do that, often with the same name as an object method. For instance, http.ListenAndServe() is a package-level function that makes a trivial http.Server and calls ListenAndServe on it.
  • Functions for passing behavior around: Sometimes you don't need to define a type and interface just to pass functionality around and a bare function is sufficient, as in http.HandleFunc() or template.Funcs() or for registering go vet checks and so on. Don't force it.
  • Functions if object-orientation would be forced: Say your main() or init() are cleaner if they call out to some helpers, or you have private functions that don't look at any object fields and never will. Again, don't feel like you have to force OO (à la type Application struct{...}) if, in your situation, you don't gain anything by it.

When in doubt, if something is part of your exported API and there's a natural choice of what type to attach it to, make it a method. However, don't warp your design (pulling concerns into your type or package that could be separate) just so something can be a method. Writers don't WriteJSON; it'd be hard to implement one if they did. Instead you have JSON functionality added to Writers via a function elsewhere, json.NewEncoder(w io.Writer).

If you're still unsure, first write so that the documentation reads clearly, then so that code reads naturally (o.Verb() or o.Attrib()), then go with what feels right without sweating over it too much, because often you can rearrange it later.

Community
  • 1
  • 1
twotwotwo
  • 28,310
  • 8
  • 69
  • 56
3

Use the method if you are manipulating internal secrets of your object

   (T *t) func someLogic() {
      t.mu.Lock()
      ...
   }

Use the function if you are using the public interface of the object

  func somelogic(T *t) {
      t.DoThis()
      t.DoThat()
     }
fabrizioM
  • 46,639
  • 15
  • 102
  • 119
0

if  you want to change T object, use

 func (t *T) someLogic() {
 // ...
 } 

if you donn't change T object and would like a origined-object way , use

 func (t T) someLogic() {
 // ...
 }

but remeber that this will generate a temporay object T to call someLogic

if your like the way c language does, use

 func somelogic(t T) {
      t.DoThis()
      t.DoThat()
 }

or

   func somelogic(t T) {
      t.DoThis()
      t.DoThat()
     }

one more thing , the type is behide the var in golang.

frank.lin
  • 1,614
  • 17
  • 26