How to implement an abstract class in Go?

2019-04-26 17:21发布

问题:

How to implement an abstract class in Go? As Go doesn't allow us to have fields in interfaces, that would be a stateless object. So, in other words, is it possible to have some kind of default implementation for a method in Go?

Consider an example:

type Daemon interface {
    start(time.Duration)
    doWork()
}

func (daemon *Daemon) start(duration time.Duration) {
    ticker := time.NewTicker(duration)

    // this will call daemon.doWork() periodically  
    go func() {
        for {
            <- ticker.C
            daemon.doWork()
        }
    }()
}

type ConcreteDaemonA struct { foo int }
type ConcreteDaemonB struct { bar int }

func (daemon *ConcreteDaemonA) doWork() {
    daemon.foo++
    fmt.Println("A: ", daemon.foo)
}

func (daemon *ConcreteDaemonB) doWork() {
    daemon.bar--
    fmt.Println("B: ", daemon.bar)
}

func main() {
    dA := new(ConcreteDaemonA)
    dB := new(ConcreteDaemonB)

    start(dA, 1 * time.Second)
    start(dB, 5 * time.Second)

    time.Sleep(100 * time.Second)
}

This won't compile as it's not possible to use interface as a receiver.

In fact, I have already answered my question (see the answer below). However, is it an idiomatic way to implement such logic? Are there any reasons not to have a default implementation besides language's simplicity?

回答1:

If you want to provide a "default" implementation (for Daemon.start()), that is not the characteristic of an interface (at least not in Go). That is a characteristic of a concrete (non-interface) type.

So Daemon in your case should be a concrete type, conveniently a struct since you want it to have fields. And the task to be done can be either a value of an interface type, or in a simple case just a function value (a simple case means it would only have one method).

With interface type

Try the complete app on the Go Playground.

type Task interface {
    doWork()
}

type Daemon struct {
    task Task
}

func (d *Daemon) start(t time.Duration) {
    ticker := time.NewTicker(t)
    // this will call task.doWork() periodically
    go func() {
        for {
            <-ticker.C
            d.task.doWork()
        }
    }()
}

type MyTask struct{}

func (m MyTask) doWork() {
    fmt.Println("Doing my work")
}

func main() {
    d := Daemon{task: MyTask{}}
    d.start(time.Millisecond*300)

    time.Sleep(time.Second * 2)
}

With a function value

In this simple case this one is shorter. Try it on the Go Playground.

type Daemon struct {
    task func()
}

func (d *Daemon) start(t time.Duration) {
    ticker := time.NewTicker(t)
    // this will call task() periodically
    go func() {
        for {
            <-ticker.C
            d.task()
        }
    }()
}

func main() {
    d := Daemon{task: func() {
        fmt.Println("Doing my work")
    }}
    d.start(time.Millisecond * 300)

    time.Sleep(time.Second * 2)
}


回答2:

An easy solution is to move daemon *Daemon to the argument list (thus removing start(...) from the interface):

type Daemon interface {
    // start(time.Duration)
    doWork()
}

func start(daemon Daemon, duration time.Duration) { ... }

func main() {
    ...
    start(dA, 1 * time.Second)
    start(dB, 5 * time.Second)
    ...
}


回答3:

The other answers provide an alternative to your problem, however they proposed solution without using abstract classes/struct, and I guess if you were interested in using abstract class like solution, here is very precise solution to your problem:

Go plaground

package main

import (
    "fmt"
    "time"
)

type Daemon interface {
    start(time.Duration)
    doWork()
}

type AbstractDaemon struct {
    Daemon
}

func (a *AbstractDaemon) start(duration time.Duration) {
    ticker := time.NewTicker(duration)

    // this will call daemon.doWork() periodically  
    go func() {
        for {
            <- ticker.C
            a.doWork()
        }
    }()
}



type ConcreteDaemonA struct { 
*AbstractDaemon
foo int
}

