Golang中工厂函数接口与实现之间的转换探讨
Golang中工厂函数接口与实现之间的转换探讨 在我的具体用例中,每个HTTP请求都需要特定的依赖项。因此我产生了这样的想法:
package main
import "fmt"
type I interface {
print()
}
type S struct {}
func (s S) print() {
fmt.Printf("%T", s)
}
func factory() S {
return S{}
}
func Ifactory() func() I {
return factory
}
问题当然是I的返回类型与S的返回类型冲突。现在我想知道是否有解决这个问题的方法。
更多关于Golang中工厂函数接口与实现之间的转换探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html
我认为这是一个合适的解决方案。感谢您的意见。
更多关于Golang中工厂函数接口与实现之间的转换探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
你好,
恐怕我没有完全理解你的意思。你是指提供一个存储包装器而不是工厂函数吗?
感谢 joncalhoun 的解释。可惜我已经走到这一步了 :-( 我原以为会有不需要让包互相认识或者使用这个"辅助包"的选项。
不过还是感谢你的付出。
你好,
这可能令人困惑
这并不令人困惑——我完全清楚这一点。问题是我是否可能不知道某种特定的解决方法。
在你的示例中,问题在于 http 包不应该知道 IImpl。
好的,我明白问题所在了。其他人给出了正确答案(要让接口返回接口,它们需要在包之间互相了解),但还有另一种方法。
你可以反转逻辑,定义一个接口“persist(storageObject)”并让你的租户类型实现它。这样可行吗?
http 包并不知道我示例中的 IImpl。包 whereTheHandlerIsCreated 了解 http 包,但反过来并不成立。
听起来你问的是,给定一个接口类型 A,有没有办法编写一个满足 func() A 的函数,而不显式声明它返回 A,对此答案是否定的。
如果我的理解正确,是的。
我设想找到一个"tenant"类型,以及满足"storage"接口的"storageDisk"和"storageCloud"类型。
你的存储接口应该有一个"store(storageObject)"方法,根据你正在写入的存储类型,将文件写入磁盘或通过HTTP PUT请求上传。
然后,如果你只是将对象传递给"tenant.storage",它应该就能正常工作。
joncalhoun:
http 包在我的示例中并不知道 IImpl。
啊,我明白了——嗯——到目前为止还不错。但是如果不同的包都在使用 factoryFunction,而它们各自有自己的 LocallyDefinedInterface 呢?
// 示例代码
func factoryFunction() LocallyDefinedInterface {
return &IImpl{}
}
我认为你的意思是,根据编译器的判断,factoryFunction 无法正常工作,因为它返回的是 whereTheHandlerIsCreated.LocallyDefinedInterface,而不是 http.LocallyDefinedInterface。这可能令人困惑,因为这两种类型在功能上看似相同,但在编译器看来是不同的。
暂且不论这是否是一个好主意,你可以尝试以下方法:
func factoryFunction(input string) http.LocallyDefinedInterface {
return IImpl{}
}
我不理解为什么要声明函数返回接口类型,因为接口只定义结构体的签名而从不包含任何实际实现。更重要的是 Go 中接口的工作方式,它更多用于定义参数而非设置返回类型。为什么工厂函数的返回类型对您不起作用?只要使用返回值的代码接受接口类型的参数,只要结构体符合接口规范就没有问题。
建议阅读这篇文章:https://medium.com/@cep21/what-accept-interfaces-return-structs-means-in-go-2fe879e25ee8
你好 Marc,
用更具体的说法,你试图实现什么目标?
我觉得你没有按照“Go语言的方式”来使用接口:你应该在使用接口的地方(作为变量)定义它们,而不是在实现接口的地方定义。
我认为两个包互不知晓的问题并不重要:如果你直接返回具体类型(请务必这样做,否则在返回nil时可能会遇到问题),然后在想要使用时检查返回的类型是否实现了某个接口,这样如何?
再次强调,如果你能提供一个具体示例,我们可以帮助你使其更符合语言习惯。
为了更准确地说明(使用另一个接口而非具体结构体):
package http
type LocallyDefinedInterface interface {
func DoSth()
}
type GiveMeAnIDependingOnTheInput func(input string) LocallyDefinedInterface
type Handler struct {
Factory GiveMeAnIDependingOnTheInput
}
package whereTheHandlerIsCreated
type LocallyDefinedInterface interface {
func DoSth()
}
type IImpl struct {}
func(i Iimpl DoSth() {
// 实现具体功能
}
func factoryFunction(input string) LocallyDefinedInterface {
return IImpl{}
}
func NewHandler() http.Handler {
return http.Handler{factoryFunction}
}
明白我的意思吗?我想利用隐式实现接口的优势来解耦 http 包与其他包。
你应该在使用接口的地方(作为变量)定义接口,而不是在实现接口的地方定义。
我确实是这样做的:
package http
type locallyDefinedInterface interface {
print()
}
package someOtherPackage
type locallyDefinedInterface interface {
print()
}
在 whereTheHandlerIsCreated 包中,工厂函数也需要返回一个接口,因为我们需要区分不同的实现
package someOtherPackage
type locallyDefinedInterface interface {
print()
}
func factory($input string) locallyDefinedInterface {
switch $input {
case 'this':
return <ConcreteImplementationA>
case 'that':
return <ConcreteImplementationB>
}
}
顺便说一下:我们有一个多租户系统,对于不同的租户,我们有不同类型的存储。这就是为什么我需要区分不同类型的原因。
这是一个典型的接口实现类型转换问题。在Go语言中,虽然S实现了I接口,但函数签名必须完全匹配才能进行赋值。
以下是几种解决方案:
方案1:使用类型转换包装函数
package main
import "fmt"
type I interface {
print()
}
type S struct{}
func (s S) print() {
fmt.Printf("%T\n", s)
}
func factory() S {
return S{}
}
func Ifactory() func() I {
return func() I {
return factory() // S自动转换为I接口
}
}
func main() {
f := Ifactory()
instance := f()
instance.print() // 输出: main.S
}
方案2:修改工厂函数直接返回接口
package main
import "fmt"
type I interface {
print()
}
type S struct{}
func (s S) print() {
fmt.Printf("%T\n", s)
}
func factory() I {
return S{}
}
func Ifactory() func() I {
return factory
}
func main() {
f := Ifactory()
instance := f()
instance.print() // 输出: main.S
}
方案3:使用泛型(Go 1.18+)
package main
import "fmt"
type I interface {
print()
}
type S struct{}
func (s S) print() {
fmt.Printf("%T\n", s)
}
func factory[T I]() T {
var t T
return t
}
func Ifactory() func() I {
return func() I {
return factory[S]()
}
}
func main() {
f := Ifactory()
instance := f()
instance.print() // 输出: main.S
}
方案4:接口类型断言(需要时使用)
package main
import "fmt"
type I interface {
print()
}
type S struct{}
func (s S) print() {
fmt.Printf("%T\n", s)
}
func factory() S {
return S{}
}
func Ifactory() func() I {
return func() I {
return factory()
}
}
// 当需要获取具体类型时使用类型断言
func getConcreteType(i I) (S, bool) {
s, ok := i.(S)
return s, ok
}
func main() {
f := Ifactory()
instance := f()
instance.print()
if s, ok := getConcreteType(instance); ok {
fmt.Printf("成功转换为具体类型: %T\n", s)
}
}
最推荐的是方案2,因为它最简洁且类型安全。在Go中,当具体类型实现了接口时,可以自动转换为接口类型,这使得代码更加灵活和易于维护。
通常建议在接收接口的地方定义接口,以避免此类问题。但有时这种方法并不奏效,在没有看到你的代码或真正理解为何要使用这个工厂模式的情况下,我的最佳猜测是你的代码类似这样:
package a
type Stringer interface {
String() string
}
type a struct{}
func (a) String() { return "a-impl" }
type b struct{}
func (b) String() { return "b-impl" }
func FactoryFunc(input string) Stringer {
if input == "" {
return a{}
}
return b{}
}
也就是说,你在这里必须定义接口,因为你的工厂函数也在这里定义,它不能返回具体类型,但稍后在HTTP处理程序中你需要类似这样的代码:
package http
type Stringer interface {
String() string
}
type Handler struct {
StringerFactory func(input string) Stringer
}
解决这个问题的一种方法是,就像我说的,让包a了解http包并这样做:
package a
import "http"
type a struct{}
func (a) String() { return "a-impl" }
type b struct{}
func (b) String() { return "b-impl" }
func FactoryFunc(input string) http.Stringer {
if input == "" {
return a{}
}
return b{}
}
另一个选择是在一个相对中立的包中定义接口。例如,我们可以将Stringer接口移动到第三个包中:
package b
type Stringer interface {
String() string
}
现在我们的代码变成:
package http
import "b"
type Handler struct {
StringerFactory func(input string) b.Stringer
}
package a
import "b"
type a struct{}
func (a) String() { return "a-impl" }
type b struct{}
func (b) String() { return "b-impl" }
func FactoryFunc(input string) b.Stringer {
if input == "" {
return a{}
}
return b{}
}
现在两个包都使用中立的b.Stringer并且完全解耦。它们都只依赖于那个b包。Ben Johnson在这篇文章中讨论了这个想法(某种程度上),而我在重构一个Web应用程序并应用Ben文章中的技术时,在这里进行了更详细的探讨。
还有其他选择。例如,你可以在两个包中都定义Stringer,然后在构造两者的第三方包(比如main包)中编写一个闭包:
import (
"a"
"http"
)
httpFactoryFn := func(input string) http.Stringer {
as := a.FactoryFunc(input)
return as.(http.Stringer) // 转换为所需的类型
}

