如何在 Go 中实现抽象 class?
How to implement an abstract class in Go?
如何在 Go 中实现抽象 class?由于 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)
}
这将无法编译,因为无法将接口用作接收器。
其实我已经回答了我的问题(见下面的回答)。但是,这是实现这种逻辑的惯用方式吗?除了语言的简单性之外,还有什么理由不使用默认实现吗?
一个简单的解决方案是将 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)
...
}
如果你想提供一个"default"实现(对于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 := 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)
}
其他答案为您的问题提供了替代方案,但是他们提出了不使用抽象 classes/struct 的解决方案,我想如果您有兴趣使用抽象 class 之类的解决方案,这里非常精确您问题的解决方案:
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 中使用抽象 classes/multi-inheritance 这里是 post 具有全面的细节。 Abstract Classes In Go
如果您不需要工厂,Max Malysh 的解决方案在某些情况下会起作用。然而,Adrian Witas 给出的解决方案可能会导致循环依赖问题。
这是我实现抽象class尊重循环依赖和良好工厂模式的简单方法。
让我们假设我们的组件具有以下包结构
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 的抽象 class,但在这个抽象中实现我们希望能够使用已实现的 A 和 B
返回的值
为此,我们应该为抽象实现的抽象成员定义另一个接口
component/base/types.go
package base
type IAbstractComponentMembers {
A() int
B() int
}
然后我们就可以着手实现抽象了"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
}
现在我们继续实施
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* 中使用抽象 "class" 并且我们还导入 impl* 包components 包,所以工厂可以注册它们,我们会找到一个循环引用。
所以我们可以有一个像这样的工厂实现
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() })
}
abstract class 的功能有以下要求
1. 不应该创建抽象的直接实例 class
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 作为第一个参数,如果默认方法需要调用任何未实现的方法,它们将调用此接口。这与我们在上面的常用方法中所做的相同 - i.work().
你可以在go中实现抽象class。
定义:
type abstractObject interface{
print()
}
type object struct{
a int
abstractObject
}
现在 object
是一个抽象 class,就像 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.
}
并使用 object
和 concreteObject
的方法:
o := &object{}
newConcereteObject(o)
o.print()
并将抽象对象转换为具体对象:
concObj := o.abstractObject.(*concreteObject)
就像其他 OOP 语言一样。
如何在 Go 中实现抽象 class?由于 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)
}
这将无法编译,因为无法将接口用作接收器。
其实我已经回答了我的问题(见下面的回答)。但是,这是实现这种逻辑的惯用方式吗?除了语言的简单性之外,还有什么理由不使用默认实现吗?
一个简单的解决方案是将 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)
...
}
如果你想提供一个"default"实现(对于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 := 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)
}
其他答案为您的问题提供了替代方案,但是他们提出了不使用抽象 classes/struct 的解决方案,我想如果您有兴趣使用抽象 class 之类的解决方案,这里非常精确您问题的解决方案:
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 中使用抽象 classes/multi-inheritance 这里是 post 具有全面的细节。 Abstract Classes In Go
如果您不需要工厂,Max Malysh 的解决方案在某些情况下会起作用。然而,Adrian Witas 给出的解决方案可能会导致循环依赖问题。
这是我实现抽象class尊重循环依赖和良好工厂模式的简单方法。
让我们假设我们的组件具有以下包结构
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 的抽象 class,但在这个抽象中实现我们希望能够使用已实现的 A 和 B
返回的值为此,我们应该为抽象实现的抽象成员定义另一个接口
component/base/types.go
package base
type IAbstractComponentMembers {
A() int
B() int
}
然后我们就可以着手实现抽象了"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
}
现在我们继续实施
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* 中使用抽象 "class" 并且我们还导入 impl* 包components 包,所以工厂可以注册它们,我们会找到一个循环引用。
所以我们可以有一个像这样的工厂实现
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() })
}
abstract class 的功能有以下要求 1. 不应该创建抽象的直接实例 class 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 作为第一个参数,如果默认方法需要调用任何未实现的方法,它们将调用此接口。这与我们在上面的常用方法中所做的相同 - i.work().
你可以在go中实现抽象class。
定义:
type abstractObject interface{
print()
}
type object struct{
a int
abstractObject
}
现在 object
是一个抽象 class,就像 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.
}
并使用 object
和 concreteObject
的方法:
o := &object{}
newConcereteObject(o)
o.print()
并将抽象对象转换为具体对象:
concObj := o.abstractObject.(*concreteObject)
就像其他 OOP 语言一样。