什么是面向对象

面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是种具有对象概念的程序编程典范,同时也是一种程序开发的抽象方针。它可能包含数据、属性、代码与方法。对象则指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。在面向对象程序编程里,计算机程序会被设计成彼此相关的对象。

面向对象程序设计可以看作一种在程序中包含各种独立而又互相调用的对象的思想,这与传统的思想刚好相反:传统的程序设计主张将程序看作一系列函数的集合,或者直接就是一系列对电脑下达的指令。面向对象程序设计中的每一个对象都应该能够接受数据、处理数据并将数据传达给其它对象,因此它们都可以被看作一个小型的“机器”,即对象。目前已经被证实的是,面向对象程序设计推广了程序的灵活性和可维护性,并且在大型项目设计中广为应用。此外,支持者声称面向对象程序设计要比以往的做法更加便于学习,因为它能够让人们更简单地设计并维护程序,使得程序更加便于分析、设计、理解。反对者在某些领域对此予以否认。

当我们提到面向对象的时候,它不仅指一种程序设计方法。它更多意义上是一种程序开发方式。在这一方面,我们必须了解更多关于面向对象系统分析和面向对象设计(Object Oriented Design,简称OOD)方面的知识。

许多流行的编程语言是面向对象的,它们的风格就是会透由对象来创出实例。

重要的面向对象编程语言包含Common Lisp、Python、C++、Objective-C、Smalltalk、Delphi、Java、Swift、C#、Perl、Ruby 与 PHP等。

基本组成

面向对象编程通常共享高端编程语言的低端功能。

可用于建构一个程序的基本工具包括:

变量 能存储一些内置类型的信息如整数与字符,也有些是数据结构像是字符串、串列与散列表等包含内置或复合的变量如指针。

程序:也称为函数、方法或例程,是指输入数据产生输出结果,现代语言还包含结构化编程结构如程序循环与条件。

类和对象

支持面向对象编程语言通常利用继承其他类达到代码重用和可扩展性的特性。

而类有两个主要的概念:

类(Class):定义了一件事物的抽象特点。类的定义包含了数据的形式以及对数据的操作。

对象:是类的实例。

其中类(Class)定义了一件事物的抽象特点。类的定义包含了数据的形式以及对数据的操作。

举例来说,“狗”这个类会包含狗的一切基础特征,即所有“狗”都共有的特征或行为,例如它的孕育、毛皮颜色和吠叫的能力。类可以为程序提供模版和结构。一个类的方法和属性被称为“成员”。

我们来看一段伪代码:

类狗
开始
    公有成员:
        吠叫():
    私有成员:
        毛皮颜色:
        孕育:
结束

在这串代码中,我们声明了一个类,这个类具有一些狗的基本特征。关于公有成员和私有成员,请参见下面的继承性的内容。

对象(Object)是类的实例。对象有时会对应到现实世界中的事物,举例来说,一个图形程序可能有圆形、矩形与画面等对象,一个在线购物系统可能有购物车、顾客与产品等类。

有时对象会表示更抽象的实体,比如一个被开启的文件或是一个提供美国惯用量测转换的服务。每个对象就是一个特定类的实例(例如,名称是“玛丽”的对象可能是类雇员的一个实例)。程序在面向对象编程当中被视为方法,变量被视为成员或属性。

例如,“狗”这个类列举狗的特点,从而使这个类定义了世界上所有的狗。而莱丝这个对象则是一条具体的狗,它的属性也是具体的。狗有皮毛颜色,而莱丝的皮毛颜色是棕白色的。因此,莱丝就是狗这个类的一个实例。一个具体对象属性的值被称作它的“状态”。(系统给对象分配内存空间,而不会给类分配内存空间。这很好理解,类是抽象的,系统不可能给抽象的东西分配空间,而对象则是具体的。)

假设我们已经在上面定义了狗这个类,我们就可以用这个类来定义对象:

定义莱丝是狗
莱丝.毛皮颜色 : 棕白色
莱丝.吠叫()

我们无法让狗这个类去吠叫,但是我们可以让对象“莱丝”去吠叫,正如狗可以吠叫,但没有具体的狗就无法吠叫。

类和对象就好比是“实型”和“1.23”,“实型”是一种数据的类型,而“1.23”是一个真正的“实数”(即对象)。

所有的“实数”都具有“实型”所描诉的特征,如“实数的大小”,系统则分配内存给“实数”存储具体的数值。

三大基本特征

封装性

具备封装性(Encapsulation)的面向对象编程隐藏了某一方法的具体运行步骤,取而代之的是通过消息传递机制发送消息给它。封装是通过限制只有特定类的对象可以访问这一特定类的成员,而它们通常利用接口实现消息的传入传出。

