【问题标题】:How to create an interface method with multiple return types如何创建具有多种返回类型的接口方法
【发布时间】:2020-06-23 11:58:26
【问题描述】:

这是对这个问题的跟进:Interface method with multiple return types

我有两个略有不同的结构。一个是关于交易结构的,另一个是关于转移结构的。目标是计算最后的数量。此外,交易结构应实现一些与传输结构不同的特定功能,反之亦然。最后他们都调用get() 函数并最终返​​回数量(类型字符串)。我不能来做类似qtyGetService(trade{}).calculateA().get() 的事情,其中​​qtyGetService()get() 可以从两个结构中调用,但calculateA() 是一种仅用于交易结构的方法。接口看起来首先有望解决此类问题,但我面临着该问题Interface method with multiple return types 中描述的问题,即接口中的方法必须返回特定类型。返回interface{} 也不是一个选项,因为我无法链接下面示例中所示的函数(甚至没有提到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())
}

游乐场链接:https://play.golang.org/p/SBCs_O9SL0k

【问题讨论】:

  • 使用接口。在具有相同签名的所有类型上定义一个共享方法,然后定义一个声明相同方法的接口。然后无论底层类型如何,您都可以调用它。
  • 停止在 Go 中编写 Java。
  • 如果您误解了我的评论,请原谅。你真的必须停止尝试在 Go 中编写 Java 代码,因为这根本不可能。例如。你在 get 方法中写了“在 gq 上对交易和转账通用的一些处理”。在 Go 中,你使用 functions 来做普通的工作,而不是方法。在 Java 中,您 必须 将所有内容放入一个方法中,并且在您的情况下您可以,因为您有继承。 Go 中没有继承,但有函数,您可以将通用计算放入函数中。在 Go 中,您使用方法来 A) 满足接口和 B) 信息隐藏。
  • 您很可能可以摆脱所有这些服务并替换它们(或通过简单功能替换它们的其他部分,可能在接口上操作)。

标签: go interface


【解决方案1】:

如果您将GetService() 受体添加到TradeTransfer,如下所示:


func (t Trade) GetService() *TradeService {
    return &TradeService{
        q: t.q + " TradeService ",
    }
}

func (t Transfer) GetService() *TransferService {
    return &TransferService{
        q: t.q + " TransferService ",
    }
}

你可以这样使用它:

    fmt.Println(Trade{q: "10"}.GetService().calculateA().get())
    fmt.Println(Transfer{q: "10"}.GetService().calculateC().get())

似乎是你想要完成的。

在接口中定义GetService() 将不起作用,因为两个实现的返回类型不同。

【讨论】:

    【解决方案2】:

    感谢您重新发布问题。在另一个问题的 cmets 中,很难提供代码示例。

    我看到你在这里有 2 个选项:

    选项 1:

    在所有具有相同签名的结构上创建一个函数calculate。在您的示例中,没有输入参数,只有返回值。如果不是这样,事情会变得有点复杂,选项 2 可能更合适。

    注意:链接函数使得在 Go 中使用接口变得更加困难。你可能想摆脱它。

    例子:

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

    有时在结构上实现函数是有意义的,即使它不需要满足接口。如果有服务不需要在调用get之前计算,只需创建这个函数:

    func (ts *SomeService) calculate() {}
    

    现在可以作为qty接口使用了。

    选项 2:

    也可能是案例不是那么统一,更难引入一个界面。

    然后您可以使用多个接口并强制转换结构以检查它是否实现了接口。然后才调用该方法。这或多或少类似于检查结构是否具有特定方法。

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

    希望这适用于您的情况并给您一些想法。

    【讨论】:

    • 谢谢。我认为选项 2 应该(在技术上)有效,但实际上它并没有任何意义,因为它并没有简化任何事情。至于选项 1,我不确定这怎么可能。我想做的是用基于 Trade and Transfer 结构的 2 个 qtyGetService 函数替换 qtyGetTransferServiceqtyGetTradeService。然后每个函数将返回一个 不同的 结构,该结构又由多个 calculateXX() 实现 - 请注意,在我的示例中,我有 calculateAcalculateB 用于贸易,calculateC转移。
    • 如果它们如此不同,那么就没有好的方法可以做到这一点。这实际上是很多人一开始就有问题的地方(不确定你是否是 Go 新手):Go 需要不同的思维方式,这只能随着时间的推移而学习。我的建议:不要过早尝试优化代码结构。只要有多个看起来相似的东西的副本。没关系。从长远来看,它甚至会更易于维护。我现在有 4 年的 Go 经验,但我仍然不得不忍受我用 1.5 年的经验做出的一些编码决定。有一天我将不得不重构它们。
    • 大部分决策都与过度工程有关:1) 编写代码比现在更灵活。 2) 编写能够处理多个本来可以分开的情况的代码;现在那里有太多的 if 语句,使代码难以阅读和维护。多份副本会更好。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-20
    • 1970-01-01
    • 2016-05-15
    • 1970-01-01
    • 2012-07-24
    • 2014-08-12
    相关资源
    最近更新 更多