如何在 Go 中实现一个抽象类?
Posted
技术标签:
【中文标题】如何在 Go 中实现一个抽象类?【英文标题】:How to implement an abstract class in Go? 【发布时间】:2015-07-27 11:14:31 【问题描述】:由于 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)
这将无法编译,因为无法将接口用作接收器。
事实上,我已经回答了我的问题(请参阅下面的答案)。但是,这是实现这种逻辑的惯用方式吗?除了语言的简单性之外,还有什么理由不使用默认实现?
【问题讨论】:
【参考方案1】:其他答案为您的问题提供了替代方案,但是他们提出了不使用抽象类/结构的解决方案,我想如果您有兴趣使用类似抽象类的解决方案,这里是您问题的非常精确的解决方案:
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:=&ConcreteDaemonAa, 0
a.Daemon = r
return r
type ConcreteDaemonB struct
*AbstractDaemon
bar int
func newConcreteDaemonB() *ConcreteDaemonB
a:=&AbstractDaemon
r:=&ConcreteDaemonBa, 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
【讨论】:
你能修复代码块中的缩进吗?【参考方案2】:如果你想提供一个“默认”实现(对于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)
// 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 := Daemontask: 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 := Daemontask: func()
fmt.Println("Doing my work")
d.start(time.Millisecond * 300)
time.Sleep(time.Second * 2)
【讨论】:
函数值解决方案在语义上最接近抽象类的 Java 或 C# 概念。这应该是公认的答案。【参考方案3】:一个简单的解决方案是将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)
...
【讨论】:
这不仅仅是简单,而是接口的想法和目的。我建议让您的答案更加有力。 这与其他语言的抽象类概念不太相似,但它可能是最惯用的 Go 解决方案(例如,它是许多核心 IO API 的工作方式)。 【参考方案4】:您可以在 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 := &concreteObjectobject: o
o.abstractObject = obj // all magics are in this statement.
并使用object
和concreteObject
的方法:
o := &object
newConcereteObject(o)
o.print()
并将抽象对象转换为具体对象:
concObj := o.abstractObject.(*concreteObject)
就像其他 OOP 语言一样。
【讨论】:
【参考方案5】:如果您不需要工厂,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
现在假设我们要创建一个仅实现 Sum 和 Average 的抽象类,但在这个抽象实现中,我们希望能够使用返回的值由实现的 A 和 B
为此,我们应该为抽象实现的抽象成员定义另一个接口
component/base/types.go
package base
type IAbstractComponentMembers
A() int
B() int
然后我们可以继续实现抽象“类”
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
现在我们继续实现
component/impl1/impl.go // 假设 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
我们为此使用单独的接口而不是使用 Adrian Witas 示例的原因是,如果我们在这种情况下使用相同的接口,如果我们导入 base impl* 中的 package 来使用抽象“类”,并且我们在 components 包中导入 impl* 包,以便工厂可以注册他们,我们会找到一个循环引用。
所以我们可以有这样的工厂实现
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 = &factoryctors: 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() )
【讨论】:
@"Juan Carlos Diaz" 请详细说明递归,你的意思是包循环引用(它们是不同的东西),我假设包组织超出了这个问题的范围,呈现的抽象类使用接口可以解决问题,所以我认为您提出的解决方案与我提出的解决方案没有太大不同,除了您已将其分解为不同的包,并添加了工厂结构 @AdrianWitas 你说得对,我的意思是循环引用,已编辑。但是,如果您使用工厂模式,您的解决方案实际上会遇到循环问题,请尝试 @"Juan Carlos Diaz",我没有看到任何包循环引用问题:请参阅一个包中的工厂扩展 play.golang.org/p/Tu0RNmRWHsa ,因此如前所述,所提供的解决方案之间的唯一区别是委托给包(它有自己的价值)和加工厂。感谢纠正。 @AdrianWitas 当然,您不会看到该游乐场链接有任何循环问题,您在单个文件、单个包中执行所有操作,这根本不是真正的工厂场景。 @AdrianWitas。我想回复解释,但 cmets 的字符数有限,所以我在这里添加了我的 cmets play.golang.org/p/Qi6sKbOF7Ob 如果您有任何建议,请告诉我。我这样做的方式是根据您的建议,但将抽象成员包装在自己的界面中进行了微小的更改【参考方案6】:抽象类的功能有以下要求 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 := alphaname: "test"
b := &betaalpha: a
b.common(b)
Output:
common called
work called
Name is test
这里要提到的一点是,所有默认方法都应该以 iAlpha 作为第一个参数,如果默认方法需要调用任何未实现的方法,它们将在此接口上调用。这与我们在上面的常用方法中所做的相同 - i.work()。
来源:https://golangbyexample.com/go-abstract-class/
【讨论】:
以上是关于如何在 Go 中实现一个抽象类?的主要内容,如果未能解决你的问题,请参考以下文章