Обертывание нескольких реализаций в Go

У меня есть приложение с несколькими одновременными реализациями одного и того же 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
}

и аналогично для других реализаций. Есть ли альтернатива, которая не заставляет меня превращать переменные-члены в функции-члены? Какова наилучшая практика для такого варианта использования?


person Scott Deerwester    schedule 22.02.2018    source источник


Ответы (2)


Лучшей практикой было бы предоставить интерфейс:

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.

person icza    schedule 22.02.2018

Мы можем применить первый подход, так как структура может встраивать интерфейс вместе с полями. Если вы посмотрите на пакет 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)
}
person Himanshu    schedule 22.02.2018