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

huangapple go评论90阅读模式
英文:

What is the idiomatic way to wrap a type?

问题

我想要更方便地包装goquery.Selection以便更方便地获取HTML和选择器字符串。

要访问goquery.Selection的方法,我是否应该在以下代码中实现一些辅助方法,比如Get()

type MySelection goquery.Selection

// 如果没有这个辅助方法,我就必须始终使用类型转换来使用goquery.Selection的方法。
func (s *MySelection) Get() *goquery.Selection {
    sel := s.(goquery.Selection)
    return sel
}

func (s *MySelection) HTML() string {
    // html, _ := s.Html() <- 错误
    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)"))
}

有更好的处理这种情况的方法吗?

英文:

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&#39;s methods.
func (s *MySelection) Get() *goquery.Selection {
	sel := s.(goquery.Selection)
	return sel
}

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

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

Are there better ways to handle this situation?

答案1

得分: 3

你还可以嵌入

type MySelection struct {
    goquery.Selection
    一些有效载荷 //如果需要的话
}

这样,你就可以免费获得 MySelection 的 goquery.Selection 方法,并且可以添加或覆盖一些方法。

英文:

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.

答案2

得分: 1

嗯,有几种方法可以“处理这个问题”。但是不要将其命名为Get()这不符合惯例

从最佳实践的角度来看,我建议:

  • 将他们的代码与你的代码解耦。
  • 实现一个反腐层(包装器,包装他们的包)

这样做的原因有很多。但对于Go来说,保持简单是最好的方式,这归结为一个问题:你想对你的代码进行单元测试吗?

如果答案是肯定的,那么我永远不会直接使用第三方包。我会用自己的接口包装他们的包。然后,在我的所有代码中使用(注入)该接口,以便在单元测试中模拟它。

再次说明,有几种模式和观点;但是,我将展示一种允许进行单元测试的包装器模式。

goquery_wrapper.go

package mypackage

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

var _mySelector *mySelector // Go标准库使用下划线表示私有类型

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

type MySelector struct {
}

func (ms *MySelector) Html() {
  // 你的自定义版本
}

// 使用你的包装器初始化全局变量
func init() {
  _mySelector = &MySelector{ ... }
}

foo.go

package mypackage

func Foo() {

  // 使用通过init()初始化的全局变量
  data := _mySelector.Html()

  // 通过构造函数使用依赖注入(IoC)
  obj := NewSomething(_mySelector)

  // 通过方法使用依赖注入(IoC)
  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) {

  // 准备

  // 覆盖你的全局变量
  oldMS := _mySelector
  _mySelector := &mockSelector{
    HtmlReturnsThis: "<b>success</b>",
  }

  // 执行

  // 由于foo.Bar使用了全局变量,它现在使用了我们在上面设置的模拟对象。
  result := foo.Bar("sample input")

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

  // 恢复原状
  _mySelector = oldMS
}

func TestFoo(t *testing.T) {

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

  // 执行

  // 或者,如果使用IoC,只需注入你的模拟对象
  result := bar.Process(mock, "sample input")

  // 断言
  ...
}

这样做使我不必在单元测试期间处理第三方包的细节。这种方法很有效,除非第三方包的API非常庞大。那样的话,我会质疑为什么要使用那么复杂的包。

英文:

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 (
  &quot;path/to/goquery.Selection&quot;
)

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 = &amp;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, &quot;input data&quot;)
}

bar_test.go

package mypackage

import (
  &quot;testing&quot;
)

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 := &amp;mockSelector{
    HtmlReturnsThis: &quot;&lt;b&gt;success&lt;/b&gt;&quot;,
  }

  // act

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

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

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

func TestFoo(t *testing.T) {
   
  // arrange
  mock := &amp;mockSelector{
    HtmlReturnsThis: &quot;&lt;b&gt;success&lt;/b&gt;&quot;,
  }

  // act

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

  // 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.

huangapple
  • 本文由 发表于 2016年4月15日 10:39:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/36637361.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定