I'm trying to use builder patterns (borrowed from Java) to allow structs to implement interfaces. For example, I would ideally like this code pattern:
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
}
As you could see, the definition of the builder determines what interfaces s
and t
implement.
How would one write the function definition of the builder NewObject()
so the Build()
method returns a struct which can (possibly) implement a Oner
and Twoer
?
Here's some clarification on how it's going to be used. I'm constructing a library barring certain structs from being passed into functions if they violate the type safety. For example:
type Oner interface {
One() int
}
type OneAndTwoer interface {
Oner
Two() int
}
type Library interface {
DoSomethingWithOner(Oner)
DoSomethingWithOneAndTwoer(Twoer)
}
Though we can define a function which always constructs a OneAndTwoer
, my constraints are whenever we construct a OneAndTwoer
, this takes a lot longer time than just constructing a Oner
func NewOneAndTwoer() OneAndTwoer {
// Do some really really complicated logic which takes a lot of time
}
func NewOner() Oner {
// Do simple logic
}
You could imagine how if we have a Threer
, Fourer
, etc, this becomes extremely unwieldly, and we have to construct constructors for all possible permutations of attributes.
This is where builder patterns come in handy. Assuming the calculations for One
, Two
, etc are independent of each other, we can pick and choose which interface we want to create.
Here is a way to do it, though it feels very clunky.
package main
import (
"fmt"
)
type FieldOner interface {
FieldOne() int
}
type FieldTwoer interface {
FieldTwo() int
}
Set up structs One and Two implementing FieldOner and FieldTwoer respectively.
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
}
Create the FieldBuilder which can store both values and whether it has been given each value, plus WithFieldOne and 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
might return One, Two, or a combination of One and Two. Since it can return multiple things which have nothing in common between them (a red flag) it returns an 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")
}
Because Build
returns an interface{}
it's necessary to typecast the result in order to actually use it possibly defeating the whole point of the exercise.
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())
}
This does not scale particularly well. Two interfaces require three cases. Three will require six. Four will require ten. Five will need fifteen...