面向对象程序设计理念
面向对象程序设计并非是面向对象编程语言出现才有的概念,实际上早在这之前人们就在各种编程语言中实践着面向对象程序设计。比如很典型的C语言中的文件读写函数,代码如下:
int fh = fopen(filepath);
fread(fh, buff, length);
fclose(fh);
fopen会在内核中创建一个文件对象,也可以称之为内核对象,并且返回一个唯一id,这个id我们称之为文件句柄。所有的文件读写操作都必须传入一个有效的文件句柄。我们仔细分析这一段代码,不难发现,这已经就是面向对象编程了。虽然在语言层面,这种写法和后来的面向对象语言还有较大差别,但在运行时的数据结构方面,和后来的高级语言所生成的数据结构完全一致。人们发现运用面向对象程序设计,能提高代码的可读性和可重用性,特别适合开发大型应用,尤其适合对大型应用进行拆解,对其进行模块化开发,这就大大地提高了开发效率。或者我们可以用一句话概括,正是由于面向对象语言的出现,才使普通程序员也能轻松驾驭大型企业级开发。这在以前是无法做到的。也由此诞生了“码农”这个概念,意为专业水平不高但也能胜任开发工作的程序员,这在以前是没有的。
基于原型链的面向对象程序设计
尽管面向对象程序设计大大地提高了程序开发效率,但是在一些专业人士看来,它也并非是完美无缺。比如JavaScript的发明者就是这么认为的。当公司要求仿照Java设计一个在网页客户端运行的用以验证客户输入是否正确的脚本时,他琢磨来琢磨去,觉得作为一门脚本语言,如果完全照搬Java的设计理念,显然太重了。另一面,Java对于函数的可复用性也并没有达到他理想的预期。我们不妨写一段伪代码,开看看为何Java对于函数的可复用性还有哪些不足之处。代码如下:
class A{
public void Show()
{
}
}
class B : A
{}
class C : A
{}
B b = new B();
C c = new C();
b.Show();
c.Show();
在上面的代码中,b.Show()和c.Show()显然不是同一个Show。也就是说,在内存中,存在两份Show的拷贝,如果我们new出更多对象,Show的副本也会随之增加。在JavaScript作者眼中,这显然是不合理的,他认为在自己新设计的脚本语言中,不应该出现这样的情况,他认为在内存中,只应该存在一份Show的实例,其他所有对Show的调用都是对该实例的引用。为此,他专门设计了原型链这样一个数据结构,并且果断抛弃了面向对象中类的概念。在他看来,只有抛弃类,才能更大程度地复用代码。在这里,我们用原型链来实现一下对Show的复用。代码如下:
var a = {
Show:function(){}
}
var b = Object.create(a);
var c = Object.create(a);
var d = Object.create(a);
b.Show();
c.Show();
d.Show();
在上述代码中,不管你基于a创建多少个实例,始终都只存在一份Show的副本。在JavaScript作者看来,这才是更为理想的代码复用。在基于原型链的面向对象中,所有的对象都挂载在一个树形结构上,根节点为一个Object类的实例,所有该树结构上的节点对Objcet上的方法调用都是引用根节点。例外的是Object.create(null),它单独存在,我们也可以说它和根节点平级。将上述代码和面向对象代码仔细对面,我们不难发现,基于原型链的面向对象实际上是面向对象的一个阉割版。原型链这样一个结构我们完全可以在面向对象编程中用同样的链式结构进行实现,也能达到一样的复用效果。但是如果我们在原型链的基础上去实现面向对象的特性,就会发现代码看上去非常别扭,许多知名的JavaScript专家都极力不推荐这样做。
作者为什么要这么做?那么,我们再回顾一下上文的论述就不难理解了。由于作者所设计的是一门脚本语言,而并非一门编译语言,因此它肯定要足够轻量,否则会加重解释器的负担。为此,作者专门设计了原型链来最大限度地复用代码。那么,到此为止,对于“函数是一等公民”,你是否又有了新的认知呢?或许我们也可以换一种讲法,就是“一个函数,只有一个副本”,你是否也这样认为呢?
好了,就到这里吧,希望你有所收获。
网友评论