英文:
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'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?
答案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 (
"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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论