I am learning Go by working through programming puzzles. There is shared boilerplate between puzzles that I want to factor out into into some kind of helper structure:
- Read the input (always the same, may change later, but in the same manner for all puzzles, eg. moving from reading files to HTTP)
- Prepare the input (may differ, but often identical)
- Process the input to arrive at a solution (always different)
I have come up with the solution shown below.
My specific questions are:
- Is there an idiomatic way to provide a "default method" for
PuzzleSolver.Prepare()
that can be overwritten, if required? I suspect the "correct" thing to do may be to write a function with the default implementation and wire each method to call that instead, but I'm not sure. - I am unsure about
Solve(Puzzle)
vsPuzzle.Solve()
. Are there common reasons to prefer one over the other?
package main
import "fmt"
type PuzzleSolver interface {
Prepare(*PuzzleInput)
Do() string
}
type Puzzle struct {
input *PuzzleInput
solver PuzzleSolver
}
func (p *Puzzle) Solve() string {
fmt.Println("==== Solving", p.input.ID, "====")
p.solver.Prepare(p.input)
ans := p.solver.Do()
return ans
}
type PuzzleInput struct {
ID int
input string
}
func (pi *PuzzleInput) Read() {
fmt.Println("Reading Puzzle with ID", pi.ID, "-- Reading is always the same.")
pi.input = fmt.Sprintf("Input for Puzle %d", pi.ID)
}
func NewPuzzle(ID int, solver PuzzleSolver) *Puzzle {
input := &PuzzleInput{ID: ID}
return &Puzzle{input: input, solver: solver}
}
type SolverOne struct{}
func (p *SolverOne) Prepare(pi *PuzzleInput) {
pi.Read()
fmt.Println("Preparing input for the first puzzle")
}
func (p *SolverOne) Do() string {
ans := fmt.Sprintf("The answer to the first puzzle!")
return ans
}
type SolverTwo struct{}
func (p *SolverTwo) Prepare(pi *PuzzleInput) {
pi.Read()
fmt.Println("Preparing input for the second puzzle. This behaviour is actually shared with SolverOne. Can I avoid implementing SolverTwo.Prepare() explicitly?")
}
func (p *SolverTwo) Do() string {
ans := fmt.Sprintf("The answer to the second puzzle!")
return ans
}
type SolverThree struct {
a, b int
}
func (p *SolverThree) Prepare(pi *PuzzleInput) {
pi.Read()
// process input ...
p.a = 10
p.b = 20
fmt.Println("Preparing input for the third puzzle. This performs an action distinct from SolverOne and SolverTwo.")
}
func (p *SolverThree) Do() string {
ans := fmt.Sprintf("The answer to the third puzzle is %d!", p.a+p.b)
return ans
}
func Solve(ps *Puzzle) string {
return ps.Solve()
}
func main() {
p1 := NewPuzzle(1, &SolverOne{})
fmt.Println(Solve(p1))
p2 := NewPuzzle(2, &SolverTwo{})
fmt.Println(Solve(p2))
p3 := NewPuzzle(3, &SolverThree{})
fmt.Println(Solve(p3))
}