Возврат интерфейсов для вложенных структур - PullRequest
0 голосов
/ 24 марта 2019

Я пытаюсь сломать циклические зависимости в проекте golang разумного размера, используя интерфейсы. У меня есть несколько вложенных структур, таких как:

// these are in one package...
type config struct {
    region string
}

type system struct {
    name string
    config config
}

func (s system) getName() string {
    return s.name
}

func (s system) getConfig() config {
    return s.config
}

func (c config) getRegion() string {
    return c.region
}

И в другом пакете, который хочет использовать их, я объявляю некоторые соответствующие вложенные интерфейсы:

type iConfig interface {
    getRegion() string
}

type iSystem interface {
    getName() string
    getConfig() iConfig
}

// and has functions like
func New(system iSystem) {
    fmt.Printf("region=%s", system.getConfig().getRegion())
}

Но когда я пытаюсь использовать их так:

theSystem := system{
    name: "testName",
    config:config{
        region:"testRegion",
    },
}

New(theSystem)      // doesn't work

Я получаю ошибку:

cannot use theSystem (type system) as type iSystem in argument to New:
system does not implement iSystem (wrong type for getConfig method)
    have getConfig() config
    want getConfig() iConfig

Кажется, потому что моя конкретная system структура возвращает конкретный тип config Go не думает, что он удовлетворяет интерфейсу iConfig - хотя я могу использовать config через интерфейс iConfig напрямую. Я ожидал, что config неявно удовлетворит интерфейс iConfig, но этого не происходит. Как я могу это исправить?

Вот ссылка на Go Playground .

Ответы [ 2 ]

1 голос
/ 24 марта 2019

Для вашего случая вы можете реализовать три пакета для кругового импорта:

  1. Конфиг-пакет

Получает конфигурацию для приложения и предоставляет ее удобным способом для его компонентов.

package config

type IConfig interface {
    GetRegion() string
}

type config struct {
    region string
}

func New(region string) IConfig {
    return &config{
        region: region,
    }
}

func (c config) GetRegion() string {
    return c.region
}

  1. Системный пакет

Создает систему на основе конфигурации вашего приложения.

package system

import (
    // "config"
)

type ISystem interface {
    GetName() string
    GetConfig() config.IConfig
}

type system struct {
    name string
    config config.IConfig
}

func New(name string, cfg config.IConfig) ISystem {
    return &system{
        name: name,
        config: cfg,
    }
}

func (s system) GetName() string {
    return s.name
}

func (s system) GetConfig() config.IConfig {
    return s.config
}
  1. Пакет стороннего производителя

Пример использования:

package main

import (
    "fmt"
    // "config"
    // "system"
)

func UseConfig(cfg config.IConfig) {
    fmt.Printf("region=%s\n", cfg.GetRegion())
}

func UseSystem(s system.ISystem) {
    fmt.Printf("region=%s\n", s.GetConfig().GetRegion())
}

func main() {
    cfg := config.New("myregion")
    s := system.New("mysystem", cfg)

    UseConfig(cfg)
    UseSystem(s)
}
1 голос
/ 24 марта 2019

Интерфейс - это подпись / контракт, которому могут соответствовать другие типы.

Подпись может включать одну или несколько сигнатур методов, что означает имена методов, аргументы (включая типы) и возвращаемые аргументы.Если он не включает какой-либо метод, это на самом деле печально известный тип interface{}, которому соответствует каждый тип.

Чтобы соответствовать интерфейсу, тип должен строго реализовывать полную сигнатуру, включая все переданные и возвращенныеаргументы и их типы.

Интерфейсы и структуры имеют разные типы.

Поэтому в следующем примере Type2 не реализует Intf2:

type Intf1 interface{
    Bar()
}

type Type1 struct {}
func (s SomeType) Bar() {}

type Intf2 interface{
    Foo() Intf1
}

// Type2 does not implement Intf2
type Type2 struct {}
func (s Type2) Foo() Type1 {}

// Won't compile
var _ Intf2 = Type2{}

Поскольку компилятор go учитываетПодпись метода Foo на Intf2 будет другой, поскольку тип возвращаемого значения отличается.Компилятор не делает вывод, что возвращаемый тип также реализует интерфейс, потому что это принесет большую сложность для этого.

Если вы хотите, чтобы этот пример работал, вам нужно изменить Type2 на следующее:

// Type2 does not implement Intf2
type Type2 struct {}
func (s Type2) Foo() Intf1 {}

Это также относится к переданным аргументам, а не только к возвращаемым аргументам.

Теперь, что касается циклических зависимостей, я предлагаю вам представить свои интерфейсы в третьем пакете, который действует как клей и пакет верхнего уровня.Одна из распространенных вещей - это иметь основной пакет, который объединяется с интерфейсами для достижения своей основной цели.

Пример:

pkg config
    type Source interface{}
    type Parser interface{}

    pkg parsers
        pkg jsonparser
            implements config.Parser
        pkg yamlparser
            implements config.Parser

    pkg sources
        pkg filesource
            implements config.Source
```
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...