Go package design: when should I define methods on types?

Suppose I have a type type T int

and I want to define logic to work with that type.

Which abstraction should you use and when?

  • Method definition for this type:

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

  • Function definition :

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

+3


source to share


3 answers


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 than Foo(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 same weatherProvider

    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 use type

    to make them a new type). So, strings.Split

    and reflect.DeepEqual

    should be functions. Also, it io.Copy

    must be a function, because it cannot simply define a method on Reader

    or Writer

    . Note that they do not declare a new type (for example strings.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

    or Page

    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 trivial http.Server

    and calls on it ListenAndServe

    .
  • 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()

    or template.Funcs()

    or for logging checks go vet

    , etc. is enough . Do not force it.
  • Functions if object orientation will be forced: Say yours main()

    or init()

    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 (à la type 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.

+5


source


Use this method if you are managing the internal of secrets

your object

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

      



Use this function if you are using public interface

object

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

      

+3


source


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.

0


source







All Articles