2017-08-06 63 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的。