如何在Go中实现抽象类?

huangapple go评论72阅读模式
英文:

How to implement an abstract class in Go?

问题

如何在Go中实现抽象类?由于Go不允许在接口中拥有字段,因此接口是无状态的对象。换句话说,在Go中是否有可能为方法提供某种默认实现?

考虑以下示例:

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

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

    // 这将定期调用 daemon.doWork()
    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)
}

这段代码无法编译,因为接口不能作为接收器使用。

实际上,我已经回答了自己的问题(请参见下面的答案)。然而,这种实现逻辑是否符合惯用方式?除了语言的简洁性之外,是否有任何不使用默认实现的原因?

英文:

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 {
            &lt;- ticker.C
            daemon.doWork()
        }
    }()
}

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

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

func (daemon *ConcreteDaemonB) doWork() {
    daemon.bar--
    fmt.Println(&quot;B: &quot;, 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

得分: 22

其他答案提供了解决您问题的另一种方法,但是它们提出的解决方案没有使用抽象类/结构体。如果您对使用类似抽象类的解决方案感兴趣,这里有一个非常精确的解决方案:

Go playground

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

如果您仍然不清楚如何在Go语言中使用抽象类/多继承,这里有一篇详细介绍的帖子:Abstract Classes In Go

英文:

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 (
&quot;fmt&quot;
&quot;time&quot;
)
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 {
&lt;- ticker.C
a.doWork()
}
}()
}
type ConcreteDaemonA struct { 
*AbstractDaemon
foo int
}
func newConcreteDaemonA() *ConcreteDaemonA {
a:=&amp;AbstractDaemon{}
r:=&amp;ConcreteDaemonA{a, 0}
a.Daemon = r
return r
}
type ConcreteDaemonB struct { 
*AbstractDaemon
bar int
}
func newConcreteDaemonB() *ConcreteDaemonB {
a:=&amp;AbstractDaemon{}
r:=&amp;ConcreteDaemonB{a, 0}
a.Daemon = r
return r
}
func (a *ConcreteDaemonA) doWork() {
a.foo++
fmt.Println(&quot;A: &quot;, a.foo)
}
func (b *ConcreteDaemonB) doWork() {
b.bar--
fmt.Println(&quot;B: &quot;, 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

答案2

得分: 14

如果你想提供一个“默认”实现(用于Daemon.start()),那不是接口的特性(至少在Go语言中不是)。这是一个具体(非接口)类型的特性。

所以在你的情况下,Daemon应该是一个具体类型,方便起见可以是一个struct,因为你希望它有字段。要执行的任务可以是接口类型的值,或者在简单情况下只是一个函数值(简单情况意味着它只有一个方法)。

使用接口类型

Go Playground上尝试完整的应用程序。

type Task interface {
    doWork()
}

type Daemon struct {
    task Task
}

func (d *Daemon) start(t time.Duration) {
    ticker := time.NewTicker(t)
    // 这将定期调用task.doWork()
    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)
}

使用函数值

在这个简单的情况下,这个版本更短。在Go Playground上尝试它。

type Daemon struct {
    task func()
}

func (d *Daemon) start(t time.Duration) {
    ticker := time.NewTicker(t)
    // 这将定期调用task()
    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)
}
英文:

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 {
&lt;-ticker.C
d.task.doWork()
}
}()
}
type MyTask struct{}
func (m MyTask) doWork() {
fmt.Println(&quot;Doing my work&quot;)
}
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 {
&lt;-ticker.C
d.task()
}
}()
}
func main() {
d := Daemon{task: func() {
fmt.Println(&quot;Doing my work&quot;)
}}
d.start(time.Millisecond * 300)
time.Sleep(time.Second * 2)
}

答案3

得分: 7

一个简单的解决方案是将daemon *Daemon移动到参数列表中(从而删除接口中的start(...)):

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)
...
}
英文:

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

答案4

得分: 2

你可以在Go语言中实现抽象类。

定义如下:

type abstractObject interface {
    print()
}

type object struct {
    a int
    abstractObject
}

现在object是一个抽象类,类似于Java中的抽象类。

你可以继承它并使用它的成员:

type concreteObject struct {
    *object
}

func (o *concreteObject) print() {
    fmt.Println(o.a)
}

func newConcreteObject(o *object) {
    obj := &concreteObject{object: o}
    o.abstractObject = obj // 所有的魔法都在这个语句中。
}

然后可以使用objectconcreteObject方法:

o := &object{}
newConcreteObject(o)
o.print()

将抽象对象转换为具体对象:

concObj := o.abstractObject.(*concreteObject)

就像其他面向对象的编程语言一样。

英文:

You can implement abstract class in go.

The definition:

type abstractObject interface{
print()
}
type object struct{
a int
abstractObject
}

Now object is an abstract class, like java's.

You can inherit it and use its members:

type concreteObject struct{
*object
}
(o *concreteObject) print() {
fmt.Println(o.a)
}
func newConcreteObject(o *object) {
obj := &amp;concreteObject{object: o}
o.abstractObject = obj // all magics are in this statement.
}

And use the object with concreteObject's methods:

o := &amp;object{}
newConcereteObject(o)
o.print()

