在上一篇介绍了逻辑编程的作用,介绍了逻辑编程中的一些概念,包括逻辑程序的结构:事实、规则和问题;知识的表达方式:谓词演算、产生式规则,以及这些概念与三维度(角色+场景+时间)理论的契合关系,正式提出了“三维度逻辑编程”这个概念。为了更好的体现“三维度”的关系,今天要重点介绍一下角色的扮演者--Actor。
其实,我是一个演员
这个标题来自电影《喜剧之王》周星驰的台词。人生如戏全靠演技,星爷这样说,我们又何尝不是呢?
(图片来自网络,侵删)
在我们的“游戏人生”这个游戏中,三好男人张三在家是丈夫,有了孩子后是父亲,他在老婆面前必须当好丈夫角色,在孩子面前必须当好父亲角色,同样张三在单位工作的时候必须当好员工角色,为了赚钱养家,老板要求996也得忍。。。加班回家一身疲惫,也得面带微笑,当好丈夫或者父亲角色。只有在夜深人静的时候,张三才能做回张三自己。张三一天深夜在某个聊天群写下了一段话:
夜深了, 我还没有睡, 想着写了2年半的代码, 是一种情怀, 能坚持到最后的勇气, 像极了18岁的自己。 我很菜,却依然坚持, 群里全是我膜拜的大神, 谈的我全不懂, 我依然愿意在群里, 发点感慨 明天继续! -----致和我一样努力写bug的垃圾码农
Actor就是那个演员
Actor,顾名思义:演员。在我们的“三维度”逻辑程序中,使用Actor这个词来表示扮演角色的原生对象。Actor对象需要扮演多种角色,在不同的地点(比如在家,在公司)、不同的时期扮演不同的角色。Actor是“社会化意义”上的人,比如行驶一个人或一群人的意志的团体,或者机构、公司,或者一部机器。如果Actor是一个人,就一定会扮演一个角色,一生要扮演很多角色,所以,Actor与角色的关系就像人和影子一样,只要在阳光下总是形影不离。
在当前这个“游戏人生”程序中,下面的示例代码描述了张三和貂蝉两个Actor对象具有的一些角色:张三是一个员工,张三同时也是貂蝉的丈夫;貂蝉是张三的妻子。
//创建实体Actor对象 Woman diaochan = new Woman() { Name = "貂蝉", Birthday = new DateTime(1990, 1, 2) }; Man zhangsan = new Man() { Name = "张三", Birthday = new DateTime(1988, 3, 5) }; //陈述事实:XX是YY角色 Worker worker1 = new Worker(zhangsan); Wife wife1 = new Wife(diaochan,zhangsan); Husband husband1 = new Husband(zhangsan,diaochan);
注意上面的代码只是在申明Actor对象的实例,陈述Actor对象具有的角色事实关系。这里借助C#对象的构造函数,来为Actor对象申明一个相关的角色,这个角色会添加到Actor对象的角色集里面。但是为什么要用角色对象的构造函数而不是Actor对象的角色集合添加角色对象呢?比如下面这样子的代码:
zhangsan.Roles.Add(new Worker); zhangsan.Roles.Add(new Husband(diaochan));
上面的代码的确可以实现Actor Has a Role的效果,但是有几个问题:
1,上述代码没有之前的代码简洁;
2,以Actor对象为主,角色对象是Actor对象的附属对象
3,描述多个Actor对象之间的关系不方便,语义不清晰。
4,无法体现出逻辑编程中的谓词关系。
所以,要解决这些问题,或者说要弄清楚“三维度”逻辑编程的特点,需要深刻的认识角色的含义,角色与Actor真正的从属关系。
Actor的马甲--角色谓词
谓词(参考定义)是用来刻画个体性质以及个体之间相互关系的词。在当前程序中,角色对象定义了对象的性质,也可以定义对象之间的关系,因此这里的角色就是一个谓词,Actor对象就是谓词对象的参数,一个谓词也可以表示多个Actor对象参数之间的关系。所以Worker、Wife、Husband都是谓词,是表达Woman/Man称谓的词。在逻辑编程中,强调对象之间的逻辑关系,这种关系就是谓词关系,逻辑编程就是谓词演算。为了更好的将逻辑编程与“三维度”(角色+场景+时间)理论联系起来,在“三维度”逻辑编程中将谓词称为角色谓词,程序的运行依赖于角色在系统中的交互。那么角色是什么,为什么这么重要呢?
角色是一种特定的关系
人的社会性表现在人和人之间各种关系的总和,这些关系形成一个复杂的网络,角色是其中的一种关系。这种关系有时候是不稳定的,有时候是稳定的。例如:
- 员工角色,他表现为某个特定的人当前是某个公司的雇员,员工和公司之间的关系是不稳定的、松散的,公司可以随时解约,所以员工角色定义的时候可以不指定从属的公司对象;
- 丈夫角色,他表现为一个男性跟一个女性的家庭关系,这种家庭关系是稳定的,强有力的,要解除这种关系(离婚)需要复杂的法律手续,所以丈夫角色定义的时候必须明确指定男方对象和女方对象,女方对象从属于南方对象。
- 妻子角色的定义与丈夫角色的定义类似。
通过这些角色关系,我们的社会有机的组织了起来,有条不紊的运行着。总之,角色表达的是一个主体对象跟一个或者多个从属对象的关系,如果角色关系是不稳定的,从属对象可以不明确指出,当它仍然存在。
名不正言不顺
天下人无非追求的就是名利,要想做事就必须有一个名头,大到行军打仗需要“出师有名”,小到送个外卖,没有一个合适的工作角色就没法正大光明的去做事情,能做某件事情的名头就是角色,这个名头代表一种身份、社会地位以及权利。科学的解释是角色即为”一定社会身份所要求的一般行为方式及其内在的态度和价值观基础“。总而言之,一个人必须以某种角色去做某些事情,在做事情的时候扮演一种角色。为了明确区分不同的角色,需要给每种角色一个约定俗成的名字,一个称谓,这便是角色谓词。
在“三维度”逻辑编程中,每一个角色都是一个唯一的角色类,它们都继承自一个角色接口,该接口定义了角色名字和角色的拥有者,在定义具体角色的时候必须确保名字的唯一性和名字的约定俗成。
interface IRole<out T> where T : Actor { string RoleName { get; } T Owner { get; } bool MatchRules(string ruleName); }
上面接口的定义反映出一个接口必定有一个它的拥有者,这个拥有者必定是一个Actor对象。这里角色拥有者作为角色对象的一个属性出现,也体现出来了角色和角色拥有者之间的主从关系。
角色才是真正的主人
前面说了角色的重要性,人必须要扮演一种角色去做某件事情,这是人的社会性的必然。人类社会就是由一套复杂的角色驱动着。这套角色,中国儒家称之为“礼”,君臣有礼,三纲五常,通过这套体系维持封建社会的稳定运转。现代国家更是设置了严密的等级体系,每个二年都在这个体系里面生活、工作,比如体制内外的人社会地位有明显的差别,我们每个人都想去当公务员,都想当大老板。正是这些理想的社会角色,让我们每个人为之期盼、奋斗,永不停留脚步,有人叫你一声所以XX总,YY局,是不是很惬意?所以,你叫别人是什么不重要,别人叫你是什么才重要,角色才是我们每个人真正的主人。
这段话表明在描述角色和Actor之间的关系的时候,应该以角色为主,Actor为辅助,所以在我们的逻辑编程中使用角色为谓词,称为角色谓词,以Actor对象为角色谓词的参数,使用C#来表达,就是前面已经说过的样子:
Worker worker1 = new Worker(zhangsan); Wife wife1 = new Wife(diaochan,zhangsan); Husband husband1 = new Husband(zhangsan,diaochan);
通过使用角色对象的构造函数,更容易将目光关注到对象的构造申明上,而不是构造的过程。我们忽略无关的细节,更容易重视代码表达的语义,体现出了角色对象和Actor对象之间的从属关系。
虽然说角色对于剧本讲故事很重要,但是再怎么重要的角色也需要一个出色的演员。演员的演技是能否驾驭角色的关键,下面需要我们再来介绍下这位演员了。
Actor的演技--角色的使用
演员的天分
没有Actor,角色将毫无意义,作为演员,它为角色而生。所以Actor对象需要内置一个角色集合,且不可移除,不可重置,只能向角色集合添加或者移除某个角色。每一个Actor对象最终都继承自一个Actor抽象类,它有诞生时间,也有名字。此文,它还有扮演某个角色的方法,这让Actor天生有了当演员的才能。
abstract class Actor { public Actor() { Roles = new List<IRole<Actor>>(); AtTime = DateTime.Now; } protected internal List<IRole<Actor>> Roles { get; private set; } public T ActAs<T>() where T : class,IRole<Actor> { foreach (IRole<Actor> role in Roles) { if (role is T) return role as T; } throw new InvalidCastException("Actor没有此角色:" + typeof(T).Name); } public DateTime AtTime { get; set; } public string Name { get; set; } }
再好的演技也需要角色
上面Actor抽象类的设计,让Actor具有了扮演角色的能力。我们知道扮演角色是为了能够执行角色定义的方法,由于Actor的角色并不是稳定的,角色在需要的时候才有,也会在特定的时候失去,所以在设计上Actor不能继承角色接口,也不应该将接口的方法直接定义在Actor对象中。那么能否让Actor对象直接执行角色对象的方法呢?也就是让对象动态添加一个方法/函数,这个功能在动态类型语言不是什么问题(比如Javascript、Python、Lisp等),但对于静态类型语言却是非常困难的问题(比如C++、Java、C#、go等)。
下面演示的是Javascript动态添加属性和方法的例子:
var object = new Object(); object.name = "name"; object.age = 19; object.fun = function(){ console.log("这是一个动态添加的方法") } object.fun();//调用新增的方法
然而,采用动态类型语言这种动态添加方法的方案难以在语义上表达调用这个方法所包含的角色语义,更为重要的是调用这种动态的方法不能保证类型安全,比如让一个员工(角色)对象调用了老板角色“发工资”的方法这种错误的事情发生。“三维度”逻辑编程提出了角色谓词的概念,并且在设计上明确要求由Actor对象来扮演角色,通过角色对象来安全的执行角色的方法,这不仅在代码上体现了角色语义,也保证了类型安全,让指定的角色干指定的事情,不会越俎代庖,每个人职责分明,系统有条不紊。
Actor通过切换角色来实现不同的功能,这个可以通过Actor对象的ActAs泛型方法来切换当前Actor对象的角色。ActAs泛型方法遍历Actor当前角色集合中所有的角色,如果找到就返回这个角色类型,找不到抛出异常,详细代码请看上面【演员的天分】。
回顾一下前面程序中Actor对象通过ActAs泛型方法切换当前角色执行角色功能的的示例代码示例,由于可以通过Actor对象来切换到拥有的角色对象,所以申明角色对象的变量都不需要了,这样能更加显示出角色谓词的强大力量:
//使用角色谓词表示事实关系
new Worker(zhangsan);
new Wife(diaochan,zhangsan);
new Husband(zhangsan,diaochan);
//场景参与人开始扮演角色 diaochan.ActAs<Wife>().Child_bearing(); zhangsan.ActAs<Husband>().Money += zhangsan.ActAs<Worker>().Work(); zhangsan.ActAs<Husband>().Child_rearing();
上面的代码通过Actor对象切换角色来执行角色对象才有的方法,如故事男女主角作为夫妻功能才有的生孩子功能,以及男主作为员工角色进行工作赚钱的功能。事实上也本来如此,整个过程理解起来非常自然。
好演员更需要好剧本
好演员当然说到是饰演某个角色演的淋漓尽致、惟妙惟肖的演员,然而一个演员要饰演这样的角色,除了自身的演技,还得遇到非常好的剧本,毕竟同样的角色大家都有机会来当,比如我也想来演一次当老板的角色,但你是当一个小卖部老板还是世界500强公司的CEO,这就要看导演给你的剧本了。演小老板有小老板的演法,演大公司CEO就必须有CEO的演法,这里的区别就是剧本中设定的角色规则,比如小老板角色设定的身价不能大于100万,而CEO角色的身价过亿,CEO可以聘请10000名员工而小老板只有自己一个员工。哪怕是同样的小老板,有的剧本是小老板一路开挂三五年做到大公司CEO,有的剧本是小老板当了不到一年就破产下岗了。这些剧本中决定角色命运的,就是编剧/导演给角色设定的规则。
所以,我们的“游戏人生”剧本要讲好故事,介绍完了演员和角色,还需要重点讲讲角色的规则,这是下一篇文章准备讲的内容,也是“三维度”逻辑编程语言的重点内容,这部分内容在《SOD框架“企业级”应用数据架构实战》已经做了相应的介绍,手里有这本书的朋友可以先一睹为快。
--------------分界线-------------------------
构思整个系列花了2年多时间,写这篇文章写了3天,反复删改,但限于本人对于编程语言知识能力的浅薄,其中谬误肯定很多,前几篇文章的内容也被PL领域的大佬批评略多。在此感谢这些大佬的批评建议,但是与大佬的水平相比较起来大佬在天上我在地上,不在一个交集上,所以这篇文章以及整个系列被大佬批判无法避免。只是希望大佬能够手下留情,在点击文章【反对】之前先回帖留言详细说明您反对的地方,谢谢支持!
如果仅反对而不回帖留言,本人只好当您为空气,您的举手之劳毫无意义,一律略过。