LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

我为什么讨厌继承?

freeflydom
2026年2月4日 16:24 本文热度 83

面向对象三要素封装、继承、多态。我上大学学习 Java 的时候,很多相关书籍都会讲解这三要素,以现在的我看来,封装和多态是必要的。

但是继承...

继承有什么问题

在我刚参加工作的第三年,我参与到了中国移动和地图 App 的开发工作中,当时我负责改造该 App 的核心网络请求模块。

那个时候还没有流行使用 OkHttp ,更没有 Retrofit 这种优秀的网络开源库,整体还是使用原始的 HttpConnection 去完成网络请求,自己封装里面的实现逻辑。

我当时理解完需求之后,整理了网络库需要满足的功能,大概如下:

  1. 完成对数据的序列化和反序列化。
  2. 完成对数据的加密解密。
  3. 返回纯文本的数据结构。
  4. 返回 JSONObject 的数据结构。
  5. 返回基于 Java 对象的数据结构。

自然而然,我想到了这种方案,

  1. 首先,BaseHttp 作为基类,负责序列化和反序列化和加解密,因为从目前看来,一个请求一定会有这两个需求。
  2. TextHttp 继承 BaseHttp,将其字节码转换成文本。
  3. JsonHttp 继承 TextHttp,将文本转换成 JSONObject 结构。
  4. 以此类推。。。

一开始,这样做非常方便,例如:如果一个请求只需要获取文本内容,那么就直接继承 TextHttp

也就是说,一个请求需要实现哪些能力,那么就继承那个基类就好了。

而使用这个网络库的人,只需要按照 API 文档中的 API 名称,用这个请求就行了,例如 Sell -> SellHttpLogin -> LoginHttp,用起来会非常方便!

但是后续改动,让我越来越 Hold 不住这种继承链了:有的 API 需要鉴权,有的不需要;部分 API 会有不同的 Header;有的 API 返回结果加密方式和之前的不一样。

慢慢的,我发现我的继承链越来越长,为了让我的 API 使用保持简洁,甚至出现了类似菱形继承的情况(这里不是说 Java 可以菱形继承):

实际上 SHACryptJsonHttp 包括其基类有大量相似的代码,只是鉴权方式问题,导致我不得不重新改写整个继承链(这里只是一个示例,真实情况比这个复杂的多)。

后面这种问题越来越多,虽然同事们用起来非常方便(对照文档直接 new 即可),但是对我的维护造成了巨大负担,同时也会犯一些非常低级的错误,例如搞错了鉴权方式!

为什么会这样呢?

一句话总结

“继承”这个特性,本来想干三件事,结果三件事互相打架,最后搞得谁都不开心。

好的,我来换个思路解释,以 Java 这种语言为例:

比如你写一个 Animal 类,再写一个 Dog 类继承它,这样 Dog 就自动有了 Animal 的所有方法和属性。

听起来很美好,对吧?(当时我就是这么认为的)

但问题来了——我们其实用“继承”做了三件完全不同的事,而这三件事根本不是一回事!

第一件事:分类 —— 这东西属于哪一类

比如:“正方形是一种矩形”,“狗是一种动物”。

这是从现实世界逻辑出发的,叫“本体论”(别被名字吓到,就是“分类”的意思)。

这合理啊,正方形确实是矩形的一种特殊情况。

第二件事: 能替换吗 —— 程序能不能当同一种东西用

比如:如果一个函数要求传入一个 Rectangle(矩形),那我能传一个 Square(正方形)进去吗?

如果可以,就叫“可替换”,这是程序正确性的问题。

但现实中不行!因为矩形可以改宽高,而正方形一改宽高就不是正方形了。

所以:虽然“正方形是矩形”在数学上成立,但在代码里不能随便替换!

这就是著名的 “正方形-矩形悖论” —— 看似合理,实则坑人。

第三件事: 省代码 —— 别重复写一样的代码

比如:Dog 和 Cat 都有 eat() 方法,于是让它们都继承 Animal,把 eat() 写在父类里。

这纯粹是为了偷懒/复用代码,跟“是不是动物”没关系。

好处:少写代码。

风险:万一以后某个“动物”不吃东西(比如机器人宠物),你就得硬改,或者搞一个新的继承链,破坏设计。

问题来了

Java 只给了你一个“继承”关键字,却让你同时干这三件事。

结果:

  • 你以为你在“分类”(正方形是矩形),
  • 但程序要求你“能替换”(正方形必须能当矩形用),
  • 而你实际只是为了“省代码”(不想重复写宽高设置)。

三件事混在一起,必然出问题!

就像公司里面的团队,虽然大家都属于同一个组,但是如果目标不一致,这必然导致开发问题!

那怎么办

这是我后来,才学到的知识。

别用“继承”干所有事!分开处理:

你想干啥正确做法
想分类?领域模型画图就行,别非塞进代码继承树里。
想保证能替换?接口(Interface) 或 协议(Protocol) ,明确约定行为。
想省代码?组合(Composition) + 委托(Delegation) ,比如让 Dog 里面“包含”一个 Eater 对象,而不是继承 Animal

还记得那句经典的建议吗:“优先使用组合,而不是继承。 ”

来,举个例子,假设你要做“交通工具”系统:

  • 错误做法(用继承干三件事)

    class Vehicle { void move() {} }
    class Car extends Vehicle { }      // 是Vehicle,能替换,还复用了move()
    class Airplane extends Vehicle { } // 同上
    

    → 这就是我早期开发 Http 框架时候的做法。问题来了,如果以后加个 Hovercraft(气垫船),它既能走陆地又能走水,继承哪个?

  • 正确做法(分开处理):

    interface Drivable { void drive(); }
    interface Flyable { void fly(); }
    class Car implements Drivable { ... }
    class Airplane implements Flyable { ... }
    class Hovercraft implements Drivable, Floatable { ... } // 多实现,灵活!
    

    → 分类归分类(你是车还是飞机),行为归行为(你会开还是会飞),代码复用靠组合

    Java 实现委派会有些麻烦,如果是 Kotlin,就方便多了!

总结

“继承”就像一把瑞士军刀,本来想切菜、开瓶、剪线都行,结果发现:切菜时刀片会弹出来割手,开瓶时螺丝刀又碍事。

所以聪明人怎么做?

切菜用菜刀,开瓶用开瓶器,剪线用剪刀。各干各的,互不干扰。

编程也一样:

别指望“继承”解决所有问题,该用接口用接口,该用组合用组合。

这样,代码才清爽,bug 才少,而你,我的朋友,才睡得着觉!

转自https://juejin.cn/post/7601384029611425807


该文章在 2026/2/4 16:24:42 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved