在Go中包装多个实现

时间:2018-02-22 16:36:02

标签: go struct interface dry embedding

我有一个应用程序,它具有同一API的多个并发实现(例如,一个由SQL数据库支持,另一个由存储在XML文件中的数据集支持)。我真正想做的是为API中的每种类型的东西定义一个父类型

  1. 包含所有实现和

  2. 共有的成员变量
  3. 定义所有实现必须具有的方法。

  4. 所以,在(无效)Go中,我想做类似的事情:

    type Person interface {
        Name string
        Title string
        Position string
        Boss() *Person
    }
    
    type Person_XML struct {
        Person
    }
    
    func (p *Person_XML) Boss() (*Person, error) {
        // Poke around an XML document and answer the question
        return boss, nil
    }
    
    type Person_SQL {
        Person
    }
    
    func (p *Person_SQL) Boss() (*Person, error) {
        // Do a DB query and construct the record for the boss
        return boss, nil
    }
    

    但是,当然,这不合法,因为只有结构有成员变量,只有接口有成员函数。我可以用这样的接口来做到这一点:

    type Person interface {
        Name() string
        Title() string
        Position() string
        Boss() Person
    }
    
    type Person_XML struct {
        NameValue string
        TitleValue string
        PositionValue string
        Person
    }
    
    func (p *Person_XML) Name() string {
        return p.NameValue
    }
    
    func (p *Person_XML) Title() string {
        return p.TitleValue
    }
    
    func (p *Person_XML) Position() string {
        return p.PositionValue
    }
    
    func (p *Person_XML) Boss() (Person, error) {
        // Poke around an XML document and answer the question
        return boss, nil
    }
    

    和其他实现类似。有没有一种替代方法不会迫使我将成员变量转换为成员函数?这种用例的最佳实践是什么?

2 个答案:

答案 0 :(得分:4)

最佳做法是提供界面:

type Person interface {
    PersonName() string
    PersonTitle() string
    PersonPosition() string
    Boss() (Person, error)
}

并提供一个结构,其中包含公共字段获取它们的方法:

type BasePerson struct {
    Name     string
    Title    string
    Position string
}

func (p *BasePerson) PersonName() string     { return p.Name }
func (p *BasePerson) PersonTitle() string    { return p.Title }
func (p *BasePerson) PersonPosition() string { return p.Position }

(注意:*BasePerson本身并未实施Person,因为它没有Boss()方法。)

嵌入*BasePerson的任何类型都会自动将其方法提升,因此要实现Person,只需要添加Boss()方法。

例如:

type PersonXML struct {
    *BasePerson
}

func (p *PersonXML) Boss() (Person, error) {
    // Poke around an XML document and answer the question
    var boss *PersonXML
    return boss, nil
}

*PersonXML确实实施了Person

使用它的示例:

var p Person
p = &PersonXML{
    BasePerson: &BasePerson{
        Name:     "Bob",
        Title:    "sysadmin",
        Position: "leader",
    },
}
fmt.Println(p.PersonName())

输出(在Go Playground上尝试):

Bob

要创建PersonSQL类型,只需在嵌入Boss()时添加*BasePerson方法:

type PersonSQL struct {
    *BasePerson
}

func (p *PersonSQL) Boss() (Person, error) {
    // Do a DB query and construct the record for the boss
    var boss *PersonSQL
    return boss, nil
}

*PersonSQL再次实施Person

答案 1 :(得分:0)

我们可以应用第一种方法,因为struct可以将接口与字段一起嵌入。如果你看一下包Kill它使用相同的方法在界面中嵌入一个接口。

// Less returns the opposite of the embedded implementation's Less method.
func (r reverse) Less(i, j int) bool {
        return r.Interface.Less(j, i)
}

所以你的方法绝对有效。您可以使用struct receiver轻松实现接口。

{{1}}