关于 Golang 和 Python 设计数据结构思维的区别

2023-05-17 21:13:38 +08:00
 Lighthughjiajin
from abc import ABC, abstractmethod


class Device(ABC):

    @abstractmethod
    def fn(self):
        raise NotImplementedError

    def sync(self):
        print("Start Sync ...")
        self.fn()
        print("Start Done.")


class PC(Device):

    def fn(self):
        print("PC Device fn!!!")


class Router(Device):

    def fn(self):
        print("Router Device sync")



def start_sync(d: Device):
    d.sync()


if __name__ == '__main__':
    p = PC()
    r = Router()
    start_sync(p)  # Run p.fn()
    start_sync(r)  # Run r.fn()

请熟悉 Go 的大佬指导下,在 Go 中如何用接口与结构体实现类似的功能?(子类只需要重写 fn 方法,在 start_sync 处调用基类的 sync 时,能调用到子类重写后的 fn 方法)

1555 次点击
所在节点    程序员
11 条回复
FreeEx
2023-05-17 21:20:15 +08:00
golang 不支持方法重载。

https://go.dev/doc/faq#overloading

> 为什么 Go 不支持方法和运算符的重载?
> 如果方法分派也不需要进行类型匹配,则它会得到简化。使用其他语言的经验告诉我们,具有相同名称但不同签名的各种方法偶尔有用,但在实践中也可能令人困惑和脆弱。仅按名称匹配并要求类型一致是 Go 类型系统中的一个主要简化决策。

> 关于运算符重载,它似乎比绝对要求更方便。同样,没有它,事情会更简单。
Alias4ck
2023-05-17 21:29:44 +08:00
语言的转换 你问 chatgpt 可能一下就搞定了
chaleaochexist
2023-05-17 21:58:41 +08:00
Contextualist
2023-05-17 22:06:51 +08:00
Go 的 interface 和 struct 那一套鼓励的是组合 (composition) 而不是继承,所以像 PC 和 Router 这种实现具体功能的类是会被作为 Device 这种实现公共方法的类的**一部分**,就是 Device 包裹住 PC 或 Router 。下面是我个人认为的对应 Go 的地道解决办法: https://go.dev/play/p/wio55S6HtdK
lesismal
2023-05-17 22:14:20 +08:00
Lighthughjiajin
2023-05-17 22:16:44 +08:00
@Contextualist 终于看到比较地道的写法,是我想了解的。
还有一点疑问,在这场景下,IDevice, Device 结构体、接口的命名有什么约定吗?
Lighthughjiajin
2023-05-17 22:20:39 +08:00
@Contextualist 这个写法下,有个缺点,Router 无法重写 Device 的 sync 方法,类似于以下 Python 代码
```
from abc import ABC, abstractmethod


class Device(ABC):

@abstractmethod
def fn(self):
raise NotImplementedError

def sync(self):
print("Start Sync ...")
self.fn()
print("Start Done.")


class PC(Device):

def fn(self):
print("PC sync")


class Router(Device):

def fn(self):
print("Router Device sync")

def sync(self):
print("Router 重写了 sync")
super().sync()
print("Router sync ...")


def start_sync(d: Device):
d.sync()


if __name__ == '__main__':
r = Router()
start_sync(r) # Run r.fn()
start_sync(PC())

```
Contextualist
2023-05-17 22:47:54 +08:00
@Lighthughjiajin 嗯对,为了清晰对应你的例子我尽量沿用了你的命名。Go 里的 Interface 一般都是“动词 + er”来命名。假设我把 fn 重命名为 act ,那接口就叫 Actor 或者 DeviceActor ,对应的实现叫 PCActor 和 RouterActor 。

关于你提到的无法重写 sync 方法的问题,这意味着在你的业务逻辑里 sync 并不是只有一个实现,那就应该也把它列进接口里。根据不同情况,可以开一个新的 interface ,也可以加进 IDevice 。
- 开一个新的 interface 那就可以实现一个 type DefaultSyncer struct{},只是初始化 Device 时得传两个值
- 加进 IDevice 就是强制要求每一个具体实现都要实现 sync
Lighthughjiajin
2023-05-17 23:33:07 +08:00
@Contextualist 感谢大佬,我整理了一下。
https://go.dev/play/p/Z5Hl57w5XdF
看看我理解的对吗?
Contextualist
2023-05-18 00:23:39 +08:00
@Lighthughjiajin 免大佬。基本没问题,就是注意一下依赖关系,Syncer 单方面依赖 Actor ,22 行是没必要的。

不过这个玩具例子有点过于复杂了😅,但愿你在实际应用中不会经常遇到这种情况。
Lighthughjiajin
2023-05-18 00:33:27 +08:00
我写的是组合多个结构体来满足函数的 Device 接口参数要求,我看你写的是组合多个结构体来调用 sync ,也就是最终都是同一个类型,就是一个结构体。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/940805

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX