跳到主要内容

面向对象设计原则

面向对象是什么

面向对象程序设计(Object-oriented programming, OOP)是一种编程思想和方法。一般来说,编程语言常常会和一种或多种编程思想相结合,并提供对应的语法上的便利。例如Java便支持面向对象编程、函数式编程、过程式编程等。编程语言与编程思想并不存在绝对的对应关系,C语言一样可以按照面向对象的方法组织代码,只是更加的麻烦而已。(早期的C++通过cfont翻译成C语言进行编译)

在面向对象中,程序被视作一些对象之间互动的描述,而不是对计算机工作流程的阐述。编写面向对象程序和组织团队工作是类似的。团队中的每个成员通过分工、协作和沟通来完成预定的目标。每个成员都具有自己的资料和能力,也有共同的地方。对应于面向对象程序设计中,便是不同的对象具有自己的数据和类方法,有着相同的父类。借用生活中的概念有助于理解面向对象。

面向对象具有两个概念、三个基本特征和五个基本原则:

  1. 两个概念:类、对象
  2. 三个特征:封装、继承、多态
  3. 五个基本原则:SOLID(单一功能、开闭原则、里氏替换、接口隔离、依赖反转)

两个概念

类(Class)是对数据和数据操作的抽象集合。它如同团队中个人的简历一般。类描述了一个事物的所有基本特征,以及基于这些特征的操作,其中特征被称为类属性,基于特征的操作被称为类方法。就像人具有自己的隐私一般,类也有对类属性和类方法的选择性展示的权力,即类属性可以被声明成私有(private)、公有(public)和受保护(protected)。根据不同的声明,不同对象可以访问的类属性和类方法也是有所有区别的。正如简历会对应一个具体的人员一般,也有针对不具体人员的简历,这种情况下它仅仅是描述要求而已。面向对象中的抽象类(Abstract Class)和接口(Interface)便做的这样的事,如果你要让它们发生作用,就必须编写一个具体的类。

对象

对象(Object)是类的实例(Instance)。一个团队中只有简历是不行的,因为干活的是人而不是简历。根据简历招来的是员工,那么根据类生产出来的事物便是对象。生产这一过程,也叫做实例化。类实例化对象,对象成为完成工作的基本单元。抽象类和接口不能实例化一个对象,只有满足抽象类和接口描述的类才能让它们实例化一个对象。好比团队的招聘要求并不能生产员工,但拿着简历来应聘并成功的人肯定是满足要求的。类通过继承抽象类和实现接口来做到这一点。程序通过访问类属性和组织调用类方法来达到目标。

三个特征

封装

封装(Encapsulation)是对一系列操作的集合和隐藏,它使得外部不必去关心具体的实现。对象暴露一个类方法供外部调用,并隐藏具体的方法实现。对于外部而言,只需要关心类方法的输入和输出。如果类方法的具体实现发生了改变,也不会影响类方法的调用方。简而言之,封装隐藏了繁复的细节,简化了代码。

继承

继承(Inheritance)是让一个类成为另一个类的拓展,换言之,让一个类兼并另一个类。一个类可以只继承一个类,也可以继承多个类。前者叫单继承,后者叫多继承。不同的编程语言,对继承的限制不同,有的只允许单继承,有的允许多继承。在继承的关系中,继承者被称作子类或派生类,被继承者称为父类、基类或超类。团队通过招聘要求来筛选对应的人员,而这些人员除了满足招聘要求外,还会有额外的能力。如果团队修改了要求,应聘的人员也会发生相应的变化。因此,子类是为了拓展他的父类。

多态

多态(Polymorphism)是指同方法而不同行为。同一个父类的不同子类,其相同的方法可能会产生不同的执行效果。其原因在于子类可以重写父类方法,使之按照自身需求变化。多态的意义在于,可以在不修改现有程序结构的情况下,实现替换不同的子类来实现不同的需求。

总的来说,面向对象的三个特征提高了解决方法的抽象性,有效的简化了复杂的问题,使解决的代码更加容易被编写。

五个基本原则

单一功能

单一功能原则(Single responsibility principle, SRP)即一个类应当仅有单个的完整功能。对于类而言,并不是功能越多越好,它应当仅围绕单个功能去编写。功能越少,代码越容易编写。代码越少,调试和修改越容易。功能越单一,影响范围越小。如果需要多个功能,应当组合不同的类去完成。

开闭原则

开闭原则(Open–closed principle, OCP)即对扩展开放,对修改封闭。什么是对修改封闭?即类一旦完成,除非自身的错误,否则不应该修改现有代码。什么是对扩展开放?即类一旦完成,只能用新类继承,并添加新的方法来完扩展。

里氏替换

里氏替换原则(Liskov Substitution principle, LSP)即使用父类的地方应当能无差异的替换成它的子类。按照里氏替换原则编写的子类,能够在替换父类后不产生错误,也不影响程序的结果。因此,在程序中使用从父类继承的类方法时,可以将类型替换成父类,而不直接使用子类。在程序运行时,父类被子类替换,也不会影响代码的正确性。由于子类必然具有父类的方法,按照父类编写的方法可以被任意子类替换,这进一步避免了代码的修改。

接口隔离

接口隔离原则(Interface-Segregation principles, ISP)即对外暴露的接口应当小而具体。换言之,调用方应该使用很少的接口去完成它所期望的功能。调用方不应该去了解复杂而庞大的接口。

依赖反转

依赖反转原则(Dependency inversion principle, DIP)有两条规定:

  1. 高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。
  2. 抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。

换言之,高层次与低层次通过抽象接口相联系,抽象接口由高层次定义,由低层次实现。核心思想是面向接口编程,而不是面向实现编程。之所以说是反转,是因为传统的方法中,低层次直接被高层次使用,高层次必须依赖具体的低层次,低层次发生变化,高层次也必须发生变化。反转后,高层次依赖于抽象接口,并不与低层次直接接触,反而低层次必须按照高层次的接口去实现。这种情况下,低层次无论如何变化也不会影响高层次,但高层次改变接口,低层次就必须改变。它们的依赖关系发生了反转。