4. 面向对象三大特性:封装、继承、多态,接口与抽象类

面向对象(OOP)是C#面试的核心基础,面试官会从语法实现、设计意图、实际场景三个层次考察你对封装、继承、多态以及接口与抽象类的理解。本章提供3个高频问答卡片,配套回答框架与追问路径,帮助你在答题时展现出“理论扎实 + 落地经验”。

📌 本章重点速览
封装:访问修饰符 + 属性 / 方法控制
继承:is-a 关系 + 重写/隐藏(new/virtual/override)
多态:运行时多态(虚方法/接口) vs 编译时多态(重载)
接口与抽象类:契约 vs 模板,语法区别与设计场景
OOP 回答框架 封装 访问修饰符 + 属性/方法 继承 is-a 关系 + virtual/override 多态 运行时多态 + 编译时多态 接口与抽象类 契约 vs 模板,语法区别与设计场景

❓ 面试问题 1:请解释封装的含义,并举例说明在C#中如何实现封装?

面试官考察点是否理解封装的核心是“隐藏实现细节,暴露必要接口”;是否掌握访问修饰符(private/protected/internal/public)的选用;是否能在设计层面(如属性、方法、类层次)运用封装。
回答框架
  1. 概念:将数据和操作捆绑,外部只通过公开接口访问;
  2. 语法实现:使用 private 字段 + 属性(get/set),或方法公开;
  3. 设计优势:降低耦合、提高可维护性;
  4. 补充例子:银行账户类,余额字段 private,通过 GetBalance() 方法访问。
简短示例回答封装的本质是“内部数据对外部隐藏”。比如定义一个 BankAccount 类,_balance 字段设为 private,通过 public decimal Balance { get; private set; } 暴露只读属性,存款/取款方法内部检查逻辑。这样外部无法直接修改余额,保证了数据安全。
可能追问
  • 为什么 C# 推荐使用属性而不是公开字段?
  • internal 与 protected internal 的区别?
  • 在大型项目中,封装粒度如何把握?(类内 vs 程序集内)
避坑提醒❌ 不要只说“封装就是把字段变成私有就完事”,必须提及“暴露合适接口”和“改变实现不影响调用者”。
✅ 可以用 .NET 框架中 StringBuilder 类作为封装范例。

❓ 面试问题 2:继承如何实现?C#中 virtual、override、new 关键字分别何时使用?

面试官考察点是否理解继承的 is-a 关系;是否掌握重写(override)与隐藏(new)的区别;能否在实际设计中避免滥用继承(组合优先原则)。
回答框架
  1. 继承语法:class Derived : Base;
  2. virtual:基类可重写的方法标记;override:子类重新实现;new:隐藏基类同名方法(非多态);
  3. 区别:override 实现多态,new 只是名称覆盖;
  4. 设计建议:优先组合,仅当存在明确 is-a 关系才用继承。
简短示例回答基类 Animal 有 virtual void Speak(),子类 Dog override Speak() 打印“汪”,Cat override 打印“喵”。另一个子类 RobotDog new Speak() 打印“机械声”,但若通过 Animal 引用调用,则仍然是 Animal.Speak()。这就是 override 与 new 的核心区别:是否参与多态。
可能追问
  • sealed 关键字作用?
  • 多重继承为什么 C# 不支持?如何通过接口近似实现?
  • 构造函数链在继承中的调用顺序?
避坑提醒❌ 不要认为 new 关键字“重写”了方法,它只是隐藏,基类引用无法调用子类新实现。
✅ 回答时可以提一下 LSP(里氏替换原则),说明正确使用继承的重要性。

❓ 面试问题 3:接口和抽象类有什么区别?实际开发中如何选择?

面试官考察点是否清晰区分语法差异(多继承 vs 单继承、默认实现、字段等);是否理解设计意图(契约 vs 模板);能否结合场景(如插件系统用接口,基类通用行为用抽象类)。
回答框架
  1. 语法对比:接口可多实现,不能有字段/构造函数/非静态实现(C# 8 后可有默认实现);抽象类可有字段、构造函数、普通方法;
  2. 设计意图:接口定义“能做什么”,抽象类定义“是什么”;
  3. 选择原则:代码复用用抽象类,跨类型能力用接口;
  4. 举例:动物基类用抽象类(字段 Name),飞行能力用 IFlyable 接口。
简短示例回答抽象类 Animal 有字段 _name 和抽象方法 MakeSound(),子类实现。而接口 IFlyable 定义 Fly(),鸟类、飞机类都可实现。接口多实现,抽象类单继承。C# 8 后接口可以有默认实现,但通常仍用于定义能力契约。
可能追问
  • 接口中能不能有静态成员?
  • 抽象类的构造方法能否 public?
  • DI(依赖注入)中常用接口还是抽象类?为什么?
避坑提醒❌ 不要机械背诵“抽象类有方法实现,接口没有”,C# 8 之后接口可以有默认实现。
✅ 强调设计层面的区分:抽象类代表“家族共性”,接口代表“跨家族能力”。
接口 vs 抽象类 语法细节 设计意图 实际选型 默认实现?字段? 多继承限制? 契约 vs 模板 is-a vs can-do 插件系统用接口 基础操作用抽象类

📁 项目案例:在线支付网关(OOP设计实战)

项目名称多商户统一支付平台(MPPay)
业务背景公司从单一支付渠道扩展到支持微信、支付宝、银行卡等 6 种支付方式,需设计可扩展的支付网关。
规模指标日交易量 20万笔,高峰 TPS 800+,接入商户 500+
技术栈.NET 6, ASP.NET Core, EF Core, RabbitMQ, Redis, SQL Server
核心难点
  • 支付渠道差异化逻辑(签名、回调、退款等)如何处理?
  • 新增支付渠道时如何做到最小改动?
  • 统一抽象层与渠道特有逻辑如何平衡?
面试高频追问
  • 支付渠道用抽象类还是接口?—— 接口,因为不同渠道无共同字段,只需契约。
  • 如何实现工厂模式创建渠道实例?—— 通过配置文件注册,反射创建。
  • 渠道回调处理如何封装?—— 抽象基类提供模板方法,子类重写实现。
  • 对于需要重试的渠道,你怎么设计?—— 将重试逻辑从渠道中分离,用装饰器模式。
可迁移回答话术“在设计可扩展的支付网关时,我选择用接口定义 IPaymentChannel,包含 Pay、Refund、VerifyCallback 等契约。每个渠道独立实现,通过工厂类获取。对于新增渠道,只需添加新实现类 + 配置文件注册,无需修改现有代码。这体现了 OCP(开闭原则)和依赖倒置。面试官如果追问为什么不用抽象类,我会回答:不同支付渠道之间没有共享字段,接口更轻量;需要共享字段才考虑抽象类。”
💡 面试加分技巧
当被问到“接口与抽象类”时,主动引出设计模式(策略模式、模板方法模式)和 SOLID 原则,展现你对 OOP 的深度应用。