DEV Community

Ahmed Nawaz Khan
Ahmed Nawaz Khan

Posted on

Idiomatic way to implement this in golang

I am experimenting to migrate a nodejs system (typescript) to golang. Its going well except i am having trouble in reproducing the following in idiomatic go

I have the following abstract class:

abstract class BaseSensor {
  beforeStart() {/*default implementation*/ }
  afterStart() {/*default implementation*/ }
  shutdown() {/*default implementation*/ }
  abstract process();

  work() {
    this.beforeStart()
    //...
    this.afterStart()
    //...
    this.process()
    //...
    this.shutdown()
  }
}
Enter fullscreen mode Exit fullscreen mode

then i have some concrete classes/sensors which implement atleast the process() method and optionally any of the other lifecycle methods.

class SensorA extends BaseSensor {
  process() {
    //SensorA specific processing
  }
}

class SensorB extends BaseSensor {
  beforeStart() {
    //SensorB overriding beforeStart()
  }
  process() {
    //SensorB specific processing
  }
}
Enter fullscreen mode Exit fullscreen mode

SensorA only implements the process() method and doesn't override anything else. SensorB implements process() and also overrides beforeStart().

What is the correct way of doing something like this in golang

Top comments (4)

Collapse
 
kashyaprahul94 profile image
Rahul Kashyap • Edited

A quick thought says Inheritance is not really a game in Go, however it embraces EmbeddedTypes for something similar.

Roughly -

type BaseSensor struct {
}

func (bs *BaseSensor) BeforeStart() {
    fmt.Println("Base Before Start")
}

func (bs *BaseSensor) AfterStart() {
    fmt.Println("Base After Start")
}

func (bs *BaseSensor) ShutdownStart() {
    fmt.Println("Base Shutdown")
}

func (bs *BaseSensor) Process() {
    fmt.Println("Base Process. You can panic here if you want it to be really abstract")
}

type SensorA struct {
    *BaseSensor
}

func (sa *SensorA) Process() {
    fmt.Println("Sensor A Process")
}

type SensorB struct {
    *BaseSensor
}

func (sb *SensorB) BeforeStart() {
    // If you want to call super like method
    sb.BaseSensor.BeforeStart()

    fmt.Println("Sensor B BeforeStart")
}

func (sb *SensorB) Process() {

    fmt.Println("Sensor B Process")
}

And then you can invoke them as follows -

    sensorA := &SensorA{
        BaseSensor: &BaseSensor{},
    }

    sensorA.Process()

    sensorB := &SensorB{
        BaseSensor: &BaseSensor{},
    }

    sensorB.BeforeStart()
    sensorB.Process()

Collapse
 
julianchu profile image
Julian-Chu

Rahul already made the important part, I'd like to add code for Work()


type ISensor interface {
    BeforeStart()
    AfterStart()
    ShutdownStart()
    Process()
}

type SensorWorker struct {
    sensor ISensor
}


func (w SensorWorker) Work() {
    w.sensor.BeforeStart()
    w.sensor.AfterStart()
    w.sensor.Process()
    w.sensor.ShutdownStart()
}

then you can do something like following:


func main() {
    sensorWorker:= &SensorWorker{sensor:sensorA}
    sensorWorker.Work()
    sensorWorker.sensor = sensorB
    sensorWorker.Work()
}

Collapse
 
ahmednawazkhan profile image
Ahmed Nawaz Khan

thanks

Collapse
 
mariocarrion profile image
Mario Carrion

Hello!

Another way to do this is: play.golang.org/p/tmzumHEIo2H

Which does not use embedding or types implementing all the methods but rather functionals options and a default implementation; with the big caveat the concrete implementations are not sharing state in their methods:

package main

import "fmt"

func main() {
    bef := BeforeSensor{}
    afs := AfterShutdownSensor{}

    s := NewSensor(WithBeforeSensor(&bef), WithAfterSensor(&afs), WithShutdownSensor(&afs))

    s.Process()
}

type (
    BeforeStarter interface {
        BeforeStart()
    }

    AfterStarter interface {
        AfterStart()
    }

    Shutdowner interface {
        Shutdown()
    }

    Processer interface {
        Process()
    }
)

type Sensor struct {
    before   BeforeStarter
    after    AfterStarter
    shutdown Shutdowner
    process  Processer
}

type SensorOption func(s *Sensor)

func WithBeforeSensor(b BeforeStarter) SensorOption {
    return func(se *Sensor) {
        se.before = b
    }
}

func WithAfterSensor(a AfterStarter) SensorOption {
    return func(se *Sensor) {
        se.after = a
    }
}

func WithShutdownSensor(s Shutdowner) SensorOption {
    return func(se *Sensor) {
        se.shutdown = s
    }
}

func WithProcessSensor(p Processer) SensorOption {
    return func(se *Sensor) {
        se.process = p
    }
}

func NewSensor(opts ...SensorOption) *Sensor {
    s := Sensor{
        before:   &DefaultSensor{},
        after:    &DefaultSensor{},
        process:  &DefaultSensor{},
        shutdown: &DefaultSensor{},
    }

    for _, opt := range opts {
        opt(&s)
    }

    return &s
}

func (s *Sensor) Process() {
    s.before.BeforeStart()
    s.after.AfterStart()
    s.process.Process()
    s.shutdown.Shutdown()
}

//-

type DefaultSensor struct{}

func (d DefaultSensor) BeforeStart() {
    fmt.Println("DefaultSensor BeforeStart")
}

func (d DefaultSensor) AfterStart() {
    fmt.Println("DefaultSensor AfterStart")
}

func (d DefaultSensor) Shutdown() {
    fmt.Println("DefaultSensor Shutdown")
}

func (d DefaultSensor) Process() {
    fmt.Println("DefaultSensor Process")
}

//- BeforeSensor

type BeforeSensor struct{}

func (s BeforeSensor) BeforeStart() {
    fmt.Println("OnlyBeforeSensor BeforeStart")
}

//- AfterShutdownSensor

type AfterShutdownSensor struct{}

func (s AfterShutdownSensor) AfterStart() {
    fmt.Println("AfterShutdownSensor AfterStart")
}

func (s AfterShutdownSensor) Shutdown() {
    fmt.Println("AfterShutdownSensor Shutdown")
}
Enter fullscreen mode Exit fullscreen mode