白话面向智能体编程(Agent Oriented Programmig, AOP)之四

时间:2022-05-03
本文章向大家介绍白话面向智能体编程(Agent Oriented Programmig, AOP)之四,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

前段时间一直周旋于各种有趣且辛苦, 或者无趣且更辛苦的事情当中, 虽然其间也切换到其他好些不同的技术领域, 但一直没有放弃在AOP方面的关注思考. 前面之所以没有能接着AOP这个话题继续和大家聊下去, 是因为感觉自己没有足够的精力和空闲来将这些想法沉淀下来, 并回锅为简单直白, 易于消化和理解, 并能引发思考的东西. 这几天, 出差在外, 工作之余免去了灶台内外的忙活. 油烟味少了, 人也觉得清爽许多; 闲暇多了, 就免不了想爬爬格子. OK, 闲话到此为止, 就让我们继续AOP的白话之旅吧J

简单回顾一下前一段旅程(,,), 在前面我们简要介绍了什么是Agent, 这个东东的来由, 以及与其他重要概念, 如Object的区别. 如果说前段理论之旅展现的奇异风光令我们心神荡漾, 那么接下来的实践之旅就会又把我们拽回到繁杂而精密的软件世界中.

在前面也有介绍过, 目前好像还没有一个专门针对AOP的IDE, 这对于急于操练一把AOP的同志来说是个不大不小的尴尬. 不过好在Agent也是由Object脱胎而来, 基于目前的OOP语言, 我们也可以来过一把AOP的瘾. 这里我们选择的是.Net 平台下的C#. 为什么要选择C#,,,, 因为如果使用Java, 文章也就不会发在这里, just jokingJ 理论上说, 任何OOP语言, Java, Delphi, C++, 都可以作为哈里.波特同学手中的魔法棒, 点化出妙不可言的Agent. 之所以选择C#, 是看中他的优雅和简洁(补充一句, 直到现在俺依然固执地认为Delphi是最优雅的语言, 虽然Borland迫于生计不得不出售整个IDE部门, 但俺依然决意守望Delphi, 直到Delphi的桅杆没入海平面的最后一刻…)

翻翻讲述Agent理论的书籍就可以查到, 一个为理论界所接受的Agent需要具有如下若干特征:

l 代理性(Action On Behalf Others)

l 自制性(Autonomy) 

l 主动性(Proactivity) 

l 反应性(Reactivity)

l 社会性(Social Ability) 

l 智能性(Intelligence) 

l 合作性(Callaboration) 

l 移动性(Mobility)

My god! 光把这些名词理解一遍就让人头大了. 不多解释了. 简单点, Simple is good. 我们现在不需要迎合理论家挑剔的口味, 只需要创造一个具有不完全特性的Agent. 女娲同学造人的时候还不是就地取材, 捧着坨泥巴就捏上了J 另一方面, 作为整个AgentWorld的缔造者, 我们必需意识到这样一点: 上述这些特性, 是分层次的, 比如反应性, 和自制性, 这应该是位于Agent心智(Mind)最底层的特性; 而社会性, 智能性, 合作性等, 则应该属于Agent的高级心智, 需要其他心智能力的支持. 在由混沌到开天地的最初步骤中, 我们只需要实现最低层次的Agent心智即可. 

综合以上考虑, 在我们捏出的第一个Agent身上, 可以看到如下几种心智:

l 同步异步的统一和同类群组

l 对外界变化的感知

l 时间驱动

l 移动性

下面我们来逐一审视这些貌似简单, 却可能蕴含无限组合及演化能量的心智要素.

同步异步的统一和同类群组

前面聊到说, 我们有认识到Object世界存在着”同步和异步被人为地剥离”的尴尬, “同步异步的统一和同类群组”则是Agent世界对这一尴尬的解答. 在我们的Agent世界中, Agent A1呼唤Agent A2协助其做某件工作M1, 并不需要了解到A2做这件事情是同步在做还是异步在做, 这是同步异步的统一; 更进一步, 如果这件事情, 和A2具有同样能力的好几个Agent都可以来做, 那么A1更不需要知道到底是哪一个具体的”A2”在做这件工作. 有点抽象噢? Ok, 这好比现实生活中的一个例子: 领导曰”上午10点让司机过来接我去开会”. 领导作为一个Agent, 他不需要了解司机这种Agent 10点开车到楼下是同步行为还是异步行为(当然司机自己要清楚), 也不需要了解开车过来的是单位上的哪个司机, 小王or小张? 这是领导需要关心的事情吗? 他只需要知道10点下楼, 就有个司机在那候着. 用程序语言来描述:

Boss的执行代码就是:

 driver.ComeHere(10:00)

Driver的代码:

 class Driver : Agent_XXX
 {
  [AgentMethod(AgentMethodCallMode.Asyn, AgentMethodRouteMode.Group)]
        public virtual void ComeHere(DateTime time){…}
        …
 }

Ok, 让我们把所有注意力都集中到蓝色的Attribute上, 这是Driver这个Agent类在”同步异步的统一和同类群组”这种心智上的体现. 为什么我们刚才说要选择C#这种优雅的语言, 就是因为我们可以使用Attribute这个特性来将Agent的心智附着在普通的Object Class上. 当然, 我们也可以额外地用一个描述心智要素的XML文件来和Class绑定, 但个人感觉而言, Attribute来的更加优雅直接一些. 

AgentMethod, 这个属性表明, ComeHere这个Method, 是Driver这个Agent的一种能力, 注意是能力不是心智. 这种能力带了特定的心智信息:

