你的位置:首页 > ASP.net教程

[ASP.net教程]11年之际,理一理面向对象那些事——面向对象的三个基本特征


从2005年毕业至今,毕业已经11年,正好现处于离职状态,所以理一理些年的一些技术,一些想法,在此也分享给大家,不对的地方也请大家尽管打脸,赞同的话呢,那就点个赞吧。:)

先说一下风格,一不照本宣科,二不复制粘贴,纯手打,三必须是结合实际开发的经验或理解来整理。

对于面向对象不同的人会有不同的理解,我的理解对象就是一个实体,一个人,一个物,都是一个对象,万事万物有特点(属性);有作用(方法);在某些条件下会有事情发生(事件);有生有死有过程(生命周期)。

然而对于面向对象的三个基本特征大家都是认同的,封装,继承,多态。 在很多面试场合也会频频问起,我们也就从这几个特征聊起。

一、封装

从字面意思理解来看就是把一个啥东西封住,然后装起来,的确如此。从编程开发的角度来说,就是把一些常用的代码提取出来封成方法/属性/事件/委托等,装在类/命名空间/程序集里。

举个栗子

大家都知道的自动售卖机,好多地铁站都有,我们去自动售卖机买一瓶可乐,使用支付宝扫一扫支付,可乐就从出货口出来了,然后就是拿到手里开喝。

  • 我们不需要知道自动售卖机中的可乐到底存放在哪里的;
  • 我们不需要知道可乐是怎么变冷的;
  • 我们也不需要知道支付宝扫一扫它是如何传输数据,怎么收款的;
  • 我们必须遵从售卖机的操作规定,我们需要选择可乐,扫一扫支付,可乐就到手了。

这就是封装。我们有很多不需要知道的内容, 只需要按规定输入内容,就能得到输出结果,重点在于无需关注售卖机中的细节。

封装的目的是什么呢?或者说为什么要封装?
  • 对外隐藏细节,便于维护。售卖机内如果某部件出现故障,维修时更换掉,对于使用者来说,就没有任何影响,以前该怎么购买就怎么购买。
  • 没有那么吓人,让用户轻松使用。如果售卖机内部是透明的,我们将无法专心购买东西,很吓人。如果汽车没有挡住发动机,那么发动起转起来的时候会更吓人。
  • 同样的使用规则,便于重用。由于隐藏了内部细节,我们只要按照规定操作即可,不管在那一台机器操作都是一样的,生产售卖机内部也可以完全一样生产。对于代码来说,封装过的代码,在哪里调动方法都一样的。
如何封装?

编码上的封装:

  • 将重用的代码提取封装成方法
  • 将多种类似行为的Class封装成泛型类
  • 将稳定不变的行为对外提取成接口

广义上的封装:

  • 类库及框架的封装。比如常用的ORM框架:EF,NHibernate,iBatis(My Batis);微软官方的.Net Farmework等等。
  • UI界面与业务逻辑分离的封装。比如常说的三层结构的模式,分离了表现层,业务逻辑层,数据层,其中表现层就可能是webform,windows from,Windows service,MVC web,然而他们的业务逻辑是一样的,那么我们就可以把相同的业务逻辑封装起来,表现层调用同一个业务逻辑。
  • 模块化,插件化的封装。比如我们的开发工具Visual Studio,安装了Resharper,Nuget等插件,每一个插件都是一种封装;我们开发一个windows form宿主程序,可以加载不同的插件/模块,以丰富主程序的功能,每一个插件/模块也是一种封装。
封装的原则:
  • 常用场景驱动原则。得有一个常用使用场景,因为常用,所以才封装出来大家用呗,在做的时候,我们往往也是找出一组场景及这组场景的样例代码开始封装。如果仅仅在一些特定条件下才会使用的代码,我们是没有必要封装一遍的,那不是自找麻烦吗?也就是过度设计的臭味。
  • 低门槛原则。封装是为了更好的使用,我们要做到API简单,如果封装得“难用”,我想也只是搬石头砸脚了,有的开发人员可能封装了,命名不规范,参数繁多,那么自己来说很好用,然而对于其他队友来说完全无法使用,不懂参数意义,不懂方法作用,那么这个封装也是失败的。
  • 尽量自说明,有注释有文档。在简单的使用场景中,一定让封装无需文档也能使用;在复杂的使用场景中,一定要有注释/文档。

