面向对象的特征
概述
面向对象的三个基本特征是:封装、继承、多态。
封装
封装最好理解了。封装是面向对象的特征之一,是对象和类概念的主要特性。
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
继承
面向对象编程 (OOP) 语言的一个主要功能就是”继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
多态
多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
实现多态,有二种方式,覆盖,重载。
三大特征的定义齐备,但定义回答不了两个真问题:每个特征解决什么工程问题(动机),以及它们在 Java 中的落地机制与代价。本文按”动机 → 机制 → 代价 → 设计准则”重讲三特征,并补上常被遗漏的第四特征”抽象”。
封装:把变化关进笼子
动机:模块的使用者不应依赖其内部实现,否则实现一改、调用方全崩。封装把”稳定的接口”与”易变的实现”隔开,修改的影响面被限制在类内部。
Java 机制:访问修饰符(private/protected/public/包级)、getter/setter(可控校验而非裸字段)、不可变对象(final 字段+无 setter,封装的极致形态)。
代价与准则:
- 过度封装(每个字段 getter/setter 全开)等于没封装——暴露面最小化:能 private 不 protected,能包级不 public
- 封装的单位是”职责”而非”类”:一个类封装一个变化原因(与单一职责原则同源)
- 破坏封装的信号:外部频繁 instanceOf+强转访问子类细节、反射改私有字段成常态
继承:复用是把双刃剑
动机:is-a 关系的代码复用——子类免费获得父类实现,扩展点通过覆写注入。
Java 机制:单继承(类)+ 多实现(接口);super 调用链;构造器不继承但首行隐式/显式调用父构造。
代价(为什么现代设计”组合优于继承”):
- 脆弱基类问题:父类内部修改可能破坏子类(子类依赖了父类实现细节)
- 破坏封装:继承让子类窥见父类 protected 内部,父子强耦合
- 层次爆炸:多维度变化用继承表达会产生类组合矩阵(咖啡+奶+糖式继承树)
- 运行时不可变:继承关系编译期固定,组合可运行时替换
准则:继承只用于稳定的 is-a 层次+模板方法骨架(如 AbstractList);行为复用优先组合/委托;扩展点优先接口+默认方法。
多态:同一调用、不同行为
动机:调用方依赖抽象而非具体类,新增实现不改调用代码(开闭原则的执行机制)。
Java 机制分两层:
- 编译期多态(重载 overload):同名方法不同参数签名,编译期按静态类型选方法——严格说是”名字复用”,与运行时多态机制不同
- 运行时多态(覆写 override + 动态绑定):变量声明类型为父类/接口、实际对象为子类时,invokevirtual 按实际类型查虚方法表(vtable)分派——“父引用调子实现”的物理基础
List<String> a = new ArrayList<>(); // 声明类型 List,实际 ArrayList |
三要素记忆:继承/实现关系、方法覆写、父引用指向子对象;缺其一即无运行时多态。
多态的设计价值:策略模式/模板方法/依赖注入全部建立在”面向抽象编程+运行时绑定”之上——Spring 注入接口、运行期换实现,就是多态在框架级的应用。
第四特征:抽象(常被遗漏)
抽象是前三者的前提:先识别”什么是不变的本质”(抽象),才能封装变化、继承共性、多态替换。Java 载体是抽象类与接口;设计层面的抽象能力(命名概念、划边界)比语法更难也更值钱——DDD 的领域建模本质就是抽象活动。
四特征的协作范式
- 抽象定边界(接口/抽象类表达概念)
- 封装护实现(变化关进类内)
- 继承建骨架(模板方法复用流程)
- 多态供替换(面向抽象注入实现)
JDK 集合框架是四特征合体的范本:Collection/List 抽象契约、AbstractList 封装骨架与继承复用、ArrayList/LinkedList 多态替换、各自封装内部数据结构。
面向对象的特征不是四个名词,而是一套控制变化影响面的协作机制:封装限制变化的可见范围,继承与多态让扩展不改旧代码,抽象决定这一切的边界画在哪。按动机理解特征,设计原则(SOLID)就都是它们的自然推论。











