V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
assiadamo
V2EX  ›  Go 编程语言

Java 仔想在 Go 中写类似 interface -> abstract class -> sub class 的玩意

  •  
  •   assiadamo · 2 天前 · 2466 次点击

    实现出来是这个样子,接受各位 Gopher 的批判

    package main
    
    import "fmt"
    
    // Require 必须要子类实现的方法
    // 对应 abstract method
    type Require interface {
    	Require()
    }
    
    // Hook 可能会被子类重写的方法
    type Hook interface {
    	Require
    	Test1()
    	Test2()
    }
    
    // IBase 类似 Java 的 interface -> abstract class -> sub class 套娃
    type IBase interface {
    	Hook
    	Common()
    }
    
    // Base abstract class
    type Base[T Hook] struct {
    	hook T
    }
    
    func NewBase[T Hook](hook T) *Base[T] {
    	res := &Base[T]{
    		hook: hook,
    	}
    	return res
    }
    
    func (b *Base[T]) Common() {
    	b.hook.Require()
    	b.hook.Test2()
    	b.hook.Test1()
    }
    
    func (*Base[T]) Test1() {
    	fmt.Println("Base.Test1")
    }
    
    func (*Base[T]) Test2() {
    	fmt.Println("Base.Test2")
    }
    
    // Sub 抽象类的子类
    type Sub struct {
    	*Base[*Sub]
    }
    
    func NewSub() *Sub {
    	res := &Sub{}
    	// 注意 %v 输出可能会有套娃死循环
    	res.Base = NewBase[*Sub](res)
    	return res
    }
    
    // Test1 复用 Base 的 Test1
    //func (*Sub) Test1() {
    //	fmt.Println("Sub.Test1")
    //}
    
    // Test2 重写 Base 的 Test2
    func (*Sub) Test2() {
    	fmt.Println("Sub.Test2")
    }
    
    // Require 必须要子类实现的 Require 不写会编译报错
    func (*Sub) Require() {
    	fmt.Println("Sub.Require")
    }
    
    func main() {
      m := make(map[int]IBase)
    	m[1] = NewSub()
    	b := m[1]
    	b.Common()
      /*
      Output: 
      Sub.Require
      Sub.Test2
      Base.Test1
      */
    }
    
    39 条回复    2025-08-28 22:40:16 +08:00
    assiadamo
        1
    assiadamo  
    OP
       2 天前
    主要是想让基类的通用方法既可以被子类调用也可以被子类重写,同时也要在编译期检测出必须被子类实现的方法,而不是靠运行时的 panic("implement me")
    wetalk
        2
    wetalk  
       2 天前   ❤️ 2
    Java 是世界上最啰嗦的语言
    BeijingBaby
        3
    BeijingBaby  
       2 天前
    最多就是 interface, struct 组合,其他就太啰嗦了。
    接口是有必要的,继承是有害的。
    log4j
        4
    log4j  
       2 天前
    ”使用嵌套而非继承“
    好像官方是这么说的
    stormtrooperx5
        5
    stormtrooperx5  
       2 天前
    作为 Gopher ,这一坨看着都头疼
    sthwrong
        6
    sthwrong  
       2 天前   ❤️ 1
    站在 gopher 的角度,毫无意义。把你这段放到 ai 里,吐槽得比我还狠。懒得贴了,免得毁号,总结一句话就是,你在给 struct 硬塞一个万能爹,其实它只缺一把接口形状的刀子。
    litchinn
        7
    litchinn  
       2 天前   ❤️ 1
    https://go.dev/doc/effective_go#embedding
    我之前也问过这个问题,唯一类似的就是这个 embedding ,但是在 go 里能用组合还是用组合吧
    sunny352787
        8
    sunny352787  
       2 天前
    通常我们定义接口的时候都是

    type IFIrst interface{
    FirstFunc()
    }

    type ISecond interface{
    SecondFunc()
    }

    type Entity struct{
    }

    func (e *Entity)FirstFunc(){
    print("first called")
    }

    func (e *Entity)SecondFunc(){
    print("second called")
    }

    func main(){
    var obj any
    obj = &Entity{}

    if first,ok:=obj.(IFirst);ok{
    first.FirstFunc()
    }

    if second,ok:=obj.(ISecond);ok{
    second.SecondFunc()
    }
    }
    assiadamo
        9
    assiadamo  
    OP
       2 天前
    @sunny352787 如果一堆 Enity 都有个写法非常固定的方法,区别只有 Enity 的 type 不同,于是只能每个 Entity 都 copy 相同的代码,改下 type 吗
    assiadamo
        10
    assiadamo  
    OP
       2 天前
    @sunny352787 为什么不用
    var _ IFIrst = (*Entity)(nil)
    var _ ISecond = (*Entity)(nil)
    sunny352787
        11
    sunny352787  
       2 天前
    @assiadamo #10 你要是这个理解能力,那我很难跟你解释啊...
    spritecn
        12
    spritecn  
       2 天前
    我是从 java 过来的,写了几天我发现这么搞太麻烦了,简单方法需要多个实现的场景,都是直接 type xxxxer func(),like:
    -----
    type Pusher func(track Track) error

    func PushToLog(track Track) error {
    slog.Infof("track: %+v", track)
    return nil
    }

    func NewMqttPusherAdapter(mqttPusher *MqttPusher) Pusher {
    return mqttPusher.Push
    }
    assiadamo
        13
    assiadamo  
    OP
       2 天前
    @sunny352787 你们 gopher 的批判方式真是不知所云,虽然好歹 show 了 code ,提了下为什么要在运行时用断言检测接口是否实现而不是编译时,这么回我,给我整这死出,都在装高手不解决实际问题吗
    sunny352787
        14
    sunny352787  
       2 天前
    @assiadamo #13 我说了,你这个理解能力我解决不了,我能力有限你找别人吧
    mightybruce
        15
    mightybruce  
       2 天前
    多用 struct 嵌套,少用 interface, 另外 go 不是面向对象的语言,其接口实现都是鸭子类型。
    mightybruce
        16
    mightybruce  
       2 天前
    其他用法套用函数式编程,强调 go 不是面向对象的语言。
    assiadamo
        17
    assiadamo  
    OP
       2 天前
    @spritecn 你用的注入的方式吗,把具体逻辑写在每个 Pusher 函数里,但这样在基类自带基础实现时怎么用呢
    if pusher != nil {
    pusher(track)
    } else {
    //基础实现
    }
    这样吗
    mightybruce
        18
    mightybruce  
       2 天前
    那个老兄的代码 Entity 已经实现了 IFIrst 和 ISecond 的接口
    darksword21
        19
    darksword21  
    PRO
       2 天前
    看着头疼,无法批判
    assiadamo
        20
    assiadamo  
    OP
       2 天前
    @mightybruce 鸭子类型好像和我这里的需求没啥关系,我关注的是代码复用和重写,interface 在实现特定模式比如策略模式和注入时很有用,struct 嵌套我想他本意也是为了复用基类的代码,但比抽象类残废,基类声明也不能指向组合他的类的实例
    var b Base = &Sub{Base: NewBase()} // 编译报错
    ,导致要实现像上面说的策略模式还要在上面套一层 interface
    assiadamo
        21
    assiadamo  
    OP
       2 天前
    @mightybruce 他在 main 里面断言,我就问了下为什么不在编译时检测
    mightybruce
        22
    mightybruce  
       2 天前
    我不想说了,我已经强调了 go 不是面向对象语言, 实现策略模式请用函数式和接口混用的方式。
    mightybruce
        23
    mightybruce  
       2 天前
    go 不存在 抽象类,go 也不存在对象( class) 这种, 所以不要再谈什么面向对象。
    assiadamo
        24
    assiadamo  
    OP
       2 天前
    @mightybruce 了解,我这也是写了很多年 Java 思维转不过来,但又需要解决实际问题,只能按这样的写法起模板方便自己。
    mightybruce
        25
    mightybruce  
       2 天前   ❤️ 1
    给一段策略模式代码
    ```
    package main

    import "fmt"

    // 定义 RouteStrategy 为一个接口,包含 CalculateTime 方法
    type RouteStrategy interface {
    CalculateTime(origin, destination string) int
    }

    // 使用函数类型作为策略
    type StrategyFunc func(origin, destination string) int

    // 实现 RouteStrategy 接口的 CalculateTime 方法
    func (sf StrategyFunc) CalculateTime(origin, destination string) int {
    return sf(origin, destination)
    }

    // 实现三种策略:步行、骑行、驾车

    func WalkStrategyFunc(origin, destination string) int {
    // 假设固定耗时 30 分钟
    return 30
    }

    func BikeStrategyFunc(origin, destination string) int {
    // 假设固定耗时 15 分钟
    return 15
    }

    func DriveStrategyFunc(origin, destination string) int {
    // 假设固定耗时 10 分钟
    return 10
    }

    // 路线规划器
    type RoutePlanner struct {
    strategy RouteStrategy
    }

    // 设置策略
    func (rp *RoutePlanner) SetStrategy(strategy RouteStrategy) {
    rp.strategy = strategy
    }

    // 估算出行时间
    func (rp *RoutePlanner) EstimateTime(origin, destination string) int {
    return rp.strategy.CalculateTime(origin, destination)
    }

    func main() {
    planner := &RoutePlanner{}

    // 使用步行策略
    walkStrategy := StrategyFunc(WalkStrategyFunc)
    planner.SetStrategy(walkStrategy)
    fmt.Println("Walk Time:", planner.EstimateTime("Home", "School"))

    // 使用骑行策略
    bikeStrategy := StrategyFunc(BikeStrategyFunc)
    planner.SetStrategy(bikeStrategy)
    fmt.Println("Bike Time:", planner.EstimateTime("Home", "School"))

    // 使用驾车策略
    driveStrategy := StrategyFunc(DriveStrategyFunc)
    planner.SetStrategy(driveStrategy)
    fmt.Println("Drive Time:", planner.EstimateTime("Home", "Work"))
    }
    ```
    monmon
        26
    monmon  
       2 天前   ❤️ 1
    我没理解错的话,说白了你就是想实现一个 “模板方法模式” https://refactoringguru.cn/design-patterns/template-method/go/example

    简单版的代码就是:

    ```go
    type Worker interface {
    MustImplementStep() // 必须被实现的方法
    OptionalHook() // 一个有默认行为的、可选的钩子方法
    }

    type BaseWorker struct{}

    func (b *BaseWorker) OptionalHook() {
    fmt.Println("-> BaseWorker: 执行默认的钩子逻辑。")
    }

    type ConcreteWorker struct {
    BaseWorker // 嵌入“基类”,OptionalHook 的默认实现。
    }

    // MustImplementStep 实现接口中必须被实现的方法
    func (c *ConcreteWorker) MustImplementStep() {
    fmt.Println("-> ConcreteWorker: 执行必须实现的步骤。")
    }

    // 编译期安全检查,如果 ConcreteWorker 未实现 MustImplementStep (注释掉上面方法)会报错
    var _ Worker = (*ConcreteWorker)(nil)

    // OptionalHook “重写”嵌入的钩子方法。
    func (c *ConcreteWorker) OptionalHook() {
    fmt.Println("-> ConcreteWorker: 开始执行重写的钩子逻辑。")

    // super.method()
    c.BaseWorker.OptionalHook()

    fmt.Println("-> ConcreteWorker: 结束执行重写的钩子逻辑。")
    }

    func RunTemplate(w Worker) {
    fmt.Println("--- 模板开始 ---")
    w.MustImplementStep()
    w.OptionalHook()
    fmt.Println("--- 模板结束 ---")
    }

    func main() {
    worker := &ConcreteWorker{}
    RunTemplate(worker)
    }
    ```
    unused
        27
    unused  
       2 天前
    Base 里一部分是默认的方法实现,一部分是组合调用的逻辑,你需要把这两部分拆开。
    assiadamo
        28
    assiadamo  
    OP
       2 天前
    @monmon 了解了,不把 Common 放在 Base 里面而是改成函数像 RunTemplate 注入 Worker ,就会简单很多
    yuezk
        29
    yuezk  
       1 天前
    每个语言都有息的习惯 (idiomatic)。入乡随俗,写 Go 就按 Go 的语言习惯来,才能扩宽视野。有点说教
    kdd0063
        30
    kdd0063  
       1 天前
    你都学 go 了为什么不学一学它的范式?推荐去看一下《七周七语言》,你需要看一些完全非 OOP 的语言开拓下对语言范式的视野,强迫自己去写一写 C 也是个方法。
    windyboy
        31
    windyboy  
       1 天前
    go 的 interface 要简洁很多
    EricYuan1
        32
    EricYuan1  
       1 天前
    让我想到了一个表情包:

    无语,跟你说不下去,典型的 Java 思维
    assiadamo
        33
    assiadamo  
    OP
       21 小时 30 分钟前
    @EricYuan1 可以发一下,P 一个 Go 土拨鼠说的版本
    huangmiao233
        34
    huangmiao233  
       21 小时 29 分钟前
    有没有像 我这样的 go 里只会用 struct 连 interface 都很少用的...日常就是定义结构体. 写 function 好像也没啥..
    assiadamo
        35
    assiadamo  
    OP
       21 小时 23 分钟前
    @huangmiao233 我类比就像把 package 级 function 当 static 用
    leeonsoft
        36
    leeonsoft  
       13 小时 36 分钟前
    用组合来代替继承,忘记继承
    liuguang
        37
    liuguang  
       12 小时 34 分钟前
    典型的 Java 思维, 现代语言已经不搞继承了,组合优于继承。
    bunny189
        38
    bunny189  
       10 小时 23 分钟前 via iPhone
    跟你说不清楚,典型的 Java 思维
    https://i.imgur.com/TKfLzat_d.webp
    bunny189
        39
    bunny189  
       10 小时 19 分钟前 via iPhone
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4040 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 01:00 · PVG 09:00 · LAX 18:00 · JFK 21:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.