l AgentMethodCallMode.Asyn  这个Attribute表明这种能力是异步调用的, 而非同步. 看到这里有些同志不免生疑, 不是说同步和异步不再区分的嘛, 怎么还有Asyn, Syn这样的标记呢? 不爽! 解释一把, 我们所说的同步和异步不再区分, 是从调用者的角度考察, 比如领导之于司机, 老师之于学生. 而对于方法的被调用者, 也就是实际执行者来说, 他当然是需要”内心”里知晓这个方法到底是如何完成的. 实际上在领导调用driver.ComeHere(10:00)之后, 领导所拥有的心智, 即线程马上可以接下去做其他事情, 而ComeHere(10:00)这个方法, 则被转移到某个Driver实例的心智, 即线程上去执行.  这里的一个小小差异, 实际上凸现出Agent和Object的一个很大区别: 在Agent世界中, 每个Agent只需要关注自己逻辑是如何执行的, 而对其他Agent的逻辑执行细节的关注, 被降低到的最低.  如果有同志觉得上面这段话过于抽象, ok, 让我们更技术化的语言重复一次: 每个Agent都带有一个独立的线程(我们可以把它理解为Agent的灵魂J), 每个Agent自身的能力(在OOP语言环境下被映射为Method)都在这个线程上来被解释和执行. 如果一个行为序列包含有若干不同Agent的能力的执行, 那么完成这个行为序列就会使用到好几个不同的线程, 并且线程之间的切换是完全透明和自动的.  深入到技术细节之后, 让我们赶紧上浮到更宏观的一个层面上来, 否则很快就会在对技术细节的探究中失去对Agent设计理念的把握能力. 现在我们已经知道: 一个Agent, 它所拥有的能力(也就是各种Method)只能由它拥有的灵魂(也就是其独占的线程)来执行. 那么显然的一个问题是: 这带来了什么样的益处? 

l AgentMethodRouteMode.Group.  这个Attribute表明Boss要一辆车, 这个请求, 是一个群组请求, 而不是针对某个Driver类的某个具体实例. 这个请求会被放到一个基于群组(也就是具有共同能力的一类Agent)的请求池中, 假设此时单位有三位司机空闲, 那么其中的一位就会接受到这个请求, 随后执行之. 这应该是对Agent理论中黑板模型的一个最最简单的实现: 有什么请求, 写到一个黑板上, 有能力响应这个请求的, 就来处理, 并把这个请求从黑板上擦掉.  大家可以想像一下这种心智的用场何在. 先往现实世界中靠, 找找现实世界中是否存在这种例子? 大量存在, 银行柜台业务, 超时购物结算. 再回到软件世界中, 我们很容易联想起一个词: 负载平衡J 如果具有某种能力(能够执行某种关键业务操作)的Agent通过Mobility心智(稍后会介绍方面内容)分布在整个系统的若干台不同机器上, 同时共享同一个群组请求池, 外部进来的业务操作请求进入请求池后, 就会为不同机器上的Agent所处理. 负载平衡乎J 当然, 谁说现在银行系统不是负载平衡的? 我们这里强调的是: 在Agent世界中实现负载平衡的简洁性. 仅仅需要一个Attribute, 并且, 由于底层的群组请求池是系统自动构造的, 因此如果需要进一步平衡负载, 只需要多new几个 driver, 并把他们移动到不同的机器上, 就好比银行里看到排队的人多了, 临时增加几个业务窗口的道理一样. 很重要的一点, Agent世界里能实现的, Obejct世界或者汇编世界里同样都可以实现, 我们看重的是实现的简洁性(偷懒性?)及于现实世界贴近的程度.

AgentMethod还有好几种组合, 这里就不一一介绍了, 陷入编码细节的讨论必然会妨碍我们对概念整体上的把握. 但这里不得不说明的是, 假设Boss需要driver在达到之后通知他一下, 应该怎么实现呢? 由于OOP的限制, 我们不得不使用一种相当不优雅的方式: 回调函数, 来解决这个问题.  于是乎, 在Boss这个Agent类中会出现如下代码:

[CallBackMethodAttribute(typeof(driver), "ComeHere")]

public virtual void CallBack_Driver_ComeHere(Message_ResultOfASYNCallMethod message){…}

这里的CallBackMethodAttribute表明, 当某个Driver达到楼下后, 会自动(对于Driver和Boss来说都是透明的)调用这个方法. 注意, 这个方法的执行, 是在Boss的心智中, 而不是由Driver来完成的, 也就是说, 系统会自动在Driver的线程中执行ComeHere操作, 随后又自动切换到Boss的线程, 来执行CallBack_Driver_ComeHere. 想像一下, 为什么会这样 and 为什么要这样? J

要不今天咱们就整到这里, 如果大家有兴趣的话, 下面的章节中, 我们会接着聊聊感知力, 时间驱动, 移动性等更好玩的话题. 整点小思考题, 注意到Agent类的每个方法上都带有virtual指示符, why? 借用Matrix中的一句话:”There is something different.”, 其实, 刚才咱们所聊的所有内容, 包括今后旅途中所看到的Agent World中大多数风景, 都位于水平面之上, 而顺着这个隐秘的线索深入下去, 您就能触摸到Agent World位于水平面之下庞大而精密的支撑架构. 水面之下, 没有任何风景, 只有高速运转的调度线程, 吞吐繁忙的缓冲池, 张合有序的读写锁, 以及无处不在的BugJ