对于c++面向对象的深刻认识和理解--哲学角度看问题(源生论)

   对象论认为:数据和逻辑不是分离的,而是相互依存的。相关的数据和逻辑形成个体,这些个体叫做对象(Object),世界就是由一个个对象组成的。对象具有相对独立性,对外提供一定的服务。所谓世界的演进,是在某个“初始作用力”作用下,对象间通过相互调用而完成的交互;在没有初始作用力下,对象保持静止。这些交互并不是完全预定义的,不一定有严格的因果关系,对象间交互是“偶然的”,对象间联系是“暂时的”。世界就是由各色对象组成,然后在初始作用力下,对象间的交互完成了世界的演进。

 

开放-关闭原则(OCP):软件实体应该可以扩展,但不可以修改。

      为什么忽然扯到OCP呢?因为,OCP正是上文讨论的哲学原理在程序世界的具体表述。我们来对比看一下,到底OCP是个什么意思。
      还是上面看病那个例子,什么叫可以扩展?就是说,因为在某个抽象层次是进行表述,就不能把话说死了,不能全是这个、那个的把每个对象都指派明白。如,那句话改成“我的右脚扭到了,要去北京航空航天大学医院去看胡青牛医生”,这句话就没有扩展性可言了,所有话都说死了,你如果去的是北医三院或临沂市人民医院,那么语义就不对了,而如果找的不是胡青牛而是华佗或扁鹊,语义也不对了。为什么无法扩展?因为所有点都指定了具体的对象。
      而原话“我生病了,要去医院看医生”则扩展性很大,因为只要不违反可映射性定义,映射到任何符合条件的对象都正确。扩展性和灵活性大大提高了。所以,“可以扩展”四字从哲学上其实是要我们在设计和开发软件时提高抽象层次,不要总在具体对象层面上进行处理。这下,你明白为什么说OCP可以提高软件的可扩展性和灵活性了吧。
      再来说说“不可以修改”,因为如果随便乱改,那就天下大乱了。还是医院那个例子,“医院”这个类所映射到的对象,一定是治病的地方。如果这东西随便改,例如明天“医院”和“食堂”的概念对换了,那麻烦了,我们所有人都要改,要把两个概念从脑子中对换过来,全世界的书、报纸、Internet……凡是依赖这两者进行表述的地方都要改,那不是天下大乱么?软件世界中也会发生这种牵一发而动全身的问题。所以我们提倡设计好的类一定要“对修改关闭”。
      以上,就是OCP的哲学意义。

里氏代换原则(LSP):子类型应该能代替掉其父类型,且代替后程序运行情况不会错乱。

      我们还是用例子去理解LSP
      现代办公几乎都要用到个人计算机,个人计算机本身是一个抽象概念,台式PC是其中一个子类。后来,发明了笔记本电脑,我们想把笔记本电脑归为个人计算机的子类,是否合理呢?根据LSP,我们将台式PC都替换成笔记本电脑,世界应该是照常运行的(当然,实际情况可能复杂些,有些地方不能用笔记本电脑替换,但这里我们忽略这种差别)。我们办公时依赖的类是“个人计算机”,而笔记本电脑完全可以替代这个类型而使得世界运行正常,所以,我们说将笔记本电脑归于个人计算机的子类是符合LSP的。
      后来,又发明了转基因黄瓜,我们也想将它归到个人计算机的子类中去,行不行呢?好的,现在我们再运用LSP,将世界上每个依赖个人计算机的地方都替换成一根转基因黄瓜。好的,世界人民都疯了!明显这种替换会令世界运行错乱。所以,我们不能让转基因黄瓜继承个人计算机。
      上面的例子是显而易见的,但有些却不那么明显。例如,现在问,兽医是医生的子类吗?这个问题,一下子还真不是很好回答,但我们可以LSP一下,现在,我们把医院里的医生都替换为兽医,你还敢去医院看病吗?嗯,这下子不用我多说了吧。
      最后一定要说明的是,LSP应用于程序世界和现实世界时有很大差别的,现实世界繁杂、不确定性因素多,而程序世界简单、确定。总之,LSP就是让你记住一条,凡是系统中有继承关系的地方,子类型一定能代替父类型,而且替换后程序运行要正常。换言之,继承是一种严格的“IS-A”关系,也是“一般和特殊”的哲学原理在程序世界中的体现。

 

