Home
avatar

.𝙃𝙖𝙣

继续补 .NET:把封装、继承、多态和接口/实体/服务层的关系理一遍

这篇主要是写给自己后面回头看的。

前一篇先把 .NET 语法和面向对象的大框架过了一遍,但看项目代码的时候,还是会反复卡在几个地方:封装、继承、多态到底落在代码里是什么意思;接口、实体、服务层为什么总是一起出现;这些东西看起来都认识,但放到项目里就容易混。

所以这篇直接把我这几天反复想的几个点记下来。

1. 先把一个误区记下来:面向对象不是“语法专题”,而是组织代码的方式

刚开始学的时候,很容易把面向对象理解成一组概念题:

  • 什么是封装
  • 什么是继承
  • 什么是多态

这种理解方式的问题是,背的时候像是懂了,但一进项目还是会乱。

因为项目里真正的问题不是“会不会默写定义”,而是:

  • 这段代码该放在哪
  • 这个类该不该暴露出去
  • 这个方法是写在实体里,还是写在服务里
  • 这个地方为什么不用具体类,而要用接口

所以我现在对面向对象的理解先换成一句更直接的话:

面向对象的重点是代码怎么分层、怎么收口、怎么避免后面越来越乱。

这个角度一换,很多东西就没那么悬了。

2. 封装:不是“藏起来”这么简单,而是先管住边界

封装这个词,我一开始也会理解成:

把东西包起来,不让别人乱碰。

这个理解不算错,但还是太轻了。

现在我觉得更实用的理解是:

把一个对象该负责的数据和行为放到一起,并且控制外部到底能碰它多少。

封装至少有两层意思:

第一层:职责收口

谁的数据,谁管;谁的逻辑,尽量也放在谁那边。

比如一个用户对象的用户名、状态、创建时间这些信息,本来就应该是它自己的东西。如果这些数据相关的判断和处理逻辑全散在外面,后面维护起来就会越来越难受。

第二层:暴露边界

不是所有字段、状态、操作都应该被外部随便改。

所以会有:

  • public
  • private
  • protected

这些访问修饰符。

以前我会把这些关键字当语法点背,现在我开始觉得它们更像是在回答一个问题:

这个东西到底该不该让外面直接碰?

如果什么都 public,那代码表面上写起来省事,但后面谁都能改,状态也容易失控。

所以我现在对封装的阶段理解是:

  • 不只是把代码放进类里
  • 更重要的是把“该归谁管”这件事先管住

3. 继承:不是为了少写代码,而是为了抽共性

继承这个东西,一开始最容易被理解成:

父类写一遍,子类少写一点。

但如果只停在这个层面,后面很容易乱继承。

我现在看继承,会更偏向一个问题:

这些类之间是不是真的有稳定的“是一种”关系?

如果只是因为有几段代码长得像,就强行继承,其实很危险。因为一旦上层定义不稳,后面子类全都会被带偏。

所以我现在对继承的理解先记成这样:

  • 继承不只是复用代码
  • 更像是在抽一组稳定共性
  • 前提是这种共性真的成立

举个很粗的例子,如果系统里有不同类型的消息通知,它们可能都有:

  • 标题
  • 内容
  • 发送时间

那你可能会想抽一个基础类,把共同字段放进去。这个方向是合理的。

但如果两个类只是“碰巧有点像”,业务含义根本不是一类,那为了省几行代码硬继承,后面大概率要出问题。

所以我现在对继承先给自己立个提醒:

只有在抽象关系真的稳的时候,继承才值得用。

不然还不如老老实实拆开。

4. 多态:重点不是定义,是“调用方不用关心你到底是谁”

多态我一开始最虚。

因为这个词听起来就比封装、继承抽象,而且只看书面定义,很容易只记住一句“同一个接口,多种实现”,但还是不知道它在项目里到底值在哪。

我现在给自己记的版本更实际一点:

多态的意义,是调用方只关心你能做什么,不关心你到底是哪一个具体实现。

这个点一旦想通,接口也就顺带好理解很多。

比如:

  • 我只需要“发送通知”这个能力
  • 至于你底下是短信、邮件还是站内信
  • 调用这边不一定要写死

多态真正解决的问题不是“概念高级”,而是:

  • 降低调用方和实现方的耦合
  • 后面好替换
  • 好扩展
  • 不至于所有代码都绑死在一个具体类上

我现在觉得,多态如果脱离接口和抽象去背,很容易发虚;但一放进“调用和实现解耦”这个场景里,就立刻顺很多。

5. 到这里我开始能理解:接口不是摆设,它是隔离变化的一层

前面我一直对接口有点模糊印象。

知道项目里很多地方会写:

  • IUserService
  • IOrderService
  • IRepository

