用Go语言包装多个实现

4
我有一个应用程序,它有多个同时运行的相同API实现(例如,一个由SQL数据库支持,另一个由存储在XML文件中的数据集支持)。我真正想做的是为API中每种类型的事物定义一个父类型,该类型:

  1. 包含所有实现共有的成员变量

  2. 定义所有实现都必须具备的方法。

因此,在(无效的)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个回答

8

最佳实践是提供一个接口:

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类型,如果嵌入了*BasePerson,则只需添加Boss()方法即可。
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接口。

Aha:嵌入。这是在PersonSQLPersonXML中使用*BasePerson的术语,使得这个例子如此简洁。作为新的Go Golang用户,我不知道这个特性。非常好的帖子,正好回答了我的问题。 - Jeff Learman

0

我们可以采用第一种方法,因为结构体可以嵌入接口和字段。如果您查看sort包,它使用相同的方法,在结构体内嵌入了一个接口。

type Interface interface {
    // Len is the number of elements in the collection.
    Len() int
    // Less reports whether the element with
    // index i should sort before the element with index j.
    Less(i, j int) bool
    // Swap swaps the elements with indexes i and j.
    Swap(i, j int)
}

type reverse struct {
    // This embedded Interface permits Reverse to use the methods of
    // another Interface implementation.
    Interface
}

所以你的方法是完全有效的。而且你可以使用结构体接收器轻松实现接口。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接