继承的话题就讨论到这里了。很多朋友在运用继承时有疑惑,或不能很好的确定继承关系,归其根本是没有真正理解继承的意义。只要能理解继承的本质意义,加上OCPLSP的运用,是可以写出正确的继承体系。

泛化耦合(Generalization Couple):由于泛化(继承)关系的存在,在两个有祖孙、父子关系的类间形成的一种逻辑关联。

      然后,我们讨论另一种耦合。
      在文章开始,我们说对象论将对象看做基本元素,而对象中有数据和方法。在现实世界中,数据并不总是简单数据。客观存在一些对象,它们的数据是另一个或另一些对象。例如,一个具体的羊群,有一项数据是很多具体的羊。其中羊也是对象。当抽象成抽象的“羊群”和“羊”类的时候,这种包含关系也随之被抽象到了类中,由此在两个类之间就形成了耦合。
      这种耦合出现的哲学基础是,对象本身固有的包含关系,在进行事物抽象时被同时抽象到了类中。所以,我个人将其称为包含耦合。
      包含耦合又分为两种情况,一种是被包含对象单纯聚合在包含对象中,但没有形成哲学意义上“整体与部分”的关系,这是一种相对较弱的联系,叫做聚合。例如,上例中羊群和羊就是聚合关系,如果拿掉一两只羊,羊群还是羊群。

      聚合(Aggregation):一种弱的拥有关系,体现A对象可以包含B对象,但B对象不是A对象的一部分。

      另一种情况是,被包含对象和包含对象形成了哲学意义上“整体与部分”的关系,如汽车和轮子,把轮子拿掉,汽车就不再是完整意义上的汽车了。这种关系叫做组合。

      组合(Composition):一种强的拥有关系,体现了严格的部分和整体的关系,部分和整体具有一样的生命周期。

      通过上面的探讨,我们认识了泛化耦合、聚合和组合三种耦合形式,最后,还有一种耦合叫依赖。什么是依赖呢?我们知道,在对象论中,将世界的演进看成是在初始作用力下,对象之间相互调用、相互协作完成的。如果两个类在需求范围内,既定逻辑上存在协作的可能,那么这两个类就存在依赖关系(或叫关联关系)。其实,我们常说的“低耦合,高内聚”、“降低耦合”等建议,主要是针对依赖说的。

      依赖(Dependency):由于逻辑上相互协作可能,而形成的一种关系。

      好的,到目前为止,我们已经认识了四种基本耦合。下面用一副图,直观感受一下世界的各种耦合。

5.1、耦合示例

      图5.1展示了几种耦合的示例。其中汽车和交通工具属于泛化耦合,轮子和方向盘组合于汽车,汽车聚合成车队,而汽车和司机具有依赖关系。这幅图只是耦合的一个小片段,实际上,世界上各种对象形成了一张复杂的耦合网,正因为有耦合的存在,世界才能演进。正如马克思主义哲学所说:联系是普遍的、客观的。所以,耦合的存在,有其深刻的哲学意义。

程序世界里的对象没有选择权。

      为什么会这样?因为如果对象有选择权,就没法贯彻OCP了!你要是活在程序世界里,不但给你包办婚姻,连吃饭、上学……一切的一切,你都得服从包办,对象一点点选择权也没有。至于谁给你包办的,那是后话。
      看了这些,你还敢去程序世界吗?不过这还不是最恐怖的,告诉你更恐怖的一点:

      程序世界里的对象不认识对象。

      没错,良好的面向对象提倡对象不认识对象!很不可思议?其实,这就是所谓的“低耦合”,我们喊了那么多年的“低耦合”,到底什么是低耦合?所谓低耦合,就是先剥夺对象的选择权,再剥夺对象的感觉。对象间谁也不认识谁,只知道对象能提供什么服务。

