Создать эквивалентный интерфейс другого пакета с интерфейсом в качестве аргумента - PullRequest
0 голосов
/ 16 сентября 2018

Я занимаюсь написанием идиоматического кода Go и обнаружил, что интерфейсы должны быть объявлены в пакетах, которые их потребляют, поскольку они неявные. Однако я пришел к такой ситуации, когда во втором пакете (пакете b) я хочу, чтобы функция вызывала функцию-получатель структуры в пакете a, не связывая ее тесно.

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

Детская площадка

package main

import (
    "fmt"
    "log"
)

func main() {
    manager := &Manager{}
    coach := NewRunnerCoach(manager)
    fmt.Println("Done")
}

// package a

type Runner interface {
    Run()
}

type Manager struct {
}

func (o *Manager) RegisterRunner(runner Runner) {
    log.Print("RegisterRunner")
}

func (o *Manager) Start() {
    log.Print("Start")
}

// package b

type RunnerCoach struct {
    runner *FastRunner
}

func NewRunnerCoach(registerer runnerRegisterer) *RunnerCoach {
    runnerCoach := &RunnerCoach{&FastRunner{}}
    registerer.RegisterRunner(runnerCoach.runner)
    return runnerCoach
}

type FastRunner struct {
}

func (r *FastRunner) Run() {
    log.Print("FastRunner Run")
}

// define ther registerer interface coach is accepting
type runnerRegisterer interface {
    RegisterRunner(runner RunnerB)
}

// declaring a new interface with the same signature because we dont want to import package a
// and import Runner interface
type RunnerB interface {
    Run()
}

Этот код не компилируется. Таким образом, вопрос здесь заключается в том, что я неправильно использую интерфейс или конкретные типы должны быть определены в отдельном пакете или, наконец, существует ли лучший шаблон кода для проблемы, которую я пытаюсь решить?

РЕДАКТИРОВАТЬ: Чтобы уточнить, пакеты a и b не импортируют друг друга. Код main () существует в отдельном пакете, который соединяет эти два.

Ответы [ 2 ]

0 голосов
/ 16 сентября 2018

IIUC, ваш вопрос не о пакетах, а сводится к тому, является ли функция (или метод) может быть приведен к другой функции, которая принимает аргументы с эквивалентными, но не тот же тип интерфейса.

Примерно так: ( Go Playground )

package main

type I1 interface{}

func f1(x I1) {}

func main() {
    f := (func(interface{}))(f1)
    f(nil)
}

Ошибка компиляции: ./g.go:8:26: cannot convert f1 (type func(I1)) to type func(interface {})

Ответ, кажется, нет, потому что Го не считает func (I1) эквивалентно func (interface{}). Go spec говорит это

Тип функции обозначает набор всех функций с одинаковыми параметрами и типами результатов.

Типы func (I1) и func (interface{}) не принимают одинаковые параметры, даже если I1 определяется как interface{}. Ваш код не компилируется для аналогичного причина, потому что func (runner RunnerB) не совпадает с func (runner Runner), и, следовательно, набор методов *Manager не является надмножеством интерфейс runnerRegisterer.

Переходя к исходному вопросу:

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

Да, идея хорошая, но она не относится к вашей реализации так, как вы думаю, что это так. Так как вы ожидаете иметь разные реализации runnerRegisterer и все они должны иметь метод с одинаковой подписью используя интерфейс Runner, имеет смысл определить Runner в общем место. Кроме того, как показано выше, Go не позволит вам использовать другой интерфейс в подписи метода все равно.

Основываясь на моем понимании того, чего вы пытаетесь достичь, вот как я думаю Вы должны изменить свой код:

  1. Определите RunnerRegisterer (примечание: это общедоступно) и Runner в одном пакет.
  2. Реализуйте ваш RunnerCoach в том же пакете и используйте выше интерфейсы. Ваш RunnerCoach использует типы, которые реализуют интерфейсы, поэтому он определяет их.
  3. Реализуйте своих бегунов в другой пакет. Вы не определяете Runner интерфейс здесь.
  4. Реализуйте ваш Manager в другой пакет, который использует интерфейс Runner определен в пакете RunnerCoach, потому что он должен принимать этот тип в качестве аргумента, если он хочет быть использован как RunnerRegisterer.
0 голосов
/ 16 сентября 2018

Решение

У вас есть тип, который используется в двух пакетах, A и B. Пакет A импортирует пакет B.

Вы должны избегать циклических зависимостей, поэтому у вас естьтри варианта:

  1. Определить тип в пакете B, чтобы он был доступен в обоих пакетах.
  2. Определить тип в пакете C и иметь как A, так и B импортировать пакет C.
  3. Измените свой дизайн так, чтобы тип не использовался как в A, так и в B.

Это ваш выбор, является ли этот тип интерфейсом или любым другим типом.

Выберите вариант, который наилучшим образом соответствует вашим целям проектирования.

Правило / Идиома

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

Я получаю импульс / идею - проблема в том, что это не очень практично.Интерфейсы были бы менее полезны, если бы они могли использоваться только в пакете, в котором они определены.Стандартная библиотека полна кода, который нарушает эту аксиому.Поэтому я не думаю, что представленное правило - применение ко всем интерфейсам во всех ситуациях, конструкциях и контекстах - является идиоматическим.

Например, посмотрите на пакет bytes .func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error) потребляет io.Reader, интерфейс, определенный в другом пакете.func (r *Reader) WriteTo(w io.Writer) (n int64, err error) потребляет io.Writer, интерфейс, определенный в другом пакете.

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