Где должны быть определены интерфейсы при использовании фабричного шаблона? - PullRequest
1 голос
/ 24 октября 2019

Я использую фабричный объект FooFactory для создания экземпляров типа Foo, который имеет некоторые закрытые члены-данные. Я использую фабрику, так что объект Bar, который создает экземпляры Foo, не должен предоставлять (или даже знать о) эти частные элементы данных: я сначала настраиваю фабрику с необходимыми личными данными, а затем даю Bar эта сконфигурированная фабрика.

Я хочу, чтобы Bar использовал эти Foo объекты через интерфейс, чтобы я мог высмеивать их с помощью gomock и проверить, что Bar использует их правильно. Из того, что я читал об интерфейсах в go, лучше всего определить интерфейсы, где они используются, а не где определены базовые типы, поэтому у меня интерфейс Fooer в том же пакете, что и мой Barобъект, и Bar использует этот Fooer интерфейс везде, где он ожидает Foo (или позже MockFoo).

Я также хочу, чтобы Bar использовал FooFactory через интерфейс дляпо той же причине, поэтому я могу посмеяться над этим и утверждать, что он создает Foo объектов, когда я этого ожидаю. И снова, я определяю интерфейс в Bar s пакете FooBuilder, который неявно реализует базовый FooFactory.

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

Здесь минимальное воспроизведение без описанной выше структуры пакета:

type Foo struct{}

func (f *Foo) FooMethod() {}

type FooFactory struct{}

func (ff *FooFactory) Build() *Foo {
    return &Foo{}
}

type Fooer interface {
    FooMethod()
}

type FooBuilder interface {
    Build() Fooer
}

func main() {
    f := &Foo{}
    ff := &FooFactory{}

    var fooer Fooer
    var fooBuilder FooBuilder

    fooer = f
    fooBuilder = ff     // << ERROR
}

Иди жалуется:

cannot use ff (type *FooFactory) as type FooBuilder in assignment:
    *FooFactory does not implement FooBuilder (wrong type for Build method)
        have Build() *Foo
        want Build() Fooer

Мой вопрос по сути, я делаю это правильно ? После опыта работы с профессиональными языками C ++ и Java, я пытаюсь принять концепцию go о неявном удовлетворении интерфейса, но это кажется странным. Тот факт, что это нелегко сделать, заставляет меня думать, что я делаю что-то не так, но все, что я читаю в Интернете об интерфейсах go, говорит об определении интерфейсов, близких к тому, где они используются, и именно это я действительно пытаюсь сделать.

1 Ответ

1 голос
/ 24 октября 2019

Концептуально

Вы правы в своем методе. Хотя, если настройка Foo не сложна, я бы сбросил настройки.

Foo и Fooer могут быть или не быть в разных пакетах. Я бы начал с того, что положил их в одну и ту же упаковку, пока в ней больше нет смысла.

Я обычно настраиваю эти ситуации следующим образом:

package foo

func NewFoo() *Foo {
    return &Foo{}
}

type Foo struct{}

func (f *Foo) FooMethod() {}

type Fooer interface {
    FooMethod()
}

И потребитель с:

package bar

func FooUser(f Fooer) {
    //Do something
}

И объединяя это с:

package main

import (
    "foo"
    "bar"
)

func main() {
    f := foo.NewFoo()
    bar.FooUser(f)
}

Таким образом, вы можете протестировать пакет foo как единое целое и протестировать пакет bar с любой структурой, которая реализует Fooer.

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

Ошибка компилятора

Бит, который вам не хватает, - это то, что FooFactory на самом деле не реализует интерфейс FooBuilder.

*Интерфейс 1032 * объявляет метод, который возвращает Fooer, но сигнатура метода для FooFactory возвращает указатель на Foo.

func (ff *FooFactory) Build() *Foo

Итак, вы объявляете метод Build, но возвращаете неверный тип, поэтому он не распознается как реализация.

Чтобы исправить ошибку, изменитеподпись реализации Build:

func (ff *FooFactory) Build() Fooer
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...