SUMTEC -- There's a thing in my bloglet.

But it's not only one. It's many. It's the same as other things but it exactly likes nothing else...

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

一直以来对于什么是AOP没有太深入的概念,直到读完白话面向智能体编程(Agent Oriented Programmig, AOP)[1]之后仍然是没有很深入的认识。也许日后有机会可以深入了解一下,不过今天想将读完该Blog之后的想法记载于此,尽管这样的思考也许挺幼稚的。我对于这些概念没有太多的知识,如果认识有误欢迎指正(通过交流获得知识我觉得是比较有效率的)。

  首先说明一下,这里的AOP当中的A是Agent,而不是Aspect。虽然目前我没有摸清楚Agent和Aspect的具体差别在哪里,但是可以肯定是有区别的。我想Aspect-OP应该是强调概念与实现的一对一映射,其中单一概念与单一职责原则比较接近,单一实现我暂时找不到比较接近的概念。关于单一实现可以这么说:我们大家都知道MVC的模式、或者一些其它的模式,这些模式其实通过我们口中说出来只是一种想法,或者说是概念。而最终形成代码的才是实现,可是也许会有很多地方会用到MVC,于是对于MVC就会有各种各样的实现。(参见“白话面向智能体编程(Agent Oriented Programmig, AOP)”系列第一篇一开始引用的文章The Ted Neward Challenge (AOP without the buzzwords)[2],原文的链接无效,这个是google出来的。)引用[2]的作者在文中提到他的研究当中最夸张的情形是,在某个软件当中有一种设计上的概念竟然有10万个(次)实施。(In one system I've studied, the concept:implementation ratio for that single requirement is well in excess of 1:100,000. )也许这么说还比较难理解,那么我们说说记录异常(to log failures),可能我们会在软件当中随手写道:
MessageBox.Show(ex);
又或者
logfile.WriteLine(ex.Message);
还可能是
myLogger.LogObjectTypeA(ex, objectA);
myLogger.LogObjectTypeB(ex, objectB);
...

  而事实上将错误记录下来应该是一个问题,或者一种设计上的概念,然而却会在软件当中存在多种不同的实施方案。这种情况对于维护来说可能是一件相当麻烦的事情,因为本来都是同一个概念里面的东西,如果我们因为概念上的问题需要进行更新维护,可能就需要对所有不同的实现进行维护,这将会是一件相当烦人的事情。

  上面说的是我对Aspect-OP的理解,那么Agent-OP呢?我想“白话面向智能体编程(Agent Oriented Programmig, AOP)”的系列说的还是比较容易理解的,其大意是,我们需要一个跟真实世界更加接近的模型。在Agent-OP里面有很多与OOP不一样的思考方向,例如“有心智”或者说“有目标感”,我想这一点是比较重要的概念。说到OOP我们自然能够想到“对象”,对象的其中一个作用是划定知识界限——什么属于这个对象以内的(比如对象当中的成员),什么属于这个对象以外的。这一概念对于软件界是一个里程碑,应当说是非常有意义的。然而我们也应该能够体会到OOP本身并没有对“驱动力”进行探讨,而事实上目前的软件中的“目标感”更多地是通过下列两种力量驱动的:用户,以及软件背后的设计。

  用户驱动并没有什么问题,毕竟使用软件的是人。然而通过软件背后的设计来驱动“目标感”,我一直都觉得不是很顺畅——因为这种设计相对来说是静态的,基本上不会根据环境的变化进行相对的调整。比如引用[1]当中提到的一个“鸡块熟了之后捞起来”的例子,也许我们会设计一个ChickenClass和一个ChefClass,ChickenClass有一个事件Cooked告诉ChefObject某个CheckenObject熟了,然后我们就会有下面的代码(C# 2k5):

CheckenObject.Cooked += ChefObject.Checken_Cooked;

  这个设计的后果是,不管厨师有多忙,鸡块觉得自己熟了之后就会踢厨师一脚,这时候厨师不管情愿不情愿都得把鸡块捞起来。事实上目前的绝大多数软件中的“厨师”本身根本就没有情愿不情愿想法,只不过是一个奴隶、一个工具而已。有人会说,有的软件中的“厨师”是有心智的,但我想还是与Agent-OP是有区别的,其区别可能在于两个方面:

1、有可能这只是无意识造成的偶然结果;
2、如果是有意识设计出这样的“厨师”,很有可能并非用的Agent-OP的工具。(可能制作的方法并不规范,可能存在一些问题。)

  也许Agent-OP比较前沿,我自己也说不清楚,换一个面向过程和面向对象来说吧。首先让我们时光倒流20年,假设我们正在使用C语言。我们能够将某些知识限定到某个范围以内吗?我想可以用struct,我们可以设计一个v-table,就像C++背后做的那样。于是我们也一样可以写出类似obj->Member(obj2); 这样的东西来,例如:(obj.v_table[ID_Member])(obj2);

  可这仍然不是OOP,还有很多的问题是需要有专门的OOP工具才能够解决的。Agent-OP也一样,我们目前如果要制作一个Agent-OP的软件,工具还是相当缺乏的,甚至连人的思想也相当缺乏的。不要忘了,光有OOP的工具,如果使用的人还是以面向过程的方式去思考和设计,最终出来的东西也不是OO的。我想Agent-OP也一样,例如让我来使用Agent-OP的工具兴许就弄不出什么花样来。

  我为什么会觉得需要Agent-OP呢?因为Agent-OP应该能够提供一定的自适应能力(比如什么时候鸡块算是熟了,什么时候我有时间去处理,以什么样的方式去处理等),有了自适应性才有可能提供自组织能力。没有自组织能力的系统,其整个设计实际上仍然是硬梆梆的。关于这点我们可以用制造行业的发展来做对比:现在的制造业有一种较新的概念叫做“柔性制造”,与此相对的是“刚性制造”。对于“刚性制造”来说,生产线的各个机构的运动方向只能够或者前后或者左右,顶多可以换一个模子或者刀具,因此其任务和目标是定死的。当任务或者目标发生改变的时候,比如原来是造汽车门的,现在要制作飞机机舱门了,那么整个生产线就报废了,对于软件来讲就是需要重新设计。但“柔性制造”就不一样了,生产线上的工作机构有许多的自由度,要制作什么完全依靠工控程序的控制,因此面对任务和目标的变化,只需要更换工控程序就可以了。对于软件来说,我们也希望能够仅仅替换某一个很小的“智能部件”,就能够在一定范围内改变任务和目标,因为我们都知道软件的需求(也就是任务和目标)的改变往往是代价非常高的。要达到这一目的,“智能部件”必须能够自行适应其外部的系统状态,或者说能够与系统的其它部分自行组织起来。要做到所有这些,以目前的技术条件看起来是相当困难的。

  说到这里,也许已经看到我对于Agent-OP的理解跟引文[1]中所述的有一定的出入了。正是因为这些出入,我对于原文的某些观点有一些不同意之处,且容我细细说来:

痒处一:OO并没有对现实世界中的实体加以区分
  引文[1]的作者认为需要区分“发票”和“员工”,因为“发票”是没有智能的死物,而“员工”是有智能的活物。死物只是被动的接受各种的操作,而活物除了会主动的进行操作之外,还有可能主动的进行调整,甚至是拒绝操作。
  然而我认为现实世界如此,并不代表虚拟的世界也必须如此。难道虚拟世界就不能够如同动画片或者科幻片中的想象那样,存在具有思想感情的“发票”吗?更尖锐一点说“机器(人)”是死物呢,还是活物?反过来也一样,“员工”一定是活物吗?还是那个例子,如果你家里有一个机器人员工你打算怎么办?当然,这样的例子比较变态,但是我想从中说明,虚拟世界的对象是否应该具有心智,并不取决于现实世界中影射的对象是否具有心智。而对于作者说的“可以引入计时器或者多线程,但是总是因为与现实不符而不爽”是非常认同的,然而实际上引起这一问题的实质并非在于虚拟对象与现实对象在心智上的映射不能相对应。关于这一问题,我想在后面的条目中会有所揭示。

痒处二:同步和异步被人为地剥离。
  因为[1]的作者认为,操作者不应该关心到底是使用同步方式还是异步方式,这个问题应该由被操作对象自行决定。
  然而我觉得这种想法可能存在问题。
  问题一:被操作对象必须具有心智才能够决定到到底是同步还是异步,假如被操作对象是一张发票,那么这如何进行解释?
  问题二:事实上现实世界绝大多数情况下都是以异步的方式进行的。老师说“同学们翻开第78页”,然后无论同学们翻不翻书,老师都会继续说下去。上司向下属布置任务,也只有上司在不停的说,决不会每一句话都等下属确认“明白”之后才说下一句话。此外,现实世界如果需要进行同步,通常都是双方都有决定权的。老师说“同学们翻开第78页”,如果哪个同学还没有翻到那一页,深怕漏听了哪一句话,肯定得说“老师等一下,我还没有翻到那一页”。(高考复习给出重点复习内容的时候大家没少遇到这种情况吧,大学期末考试老师给出考点的时候也没有少遇到这种情况吧?)而上司如果深怕下属没有想明白某句话的时候,也会问下属“明白了没有?”或者“有没有什么问题?”,等到下属说“明白”或者“没问题”之后才继续说下去。
  问题三:其实现实世界当中的同步是通过等待他人“虚拟”出来的概念,正如计算机世界的异步是通过独立线程“虚拟”出来的概念。只不过目前对于计算机世界来说,被调用方向要主动要求调用方等待(或者不要等待)是一件较为困难的事情。除了软件方法当中缺乏这样的概念和工具之外,这也对调用方提出了更高的知识要求:原来只要知道“同步”(或者“异步”)调用对方的处理方法就能够完成工作,而现在却还需要知道如果对方要求“异步”(或者“同步”)的话怎么办。较为简单的办法是调用方继续假装一切按照同步的方式进行,但是这样的话有何意义呢?反过来也有类似的问题。当然,问题总是能够解决的,只是这种解决所付出的代价和获得的回报之间是否存在利益。很明显,以目前的技术条件不可能在所有的操作面前我们都能够从中获利。(假如是工作流引擎的话,可能性会比较大,毕竟工作流比较贴近现实,异步的操作实际上比较多。)

痒处三:无法自然地模拟现实世界中的感知能力(Sensebility)
  引文[1]作者认为,目前软件的问题在于缺乏感知能力。例如:“如果鸡块的颜色由肉色转变至金黄色,俺就必须做出相应的操作/处理:把鸡块捞出锅来。”而目前的软件设计更多的是有鸡块引发“熟了”这个事件,然后因为“我”订阅了鸡块的“熟了”事件因而此时被鸡块“踢了一脚”,“我”就立刻按照程序把鸡块捞出来。从这种描述看来,“我”并不是通过观察“鸡块”的颜色是否变黄来判断“鸡块”是否“熟了”。
  而我认为这种想法也很有问题(也许只是作者的例子举的不恰当)。

  首先,这里并不是没有进行观察,只是观察的是一个现实世界当中不存在的观察量。假如我换一种方法,变成订阅“鸡块”的颜色值变化事件(BackColorChanged),当颜色值发生改变的时候鸡块就引发事件。而如果“我”发现“鸡块”的颜色偏黄到一定的程度((BackColor.R + BackColor.G)/BackColor.B > threashold),“我”就捞起“鸡块”。怎么样,现在改成观察鸡块是否变黄来决定鸡块是否该捞起来了吧?可是你一定还是觉得不对,因为根本原因不在观察什么,而在于“我”被鸡块“踢了一脚”。
  在现实世界如果你被鸡块踢了一脚告诉你该捞起来了,你也许会觉得不爽,但在计算机的世界未必如此。假如你给KFC开发一个自动捞鸡块的机器,客户一定期望着鸡块熟了的时候能够踢机器一脚,让它立刻把鸡块捞起来。而且在可以有直接的、积极的、主动的方法进行沟通的情况下,为什么非得要屏蔽这一方法,舍近求远舍本求末的采取间接的、消极的、被动的方法进行沟通呢?现实世界的鸡块之所以不会再熟了的时候踢你一脚,是因为现实世界的鸡块确实是死物,并不是你不期待它会踢你一脚。如果鸡块炸糊了,你会感到更不爽的。再举一个例子,为什么现实世界的烧水的水壶还带一个“哨子”,水烧开了就叫呢?就是因为实际上人们期望水烧开的时候水会“踢”你一脚,让你赶紧把火给关了。虽然水是死的,我们却希望让它活过来,不是吗?
  因此我觉得这里是本末倒置了:很多时候我们之所以希望贴近现实,只是因为我们期望它能够跟现实当中那样方便;而如果现实当中的情况不符合我们的期望的时候,我们就不应该按照现实中的那样去做。

  其次,这个例子之所以能够引起我们的感触,是因为我们需要对“鸡块”的颜色做出判断,但是计算机竟然不需要。而且现实当中每个人对于“鸡块”的颜色变成什么样了才算熟,并没有一致的答案。其根本原因在于现实世界有很多很多的“量”,大体可以分为两种“可观测量”和“不可观测量”。鸡块的便面颜色属于可观测量,是否熟了属于不可观测量。于是我们会面对根据可观测量猜测不可观测量的问题,而每个人之所以会有不同的答案,原因在于猜测过程当中需要用到很多知识,也包括经验。每个人的知识不同,因此猜测的准确度就不可能相同。在这个例子当中,我们的根本任务是根据不可观测量的变化作为进一步操作的依据,也就是说“如果鸡块熟了,就捞起来”。但是“熟了”这个量不可直接测得,因此只好根据可观测量猜测不可观测量的值。可是如果现在给你一个神奇设备,能够直接告诉你鸡块“熟了”,你还会多此一举去判断鸡块的颜色吗?也许会看一下,以防设备坏了不知道,但是如果设备没坏,我想你是不会费神去根据鸡块颜色来判断鸡块是否熟了。
  简而言之,如果软件中的鸡块能够提供“熟了”的信息,就不应该屏蔽它然后提供“表面颜色”来让厨师猜测,此举舍近求远,浪费脑细胞——除非你在设计一款猜谜游戏,玩家扮演的是“厨师”这个角色……
  当然,从另一个角度看,我们也确实可以看到,现在常见的商业软件方法并没有“不可观测量”这个概念(但是在科学计算里面经常可以看到,不过也不是作为软件设计概念存在的),更不要说对此有什么解决方案了。如果我们正在设计的部分属于跟现实世界进行互动的部分,那么这一概念是不能够忽略的。对于工作流引擎来说就是这样的一种情况——什么时候属于“完成”了或者“出状况”了,有的时候并没有一个直接可观测得标志能够表明这一情况。如果软件足够智能,就应该考虑根据可观测量对不可观测量进行猜测的能力。

  最后,作者在文中接近最后所提出的一个说法,我想要提出比较强烈的异议。作者说:
  “在现有的delegate解决方案中,我们只能针对实例(Instance)进行注册,而不可以针对类型(Type)进行注册。……如果能够提供针对类型的注册机制,只要将俺的后续操作到鸡块类上注册一次,在感知范围内的所有鸡块,管他是十块还是二十块,都能被俺感知到颜色上的变化并执行正确的后续操作,这样会来的更简洁,更自然。
  首先,至少在.NET里面类型上面完全可以有delegate/event,比如说AppDomain.UnhandledExceptions就是类型上的事件,针对类型进行注册在.NET里面是完全可能的,并且不难看到。因此说这个并不是OO的问题,顶多属于某种语言内部的问题。其次,这种通过注册到某个类上面来对所有正在炸的鸡块进行观测,是一种很不明智的做法。因为这不属于类型上面的知识范畴,而应该属于某个锅以内的鸡块集合的范畴。假如现在有三个锅正在炸鸡块,那引文[1]作者的想法可就得挨屁股了。

  不过话又说回来了,我非常认同作者说的其中一点,那就是:不应该由鸡块决定什么时候被捞起来,而应该由厨师来决定,尤其是要根据他自己所处环境的其他状况来决定——海啸来了还管鸡块干什么,赶紧逃命吧!这是目前的模型所欠缺的,不管厨师的情况如何,鸡块熟了都会引起厨师的相应动作。当然了,你可以这么写:

PriorityQueue needToBeFishedOut = new PriorityQueue();

private void Chef_ChickenCooked(object sender, EventArg e)
{
    needToBeFishedOut.Enqueue(sender);
    needToBeFishedOut.BringUpPriority();
}

然后再弄一个线程或者计时器进行相应的处理。然而正如作者所言,很不爽。好比让你用C来写真正OO的程序,用asm来写网页,或者用小刀来刻硬盘。工具不对,做起来既费劲又不规范,因此Agent-OP确实需要发展。

最后,我还是要补充一句,OOP和Agent-OP属于不同层次的概念,因此Agent-OP和OOP的关系更像是Class和Method的关系,前者不可能替代后者,并且应该在后者的基础上发展。(我甚至觉得Agent-OP已经不属于语言范畴的东西了,不一定会出现像OO出现之后,世界上所有语言都向OO的方向发展那样的壮观场景了。)

posted on 2005-07-12 14:49  Sumtec  阅读(4795)  评论(9编辑  收藏  举报