Azige 的面向对象哲学(一) —— 类与对象

20150227修订

前言

  很久之前就打算想写一篇这样的文章,不过一直太懒了没动力写。现在总算是下定决心打起精神来写了,虽然有没有人看就不知道了……

  一直以来,本人思考过很多种理解面向对象的方法,但是一直都没有在数学领域找到合适的理解方式,这也难怪,因为面向对象的思想早已超出了数学的领域了。本文将试着描述从哲学的角度来理解面向对象的思想的方式,这可能不是最正确的认识面向对象的方式,但是对于初学者可能还是可以起到帮助的。

  本文假设读者有一定的编程基础,了解 C 和 Java 语言,因为本文将会用这些语言的代码举一些例子。

一切皆对象

  标题的这句话即使是尚未接触过面向对象的程序员可能都有所耳闻,而经常使用面向对象的语言进行开发的程序员可能已经听烂了,不过,真正理解了这句话的程序员恐怕不多。本人曾经听一位老师说,将 object 翻译成“对象”是一个很失败的翻译, object 这个单词有很多意思,目标、宾语、客体、对象,但是以上这些都不是 OOP 里的那个 object 的意思,那么这个 object 究竟是什么意思?对, object 这个单词还有一个意思,就是“物体”。“物体”更通俗的说就是“东西”,如果说“一切都是东西”,相信没有任何人会不理解这句话,有什么东西不是“东西”呢?用哲学的术语来描述, "object"就是“事物”,是对任何一种实在的最抽象的描述。

  为了保持一致性,之后本文还是会用对象这个词来称呼 object ,但是也希望读者能够记住 object 的真正的涵义。(附带一提, OOP 在繁体中文世界中被翻译为“物件導向程式設計”,是不是比“面向对象编程”要好一些?)现在,我们能够用“对象”这个词来描述任何实在,就像我们能用“东西”这个词来称呼任何东西,但是相信没有人会这样一直做。我们不会对朋友说“请给我一个东西”,因为这样根本没有意义,朋友根本不知道该给我们什么东西。世界上有很多种事物,每一种事物都会有它的名字,我们也能认识到每一种事物的性质,这就是这种东西的 概念 。认识一种概念,我们就能够认识在这种概念之下的所有事物,无论这些事物有着怎样的 特性 ,它必然拥有此概念所描述的 共性 。例如,你对你的朋友说“我要一支 ”,那么无论朋友交给你的是 钢笔 还是 铅笔 ,你都能知道,你朋友给你的这个东西能够用来 写字 ,除非你与你的朋友对 的概念认识不一样。

从面向过程到面向对象

  先来一个传统的 hello world 的例子,比较一下 C 与 Java。

// C
printf("hello world!");

// Java
System.out.println("hello world!");

  很多人初学的时候都会抱怨 Java 的 hello world 程序又臭又长(这里甚至还省掉了又臭又长的类定义和主方法的定义),而 C 看起来就挺简洁的。但是 Java 设计成这样并不是没有理由的,来看看一般的初学者可能没写过的 hello world 程序。

向文件输出 hello world:

// C
FILE* f = fopen("out.txt", "w");
fprintf(f, "hello world!");
fclose(f);

// Java
PrintStream out = new PrintStream(new FileOutputStream("out.txt"));
out.println("hello world!");
out.close();

向内存输出 hello world:

// C
char buffer[20];
sprintf(buffer, "hello world!");

// Java
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
PrintStream out = new PrintStream(buffer);
out.println("hello world!");
out.close();

向网络输出 hello world:

// C
// 对不起,本人没试过

// Java
Socket socket = new Socket("127.0.0.1", 6789);
PrintStream out = new PrintStream(socket.getOutputStream());
out.println("hello world!");
out.close();
socket.close();

  尽管 Java 看起来可能还是又臭又长,但是我们可以发现一点好处了:在 C 中,你得记住 printf 函数,fprintf 函数,sprintf 函数,以及不知道什么时候会遇到的 Xprintf 函数,而在 Java 中,对于初学者来说只要记住 PrintStream 的 println 方法就能向任何地方输出字符串了。

抽象

  回到上上一节的关于笔的话题,为了输出一些字符串,我们也需要编程语言给我们提供一只“笔”,在 hello world 程序中,这只“笔”是 C 的 printf 函数系列以及 Java 的 PrintStream.println(注意一下,System.out 是 PrintStream 类型的)。假设我们有一只铅笔,我们可以用它在纸上写字,也可以在合适的桌子上写字,甚至能在墙上写字,这在生活中是理所当然的事情。然而在 C 中,我们却不得不用“纸上写字专用铅笔”在纸上写字,用“桌上写字专用铅笔”在桌上写字,用“墙上写字专用铅笔”在墙上写字,这在生活中应该是一种很糟糕的体验。在 Java 中,“能写字的东西”被抽象成了 OutputStream,而 PrintStream 能向任何的 OutputStream 以 println 的方法输出字符串,无论是文件还是内存或者是网络都一视同仁,就如同我们的铅笔能在任何合适的东西上写字一样。这就是对“能写字的东西”的抽象的好处。

用类来描述概念

  为了描述事物的概念,大部分 OOP 的编程语言使用的是类(Class)。类是一种概念,而类的实例被称为对象,从类到对象的过程称为实例化(Instantiation);反过来,对象是一种实在,对象可以被抽象成类,从对象到类的过程称为抽象化(Abstraction)。一个类的任何对象都应当能够访问类中所定义的成员,这是这个类的对象的 共性,而方法的具体实现、对象的字段的值则是某些或某个对象的 特性

  再看一下之前的几个 Java 的 hello world 程序,PrintStream 是一个 Java 类(全名是 java.io.PrintStream),而每个程序都用到了一个 PrintStream 的对象,除去第一个是平台提供给我们的能向标准输出流输出的 System.out,后边的都是我们自己构造的。这些对象的 共性 是能够使用 println 方法向目标输出字符串,而 特性 则是每一个对象的输出目标都不相同。另外,FileOutputStream ByteArrayOutputStream 以及由 Socket.getOutputStream 方法返回的对象的类型,他们都派生于一个更抽象的类型,也就是“能写字的东西”——OutputStream,这是泛化(Generalization),下一章再提这个内容。

  总结一下,类描述了一类的对象的概念,如果我们能够认识一个类,我们也就能够认识这个类的所有的对象,能够在这些对象上访问这个类所定义的成员。类的作用并不是将数据与方法打包,也不是为了封装,最重要的是为了让程序员正确认识对象而定义了一种概念,为了让程序员使用对象而约定了一种类型。

© 2014-2020 Azige

知识共享许可协议
本站文字,除去已附加许可证的代码以外,均采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。