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 · 8 天前 · 2817 次点击

    实现出来是这个样子,接受各位 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
       8 天前
    主要是想让基类的通用方法既可以被子类调用也可以被子类重写,同时也要在编译期检测出必须被子类实现的方法,而不是靠运行时的 panic("implement me")
    wetalk
        2
    wetalk  
       8 天前   ❤️ 2
    Java 是世界上最啰嗦的语言
    BeijingBaby
        3
    BeijingBaby  
       8 天前
    最多就是 interface, struct 组合,其他就太啰嗦了。
    接口是有必要的,继承是有害的。
    log4j
        4
    log4j  
       8 天前
    ”使用嵌套而非继承“
    好像官方是这么说的
    stormtrooperx5
        5
    stormtrooperx5  
       8 天前
    作为 Gopher ,这一坨看着都头疼
    sthwrong
        6
    sthwrong  
       8 天前   ❤️ 1
    站在 gopher 的角度,毫无意义。把你这段放到 ai 里,吐槽得比我还狠。懒得贴了,免得毁号,总结一句话就是,你在给 struct 硬塞一个万能爹,其实它只缺一把接口形状的刀子。
    litchinn
        7
    litchinn  
       8 天前   ❤️ 1
    https://go.dev/doc/effective_go#embedding
    我之前也问过这个问题,唯一类似的就是这个 embedding ,但是在 go 里能用组合还是用组合吧
    sunny352787
        8
    sunny352787  
       8 天前
    通常我们定义接口的时候都是

    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
       8 天前
    @sunny352787 如果一堆 Enity 都有个写法非常固定的方法,区别只有 Enity 的 type 不同,于是只能每个 Entity 都 copy 相同的代码,改下 type 吗
    assiadamo
        10
    assiadamo  
    OP
       8 天前
    @sunny352787 为什么不用
    var _ IFIrst = (*Entity)(nil)
    var _ ISecond = (*Entity)(nil)
    sunny352787
        11
    sunny352787  
       8 天前
    @assiadamo #10 你要是这个理解能力,那我很难跟你解释啊...
    spritecn
        12
    spritecn  
       8 天前
    我是从 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
       8 天前
    @sunny352787 你们 gopher 的批判方式真是不知所云,虽然好歹 show 了 code ,提了下为什么要在运行时用断言检测接口是否实现而不是编译时,这么回我,给我整这死出,都在装高手不解决实际问题吗
    sunny352787
        14
    sunny352787  
       8 天前
    @assiadamo #13 我说了,你这个理解能力我解决不了,我能力有限你找别人吧
    mightybruce
        15
    mightybruce  
       8 天前
    多用 struct 嵌套,少用 interface, 另外 go 不是面向对象的语言,其接口实现都是鸭子类型。
    mightybruce
        16
    mightybruce  
       8 天前
    其他用法套用函数式编程,强调 go 不是面向对象的语言。
    assiadamo
        17
    assiadamo  
    OP
       8 天前
    @spritecn 你用的注入的方式吗,把具体逻辑写在每个 Pusher 函数里,但这样在基类自带基础实现时怎么用呢
    if pusher != nil {
    pusher(track)
    } else {
    //基础实现
    }
    这样吗
    mightybruce
        18
    mightybruce  
       8 天前
    那个老兄的代码 Entity 已经实现了 IFIrst 和 ISecond 的接口
    darksword21
        19
    darksword21  
    PRO
       8 天前
    看着头疼,无法批判
    assiadamo
        20
    assiadamo  
    OP
       8 天前
    @mightybruce 鸭子类型好像和我这里的需求没啥关系,我关注的是代码复用和重写,interface 在实现特定模式比如策略模式和注入时很有用,struct 嵌套我想他本意也是为了复用基类的代码,但比抽象类残废,基类声明也不能指向组合他的类的实例
    var b Base = &Sub{Base: NewBase()} // 编译报错
    ,导致要实现像上面说的策略模式还要在上面套一层 interface
    assiadamo
        21
    assiadamo  
    OP
       8 天前
    @mightybruce 他在 main 里面断言,我就问了下为什么不在编译时检测
    mightybruce
        22
    mightybruce  
       8 天前
    我不想说了,我已经强调了 go 不是面向对象语言, 实现策略模式请用函数式和接口混用的方式。
    mightybruce
        23
    mightybruce  
       8 天前
    go 不存在 抽象类,go 也不存在对象( class) 这种, 所以不要再谈什么面向对象。
    assiadamo
        24
    assiadamo  
    OP
       8 天前
    @mightybruce 了解,我这也是写了很多年 Java 思维转不过来,但又需要解决实际问题,只能按这样的写法起模板方便自己。
    mightybruce
        25
    mightybruce  
       8 天前   ❤️ 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  
       8 天前   ❤️ 2
    我没理解错的话,说白了你就是想实现一个 “模板方法模式” 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  
       8 天前
    Base 里一部分是默认的方法实现,一部分是组合调用的逻辑,你需要把这两部分拆开。
    assiadamo
        28
    assiadamo  
    OP
       8 天前
    @monmon 了解了,不把 Common 放在 Base 里面而是改成函数像 RunTemplate 注入 Worker ,就会简单很多
    yuezk
        29
    yuezk  
       8 天前
    每个语言都有息的习惯 (idiomatic)。入乡随俗,写 Go 就按 Go 的语言习惯来,才能扩宽视野。有点说教
    kdd0063
        30
    kdd0063  
       8 天前
    你都学 go 了为什么不学一学它的范式?推荐去看一下《七周七语言》,你需要看一些完全非 OOP 的语言开拓下对语言范式的视野,强迫自己去写一写 C 也是个方法。
    windyboy
        31
    windyboy  
       7 天前
    go 的 interface 要简洁很多
    EricYuan1
        32
    EricYuan1  
       7 天前
    让我想到了一个表情包:

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