何东西用得好不好,不在于是不是熟练掌握用法,而在于是不是用对了地方。而要想用对地方,就要弄清楚这个东西的“怎么出来的”和“出来是做什么用的”。

类与接口的不同点有以下几点:
      I. 抽象范畴不同。类是对象“体征”的抽象,接口是对象行为的抽象。
      II. 抽象动机不同。抽象出类是为了帮助记忆、认识世界,抽象出接口是为了实现低耦合交互。
      III. 关注不同。类关注共同的体征,接口关注用来交互的行为。
      IV. 存在范畴不同。类存在于抽象层次树上,接口存在于接口网。
      V. 应用范畴不同。类应用于结构范畴,是静态概念,接口应用于运作范畴,是动态概念。

接口的哲学意义:对客户类的保证,对服务类的约束。

      正是接口约束了服务类必须实现什么功能,客户类才可以在不知道具体服务类的情况下“放心”进行交互,因为接口对客户类提供了一种保证。希望各位能好好体会接口的这种哲学意义,这对于对象论的良好运行体质的理解非常重要。
      可是,这样还不够,我们还有一个非常重要的问题没有讨论:谁有权利定义接口?或者说服务类和客户类谁拥有接口?当然,理论上时谁拥有都可以,但却会对世界的运作产生巨大影响。我们先看服务类拥有接口的情形。

6.4、服务类拥有接口

      如图6.4,由于服务类拥有制定接口的权利,所以各个服务类都定义了自己的接口,一般情况下他们的接口是不相容的。如图,司机可以驾驶汽车,但由于轮船、飞机各自有自己的可驾驶接口,所以会开汽车未必会开飞机和轮船,如果要开飞机或轮船还要一个个学,现实世界中就是这样一种情况。所以,这种世界的运行其实接口几乎没有起到作用,由于服务类是“大爷”,所以它们可以指定诸多霸王条款,而客户必须忍气吞声去迁就,所以,实际的依赖方向还是从客户类到服务类。
      下面在看看客户类拥有接口会是什么样子。

6.5、客户类拥有接口

      看上图,客户终于翻身做主人了,现在客户拥有定义接口的权利,服务类必须无条件实现,这下好了,只要会开汽车,就会开轮船和飞机,因为客户有权利定义一个统一的接口,服务类必须无条件实现!这样,三种交通工具的驾驶方法必须完全一致(虽然现实世界还没有这样),这回客户终于可以扬眉吐气,体会一把“顾客是上帝”的感觉了。
      在图6.5的情况下,司机可以有权定义接口,他不必“知道”服务类,而服务类必须“知道”客户定义了什么接口,你有没有发现,依赖的方向已经悄悄倒置过来了!变成服务类依赖客户类了(谁知道谁,谁就依赖谁)!这就是“依赖倒置”的由来。不必说,所谓依赖倒置原则就是让我们必须按图6.5的方式运行世界,而不能按图6.26.36.4的方式。下面正式定义依赖倒置原则。

      依赖倒置原则(DIP):客户类和服务类都应该依赖于抽象(接口),并且客户类拥有接口。

      我想,看过上述来龙去脉,已经不用我再去解释这个原则了吧。

程序世界是有这么一个统治者,他就是大名鼎鼎的“依赖注入容器(DI)”,也有人叫做“控制反转容器(IoC)”。      什么叫依赖注入?什么叫控制反转?如果你看了上面的文章,那太好理解了,依赖注入就是容器挑选符合接口的服务类为客户类提供服务。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值