明辨概念:型(type)、类(class)、对象(object)、多态(polymorphism)、函数式编程(functional)

明辨概念:型(type)、类(class)、对象(object)、多态(polymorphism)、函数式编程(functional)

 

Cpp程序员面试常常会被问到如下问题:什么叫虚函数?

OO新手常常如下回答:你写个class, 里面的method前面加上virtual, 那个method就是虚函数。

这个回答跟没说一样,这就是Cpp虚函数的定义,其实就是背概念。

话说回来,这道题出得就不好。

一个绕弯子的问题是应该这样问:虚函数和多态什么关系?

标准答案是:多态是一个设计决策。如果程序员设计时需要使用多态这个feature,那么在cpp/c#里面,它用虚函数实现。在java里面不需要任何关键字,所有函数都是多态的。

言归正传,定义如下概念:

什么叫型type:型就是对于一个软件模块,它对外提供了哪些操作。那些操作的集合叫做type

如何定义type: 在java里,interface关键字用于定义一个type

什么叫类(class):类是一个软件模块,其中有field和method两样东西,其中method供这个软件模块的client使用,client不需要知道field的任何细节。例如,一个复数的class,它的client使用的method是加减乘除 add, sub, multi, divide.至于复数内部使用直角坐标还是极坐标存储,client不需要关心。

使用类有什么好处?

答:软件的模块化。也就是信息隐藏。Client只依赖method, 这样如果这个模块class实现的有错误,或者比如complex的内部存储想从极坐标换为直角坐标,client不需要改动任何代码。

什么叫做多态:

答:考虑你实现一个复数类。你有两个方式定义:

第一种:class Complex

{

  complex Add(complex c);

  complex sub (complex c);

  complex multi (complex c);

  complex divide (complex c);

}

Complex c = new Complex();

 

第二种:

Interface Operator

{

  Add();

  Sub();

  Multi()

  Divide();

}

class Complex implement operator

{

                …

}

Operator a = new Complex();

在第二种实现中,client代码中的变量a,并没有耦合Complex这个class,除了这一句new语句;其它地方都是仅仅耦合了operator这个Interface。这样,如果别人写了一个类 class Fraction implement Operator,那么client代码改写为Operator a = new Fraction ();,其它代码不用改变。也就是一套client代码,可以有多种不同的行为。这叫多态,(严格地说,这叫对象多态。因为还有另外一种泛型多态。)OO社区流行的概念"依赖注入(InversionofControl)",以及 Rober Martin在《敏捷软件开发》提出的"依赖倒置原则",指的都是这种编程风格:所有变量都用interface声明,而具体类是Fraction还是Complex可以由外界注入,这样client代码不需要任何修改。

什么叫对象:

新手会说,Class的instance叫对象。这话也跟没有回答一样,因为这也是教科书里面的原文。这个问题其实非常复杂。如果这样定义对象:有标识的东东。那么,Class的instance不都是对象。这句话怎么讲?考虑

Operator a = new Complex();

Operator b = new Complex();

当你用以java为例解决一个domain中的问题时,你可能会需要比较 if (a == b)或者比较 if (a.equals(b)).

前一种比较,是比较a 和b是否是同一个东东,也就是在比较它们的标识。 而if (a.equals(b))是在比较这两个东东的值是否相等。

如果你用以java为例解决了你的domain的问题,但是从来没有需要维持过a,b作为标识,那么你的a,b不是对象。因为它们的标识对于你这个domain没有意义。比如Complex类,如果你的domain是解决复数运算,你不太可能用到if (a == b),你经常会用到if (a.equals(b))。前者称为引用语义,后者称为值语义。Java语言没有显式区分这两个语义,程序员自己也很少显示区分这两个语义。如果代码中只出现了if (a.equals(b)) 代码,那么其实你的domain不需要a,b的标识,比如数学domain问题。只有具有引用语义的instance of a class才是object.

 

