Как использовать шаблон для построения структуры, которая динамически реализует интерфейсы - PullRequest
0 голосов
/ 07 мая 2019

Я пытаюсь использовать шаблоны компоновщика (заимствовано из Java), чтобы структуры могли реализовывать интерфейсы. Например, в идеале мне хотелось бы такой кодовый шаблон:

package main

import "fmt"

type Oner interface {
    One() int
}

type Twoer interface {
    Two() int
}

func main() {
    s := NewObject().
        WithOne(1).
        Build()

    _, ok := s.(Oner)
    fmt.Println(ok) // Prints true

    _, ok = s.(Twoer)
    fmt.Println(ok) // Prints false

    t := NewObject().
        WithOne(1).
        WithTwo(2).
        Build()

    _, ok = t.(Oner)
    fmt.Println(ok) // Prints true

    _, ok = t.(Twoer)
    fmt.Println(ok) // Prints true
}

Как вы могли видеть, определение компоновщика определяет, какие интерфейсы s и t реализуют.

Как можно написать определение функции построителя NewObject(), чтобы метод Build() возвращал структуру, которая может (возможно) реализовать Oner и Twoer?


Edit:

Вот некоторые пояснения о том, как это будет использоваться. Я создаю библиотеку, которая запрещает передачу определенных структур в функции, если они нарушают безопасность типов. Например:

type Oner interface {
    One() int
}

type OneAndTwoer interface {
    Oner

    Two() int
}

type Library interface {
    DoSomethingWithOner(Oner)
    DoSomethingWithOneAndTwoer(Twoer)
}

Хотя мы можем определить функцию, которая всегда создает OneAndTwoer, мои ограничения заключаются в том, что всякий раз, когда мы создаем OneAndTwoer, это занимает намного больше времени, чем простое построение Oner

func NewOneAndTwoer() OneAndTwoer {
    // Do some really really complicated logic which takes a lot of time
}

func NewOner() Oner {
    // Do simple logic
}

Вы могли бы представить, как, если у нас есть Threer, Fourer и т. Д., Это становится чрезвычайно громоздким, и мы должны конструировать конструкторы для всех возможных перестановок атрибутов.

Здесь пригодятся шаблоны для строителей. Предполагая, что вычисления для One, Two и т. Д. Не зависят друг от друга, мы можем выбрать, какой интерфейс мы хотим создать.

1 Ответ

2 голосов
/ 07 мая 2019

Вот способ сделать это, хотя это выглядит очень неуклюже.

package main

import (
  "fmt"
)

type FieldOner interface {
    FieldOne() int
}

type FieldTwoer interface {
    FieldTwo() int
}

Настройка структур One и Two, реализующих FieldOner и FieldTwoer соответственно.

type One struct {
    one int
}

func (f One) FieldOne() int {
    return f.one
}

type Two struct {
    two int
}

func (f Two) FieldTwo() int {
    return f.two
}

Создание FieldBuilderкоторый может хранить оба значения и то, было ли ему задано каждое значение, плюс WithFieldOne и WithFieldTwo.

type FieldBuilder struct {
    one int
    has_one bool
    two int
    has_two bool
}

func NewObject() FieldBuilder {
    return FieldBuilder{ has_one: false, has_two: false }
}

func (f FieldBuilder) WithFieldOne(one int) FieldBuilder {
    f.one = one
    f.has_one = true
    return f
}

func (f FieldBuilder) WithFieldTwo(two int) FieldBuilder {
    f.two = two
    f.has_two = true
    return f
}

Build может возвращать один, два или комбинацию одного и двух.Поскольку он может возвращать несколько вещей, которые не имеют между ними ничего общего ( красный флаг ), он возвращает interface{}.

func (f FieldBuilder) Build() interface{} {
    switch {
    case f.has_one && f.has_two:
        return struct {
            One
            Two
        }{
            One{one: f.one}, Two{two: f.two},
        }
    case f.has_one:
        return One{ one: f.one }
    case f.has_two:
        return Two{ two: f.two }
    }
    panic("Should never be here")
}

, поскольку Build возвращает interface{}, этонеобходимо типизировать результат, чтобы фактически использовать его, возможно, нанося поражение всему смыслу упражнения.

func main() {
    s := NewObject().
        WithFieldOne(1).
        Build()

    s1, ok := s.(FieldOner)
    fmt.Println(s1.FieldOne())

    _, ok = s.(FieldTwoer)
    fmt.Println(ok) // Prints false

    t := NewObject().
        WithFieldOne(1).
        WithFieldTwo(2).
        Build()

    t1, ok := t.(FieldOner)
    fmt.Println(t1.FieldOne())

    t2, ok := t.(FieldTwoer)
    fmt.Println(t2.FieldTwo())
}

Это не особенно хорошо масштабируется.Два интерфейса требуют три случая.Три потребуют шесть.Четыре потребуют десять.Пять понадобится пятнадцать ...

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