Reputation: 1942
This is a follow up to this question: Interface method with multiple return types
I have two structs which are slightly different. One is about a trade struct, the other about a transfer struct. The goal is to calculate the quantity at the end. Also the trade struct shall implement some specific function not common to the transfer struct and the other way around. At the end they all call a get()
function and ultimately return the quantity (type string). I can't come to do something like that qtyGetService(trade{}).calculateA().get()
where qtyGetService()
and get()
can be called from both struct, but calculateA()
is a method only for trade struct. Interfaces look at first promising to solve such issues but I'm facing the issue described in that question Interface method with multiple return types that a method in an interface must return a specific type. Returning an interface{}
wouldn't be an option either as I would not be able to chain the functions like shown in the example below (not even mentioning the use of reflect
).
package main
import (
"fmt"
)
type Trade struct {
q string
// many other attributes
}
type Transfer struct {
q string
// many other attributes
}
type TradeService struct {
q string
// some other attributes
}
type TransferService struct {
q string
// some other attributes
}
type GetQty struct {
q string
// some other attributes
}
func qtyGetTradeService(str *Trade) *TradeService {
// some processing on the initial struct
return &TradeService{
q: str.q + " TradeService ",
}
}
func qtyGetTransferService(str *Transfer) *TransferService {
// some processing on the initial struct
return &TransferService{
q: str.q + " TransferService ",
}
}
func (ts *TradeService) calculateA() *GetQty {
// some processing on ts
return &GetQty{
q: ts.q + " CalculateA ",
}
}
func (ts *TradeService) calculateB() *GetQty {
// some processing on ts
return &GetQty{
q: ts.q + " CalculateB ",
}
}
func (ts *TransferService) calculateC() *GetQty {
// some processing on ts
return &GetQty{
q: ts.q + " CalculateC ",
}
}
func (gq *GetQty) get() string{
// some processing on gq common to both trade and transfer
return gq.q + " CommonGet "
}
func main() {
// this works just fine
fmt.Println(qtyGetTradeService(&Trade{q: "10"}).calculateA().get())
fmt.Println(qtyGetTransferService(&Transfer{q: "10"}).calculateC().get())
// But this would be "better" to do something like this:
fmt.Println(qtyGetService(&Trade{q: "10"}).calculateA().get())
fmt.Println(qtyGetService(&Transfer{q: "10"}).calculateC().get())
}
Link to playground: https://play.golang.org/p/SBCs_O9SL0k
Upvotes: 0
Views: 3669
Reputation: 136
If you add a GetService()
receptor to both Trade
and Transfer
like this:
func (t Trade) GetService() *TradeService {
return &TradeService{
q: t.q + " TradeService ",
}
}
func (t Transfer) GetService() *TransferService {
return &TransferService{
q: t.q + " TransferService ",
}
}
You will be able to use it like this:
fmt.Println(Trade{q: "10"}.GetService().calculateA().get())
fmt.Println(Transfer{q: "10"}.GetService().calculateC().get())
Seems to be what you are trying to accomplish.
Defining GetService()
in a interface would not work because the return type of both implementations is different.
Upvotes: 0
Reputation: 7440
Thx for reposting the question. In the comments of the other question it would have been hard to provide a code example.
I see 2 options you have here:
Create a function calculate
on all structs with the same signature. In your example there are no input params, only a return value. If that is not the case, things get a bit more complex and option 2 might be a better fit.
Note: chaining functions makes it harder to use interfaces in Go. You might want to get rid of that.
Example:
type qty interface{
calculate() qty // chaining can be problematic. best no return value here.
get() string // or whatever type get should return
}
func (ts *TradeService) calculate() qty {
ts.calculateA()
return ts
}
func (ts *TransferService) calculate() qty {
ts.calculateC()
return ts
}
func main() {
// Now this should
fmt.Println(qtyGetService(&Trade{q: "10"}).calculate().get())
fmt.Println(qtyGetTransferService(&Transfer{q: "10"}).calculate().get())
// or without chaining (if the return value is removed from `calculate`):
printVal(qtyGetService(&Trade{q: "10"}))
printVal(qtyGetTransferService(&Transfer{q: "10"}))
}
func printVal(service qty) {
service.calculate()
fmt.Println(service.get())
}
Sometimes it makes sense to implement a function on a struct even if it is not needed to satisfy an interface. If there is a service that doesn't need to calculate before calling get
just create this function:
func (ts *SomeService) calculate() {}
Now it can be used as qty
interface.
It could also be that the cases are not so uniform and harder to bring into a single interface.
Then you could work with multiple interfaces and cast the struct to check if it implements the interface. Only then call the method. This is more or less like a check if the struct has a certain method or not.
type calculatorA interface {
calculateA()
}
type calculatorB interface {
calculateB()
}
type getter interface {
get()
}
func main() {
service := qtyGetService(&Trade{q: "10"})
if ok, s := service.(calculatorA); ok {
s.calculateA()
}
if ok, s := service.(calculatorB); ok {
s.calculateB()
}
var val string
if ok, s := service.(getter); ok {
val = s.get()
}
fmt.Println(val)
}
Hope this is applicable to your case and gives you some ideas.
Upvotes: 1