首页 > 代码库 > 面向对象程序设计——序言之基本原则

面向对象程序设计——序言之基本原则

从汇编语言、C语言到Java、C#、Python,编程语言一直在不断发展与演进。与此同时,程序设计思想也从面向过程、面向对象、面向服务、面向模式等方面不断被探索。然而归根结底,这些产物的目的,都是为了更方便地解决不断遇到的越来越复杂的问题。而且,这些内容也并非是互斥的。

  例如,在一个复杂的系统,我们同时需要满足实际功能的快速高效以及管理模块的高可扩展、可维护,那么较为明智的选择是通过低级语言来实现基础的功能,而通过高级 语言来构建管理能力,使其协同工作来提供系统能力。

  再如,在一个面向服务的系统中,我们以服务的形式提供对外体现,但是其内部逻辑则可以通过面向对象的方式来构建,而其更细粒度的如方法的实现等则必然是面向过程的。

  在此处,我们将聚焦于面向对象的程序设计,交流一下面向对象相关的概念及方法论,以便大家共同提高,携手在软件领域走得更远。


  首先是面向对象的程序设计原则。这些原则都是前人智慧的结晶,位我们构建优秀的面向对象系统提供了方向和依据。当然,大多数情况下,我们可能并不能同时满足所有的原则,即便它们是原则,具体的问题仍需具体分析。

  对于这些原则,普遍的认知也并不完全统一,这里以我个人更偏向的七大原则来探讨。

  1. 开闭原则(Open-Closed Principle,OCP)

      对修改关闭,对扩展开放。

      我理解这是诸多原则中最重要的原则,可以认为是原则的原则,其他的原则也都是围绕此原则展开。当然万事也无绝对,这个开放与关闭的程度也需要具体看待。

  2. 单一职责原则(Single Responsibility Principle,SRP)

      每个元素都应聚焦于自己的能力,而不应该提供超出自己边界的能力。

      很多人认为可以通过趋势内容变更的因素来判断是否满足此原则,即是否存在多个不同的理由来促使内容更改。我个人是认可这种观点的,但判断起来相对繁琐。

      单一职责原则要求我们认清边界,这个边界可以是系统边界、模块边界甚至类型的边界、接口的边界。认清边界对于整个软件系统是非常重要的,边界不清会导致系统更难演进与维护,非常可能导致系统进入一个恶性的循环(因为边界被破坏导致需要花费超高的成本来维护原本不应该由自己维护的内容,而在此期间对边界却又进行了更严重的破坏)。

  3. 最少知识原则(Least Knowledge Principle,LKP)

      这个原则可以从两个方面来理解。首先一个元素应该尽量少地了解其他元素的细节;同时一个元素也应当提供尽可能少的细节给其他元素。

      这里的元素也是多方面的,如系统、模块或类型等。这个原则要求我们在满足功能的前提下避免提供多余的信息,有时候可能因为一些不必要的信息而对我们的系统带来阻碍,或者在演进过程中因为利用了之前提供的无用信息而带来不必要的问题。

      在系统设计,尤其是接口设计的过程中,我们需要多想想这个原则。因为违背这个原则而带来的问题通常都是代码的复制、粘贴导致的。

  4. 依赖倒置原则(Dependence Inversion Principle,DIP)

      依赖于接口而不要依赖于实现。

      这个原则使我们将实现至于接口的两端,只要接口不发生变化,两端的实现是可以独立进行演化甚至替代的。比较容易理解的例子即为数据访问层,业务逻辑层完全依赖数据访问层的接口,那么在底层数据引擎发生改变时会非常容易对数据访问逻辑进行替换,在接口不变的情况下业务逻辑不需要对其进行感知。

  5. 接口隔离原则(Interface Segregation Principle,ISP)

      将提供不同能力的内容拆分到不同的接口之中,实际上该原则与单一职责原则遥相呼应。

      其实在这里有一点需要注意,各种面向对象语言都有一个共同点,即可扩展一个基类但可实现多个接口。依次我们可以很方便地理解,接口的定义与实现其实是相分离的。在定义接口的过程中我们一定要明确其职责和范围,以及提供能力的场景。而对于实现,我们可以同时实现一组有关联的接口,以使系统简单易懂。

  6. 合成复用原则(Composite Reuse Principle,CRP)

      在复用功能时首先使用合成而不是继承。

      这个原则也很容易理解,我们的目标通常是构建一个可组装可配置的系统,而非稍有变动就需重新打包编译。当然这里并不是说任何时候都要通过组合来替代继承,此时需要关注实际内容的合理性,需要以面向对象的思路来定义场景,即这个问题在实现世界是如何表现的。然后在两者皆可时,即不违背建模时,我们应首先选择合成的方式。

  7. 里氏替换原则(Liskov Substitution Principle,LSP)

      即父类出现的地方均可以子类替代。

      其实这个原则的意思为子类应是对父类进行扩展,而不应该变更父类的原有行为。在面向对象程序中,这通常是天然支持的。


  围绕这七大原则,前人同时总结了多种设计模式,如大家熟知的23种设计模式,当然随着时代的发展,当前的设计模式可能远远超过这个数字。而对于设计模式,我们可以认为其就像数学中的公式一般,并非没有它们就无法解决问题,而是利用它们我们可以更方便、更快捷、更准确地解决问题,同时也可以构建出更容易理解的系统。同时优秀的设计模式是可以复制的,即可以指导其他参与者在后续遇到类似问题时的解决思路,这是一种良性循环。

  对于设计模式的应用,普遍的观点更类似于这样的三个境界。

  • 看山是山,即我们设计系统时,为了满足功能而设计,这时可能我们并不知道什么是设计模式,但是我们依旧可以利用所知所学解决问题,虽然通常这种解决方法是较为低效和不利于扩展和维护的
  • 看山不是山,此时再看到一些需求时,我们会想想或者查查是否可以通过相关的设计模式来解决这样的问题,这个时候我们通常已经认识到设计模式的可贵,并尝试不断去应用它们,以使它们服务于我们的系统
  • 看山还是山,这个时候,我们可能已经不再关注于设计模式,即当再遇到一个问题需要解决时,自然而然地就会想到应该如何解决,而通常没有考虑这是使用了什么设计模式,我们通常更向往这样的状态,无招胜有招

  后面我们再针对设计模式逐个进行探讨,以使其更好地为我们的系统服务,使我们可以构建出更好的系统。处于软件领域中,我们应当有一种骄傲。这是我们的机会,我们可以努力改变一些东西;这也是世界的机会,可以提供一些东西供我们改变。

面向对象程序设计——序言之基本原则