包装类型的惯用方式是什么?

I want to wrap goquery.Selection for getting HTML and selector string more conveniently.

To access methods of goquery.Selection, should I implement some helper method such as Get() on the following code?

type MySelection goquery.Selection

// Without this helper method, I should always use type conversion
// to use goquery.Selection's methods.
func (s *MySelection) Get() *goquery.Selection {
    sel := s.(goquery.Selection)
    return sel
}

func (s *MySelection) HTML() string {
    // html, _ := s.Html() <- error
    html, _ := s.Get().Html()
    return html
}

func (s *MySelection) String() string {
    return fmt.Sprintf("%v#%v.%v",
        goquery.NodeName(s.Get()),
        s.Get().AttrOr("id", "(undefined)"),
        s.Get().AttrOr("class", "(undefined)"))
}

Are there better ways to handle this situation?

You also can embed

type MySelection struct {
    goquery.Selection
    some payload //if needed
}

and you will have goquery.Selection methods for MySelection for free and can to add or overwrite some.

Well, there are several ways to "handle this." But don't name it Get(): it isn't idiomatic.

From a best-practices perspective, I would recommend:

  • Decouple their code from your code.
  • Implementing an Anti-Corruption Layer (a wrapper that wraps their package)

The reasons for this is many. But for Go, it's best to keep it simple - which boils down to one question: do you want to unit test your code?

If the answer is yes, then I would never use a 3rd party package directly. I'd wrap their package with my own interface. Then, use use (inject) that interface throughout all of my code so to allow me to mock it up in unit tests.

Again there are several patterns and opinions; but, i am going to show this one of a wrapper allowing for unit testing.

goquery_wrapper.go

package mypackage

import (
  "path/to/goquery.Selection"
)

var _mySelector *mySelector // Go stdlib uses underscores for private types

type mySelector interface {
  Html() string
  ...
}

type MySelector struct {
}

func (ms *MySelector) Html() {
  // your custom version
}

// initialize the global var with your wrapper
func init() {
  _mySelector = &MySelector{ ... }
}

foo.go

package mypackage

func Foo() {

  // uses the global var initialized with init()
  data := _mySelector.Html()

  // IoC using D.I. through constructors
  obj := NewSomething(_mySelector)

  // IoC using D.I. through methods
  result := bar.Process(_mySelector, "input data")
}

bar_test.go

package mypackage

import (
  "testing"
)

type mockSelector struct {
  HtmlWasCalled bool
  HtmlReturnsThis string
}

func (ms mockSelector) Html() string {
  ms.HtmlWasCalled = true
  return ms.HtmlReturnsThis
}

func TestBar(t *testing.T) {

  // arrange

  // override your global var
  oldMS := _mySelector
  _mySelector := &mockSelector{
    HtmlReturnsThis: "<b>success</b>",
  }

  // act

  // since foo.Bar is using the global var, it now uses
  // our mock we set above.
  result := foo.Bar("sample input")

  // assert
  if result != expected {
    t.Fail()
  }

  // put it back the way it was
  _mySelector = oldMS
}

func TestFoo(t *testing.T) {

  // arrange
  mock := &mockSelector{
    HtmlReturnsThis: "<b>success</b>",
  }

  // act

  // or, just inject your mock if using IoC
  result := bar.Process(mock, "sample input")

  // assert
  ...

}

This decouples me from having to deal with the 3rd party package nuances during unit testing. Works well, except when the API of the package is huge. Then, I question even why I am using the package to begin with if it is that complicated.