如何在Go语言中实现一个抽象类?

oknrviil  于 2022-12-07  发布在  Go
关注(0)|答案(6)|浏览(219)

如何在Go语言中实现一个抽象类?由于Go语言不允许在接口中包含字段,因此这将是一个无状态对象。那么,换句话说,在Go语言中是否有可能为一个方法提供某种默认的实现呢?
请考虑以下示例:

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

这将不会编译,因为它不可能使用接口作为接收器。
事实上,我已经回答了我的问题(见下面的答案)。然而,这是实现这种逻辑的惯用方法吗?除了语言的简单性之外,还有什么理由不使用默认实现吗?

ki1q1bka

ki1q1bka1#

其他的答案提供了一个替代你的问题,但是他们提出的解决方案没有使用抽象类/结构,我猜如果你有兴趣使用抽象类一样的解决方案,这里是非常精确的解决方案,你的问题:
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)
}

如果这还不清楚如何在go-lang中使用抽象类/多重继承,这里有一个完整的细节。Abstract Classes In Go

lpwwtiir

lpwwtiir2#

如果你想提供一个“default”实现(对于Daemon.start()),这不是 interface 的特性(至少在Go语言中不是),而是 concrete(非接口)类型的特性。
所以Daemon在你的例子中应该是一个具体的类型,方便的是struct,因为你希望它有字段。要完成的任务可以是一个接口类型的值,或者在一个简单的例子中只是一个函数值(简单的例子意味着它只有一个方法)。

带接口类型

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

带函数值

在这个简单的例子中,这个更短。在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)
}
jjhzyzn0

jjhzyzn03#

一个简单的解决方案是将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)
    ...
}
x7yiwoj4

x7yiwoj44#

你可以在go中实现抽象类。
定义:

type abstractObject interface{
    print()
}

type object struct{
    a int
    abstractObject
}

现在object是一个抽象类,就像java的。
您可以继承它并使用其成员:

type concreteObject struct{
    *object
}

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

func newConcreteObject(o *object) {
    obj := &concreteObject{object: o}
    o.abstractObject = obj // all magics are in this statement.
}

并使用objectconcreteObject的方法:

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

并将抽象对象投射到具体对象上:

concObj := o.abstractObject.(*concreteObject)

就像其他OOP语言一样。

oogrdqng

oogrdqng5#

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

这就是我实现抽象类的方法,这是一种尊重循环依赖关系和良好工厂模式的简单方法。
让我们假设我们的组件具有以下包结构

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

定义组件的定义,在本例中它将在此处定义:

组件/类型.go

package component

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

现在,假设我们要创建一个抽象类,它只实现SumAverage,但在此抽象实现中,我们希望能够使用已实现的AB返回的值
要实现这一点,我们应该为抽象实现的抽象成员定义另一个接口

组件/基础/类型.go

package base

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

然后我们就可以着手实现抽象的“类”

组件/基础/抽象.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
}

现在,我们开始实施

组件/impl 1/impl.go//对impl 2取类似值

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
}

我们使用一个单独的接口而不是使用Adrian Witas示例的原因是,如果我们在本例中使用相同的接口,如果我们导入impl*中的base包以使用抽象“类”,并且导入components包中的impl包,以便工厂可以注册它们,我们将发现循环引用。
所以我们可以有一个这样的工厂实现

组件/工厂.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() })
}
4zcjmb1e

4zcjmb1e6#

抽象类的功能有以下要求1.不应创建抽象类的直接示例2.应提供默认字段和方法。
接口和结构体的组合可以用来满足以上两个要求。

package main

import "fmt"

//Abstract Interface
type iAlpha interface {
    work()
    common(iAlpha)
}

//Abstract Concrete Type
type alpha struct {
    name string
}

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

//Implementing Type
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)
}

  Output:
    common called
    work called
    Name is test

这里要提到的一个重要点是所有默认方法都应该有iAlpha作为第一个参数,如果默认方法需要调用任何未实现的方法,它们将在此接口上调用。这与我们在上面的公共方法- www.example.com()中所做的相同i.work。
来源:https://golangbyexample.com/go-abstract-class/

相关问题