如何结合面向对象设计实现Protocol Buffer生成代码?
我在Java项目中遇到了一个通用的Protocol Buffer(以下简称Protobuf)生成代码/类的功能实现问题,这个问题同样适用于所有面向对象编程场景。
此前我的项目里,序列化与反序列化的常规做法是为各开发语言编写JSON自定义序列化/反序列化器,将JSON消息转换为包含自定义业务方法的对应类。切换到Protobuf后,自动生成代码省去了手动编写序列化/反序列化器的工作量,但我不清楚该如何基于反序列化后的Protobuf生成类实现业务逻辑。
Protobuf官方教程明确警告不要通过继承生成类来扩展功能:
Protocol buffer类本质是数据持有者(类似C语言的struct),不提供额外功能,并非对象模型中的理想一等公民。若要为生成类添加更丰富的行为,最佳方式是将生成的Protocol Buffer类封装到应用专属类中。若无法控制.proto文件的设计(例如复用其他项目的文件),封装同样是好选择:可通过封装类打造适配应用环境的接口,隐藏部分数据与方法,暴露便捷函数等。绝对不要通过继承生成类来添加行为,这会破坏内部机制,且不符合良好的面向对象实践。
目前我梳理了三个可选方向,但不确定哪个是最优解:
无视警告,通过继承扩展类:这种方式要么需要修改自动生成的Protobuf代码(我不愿这么做),要么可通过protoc插件自动为生成类型添加继承结构。不过Protobuf的一个已关闭Issue也指出该方案存在问题——虽技术上可行,但插件缺乏完善的文档与支持,且仍违背官方教程的警告。或许能通过类型转换技巧实现无需修改生成代码,但我不确定可行性。该方案的优势是系统易变更,能自动响应底层Schema变化:仅需编写业务功能方法(而非getter/setter),若底层字段失效会抛出编译期异常,确保Schema变更时仅需修改应用代码,符合我的需求。
按官方建议创建封装类,包裹生成代码:这遵循委托设计模式,但并非所有语言都易实现。比如在Java中,委托逻辑需要手动编写,每次Protobuf Schema变更都要修改封装类代码,维护成本会随着Schema规模增大而显著提升。虽有实验性的Lombok委托类、Scala原生支持委托等方式,但我不确定这是否是最佳方向。
不在领域逻辑中使用Protobuf生成类,手动创建带自定义逻辑的类:这和当前项目处理JSON的方式类似,区别是先将消息转换为Protobuf生成类,再转换为自定义业务类。可以借助Java中的工具实现两类间的自动转换,但会增加额外的系统开销,且无法利用Protobuf生成代码的自动方法。该方案的优势是完全具备序列化格式无关性,支持JSON的类也可适配Protobuf,但所需样板代码最多:需重复实现Protobuf生成类的所有getter/setter,编写领域逻辑及转换器类/方法。不少技术讨论都推荐这种方案。
我发现相关的技术讨论都比较陈旧且稀少,想知道这是罕见问题、过于小众,还是我想多了?请问最优实践方向是什么,以及如何更好地理解与解决此类问题?
内容的提问来源于stack exchange,提问作者Andrew Freeman