对象,封装,多态是3个独立的概念。封装指的是隐藏内部表示,instance可以响应一集message。多态指的是对于同样的message,不同的instance可以有不同的行为。消息传递不是对象的本质,消息传递仅仅是多态,可以实现软件的松耦合。对象指的是,一个东东的message的行为与message到达的时序有关或者说与你(client)调用method的时序有关。为了区分这种时序,需要维持那个东东的标识。如下例所示一个媒体播放器,a->Play()只有在加电PowerOn()之后才可以真正的工作。所以你需要维持a那个标识。

a->PowerOn();

a->Play();

请与complex比较,add的行为与sub是否被调用过无关,所以Complex的instance不是一个object。一言以毕之,对象是个状态机。

 

什么叫函数式(functional)编程:

考虑Complex的add的实现,你可以这样写(伪Cpp代码):

Complex:: add(Complexother)

{

   Return Complex( this.real+other.real, this.image+other.image)

}

也可以这样写:

Complex:: add(Complexother)

{

  this.real += other.real;

  this.image += other.image;

  return * this;

}

第二种实现,你其实modify了instance里面内部的值,而第一种实现,没有改写,而是返回了一个新的Complex的instance。考虑一下数学函数 f(x) = x +3; g(x) = x + 5;

如果你用第二种modify的方式实现那两个数学函数f(x), g(x),结果是”数学不正确”的。假设x = 6,运行完 f(6)之后,x的值已经被变了。这不符合数学这个domain的规范.函数式编程就是一个变量初始化之后不许改变它的值,这样写出来的函数跟数学函数一样。其效果就是同样的g(x),给一个x的值,每次g(x)给出的结果都是相同的。如果f(x)会改写x,那么g(x)的结果就不确定了,它取决于你计算g(x)是在算过f(x)之前,还是之后。

那么,我写一个class的method的时候,是选择赋值模式(modifier),还是非赋值模式(functional/immutable)?答案是,与你的domain有关。如果你的domain是数学,例如本文的complex,你肯定是非赋值模式,因为你的domain是非赋值的。

然而考虑

a->PowerOn();

a->Play();

PowerOn()必然会改写instance内的值,否则Play()执行的时候怎么知道PowerOn()是执行过还是没有执行过?

考虑PowerOn和Play运行在两个不同的线程中,Play就会比较困惑了,因为它和PowerOn共享相同的一个状态变量。这两个method需要加锁访问共享变量。这就是modify模式在多线程里面的问题,这就是多核时代,函数式编程流行的技术背景:可以看出,modifier模式与functional是正好相反的:如果method都是functional的,那么这个instance的状态是完全由构造函数决定的,这个instance就是无状态的,如本文中的complex类,它不是一个object。这就是CMU的Robert Harper说"OO是反并行化、反模块化"的原因,一个instance的method如果是modifier,那么那个instance不可以被多线程安全地访问。

 

 总结:

对象,封装,多态是3个独立的概念。

type用于定义软件模块的接口。class用于实现该软件模块。

封装指的是信息隐藏。

多态指的是,如果变量以type声明,那么任何使用该变量的代码,可以替换该变量为任何implement那个interface的class。藉此可以实现软件的松耦合,依赖注入等等。在静态语言中,多态以implement interface实现;在duck type的动态语言中,多态以method查找实现从而更加松耦合(Python/Ruby,本文未展开讲)。消息传递不是对象的本质,消息传递仅仅是多态,可以实现软件的松耦合。

对象指的是,程序需要维持class的instance的标识。与对象完全相反对应的是functional

 

最后,抛出一个开放问题:为什么python/java等等语言,都没有在语言关键字层面区分出对象语义与值语义?难道那些语言的发明者没有达到本文的深度么?肯定不是的。
但是,我不明白,为什么没有任何语言,显式地区分了对象语义与值语义?(Cpp是纯值语义)

posted on 2013-05-23 16:03  SimonBlog  阅读(510)  评论(2编辑  收藏  举报

导航