对于程序猿来讲,这个问题很熟悉,编程的话一定会涉及到。尤其在使用开源库时,一般会选择链接库文件,include头文件,也有一些开源库可能直接使用hpp文件,头文件和实现都在一个文件。依稀记得几年前问了徐先生一句,我的一个函数如何获取另一个函数的某个参数,他说方法有好几种,不过后面我也没具体在网上搜索查询,今天算是做一个小结吧。
一、范围划定
进程内不同类(模块)之间函数访问的方式有几种,有什么优缺点?
在进程内,不同模块/类之间调用函数,常见的方式大致可以分为以下几类。下面列出 6 种典型方案,并对它们的优缺点做简要比较:
1、编译时静态链接(直接调用)
做法
两个模块在编译/打包期就确定依赖关系,源文件里 #include(C/C++)或 import(Java/Python)对方的头文件/模块,然后直接调用对方的类方法。
优点
运行效率高(编译器能做内联、优化)。
代码路径清晰、可读性好。
IDE/编译器能进行完整的类型检查与重构支持。
缺点
耦合度高,改动一方常需要重新编译、回归测试。
难以做到运行时替换或插件式扩展。
2、回调/函数指针/委托(Callback / Delegate)
做法
将目标函数指针、函数对象/Lambda、接口实例等以参数形式传入被调用方,运行时再“回调”执行。
优点
典型的“依赖倒置”,可减少模块间直接依赖。
支持运行时灵活替换具体实现。
缺点
代码可读性略差,调用链不如直接调用直观。
调试时要追踪回调栈,比直调更麻烦。
若函数签名不统一,接口设计成本增高。
3、观察者/发布-订阅(Observer / Pub‑Sub / Event Bus)
做法
定义一个事件总线(EventBus),模块 A 发布(publish)某类事件,模块 B 订阅(subscribe)并在收到通知时执行回调。
优点
极低耦合:发布者和订阅者互不依赖。
易于广播式通知,多对多场景非常合适。
缺点
隐式调用链,不容易从代码上看出事件流向。
难以保证处理顺序,调试、错误定位成本高。
性能略差,事件分发有额外开销。
4、服务定位器/单例模式(Service Locator / Singleton)
做法
将若干公共服务或工具类做成全局单例,其他模块通过 ServiceLocator.get(…) 或者 Singleton.getInstance() 获取并调用。
优点
使用方便,任何地方都能拿到实例。
对已有代码改动最小,快速引入。
缺点
隐藏依赖关系,模块之间的依赖不显式,维护困难。
单例全局状态易导致测试困难、潜在并发问题。
5、反射/动态调用(Reflection / Dynamic Invocation)
做法
通过语言运行时的反射 API(如 Java 的 Class.forName、Python 的 getattr)或动态代理,根据字符串/元数据决定调用哪个方法。
优点
极度灵活,可在运行时根据配置或元数据决定行为。
适合框架层面通用功能(比如 RPC 框架、序列化库)。
缺点
性能相对较差,有额外的运行时开销。
编译期难以检查,容易在运行时才暴露调用错误。
可读性、可维护性下降。
6、动态加载插件(Plugin/Module Loader)
做法
使用操作系统的动态链接库(如 dlopen/LoadLibrary),或语言级别的动态导入(如 Python 的 importlib),运行时加载外部模块,并通过预定义接口调用。
优点
支持热插拔式扩展,方便做插件化架构。
主程序与插件仅通过接口契约耦合,升级、部署更灵活。
缺点
实现复杂度高,需要处理版本兼容、生命周期管理。
调试/打包/部署流程更繁琐。

选择哪种方案,需根据项目规模、性能要求、可维护性以及模块间耦合度等因素综合权衡。
二、几个其他问题
1、rpc问题
如果未来程序朝着跨进程跨硬件演变,设计接口时如何考虑进程内与跨进程兼容?
欢迎关注:
一个希望50岁退休的打工人~
共同交流进步,体会工业化进程~
290

被折叠的 条评论
为什么被折叠?