And cast abstract object to concrete object:

concObj := o.abstractObject.(*concreteObject)

Just like other OOP languages.

答案5

得分: 1

Max Malysh的解决方案在某些情况下是可行的,如果你不需要一个工厂的话。然而,Adrian Witas提供的解决方案可能会导致循环依赖的问题。

这是我实现抽象类的简单方法,遵循循环依赖和良好的工厂模式。

假设我们有以下组件的包结构:

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

在这个例子中,我们在这里定义组件的定义:

component/types.go

package component

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

现在假设我们想创建一个只实现了SumAverage的抽象类,但在这个抽象实现中,我们希望能够使用实现的AB返回的值。

为了实现这一点,我们应该为抽象实现的抽象成员定义另一个接口。

component/base/types.go

package base

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

然后我们可以继续实现抽象类。

component/base/abstract.go

package base

type AbstractComponent struct {
    IAbstractComponentMembers
}

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

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

现在我们继续实现具体的实现。

component/impl1/impl.go // 假设impl2也有类似的实现

package impl1

type ComponentImpl1 struct {
    base.AbstractComponent
}

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

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

// 这是如何构建这个组件的方法
func New() *ComponentImpl1 {
    impl1 := &ComponentImpl1{}
    abs := &base.AbstractComponent{
        IAbstractComponentMembers: impl1,
    }
    impl1.AbstractComponent = abs
    return impl1
}

我们之所以使用一个单独的接口,而不是使用Adrian Witas的示例,是因为如果我们在这种情况下使用相同的接口,如果我们在impl中导入base包来使用抽象类,并且我们在component包中导入impl**包,以便工厂可以注册它们,我们将会遇到循环引用的问题。

因此,我们可以有一个类似这样的工厂实现。

component/factory.go

package component

// 默认要使用的组件实现
const defaultName = "impl1"
var instance *Factory

type Factory struct {
    // 组件构造函数的映射
    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
}

// 在工厂中注册实现
func init() {
    Factory().Register("impl1", func() IComponent { return impl1.New() })
    Factory().Register("impl2", func() IComponent { return impl2.New() })
}

以上是翻译好的内容,请查阅。

英文:

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 := &amp;ComponentImpl1{}
abs:=&amp;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 = &quot;impl1&quot;
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(&quot;component not found&quot;)
}
return ctor(), nil
}
func (f *factory) Register(name string, constructor func() IComponent) {
f.ctors[name] = constructor
}
func Factory() *Factory {
if instance == nil {
instance = &amp;factory{ctors: map[string]func() IComponent{}}
}
return instance
}
// Here we register the implementations in the factory
func init() {
Factory().Register(&quot;impl1&quot;, func() IComponent { return impl1.New() })
Factory().Register(&quot;impl2&quot;, func() IComponent { return impl2.New() })
}

答案6

得分: 1

抽象类的功能有以下要求:

  1. 不应该能够直接创建抽象类的实例。
  2. 抽象类应该提供默认的字段和方法。

可以使用接口和结构体的组合来满足上述两个要求。例如,我们可以看到以下示例代码:

package main

import "fmt"

// 抽象接口
type iAlpha interface {
    work()
    common(iAlpha)
}

// 抽象具体类型
type alpha struct {
    name string
}

func (a *alpha) common(i iAlpha) {
    fmt.Println("common called")
    i.work()
}

// 实现类型
type beta struct {
    alpha
}

func (b *beta) work() {
    fmt.Println("work called")
    fmt.Printf("Name is %s\n", b.name)
}

func main() {
    a := alpha{name: "test"}
    b := &beta{alpha: a}
    b.common(b)
}

输出
common called
work called
Name is test

这里需要提到的一个重要点是,所有默认方法都应该将iAlpha作为第一个参数,如果默认方法需要调用任何未实现的方法,它们将在该接口上调用。就像我们在上面的common方法中所做的一样 - i.work()

来源:https://golangbyexample.com/go-abstract-class/

英文:

The functionality of abstract class has below requirements

  1. It should not be possible to create direct instance of abstract class
  2. It should provide default fields and methods.

A combination of interface and struct can be used to fulfill above two requirements. For example we can see below

package main
import &quot;fmt&quot;
//Abstract Interface
type iAlpha interface {
work()
common(iAlpha)
}
//Abstract Concrete Type
type alpha struct {
name string
}
func (a *alpha) common(i iAlpha) {
fmt.Println(&quot;common called&quot;)
i.work()
}
//Implementing Type
type beta struct {
alpha
}
func (b *beta) work() {
fmt.Println(&quot;work called&quot;)
fmt.Printf(&quot;Name is %s\n&quot;, b.name)
}
func main() {
a := alpha{name: &quot;test&quot;}
b := &amp;beta{alpha: a}
b.common(b)
}
Output:
common called
work called
Name is test

One important point to mention here is that all default method should have iAlpha as first argument, and if default method needs to call any unimplemented method they they will call on this interface. This is same as we did in common method above - i.work().

Source: https://golangbyexample.com/go-abstract-class/

huangapple
  • 本文由 发表于 2015年5月15日 21:42:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/30261032.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定