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))
}