Type Composition: overriding interface types

2020-03-05 03:30发布

I want to compose a type of another type, but replace one of the fields (which is an interface value) with a fake. The problem I am getting is the underlying field is being used, so I can't seem to override the field.

I've demoed the problem here: https://play.golang.org/p/lHGnyjzIS-Y

package main

import (
    "fmt"
)

type Printer interface {
    Print()
}

type PrinterService struct {}

func (ps PrinterService) Print() { fmt.Println("PrinterService") }

type Service struct {
    Client PrinterService
}

func (s Service) PrintViaMethod() { s.Client.Print() }

type FakeService struct {
    Service
    Client Printer
}

type SomeOtherService struct {}

func (sos SomeOtherService) Print() { fmt.Println("SomeOtherService") }

func main() {
    s := FakeService{Client: SomeOtherService{}}
    s.PrintViaMethod()
}

Why does it print "PrinterService"? I want it to print "SomeOtherService".

Thanks.

3条回答
Anthone
2楼-- · 2020-03-05 03:49

Why does it print 'PrinterService'? I want it to print 'SomeOtherService'.

Because that's what your code says to do. PrintViaMethod calls s.Client.Print(), and s.Client is a (zero value) instance of PrinterService, which outputs PrinterService.

What you probably want is to call s.Print() in main(). I don't see any reason for your PrintByMethod function at all.

查看更多
【Aperson】
3楼-- · 2020-03-05 04:03

As per Flimzy you are calling print s.Client.Print() which is of type PrinterService implemented as receiver to Print() function printing PrinterService. You can also change type of Client PrinterService in Service struct to Someother service

package embedded

import (
    "fmt"
)

type Printer interface {
    Print()
}

type PrinterService struct{}

func (ps PrinterService) Print() { fmt.Println("PrinterService") }

type Service struct {
    Client SomeOtherService
}

func (s Service) PrintViaMethod() { s.Client.Print() }

type FakeService struct {
    Service
    Client Printer
}

type SomeOtherService struct{}

func (sos SomeOtherService) Print() { fmt.Println("SomeOtherService") }

func Call() {
    s := FakeService{Client: SomeOtherService{}}
    s.PrintViaMethod()
}
查看更多
ゆ 、 Hurt°
4楼-- · 2020-03-05 04:16

By s.PrintViaMethod(), you are calling the promoted method FakeService.Service.PrintViaMethod(), and the method receiver will be FakeService.Service which is of type Service, and Service.PrintViaMethod() calls Service.Client.Print(), where Service.Client is of type which PrinterService, that's why it prints "PrinterService".

In Go there is embedding, but there is no polymorphism. When you embed a type in a struct, methods of the embedded type get promoted and will be part of the method set of the embedder type. But when such a promoted method is called, it will get the embedded value as the receiver, not the embedder.

To achieve what you want, you would have to "override" the PrintViaMethod() method by providing your implementation of it for the FakeService type (with FakeService receiver type), and inside it call FakeService.Client.Print().

By doing so s.PrintViaMethod() will denote the FakeService.PrintViaMethod() method as that will be at the shallowest depth where the PrintViaMethod() exists (and not FakeService.Service.PrintViaMethod()). This is detailed in Spec: Selectors.

For example:

func (fs FakeService) PrintViaMethod() {
    fs.Client.Print()
}

Then the output will be (try it on the Go Playground):

SomeOtherService

See related questions and answers with more details:

Go embedded struct call child method instead parent method

Does fragile base class issue exist in Go?

查看更多
登录 后发表回答