举个例子,接口能确保幼犬这一特征只能被赋予狗这一类。通常来说,成员会依它们的访问权限被分为3种:公有成员、私有成员以及保护成员。

有些语言更进一步:Java可以限制同一包内不同类的访问;C#和VB.NET保留了为类的成员聚集准备的关键字:internal(C#)和Friend(VB.NET);Eiffel语言则可以让用户指定哪个类可以访问所有成员。

具备封装性(Encapsulation)的面向对象程序设计隐藏了某一方法的具体执行步骤,取而代之的是通过消息传递机制传送消息给它。

因此,举例来说,“狗”这个类有“吠叫()”的方法,这一方法定义了狗具体该通过什么方法吠叫。但是,莱丝的朋友并不知道它到底是如何吠叫的。

/* 一个面向过程的程序会这样写: */
定义莱丝
莱丝.设置音调(5)
莱丝.吸气()
莱丝.吐气()

/* 而当狗的吠叫被封装到类中,任何人都可以简单地使用: */
定义莱丝是狗
莱丝.吠叫()

继承

继承性(Inheritance)是指,在某种情况下,一个类会有“子类”。子类比原本的类(称为父类)要更加具体化。

例如,“狗”这个类可能会有它的子类“牧羊犬”和“吉娃娃犬”。在这种情况下,“莱丝”可能就是牧羊犬的一个实例。子类会继承父类的属性和行为,并且也可包含它们自己的。我们假设“狗”这个类有一个方法(行为)叫做“吠叫()”和一个属性叫做“毛皮颜色”。它的子类(前例中的牧羊犬和吉娃娃犬)会继承这些成员。这意味着程序员只需要将相同的代码写一次。

在伪代码中我们可以这样写:

类牧羊犬 : 继承狗

定义莱丝是牧羊犬
莱丝.吠叫()    /* 注意这里调用的是狗这个类的吠叫方法。*/

回到前面的例子,“牧羊犬”这个类可以继承“毛皮颜色”这个属性,并指定其为棕白色。而“吉娃娃犬”则可以继承“吠叫()”这个方法,并指定它的音调高于平常。

子类也可以加入新的成员,例如,“吉娃娃犬”这个类可以加入一个方法叫做“颤抖()”。设若用“牧羊犬”这个类定义了一个实例“莱丝”,那么莱丝就不会颤抖,因为这个方法是属于吉娃娃犬的,而非牧羊犬。

事实上,我们可以把继承理解为“是”或“属于”。莱丝“是”牧羊犬,牧羊犬“属于”狗类。因此,莱丝既得到了牧羊犬的属性,又继承了狗的属性。

我们来看伪代码:

类吉娃娃犬 : 继承狗
开始
    公有成员:
        颤抖()
结束
类牧羊犬 : 继承狗

定义莱丝是牧羊犬
莱丝.颤抖()    /* 错误:颤抖是吉娃娃犬的成员方法。 */

当一个类从多个父类继承时,我们称之为“多重继承”。如一只狗既是吉娃娃犬又是牧羊犬(虽然事实上并不合逻辑)。多重继承并不总是被支持的,因为它很难理解,又很难被好好使用。

ps: java 只支持单层胡继承,但是支持实现多个接口。

多态

多态(Polymorphism)是指由继承而产生的相关的不同的类,其对象对同一消息会做出不同的响应。

例如,狗和鸡都有“叫()”这一方法,但是调用狗的“叫()”,狗会吠叫;调用鸡的“叫()”,鸡则会啼叫。

我们将它体现在伪代码上:

类狗
开始
    公有成员:
        叫()
        开始
            吠叫()
        结束
结束

类鸡
开始
    公有成员:
        叫()
        开始
            啼叫()
        结束
结束

定义莱丝是狗
定义鲁斯特是鸡
莱丝.叫()
鲁斯特.叫()

这样,虽然同样是做出叫这一种行为,但莱丝和鲁斯特具体做出的表现方式将大不相同。多态性的概念可以用在运算符重载上,可以根据需求查看相关界面。

抽象性

抽象(Abstraction)是简化复杂的现实问题的途径,它可以为具体问题找到最恰当的类定义,并且可以在最恰当的继承级别解释问题。

举例说明,莱丝在大多数时候都被当作一条狗,但是如果想要让它做牧羊犬做的事,你完全可以调用牧羊犬的方法。如果狗这个类还有动物的父类,那么你完全可以视莱丝为一个动物。

基本原则

open close 开闭原则

LSP 里氏替换原则

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

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

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

迪米特法则(Law of Demeter,LoD)

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

参考资料

wiki