二、继承

继承就是继续和传承,和现实中的子承父业,财产继承意思很接近。面向对象中的继承更有特点:

  • 继承了父亲的财产(方法,属性,事件等),父亲仍然拥有。
  • 继承了父亲的财产可能发生变化(重写),也可能是白条(抽象方法)。
继承的意义何在?
  • 个人理解继承是实现面向对象的基础,现实世界中的各种大类到小类,到具体的物种都是一个继承关系,比如(动物->鸟类->燕子)。
  • 行为/动作/特性继承(接口实现):和类别继承不一样,是对不同类别的对象的相同行为/特性的提取。比如计算机的USB接口设备,USB鼠标,USB键盘,USB打印机,它们是不同类别,它们有相同特性:支持USB接入计算机。所以可以抽象出它们的共同继承接口:USB.
  • 为面向对象的最最最好的理念——多态,打下坚实的基础。
如何实现继承

继承的实现有两种:类的继承,接口的继承。

这两种继承实现有不同的使用环境,所以我们引申出另外一个常见的问题:抽象类和接口有什么异同?这个问题在很多面试场景也会出现。

抽象类和接口的相同点:

  1. 都不能直接实例化。
  2. 都可以继承实现一些方法属性等。

抽象类和接口的不同点:

  1. 很重要:抽象类继承方向是相同的物种/类别的抽象,接口是一种规范或者是相同的行为/动作/特性的抽象。这一点很大程度决定了选择抽象类还是接口,这可以作为一个基本的准则把。
  2. 抽象类只能继承一个,接口可以继承多个。
  3. 抽象类可以实现一些具体的方法,属性,接口不能。
  4. 接口可以很好地实现回调,抽象类有局限性。因为抽象类只能继承实现一个。
  5. 抽象类定义好以后,很容易修改;通常接口定义好后不能再修改,
继承的原则
  • 上边的抽象类和接口第一条不同点作为选择抽象类和接口的基本原则。
  • 接口不变原则。定义好接口后,就不要变化了。否则第三方之前实现的接口会出问题。
  • 面向抽象/接口编程。接口抽象行为,抽象类抽象类别/种类,抽象出各种变化,让实体和实体之间依赖抽象,而不依赖具体。
  • 接口定义一般以I开头,所有定义“Public”。
  • 预计可能会出现版本问题的时候,建议使用抽象类。如果采用接口,会强制要求旧版本的实现重新修改和编译,抽象类则不会,增加的方法可以使用虚方法来实现默认方法。

三、多态

多态:多种姿态,并不是多种态度,呵呵。。多态个人认为是面向对象开发/设计的精髓所在。

也正是因为有了继承,那么我们的子类就可以以各种姿态出现(父类,父类的父类,基类,接口),这就是面向对象的多态。

多态的意义
  • 应对变化。有了多态从而让我们的开发,并不直接依赖具体的子类/实现,只需要依赖基类/接口,通过配置的方式,我们可以让依赖的对象传入基类/接口的不同实现,就可以赢多各种各样的需求变化,从而实现以不变(接口/基类)应万变。
  • 增强扩展。因为不同的实现可以以相同的姿态出现,所以我们定义一个插件接口,这些插件就可以是想各种各样的功能进来,增强程序的扩张性。比如Visual Studio的Resharper,Nuget等插件。
  • 设计模式基础。各种丰富多彩的GoF设计模式基本上都是基于多态而生。
多态的实现

多态的基础是继承,多态的实现也就是继承的实现,分两种:类继承,接口实现。

多态的原则
  • 通常类继承的多态,会定义一些虚方法或者抽象方法。以便子类重写方法。
  • 因为继承是多态的基础,所以一定要遵循继承的原则,实现好继承才能实现好多态。