2017-08-06 97 views
0

我想创建一个界面,以便轻松添加新的存储后端。golang API接口,我错过了什么?

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() 
} 

但似乎无法定义,满足应该返回满足接口本身以及对象的功能。

File.New()返回满足存储的Storage类型的对象。

S3.New()返回S3类型的对象。 S3应该满足接口存储很好,但我得到这个:

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

我在做什么错? 我希望我缺少一些基本的东西。

+2

S3的结构和文件结构有diferent返回obj类型,尽量让他们都通过使用接口作为回报返还相同种类的obj。并且在将struct作为接口返回时不要忘记使用指针。 – Kebeng

+1

Go没有协变性。 –

+0

@Kebeng我不介意在这里得到一个对象的副本,所以指针是没有必要的。并且让它们成为“同一类型”就是关键。 – xxorde

回答

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 
} 

这意味着您将收到存储的实例回来,可以这么说。如果你想从S3访问任何东西而不必使用type assertion,最好在接口中公开该S3函数/方法。

因此,假设您想要一种方法在S3客户端中列出您的对象。一个好的方法来支持,这将是更新存储接口,包括列表,更新S3所以它有自己的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 
} 

当谈到时间,以增加对谷歌云存储,Rackspace的云文件支持,Backblaze B2或任何其他对象存储提供者,他们每个人也需要实现List()([] entry,error) - 这很好!一旦你以你需要的方式使用这个List函数,添加更多的客户端/提供者将更像是开发插件,而不是实际编写/构建代码(因为你的设计在那点完成了)。

具有令人满意的接口的真正关键是准确地签名匹配,并将接口想象为您希望每个存储提供程序类型为了实现您的目标而处理的常见函数/方法的列表。

如果您有任何疑问或如果有的话,我写不清楚,请评论,我会很乐意澄清或调整我的文章:)

3

这个代码是没有意义的。您要么实现一个工厂模式,该工厂模式绑定到工厂要生成的类型的结构,要么通过重新实现已有的new关键字并将其绑定到一个结构为nil的结构,从而以错误的方式重新发明轮子你会使用它的时间。

您可以摆脱辅助函数的和简单的使用

s := new(S3) 
f := new (File) 

或者你可以使用一个静态工厂功能,如:

// Do NOT tie your Factory to your type 
function New() S3 { 
    return S3{} 
} 

或者,这似乎更符合您的使用案例,创建一个工厂接口,实现它并让其New()函数返回一个Storage实例:

type StorageFactory interface { 
    New() Storage 
} 

type S3Factory struct {} 

function (f *S3Factory) New() Storage { 
    return S3{} 
} 

有注册你的工厂的各种方法。你可以使用一个全局变量和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() 
} 

而且某处S3包

func init() { 
    Registry["s3"] = function(){ return S3Factory{}} 
} 

你甚至可以认为使工厂服用则params的。