英文:
golang API interface, what am I missing?
问题
我想创建一个接口,以便轻松添加新的存储后端。
package main
// Storage是描述存储后端的接口
type Storage interface {
New() Storage
}
// File是满足Storage接口的存储类型
type File struct {
}
// New返回一个新的File
func (File) New() Storage {
newFile := File{}
return newFile
}
// S3是满足Storage接口的存储类型
type S3 struct {
}
// New返回一个新的S3
func (S3) New() Storage {
newS3 := S3{}
return newS3
}
func main() {
// 可供选择的后端列表
myStorage := make(map[string]Storage)
myStorage["file"] = File{}
myStorage["s3"] = S3{}
// 根据需要使用其中一个后端
myStorage["file"].New()
myStorage["s3"].New()
}
但是,似乎不可能定义和满足一个应该返回满足接口本身的对象的函数。
File.New()返回一个满足Storage接口的Storage类型的对象。
S3.New()返回一个S3类型的对象。
S3也应该满足Storage接口,但我得到了以下错误:
./main.go:32: cannot use S3 literal (type S3) as type Storage in assignment:
S3 does not implement Storage (wrong type for New method)
have New() S3
want New() Storage
我做错了什么?
希望我在这里漏掉了一些基础知识。
英文:
I want to create an interface to make it easy to add new storage backends.
package main
// Storage is an interface to describe storage backends
type Storage interface {
New() (newStorage Storage)
}
// File is a type of storage that satisfies the interface Storage
type File struct {
}
// New returns a new File
func (File) New() (newFile Storage) {
newFile = File{}
return newFile
}
// S3 is a type of storage that satisfies the interface Storage
type S3 struct {
}
// New returns a new S3
func (S3) New() (newS3 S3) {
newS3 = S3{}
return newS3
}
func main() {
// List of backends to choose from
var myStorage map[string]Storage
myStorage["file"] = File{}
myStorage["s3"] = S3{}
// Using one of the backends on demand
myStorage["file"].New()
myStorage["s3"].New()
}
But it seems not possible to define and satisfy a function that should return an object that satisfies the interface itself as well.
File.New() returns an object of type Storage which satisfies Storage.
S3.New() returns an object of type S3.
S3 should satisfies the interface Storage as well but I get this:
./main.go:32: cannot use S3 literal (type S3) as type Storage in assignment:
S3 does not implement Storage (wrong type for New method)
have New() S3
want New() Storage
What am I doing wrong?
I hope I am missing something basic here.
答案1
得分: 3
这段代码完全没有意义。你要么正在实现一个与要生成的结构体类型相关联的工厂模式,要么正在以错误的方式重新实现已经存在的 new
关键字,并将其与一个在使用时为 nil
的结构体相关联。
你可以摒弃辅助函数,直接使用以下方式:
s := new(S3)
f := new(File)
或者你可以使用一个静态的工厂函数,如下所示:
// 不要将工厂与类型绑定在一起
func New() S3 {
return S3{}
}
或者,更适合你的用例,创建一个工厂接口,实现它并使其 New()
函数返回一个 Storage
实例:
type StorageFactory interface {
New() Storage
}
type S3Factory struct {}
func (f *S3Factory) New() Storage {
return S3{}
}
有多种方法可以注册你的工厂。你可以使用全局变量和 init 函数:
import "example.com/foo/storage/s3"
type FactoryGetter func() StorageFactory
type FactoryRegistry map[string] FactoryGetter
// Registry 将在存储提供程序包的 init 函数中更新
var Registry FactoryRegistry
func init(){
Registry = make(map[string] FactoryGetter)
}
// 为了简洁起见,使用一个常量。例如,将其命名为 storageProvider
const storageProvider = "s3"
func main(){
f := Registry[storageProvider]()
s := f.New()
s.List()
}
然后在 S3 包的某个地方:
func init() {
Registry["s3"] = func(){ return S3Factory{}}
}
你甚至可以考虑让工厂函数接受参数。
英文:
This code does not make sense at all. You are either implementing a factory pattern which is tied to a struct which is of the type the factory is going to produce or you are reinventing the wheel in a wrong way by reimplementing the already existing new
keyword and tie it to a struct which is nil
the time you would use it.
You can either get rid of the helper function and simply use
s := new(S3)
f := new (File)
Or you could use a static Factory function like:
// Do NOT tie your Factory to your type
function New() S3 {
return S3{}
}
Or, which seems to better suit your use case, create a factory interface, implement it and have its New()
function return a Storage
instance:
type StorageFactory interface {
New() Storage
}
type S3Factory struct {}
function (f *S3Factory) New() Storage {
return S3{}
}
There are various ways of registering your factory. You could use a global var and init
import "example.com/foo/storage/s3"
type FactoryGetter func() StorageFactory
type FactoryRegistry map[string] FactoryGetter
// Registry will be updated by an init function in the storage provider packages
var Registry FactoryRegistry
func init(){
Registry = make(map[string] FactoryGetter)
}
// For the sake of shortness, a const. Make it abflag, for example
const storageProvider = "s3"
func main(){
f := Registry[storageProvider]()
s := f.New()
s.List()
}
And somewhere in the S3 package
func init() {
Registry["s3"] = function(){ return S3Factory{}}
}
You could even think of making the Factories taking params.
答案2
得分: 2
我喜欢你在这里所做的工作,实际上我也曾参与过涉及非常相似设计挑战的项目,所以我希望我的建议能对你有所帮助。
为了满足接口的要求,你需要更新你的代码,从...
// New returns a new S3
func (S3) New() (newS3 S3) {
newS3 = S3{}
return newS3
}
改为
// New returns a new S3
func (S3) New() (newS3 Storage) {
newS3 = S3{}
return newS3
}
这意味着你将会得到一个 Storage 的实例。如果你想要在不使用类型断言的情况下访问 S3 中的任何内容,最好是在接口中公开该 S3 函数/方法。
假设你想要一种方法来列出 S3 客户端中的对象。支持这一点的一个好方法是更新 Storage 接口以包括 List,并更新 S3 以拥有自己的 List 实现:
// Storage 是描述存储后端的接口
type Storage interface {
New() (newStorage Storage)
List() ([]entry, error) // 或者你更喜欢的触发 List 的方式
}
...
// New returns a new S3
func (S3) List() ([] entry, error) {
// 初始化 "entry" 切片
// 做一些工作,循环遍历页面或其他操作
// 返回 entry 切片和存在的错误(如果有)
}
当需要添加对 Google Cloud Storage、Rackspace Cloud Files、Backblaze B2 或任何其他对象存储提供商的支持时,它们也需要实现 List() ([] entry, error) - 这是很好的!一旦你在需要的方式上使用了这个 List 函数,添加更多的客户端/提供商将更像是开发插件,而不是实际编写/架构代码(因为你的设计在那时已经完成)。
满足接口的真正关键是确保签名完全匹配,并将接口视为一系列常见的函数/方法列表,你希望每个存储提供程序类型都能处理以实现你的目标。
如果你有任何问题,或者我写的任何内容不清楚,请留言,我将很乐意进行澄清或调整我的回答
英文:
I like what you're doing here and I've actually worked on projects that involved very similar design challenges, so I hope my suggestions can help you out some.
In order to satisfy the interface, you'd need to update your code from...
// New returns a new S3
func (S3) New() (newS3 S3) {
newS3 = S3{}
return newS3
}
to this
// New returns a new S3
func (S3) New() (newS3 Storage) {
newS3 = S3{}
return newS3
}
This means you will receive an instance of Storage back, so to speak. If you want to then access anything from S3 without having to use type assertion, it would be best to expose that S3 function/method in the interface.
So let's say you want a way to List your objects in your S3 client. A good approach to supporting this would be to update Storage interface to include List, and update S3 so it has its own implementation of List:
// Storage is an interface to describe storage backends
type Storage interface {
New() (newStorage Storage)
List() ([]entry, error) // or however you would prefer to trigger List
}
...
// New returns a new S3
func (S3) List() ([] entry, error) {
// initialize "entry" slice
// do work, looping through pages or something
// return entry slice and error if one exists
}
When it comes time to add support for Google Cloud Storage, Rackspace Cloud Files, Backblaze B2, or any other object storage provider, each of them will also need to implement List() ([] entry, error) as well - which is good! Once you've used this List function in the way you need, adding more clients/providers will be more like developing plugins than actually writing/architecting code (since your design is complete by that point).
The real key with satisfying interfaces is to have the signature match exactly and think of interfaces as a list of common functions/methods that you'd want every storage provider type to handle in order to meet your goals.
If you have any questions or if anything I've written is unclear, please comment and I'll be happy to clarify or adjust my post
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论