func newConcreteDaemonA() *ConcreteDaemonA {
  a:=&AbstractDaemon{}
  r:=&ConcreteDaemonA{a, 0}
  a.Daemon = r
  return r
}


type ConcreteDaemonB struct { 
*AbstractDaemon
bar int
}

func newConcreteDaemonB() *ConcreteDaemonB {
  a:=&AbstractDaemon{}
  r:=&ConcreteDaemonB{a, 0}
  a.Daemon = r
  return r
}



func (a *ConcreteDaemonA) doWork() {
    a.foo++
    fmt.Println("A: ", a.foo)
}

func (b *ConcreteDaemonB) doWork() {
    b.bar--
    fmt.Println("B: ", b.bar)
}


func main() {
    var dA  Daemon = newConcreteDaemonA()
    var dB  Daemon = newConcreteDaemonB()

    dA.start(1 * time.Second)
    dB.start(5 * time.Second)

    time.Sleep(100 * time.Second)
}

If this is still not obvious how to use abstract classes/multi-inheritance in go-lang here is the post with comprehensive details. Abstract Classes In Go



回答4:

The solution by Max Malysh would work in some cases if you don't need a factory. However the solution given by Adrian Witas could cause cyclic dependencies issues.

This is the way I achieved implementing an abstract class the easy way respecting cyclic dependencies and good factory patterns.

Let us assume we have the following package structure for our component

component
  base
    types.go
    abstract.go
  impl1
    impl.go
  impl2
    impl.go
  types.go
  factory.go

Define the definition of the component, in this example it will be defined here:

component/types.go

package component

type IComponent interface{
    B() int
    A() int
    Sum() int
    Average() int
}

Now let's assume we want to create an abstract class that implements Sum and Average only, but in this abstract implementation we would like to have access to use the values returned by the implemented A and B

To achieve this, we should define another interface for the abstract members of the abstract implementation

component/base/types.go

package base

type IAbstractComponentMembers {
    A() int
    B() int
}

And then we can proceed to implement the abstract "class"

component/base/abstract.go

package base

type AbstractComponent struct {
    IAbstractComponentsMember
}

func (a *AbstractComponent) Sum() int {
    return a.A() + a.B()
}

func (a *AbstractComponent) Average() int {
    return a.Sum() / 2
}

And now we proceed to the implementations

component/impl1/impl.go // Asume something similar for impl2

package impl1

type ComponentImpl1 struct {
    base.AbstractComponent
}

func (c *ComponentImpl1) A() int {
    return 2
}

func (c *ComponentImpl1) A() int {
    return 4
}

// Here is how we would build this component
func New() *ComponentImpl1 {
    impl1 := &ComponentImpl1{}
    abs:=&base.AbstractComponent{
        IAbstractComponentsMember: impl1,
    }
    impl1.AbstractComponent = abs
    return impl1
}

The reason we use a separate interface for this instead of using Adrian Witas example, is because if we use the same interface in this case, if we import the base package in impl* to use the abstract "class" and also we import the impl* packages in the components package, so the factory can register them, we'll find a circular reference.

So we could have a factory implementation like this

component/factory.go

package component

// Default component implementation to use
const defaultName = "impl1"
var instance *Factory

type Factory struct {
    // Map of constructors for the components
    ctors map[string]func() IComponent
}

func (f *factory) New() IComponent {
    ret, _ := f.Create(defaultName)
    return ret
}

func (f *factory) Create(name string) (IComponent, error) {
    ctor, ok := f.ctors[name]
    if !ok {
        return nil, errors.New("component not found")
    }
    return ctor(), nil
}

func (f *factory) Register(name string, constructor func() IComponent) {
    f.ctors[name] = constructor
}

func Factory() *Factory {
    if instance == nil {
        instance = &factory{ctors: map[string]func() IComponent{}}
    }
    return instance
}

// Here we register the implementations in the factory
func init() {
    Factory().Register("impl1", func() IComponent { return impl1.New() })
    Factory().Register("impl2", func() IComponent { return impl2.New() })
}


标签: oop interface go