但一开始说实话,挺容易觉得它们有点“形式主义”——既然最后还是要写实现类,为什么不直接调实现类?

这几天往后看一点以后,我慢慢明白接口真正值在哪了。

我现在对接口的理解是:

接口先定义“要提供什么能力”,具体怎么做,再交给实现类。

这样带来的直接好处就是:

  • 调用方依赖的是能力定义,不是某一个死实现
  • 后面如果实现方式变了,调用方不一定跟着大改
  • 测试、替换、扩展都会轻一点

所以我现在看接口,不再只是把它当成 .cs 文件里多出来的一层,而是会先问:

这个地方未来有没有变化可能?如果有,值不值得先隔一层?

当然,我现在也不想把接口神化。

不是所有地方都必须为了“规范”先写一个接口。

但至少在服务层这种会不断变、会被调用、会被替换的位置上,接口确实有价值。

6. 实体:我现在先把它理解成“业务数据长什么样”

前面看项目的时候,另一个总出现的词就是实体。

我现在先不给它套太多理论,先记最实用的理解:

实体主要是在描述业务数据本身。

比如用户、订单、任务、日志,这些东西在系统里都不是一句话,而是一组稳定字段。

举个很粗的例子,一个用户实体里可能会有:

  • Id
  • Name
  • Phone
  • Status
  • CreateTime

这些字段合起来,才比较像系统里的“用户”到底长什么样。

所以我现在看实体,会优先想到:

  • 这不是服务
  • 这不是接口
  • 它先解决的是“数据结构怎么表示”

当然,后面更深入学的时候,实体不一定只是纯数据袋子,这个我现在也知道。但至少在我当前阶段,先把“实体主要在表示业务对象的数据结构”这个点抓住,对我已经很有用了。

7. 服务层:我现在理解为“把业务动作集中起来的地方”

如果实体偏“长什么样”,那服务层我现在先理解成:

系统要做什么事,主要集中放在这里。

比如:

  • 创建用户
  • 修改状态
  • 提交订单
  • 发起任务
  • 校验参数
  • 调数据库
  • 组织返回结果

这些动作如果全都塞进控制器里,代码很快会炸;如果全都塞进实体里,也不合适。

所以服务层的价值,我现在理解成两点:

第一,收业务逻辑

把“这件事到底怎么做”的主流程集中起来。

第二,隔开上下游

上面接控制器,下面接实体、仓储、数据库或者别的组件。

这样控制器不用什么都懂,底层数据层也不用直接暴露业务细节。

我现在越来越觉得,服务层其实就是一个“业务编排层”。

它不一定做所有细节,但它至少要把一次业务动作串起来。

8. 现在把接口、实体、服务层放在一起看,关系终于顺一点了

前面最容易乱的就是这三个东西总是一起出现。

我现在先把它们记成一个最粗但够用的关系:

实体

描述“数据长什么样”。

接口

描述“这个能力长什么样”。

服务层

实现“这个能力到底怎么做”。

如果再往前推一点,我现在脑子里大概会这样想:

  • 控制器接请求
  • 服务层处理业务
  • 服务层会操作实体
  • 服务层通过接口暴露能力
  • 具体实现类去真正把事情做完

这个理解虽然还不算很深,但至少比之前把这些词全混在一起要好多了。

我现在再看项目结构时,至少不会再觉得每一层都长得差不多。

9. 我现在给自己记一个更实用的判断法

后面如果再看代码,我准备先这样问自己:

这是数据,还是动作?

  • 如果重点在字段和状态,更像实体
  • 如果重点在处理流程,更像服务层

这是定义能力,还是实现能力?

  • 如果只是约定能做什么,更像接口
  • 如果是真正把逻辑写出来,更像实现类

这个东西该不该暴露给外面?

  • 如果不该乱碰,就收一层,别全开 public
  • 如果将来可能会换实现,就考虑先隔接口

我感觉这种问法,比硬背术语对我更有用。

因为它能直接落到“写代码时怎么摆位置”这个层面。

10. 现阶段结论:后端难的不是会不会写,而是会不会收

这几天看下来,我现在对 .NET 和后端这条线最大的感受反而不是“语法多”,而是:

后端难的地方,是收口。

  • 数据怎么收
  • 逻辑怎么收
  • 依赖怎么收
  • 边界怎么收
  • 变化怎么收

封装、继承、多态这些概念,如果只停在书面定义,确实容易空。

但一旦放到项目结构里去看,就会发现它们都在回答类似的问题:

代码多起来以后,怎么别让它散掉。

我现在越来越觉得,面向对象真正有用的地方,在项目开始变复杂的时候。

先记到这里。

.NET C# 面向对象 接口 服务层