日本DMM网的 アプリやサイト利用でポイントGET 怎么弄啊 上面的游戏这日子没法过了txt下啊 怎么下啊。

用数据分析AV女优,寻找下一位苍井空
毕竟苍老师已经30岁了
2008年,美国国家工程学院对25000名工程师进行调查,并借此总结出一份他们心目中21世纪将要面临的宏大挑战,这是预测,同时也是创新的根源推动力。在排名前14名的挑战中,例如“先进的个性化学习”“制造用于科学探索的工具”“增强虚拟现实”……等等已经在过去的五年里得到了飞速的发展。当年,因为诸葛亮对天气的预测,让他成功的借来东风,又凭借对周瑜的人性预测,让他成功的逃脱魔爪,由此可见合理有效大胆的预测是一件利国利民功在千秋的事情。提升逼格的套话已经说完,那么,让我们开始从AV女优群体的数据分析中,预测谁将成为下一个苍井空老师,毕竟她已经30岁了。老兵永远不死,只会慢慢凋零……一.数说AV女优群体1.数据样本两个月前,我曾写过一篇《》,但是里面的数据样本和分析主要是针对欧美艳星,因为欧美的从业者们拥有一个非常完善的信息资料库,从个人常规资料到身体哪个地方有纹身,均有数据可查可分析。欧美很容易把所有的行业都做的信息化科技范儿十足,例如《生活大爆炸》里,那用火星机器人给自己撸管的霍华德。但是不少朋友在看了那篇文章后,纷纷发来热情洋溢的贺电,请求我能走出国门放眼小小寰球,分析一下与我们一衣带水的日本AV女优市场。与欧美同行相比,东瀛AV市场整体就显得更娱乐化一些,虽然经常有影片的创意让人瞠目结舌,但是缺少整体的通盘筹划和信息化建设。特别需要一提的是,很多AV事务所官网或者是成人网站,是禁止海外IP浏览的。在一个初冬阳光明媚的清晨,暖洋洋的日光撒在我的身上,我在QQ上点开一个人的头像,默默地发去一条链接。过了两分钟,远在澳大利亚的他恨不得哭了,“独在异乡为异客,每逢佳节倍思亲,一骑红尘妃子笑,故人发个黄网来。”他说他在那边很孤单,很久没有跟人聊天了,没想到我会突然跟他说话,但是发一条打不开的成人网站链接就不对了。这时我的心理平衡了,原来日本成人网站不光屏蔽发展中国家的屌丝,连发达国家也一起屏蔽啊!在这种无比艰难的情况下,汇总了1129名AV女优的资料作为数据样本,选择的主体基本是单体女优和企划单体女优,要求是至少出过一部自己为独立女主的影片,这些人是AV女优市场的主力军。而那些路人甲一般的企划女优实在如海洋般浩瀚,非本人能力范围内。2.基本信息从样本中所有AV女优的星座来说,排名第一的是射手座,占总人数的12%,紧随其后的是双鱼和白羊座,这三个星座所占比重均超过了10%。(av女优的星座排名:1射手座2白羊座3双鱼座4处女座5天蝎座)处女座虽然和天蝎座一起并列第四,但是值得一提的是,在已知的自杀AV女优中,处女座占了半壁江山,例如2007年在家中上吊的美咲沙耶,自杀前患有严重的抑郁症和酗酒。而人数最少的星座则是金牛座,恐怕是因为AV女优这个职业与内向沉稳的金牛座原本就气场不合。而在血型方面,居然40.8%的女优是A型血。顺手搜了一下A型血人的特点,“能确实遵守约定和规则”“无论如何也会努力达成目标,耐劳力很强”,看到这里,我真的相信了血型性格说,毕竟对于AV女优来说,在录像带时代一部片子40-60分钟,但是到了DVD时代,基本是90分钟起,长的也有3-4个小时的。如果没有A型血坚忍不拔的性格,那么哪怕自己身体不动,任人蹂躏90分钟也是一大挑战能消耗很多卡路里。其次是O型血,也占据了31%,而B型和AB型相比之下,就少得可怜了。对于大部分的女优,她们所使用的名字都是艺名,由此也可以猜到她们资料中的出生地恐怕也真实不到哪里去。事实正是如此,在769个能查到出生地的女优中,有30%的人说自己是东京人,其次有10.6%的人说自己是神奈川县的。如果我们借此就粗暴的断定东京盛产AV女优是不公平的,这就跟市场上其实没有那么多阳澄湖大闸蟹一个道理。3.身高身材从一个时代人平均身高的变化,可以看出一个民族的营养状况。而从AV女优的身高变化,可以看出多年来人们选美的标准变化。根据出道时间来区分AV女优,在90年代出道的女优平均身高为157.8厘米,而到了年,这五年间出道的AV女优平均身高已经到了158.5厘米,随着时代的发展,AV女优的身高越来越高,这四年出道的新人,平均身高已经突破了160大关,达到了160.42厘米。娇小可爱一直是日本女性的代名词,而AV女优中萝莉系女生也颇多,凭借着姣好的面容和娇小的身材让大叔和屌丝们泛起浓浓的保护欲望。在1129名AV女优中,身高低于150厘米的有40人。但是常言道“有志不在年高,有胸不在身高”,这40人中,排名第一的居然是E罩杯,有10个人拥有这傲人的胸怀。另外G奶就有五人,另外还有身高148厘米却有着J奶的芹泽由衣。这其中值得一提的是长濑爱,作为当年和武藤兰齐名的行业翘楚,赖以成名的绝技是妖媚一般的腰部扭动,但是正因为她的职业操守,导致了她因为腰椎间盘突出而短暂停止演出。而她的身高,也因为腰椎间盘突出,由145厘米变成了143厘米。看到这些感人的事迹,我简直都要哭了。一个身高仅仅145厘米的小女孩,对工作的严谨与敬业让她早早的患上了职业病,甚至她曾经在拍摄中,因为运动量过大而导致休克,紧急送去医院。我掩卷深思,不知道她们的工伤医疗报销,单位负担多少比例。而身高在150-159厘米区间内的AV女优有542人,占据总数的48%。而其中A罩杯的有4人。要知道在这个群体中,如果上帝没有给你一个高耸的胸部,也没有给你修长的美腿,那么一定会在其他地方给你补偿。例如其中小泽圆,身材并不突出,但是凭借自己清新可人的容貌拍摄了上百部AV影片,也参演了香港著名的三级片《偷情男女》,卖力的演出赢得了观众的一致好评。因为她作品的屡破销量,被人称为“AV届的女皇”。(她的罩杯各处说法不一,有A有B,但是83CM的胸围至少证明了不大。)这个事情告诉我们,如果看官你或者矮或者穷或者丑或者挫或者傻,这都不可怕,因为上帝是公平的,你会在其他地方体现自己的价值。如果你又矮又穷又丑又挫又笨,那么我只好说,你就是上帝用来激励成功人士的参照物。在此区间内,最大比重的是C罩杯,占据了总数的21%,这是一个平胸分界线。而排名第二的是E罩杯,占了16%。G罩杯以9%排名第五,其中最知名的当属身高155厘米胸围90G的苍井空老师。而位于金字塔顶端的,是S1旗下的专属AV风子,她虽然没有冷艳的容颜,152的身高也容易消失在人群中,但是她那傲人的P罩杯却成为其独特的标识。而福岛地震后,她又出来为福岛的西瓜背书,证明其天然无污染,简直就是新时代德艺双磬的典范,就是长得差点儿意思。在近年来,虽然巨乳一词出现频率最高,但是”长身”一词也异军突起。毕竟对于看多了娇小可人的小女生,突然来一高个美腿MM,别有一番风味。身高最高的当属星野沙果恵,身高1米88。最出名的当属号称日本女排国手内田真由,身高1米82。不过遍查07-09年的日本女排阵容,至少在国际大赛上并没有这个182的姑娘。哪怕入选过国家集训队,这个噱头依然足够拍摄商做宣传了。她的片子适合重口味人士以及想换性取向的人士观看,基本上是男优成了被欺凌被蹂躏被侮辱的一方,让人不得不唏嘘感叹,身高才是硬通货啊。4.年龄时代在发展,大家的营养越来越好,从身高到罩杯均有了长足的进步,例如70后女优一共有64人,平均身高为157.8厘米,A-C罩杯占据了42%。而80后的平均身高为158.2厘米,A-C罩杯的比重剧降到了23.3%,可见如果你低于C杯,怎么好意思在这行里混呢?而90后女优的平均身高已经到了159厘米,罩杯则被清一色的“童颜巨乳”所占领,仅仅是从G杯到M杯的巨乳女优,就占了32%,代表人物当属异常火爆的橘梨纱,从AKB48成员下海拍AV,纯情冷艳的面容成为宅男女神,从2月其第一本全裸写真发售到八月的《橘梨纱紧缚中出し》,她的每一部作品都成功横扫AV界,在DMM网站的单一作品2013年上半年销量排行榜上,前三名全被她占据。为了搏出位,同为90后美少女的上原亚衣甚至不惜与黑人大战,但是仍然不敌脱衣服就能脱30分钟的橘梨纱,这就是命。5.作品数目有句话叫做“以大多数人的努力程度之低,根本没有轮到去拼天赋”。这句话用在AV女优行业再合适不过。要想出人头地,除了爹妈给的硬件,更需要努力拼搏的精神。例如武藤兰老师,在2002年一共有212部AV作品面世,并因此创造了“一年之内售出最多色情影片女演员”的吉尼斯世界纪录,而2003年,她勇攀高峰,超越自己,创纪录的出演了304部作品,成为年度最多作品纪录保持者。365天演了304部作品,她就是传奇。而1997年出道的风间由美,则以当时少见的G罩杯19岁少女成为了宠儿。岁月如梭,转眼间,在去年10月,风间由美举办了自己从业15周年的纪念活动,在15年如一日的辛勤劳动中,她以1724部作品成为了现役AV女优中的敬业模范。排名第二的花蕾,虽然2006年才出道,但是作品数已经达到了1524部,87年出生的她如果能保持这个热血狂飙的速度,那么追上风间阿姨指日可待。6.出道原因等奇葩数据虽然我们很愿意相信,这些失足少女投身风月场合都是因为有着一个悲惨的童年,要供应弟弟妹妹上学,而不得不毅然决然的脱掉衣裳,眼含热泪的躺倒在榻榻米上。我国淫民日报的网站曾经发过一条新闻,叫做《日本AV女优背后的病态社会》,在里面描绘了AV女优因为要承担起家庭的重任而走上了拍摄AV的道路。但是从已知的数据来看,成名快收入高成为了新出道女优的首选原因,排名第三的是“刺激,体验不一样的人生”。例如1994年出生的仓多真央,她出道的原因据称是多次给AV制造商SI公司写信求援,说男友无法让她满足。后来,SI就把她打造成了女优。而在1129名女优中,以处女身份出道的共有7人,其中椎名彩为了出位,不惜在荧幕前献出了自己的处子之身,只可惜销量巨惨,只拍了四个月就退出了江湖。但是几个月之后,她再次重出江湖,不过换了一个名字,叫做水树美叶。对于大神一条绮美香,嗯,其实大神也可以叫做“大婶”,作为48岁才出道的她,我实在无法再漠视世道的艰辛和人心的不古。而如果有重口味的有心人看了视频,再回过头来看一下她的封面,一定会哭着对我说,封面都是骗人的。二.谁会是下一个苍老师虽然苍老师在日本本土的影响力已经在走下坡路,但是最近两年,其在中国内地市场掀起了一波又一波的AV女优来华潮。从苍井空到波多野结衣再到冲田杏梨,从麻生希到泷泽萝拉再到松岛枫,这些硬盘女神一个个的走到了广大宅男的身边。那么,谁会像苍老师一样,成为下一个全民狂欢的偶像呢?12月1日,日本AV界最著名的“2013日本成人放送大赏”将开启投票,这一奖项是由主办方与16个成人频道一起从日本当年上市的众多成人影片中,选出入围名单,再开启网友投票,从而选出当年度最佳女优,最佳新人女优和最佳熟女女优,是日本AV领域的最高奖,堪比奥斯卡颁奖。顺便说一句,为了体现演员与观众间的鱼水之情,获奖者最后上台后,需要向台下扔出自己的亲笔签名小内裤,以感谢热心观众的支持。如果把日本成人放送大赏之前五年的最佳女优,DMM网站上的2013年上半年AV销量前三名,新浪微博粉丝数最多的两人分别选出来,一共10人。这10人无疑涵盖了中日两国人民共同的审美观,对这10人进行一次人物肖像合成工作,至少能管中窥豹看出什么样的女优最有可能成为下一个AV女神。日本成人放送大赏过去五年的最佳女优分别是佐藤遥希,成濑心美,麻仓优,原纱央莉,明日花绮罗。2013年DMM网站统计的上半年销量前三名分别是橘梨纱,上原亚衣,彩美旬果。新浪微博AV女优粉丝数最多的五个人为苍井空,波多野结衣。当用图像合成软件,进行两两捉对厮杀之后,就能得到的一张AV女优大众脸。按照著名美剧&&犯罪心理&&里的专业描述,我们对未来最有可能成为下一个AV女神的女子进行一下人物侧写。“她家境比较富裕,并不是苦大仇深卖身葬父,射手座A型血,从小喜欢古典芭蕾和骑马,性格外向渴望尝试不同的生活,追求刺激和成就感。身高在165厘米左右,E罩杯,长相如下图所示。”三.尾声如果各位同学真的在马路上见到一位这样的姑娘,请不要贸然的上去搭讪,不要急于挑战这类似哥德巴赫猜想一般的难题,因为这样的姑娘,不是普通人可以驾驭的。我们唯一能做的,就是放下工作的压力,忘记生活的烦恼,静静的双手合十,用自己一颗赤诚的心祝愿她早日成为我们大家的硬盘女神。再顺便说一句,转载图文抹去作者快刀青衣和虎嗅信息的,这辈子就注定只能和右手为伴。
文章为作者独立观点,不代表虎嗅网立场
分享到朋友圈
分享到微信朋友圈,炫耀你的微信影响力!~
您可能感兴趣的文章
要评论?请先
没有帐号?点击
蓝翔技校终身形象代言人
#向作者提问#
你每天真的上班吗……
登录后可点评,点击登录
作者你这么屌,你家里人知道么?首席鉴黄师的节奏挖
作者就任阅片儿湿主任
回复 :你骗谁啊。。。你上班时间就是看片我就信了。。。//@快刀青衣:你才职业鉴黄师呢……哈哈,我真的上班。。。很一本正经的开会做工作~~
回复 :你骗谁啊~//@快刀青衣:你才职业鉴黄师呢……哈哈,我真的上班。。。很一本正经的开会做工作~~
你才职业鉴黄师呢……哈哈,我真的上班。。。很一本正经的开会做工作~~
职业鉴黄师?哈哈哈
回复 :难不成是我党的阅片员?传说中的工作啊。。。//@潘乱:他的工作就是阅片啊,然后决定这些片子能不能流放市场。
他的工作就是阅片啊,然后决定这些片子能不能流放市场。
这样的作者居然没有被评为虎嗅网年度最佳作者,真是虎嗅网的bug啊~~
登录后可点评,点击登录
读完荡气回肠,想起电影《地下》,里面有次充满很有末世感的一撸。当时德军轰炸贝尔格莱德,满城慌走,妓女在做爱中途逃窜,马高一人站窗帘边继续加紧撸管,在炸弹下落之前完成高潮。
登录后可点评,点击登录
下了还没看,太长了。
这是神撸。。。
哈哈,涨知识。,。。我去看看去~~~
我只想说这个片子导演的表达手法让我凌乱了
请问快刀青衣大哥,数据库能开放吗?无比期待开发数据接口~~
登录后可点评,点击登录
回复 :可以用这些数据弄个站点倒流量赚点小钱了,呵呵//@Benli:哈哈哈,申请API接口
哈哈哈,申请API接口
太失败了,没有朱音唯
登录后可点评,点击登录
回复 :蚊香即prestige,就是她拍片呆的片商,你百度下prestige就行了//@宅先生:不明白你在讲什么。//@勤快的海饭:去年在蚊香消失匿迹的人,可没有参考的资格哦。。怪只怪她活在蚊香
去年在蚊香消失匿迹的人,可没有参考的资格哦。。怪只怪她活在蚊香
对不起。。。忠孝不能两全啊~~~~
作者仍健在吗?你是在用灵魂来写作吗?这种大数据伤身啊~
登录后可点评,点击登录
这篇文章的评论体现了中国网民的高素质,凡是和政府有关的评论皆是骂声一片,但是贴近人民生活的文章评论皆为楼主辛苦了,楼主好人,楼主一声平安,同意的顶起
登录后可点评,点击登录
我一直以为虎嗅的编辑只懂电子商务和互联网,原来他们还懂AV女优呀! /偷笑
不稀奇,我相信大多数人都是先懂AV女优,后懂电子商务和互联网的,对吧?~
登录后可点评,点击登录
不稀奇,我相信大多数人都是先懂AV女优,后懂电子商务和互联网的,对吧?~
虎嗅君,哪里找的这么多配图
登录后可点评,点击登录
好不容易有人识货啊………………最后合成图合得我都吐血了,都木有人夸~~~
回复 :你一点也不给虎嗅蹭你美名的机会!//@快刀青衣:苦逼作者都是自己做的图啊!
苦逼作者都是自己做的图啊!
#向作者提问#
你接片吗?我这男女演员都招,HR也交给你
登录后可点评,点击登录
回复 :乱爷懂我。//@快刀青衣:请问你是天使投资人啊?
回复 :这家伙是根正苗红的富二代,想创业,希望打造中国的S1。//@快刀青衣:请问你是天使投资人啊?
请问你是天使投资人啊?
虎嗅明年的最佳作者能送到日本去么?要不就这么定了吧@快刀青衣 @虎嗅网
登录后可点评,点击登录
这个绝对是huxiu年底第一奇文
登录后可点评,点击登录
回复 :下篇奇文完成日,千万别忘@我//@快刀青衣:瞎说,我的下一篇才是第一奇文
瞎说,我的下一篇才是第一奇文
唉,能力时间所限,只能从人分析,如果从所有的作品纬度,可以分析更多的口味变化。
登录后可点评,点击登录
怎么没有樱井莉亚?-_-||
登录后可点评,点击登录
无码不列入考虑名单内的
哈哈,我错了……其实我在做头图的时候,想了很久要不要放她~~~
严重鄙视利用大数据抹黑AV产业的无节操行为[鄙视][鄙视][鄙视]
登录后可点评,点击登录
请问 一剑浣春秋 是你师父吗?
剑大看到这篇文 估计会气死
登录后可点评,点击登录
剑大看到这篇文 估计会气死
微笑不语,作者最后一句好贱..O(∩_∩)O~呵呵~
回头又看了下最后一句,贱已不足以形容了,蠢更合适
登录后可点评,点击登录
回头又看了下最后一句,贱已不足以形容了,蠢更合适
看标题知作者系列…现在看到大数据就想到啪啪啪行业怎么想都是虎嗅的错……
登录后可点评,点击登录
快刀青衣大哥,阅片无数才能写出如此快文,哈哈。
登录后可点评,点击登录
这一大堆数据放在这儿有什么实际意义么?
十八届三中全会有什么实际意义吗?
登录后可点评,点击登录
十八届三中全会有什么实际意义吗?
苦逼PM,无节操,有底线。
kuaidaoqingyi
?本周活跃排名
收录此文章的文集dmm怎么上?在国内怎么打开dmm?
DMM是一家面向成年男女健康服务而不断进取的创意型情趣品牌。但是它是一家日本网站,很多国内朋友都不知道dmm怎么上?不用担心,下面小编就为大家介绍在国内怎么打开dmm的方法,一起来看看吧
This page is not available in your area.
このペ`ジはお住まいの地域からご利用になれません.
出现这样的字样怎么解决。
这里小编就教大家用代理解决的方法。
直接用索取到的服务器和账号密码设置代理
XP系统:按照如图所示的方法设置好。然后地址填入服务器后确定。会让您输入一个账号密码。即对应服务器的账号密码
WIN7系统:WIN7系统比较简单图上已经全部标注了。服务器和账号密码同XP。
WIN8系统:进入控制面板后找到网络共享中心设置流程和WIN7差不多。在设置完毕之后需要在右下角进入WIN8特有的网络选择界面找到V*N输入账号密码链接。
WIN8如果出现691,930之类的错误还需要设置一下属性。
连接成功之后即可打开DMM。
用简便的客户端设置代理
首先我们先。
2、注册一个账号登陆上去。
3、选择非限制区域启动连接。日本即可。
4、成功后可以查询如下信息。
5、和一般代理设置方法一样。DMM即可打开。舰娘也可以进入。
以上就是在国内怎么打开dmm的方法介绍,大家看明白了吗?希望能对大家有所帮助!482,381 一月 独立访问用户
您目前处于:
领域模型管理与AOP
领域模型管理与AOP
Mats Helander
相关厂商内容
基础架构代码放在哪里?
随着基础架构代码的增长,找到一个处理它的优良架构变得越来越重要。问题主要在于&&我们是否允许把一些基础架构代码放在我们的领域模型类里面,还是无论如何应该避免这样做?
避免基础架构代码进入领域模型类的论点是强有力的:领域模型应该表示应用程序所处理的核心业务概念。对于想大量使用其领域模型的应用来说,保持这些类干净、轻量级、易于维护是一个极好的架构目标。
另一方面,我们接下来将会看到,保持领域模型类完全不含基础架构代码&&通常被称为使用POJO/POCO(Plain Old Java/CLR Objects)领域模型,这种极端的路线也被证明是有问题的。最终往往导致采用笨重的、低效率的变通方法来解决问题&&而且有些功能用这种方式根本不可能实现。
也就是说,我们遇到的还是一个权衡利弊的情况,我们应该尽量在领域模型类里面只放必不可少的基础架构代码,决不超出这个限度。我们付出领域模型的轻微发胖,换来效率的提高以及使一些必要领域模型管理功能有可能实现。毕竟,软件架构很大程度上是关于如何做一笔好买卖。
重构的时机到了
不幸的是,长远看来,台面上的交易条件可能不够好。为了支持许多最有用和最强大的功能,你需要在领域模型类中放入基础架构代码实在太多了。其数量之大,很可能你的系统还没完成,业务逻辑代码就已经被淹没了。
也就是说,除非我们能找到一种方法鱼和熊掌兼得。本文试图分析我们能否找到这样一种方式,既能将必要的基础架构代码分布到领域模型中,却又不会使领域模型类变得杂乱。
我们先从一个应用看起,它将所有有关的基础架构代码都放到了领域模型类中。接着我们将重构这个应用,并且只用众所周知的、可靠的、真正面向对象的设计模式,使应用最后能具备相同的功能,但是基础架构代码却不会弄乱领域模型类。最后,我们将看看我们如何使用面向方面编程(Aspect Oriented Programming,AOP)来更简单地达到相同的效果。
但是,为了看出AOP为何能帮助我们处理DMM需求,我们首先看看没有AOP的时候我们的代码会是什么样&&首先是&最原始&的形式,这种形式里,所有的基础架构代码都放在领域模型类里面,然后是重构后的形式,其中基础架构代码已经被分离出领域模型类&&虽然仍然分布在领域模型中!
重构肥领域模型
大部分的领域模型运行时管理是基于拦截的&&也就是说,当你在代码中访问领域模型对象时,你所有对对象的访问都会根据相应功能的需要被拦截下来。
一个明显的例子就是脏跟踪(dirty tracking)。它可以用于应用的很多部分,以了解一个对象什么时候已经被修改了、但是仍未保存(它处于&脏&状态)。用户界面可以利用该信息提醒用户是否打算放弃任何未保存的修改,而持久化机制则可以利用它来辨明哪些对象是真正需要被保存到持久化介质中的,从而避免保存所有的对象。
脏跟踪的一种方法是保持领域对象最初的、未修改版本的拷贝,并在每次想知道一个对象是否已经被修改的时候去比较它们。这个方案的问题是既浪费内存,又慢。一个更有效率的方法是拦截对领域对象setter方法的调用,以便每当调用对象的一个setter方法的时候,都为该对象设置一个脏标记。
脏标记放在哪里?
现在我们来看看把脏标记放在哪里的问题。一种是将它放在一个字典结构中,对象和标记分别作为键和值。这样做的问题在于,我们必须让程序中所有需要它的部分都能访问到这个字典。前面的例子已经可以看出,需要访问字典的包括用户界面和持久化机制这样截然不同的部分。
将字典放在这些组件的任何一个内部,都会使其它组件难以访问它。在分层结构中,底层不能调用其上层(除了中心领域模型,它常常处于一个公共的、垂直的层里面,能被其它所有的层调用),因此要么把字典放在需要访问它的最低一层(图1),要么放在公共的、垂直的层里面(图2)。两种选择都不是很有吸引力,因为它引起了应用组件间不必要的耦合和不均衡的责任分配。
一个更吸引人的、顺应面向对象思想的选择,是将脏标记放到领域对象本身中去,这样每个领域对象都带有一个布尔型的脏属性,来表明它是不是脏的(图3)。这样,任何组件想知道一个领域对象脏与否,可以直接问它。
因此,我们把部分基础架构功能代码放在领域模型中,其部分原因就是我们希望从应用的不同部分都能拥有这些功能,而不会过度地增强耦合。用户界面部分不该知道如何向持久化组件询问脏标志,并且,我们宁愿在分层的应用架构中设计尽可能少的垂直层。
这个理由很重要,单凭它就足以让一些人考虑采纳本文将要检验的这种方法,不过我们还是先看看其他方法。但是在这样做之前,我们先粗略地看一下争论的另一方&&我们在领域模型类中限制基础架构代码的原因。
肥领域模型反模式
让我们看看,引入脏标记、并在适当时机要求拦截唤醒脏标记之后,领域类会是什么样子。这是一个C#代码的例子。
public class Person : IDirty{
protected string
public virtual string Name
get { return }
if (value != name)
((IDirty)this).Dirty = true;
name = value;
private bool
bool IDirty.Dirty
get { return }
set { dirty = value; }
}}public interface IDirty{
bool Dirty {
如例中所见,脏标记在接口(IDirty)中定义,然后该接口由类显式地实现。显式接口实现(explicit interface implementation)是C#的一个良好的特性,它可以让我们避免在类的默认API中乱糟糟地堆满基础架构的相关成员。
这是有用的,例如在Visual Studio IDE中,除非将对象显示地转换为IDirty接口,否则Dirty标记在代码完成下拉菜单中是不可见的。事实上,可以从清单2中看出,为了访问Dirty属性,对象首先必须转换为IDirty接口。
例子中的拦截代码只有两行(清单2)。不过那是因为到目前为止,我们的例子中只有一个属性。如果我们有更多的属性,我们将不得不在每个setter方法中重复这两行代码,并将比较对象改成相对应的属性。
if (value != name)
((IDirty)this).Dirty = true;
因此,写代码不会很难&&并且我们的领域模型现在支持脏跟踪,应用中所有的组件只要用到对象模型都可以访问到脏跟踪信息。从不需要随时保留对象未经修改版本的拷贝来说,这种做法还是节省资源和快速的。
这种做法不利的一面是,你的领域模型类不再严格关注于商业业务。在领域模型类中忽然添加大量的基础架构代码,使得实际的业务逻辑代码变得更加难以理解,并且类本身变得更加不可靠、难以变更、难以维护。
如果脏跟踪是唯一必须插入到领域模型类中(或至少这样做有好处)的基础架构功能,我们可以不用过于担忧。但是不幸的是情况并非如此。这些功能的列表在不断增加,表1中只列举了一些例子:
对象持有一个脏标志,表示对象是否已经被修改、但仍未保存。基于拦截和新成员的引入(脏标记属性及其getter、setter方法)。
对象第一次被访问的时候才加载对象状态。基于拦截和新成员的引入。
初始值跟踪
当一个属性被修改(但尚未保存)的时候,对象保留未修改值的拷贝。基于拦截和新成员的引入。
反转(Inverse)属性管理
双向关系中的一对属性自动保持同步。基于拦截。
对象支持数据绑定接口,从而在数据绑定情况下使用。基于新成员的引入(数据绑定接口的实现)。
对象的变更分发到监听系统。基于拦截。
对象(或者至少是它们的状态,参见备忘录模式[GoF设计模式])拷贝到本地存储器中,随后的请求可以在本地存储器中查询,而不用到远程数据源。基于拦截和新成员的引入。
这些功能&&并且可能还有更多功能&&都依赖于添加到领域类中的基础架构代码,干净、易于维护的领域模型忽然变得似乎遥远起来。
我曾经描述过这个问题,其中,领域模型类中的代码不断增长,以至变得很难处理,就像肥领域模型反模式[Helander肥领域模型]。它和Martin Fowler描述的贫血领域模型反模式[Fowler贫血领域模型]正好相反,贫血领域模型反模式中领域模型类包含的逻辑过少。
Fowler在他对贫血领域模型的描述中,解释了将业务逻辑放在领域模型之外是一种错误&&实际上是一种反模式,因为不能充分利用面向对象的概念和构造。
我同意他的论点&&我甚至会更进一步认为这句话对基础架构代码也一样成立。基础架构代码也可以通过分布在领域模型里,来利用面向对象的概念和构造。如果一刀切地避免在领域模型中放置任何基础架构代码,我们的代码也将沦为贫血领域模型反模式相关问题的牺牲品。
不允许在领域模型中添加基础架构代码&&没有充分利用面向对象&&由此带来的限制我们已经看过:在应用的不同组件之间共享基础架构功能变得更难,并且我们常常以低效率的&变通方法&而告终,比如用比较初始值来替代基于拦截的脏跟踪。甚至还有一些功能在没有拦截的情况下根本实现不了,如懒加载和反转属性管理。
那么,解决方案是什么呢?如果我们把所有的基础架构代码放置到领域模型类中,这些类就变得&肥胖不堪&&&难于使用和维护。但是,如果我们把它放在领域模型类之外,我们又使其变为&贫血的&领域模型,基础架构代码不能充分利用由面向对象的潜力。
我们似乎进退维谷。更具体地说,在一个不易维护的肥领域模型和一堆与贫血领域模型共存的低效的变通办法之间,我们进退两难。这显然不是一个让人特别愉快的境遇。现在该解决这个存在很久的问题了:什么方法可以重构,让我们走出这片混乱?
使用一个公共的基础架构基类
一种想法是尝试将尽可能多的基础架构代码放到一个公共的基类中去,领域模型类继承该基类。然而,这个想法的主要问题在于,它对引进新的基础架构成员(像脏标记)到领域模型类中有用的,但是它没有为我们提供拦截,而拦截是我们想要支持的许多功能要求的。
基类方法的另一个问题是,当需要调整功能的应用方式的时候,它不能提供必要级别的粒度。如果不同的领域模型类有不同的基础架构需求,那么我们就会遇到问题。我们也许能用一组不同的基类(也许互相继承)来解决这些问题,但是如果我们遇到单一继承,比如C#和Java的情况,要是领域模型本身也想使用继承,就没法这么做了。
为了明白这为什么会变成一个问题,让我们稍稍扩展一下我们的例子,使其变得稍微&生动&一些:假设我们有一个Person类,已经能够进行脏跟踪,还有一个Employee类同时具备脏跟踪和懒加载能力。最后,Employee类需要继承Person类。
如果我们欣然把基础架构代码放进领域模型中,那么我们有一个像清单1的Person类,一个像清单3的Employee类。
public class Employee : Person, ILazy{
public override string Name
if (!loaded)
((ILazy)this).Loaded = true;
//call the persistence component
//and ask it to load the object
//with data from the data source
//(code omitted for brevity...)
return base.N
if (!loaded)
((ILazy)this).Loaded = true;
//perform lazy loading...
//(omitted)
base.Name = value;
private decimal
public decimal Salary
if (!loaded)
((ILazy)this).Loaded = true;
//perform lazy loading...
//(omitted)
if (!loaded)
((ILazy)this).Loaded = true;
//perform lazy loading...
//(omitted)
if (value != salary)
((IDirty)this).Dirty = true;
salary = value;
private bool
///The Loaded property is &write-once& -
/// after you have set it to true you can not set
/// it to false again
bool ILazy.Loaded
get { return }
if (loaded)
loaded = value;
正如你看到的,领域模型类中的实际业务内容有被淹没的危险,基础代码的膨胀已经开始悄悄蔓延了。肥领域模型就埋伏在前方,这种迹象应该促动我们尝试一种不同的方法&&即使它可能在前面阶段更费事一点儿。我们这么做是因为就长期而言,我们希望一个干净的领域模型能够收回在可维护性和可用性上的投资。
因此,我们首先创建一个DirtyBase基类提供脏标志,然后我们创建一个LazyBase提供一个加载标志。使用多继承的语言,我们可以让Person类继承DirtyBase类,Employee类继承DirtyBase和LazyBase,如图4描绘的一样。
但是如果我们要使用的语言不支持多继承呢?好,我们能做的第一件事是让LazyBase类继承DirtyBase(见图5)。那样,我们仍让Employee类继承LazyBase类,并仍有脏标记和加载标记。它可能不是最佳的解决方案(如果我们有一个对象需要懒加载,但是不需要脏跟踪呢?),但是至少在这个例子中,它是一种选择。
不过,这仍然给我们留了一个问题,在像C#和Java这样的语言里面,Employee类不能同时继承Person类和LazyBase类。在这些语言中,剩下的办法就是创建一个基类同时包括脏标记和加载标记(图6),就让Person类继承一个多余的加载标记。
所以基类方法存在两个问题:它不能为许多功能(包括脏跟踪和懒加载)提供所需的拦截,并且(至少在单继承平台上)它导致领域类继承了一些它们并不必需的功能。
使用基础架构代理子类
幸运的是,面向对象提供了一个绝好的方法来一石击二鸟。为了同时提供拦截和粒度控制,我们所要做的就是创建继承领域模型类的子类&&每个(具体的)领域模型类都有一个新子类。
这个子类能拦截对其基类成员的访问,通过重写(override)基类的成员来实现,重写的版本首先执行拦截相关的活动,然后将执行传递给基类的成员。
为了避免在所有的子类中都创建公共的基础架构成员(比如脏标记),仍然可以把它们放在所有领域类都会继承的公共基类里面;而少数类的专属功能,则可以将必要的成员放置在相关的子类中(图7)。
公共基类实现了所有领域模型类公有的基础架构成员。不是所有领域类公用的那些功能则使用接口。
领域模型类脱离开了基础架构代码。
代理子类重写领域类成员来提供拦截。它们还实现了基础架构成员,这些成员不是所有领域类公有的。
用子类的方式来提供拦截,本质上是代理模式[GoF设计模式]的一种实现。还有一种变种是使用领域模型接口&&一个领域模型类一个接口&&接口能被代理类和领域类同时实现(图8)。
代理类在内部属性中持有一个领域类实例,它实现领域接口的时候把所有调用都转发给背后的领域对象。这实际上是在Gang of Four的《设计模式》[GoF设计模式]中描述的代理模式&&只不过使用了子类来实现。
使用子类的好处是,你不必非要创建一堆没有其他用处的领域接口。同时还有一个好处,代理子类中的代码能使用&this&关键字(VB.NET中是&Me&)调用继承自领域类的代码,而基于接口的代理中的代码将不得不通过内部属性来引用领域对象。子类还能访问受保护的成员,而基于接口的代理则要通过反射才能访问这些受保护的成员。
使用基于接口的代理还有另一个缺点,如果在领域类中有一个方法返回&this&的引用,调用代码就会得到一个&未代理&的领域类实例,这意味着脏跟踪和懒加载之类的功能在这个实例中是不可用的。
由于这些问题,我个人倾向于基于子类的代理方法,并且在整篇文章中我们会继续探讨这个方法。但是,请记住,在这篇文章中讨论的所有技术,也可以使用基于接口的代理方法来实现。
POJO/POCO领域模型
如果我们比较一下刚刚讨论的基于代理模式的方法和我们先前的C#例子代码,我们的领域类现在变得完全干净、没有任何基础架构代码,就像在清单4前面部分中看到的一样。所有的基础架构代码已经移到了基类和代理子类中,如清单4后面部分所示。
//Domain Model Classespublic class Person : DomainBase{
protected string
public virtual string Name
get { return }
set { name = value; }
} } public class Employee : Person {
protected decimal
public virtual decimal Salary
get { return }
set { salary = value; }
}}//Infrastructure Base Classpublic class DomainBase : IDirty{
private bool
bool IDirty.Dirty
get { return }
{ dirty = value; }
}}//Infrastructure Proxy Subclassespublic class PersonProxy : Person{
public override string Name
get { return base.N }
if (value != this.name)
((IDirty)this).Dirty = true;
base.Name = value;
}}public class EmployeeProxy : Employee, ILazy{
public override string
if (!loaded)
((ILazy)this).Loaded = true;
//call the persistence component
//and ask it to load the object
//with data from the data source
//(code omitted for brevity...
return base.N
if (!loaded)
((ILazy)this).Loaded = true;
//perform lazy loading...
//(omitted)
if (value != this.name)
((IDirty)this).Dirty = true;
base.Name = value;
public override decimal Salary
if (!loaded)
((ILazy)this).Loaded = true;
//perform lazy loading...
//(omitted)
return base.S
if (!loaded)
((ILazy)this).Loaded = true;
//perform lazy loading...
//(omitted)
if (value != this.salary)
((IDirty)this).Dirty = true;
base.Salary = value;
private bool
/// The Loaded property is &write-once& -
/// after you have set it to true you can not set
/// it to false again
bool ILazy.Loaded
get { return }
if (loaded)
loaded = value;
通过与公共基类结合使用的代理模式,我们成功解决了我们看到的所有问题,无论到目前为止嘲弄我们的这些问题是来自于贫血领域模型反模式,还是肥领域模型反模式:
我们能将有关的基础架构代码分布到领域模型中,使我们应用中的所有部分都很容易访问,并使它能用面向对象和高效率的方法实现。
我们能够把我们想要的基础架构代码都分布到领域模型中,但避免了肥领域模型的结局。事实上,我们实际的领域模型类中的代码,仍然能够保持干净,完全集中在它们需要关注的业务方面。
我们可以混合搭配,只增加每个领域模型类需要的基础架构代码。
我们能构建需要拦截支持的功能。
应该注意,按照POJO/POCO的严格定义,我们的领域模型类不应该继承基础架构基类。不过,我们可以很容易改过来,只要将所有的逻辑从公共基类移到代理子类中去,就可以得到一个完全的POJO/POCO领域模型。如果一定要满足严格的POJO/POCO,我们的方法很容易满足这项要求,只是举手之劳。如果不强求,我们可以使用基类,此时领域模型类的代码仍然完全不包含任何实际的基础架构代码。
因此到目前为止我们已经整理出了一个架构,能让我们把完全POJO/POCO的领域模型类和将基础架构代码分布到领域模型中去的目标结合在一起。如果我们的目标仅仅是避免肥领域模型,而不强求符合POJO/POCO定义,那么我们就能走&半POJO/POCO&的路线,用一个公共基类来节省一些工作。
使用抽象工厂模式
这听起来很棒,对不对?你可能在想,用它是不是没有任何问题呢。软件业的人都有些吹毛求疵,你大概已经在疑心有那么一两个棘手问题该冒头了吧。
你是对的。有两件值得关注的事情马上就自己显现出来了。第一个相当轻微:为了让子类能够重写领域模型类的成员,并提供拦截,所有的领域模型成员(或至少打算拦截的那些成员)必须是虚拟的。
第二个问题好像更为糟糕一些:既然你想让你的程序使用代理子类的实例,你必须在应用代码中查找所有创建领域模型类实例的地方,将它们改成创建相应的代理子类的实例。
要修改已存在的应用,听起来像个噩梦吧?没错。要想避免这种大规模的查找和替换操作,只有一条出路,就是从一开始就避免使用&new&关键字来创建领域模型类的实例。
避免&new&的传统方法就是使用抽象工厂模式[GoF设计模式]。调用一个工厂类的Create()方法,而不是任由客户代码使用&new&来创建实例。Create()方法负责调用&new&,并且在返回新的实例之前会对其做一些额外的相关设置操作。
如果你在调用领域模型的时候全都使用抽象工厂模式,那就最好不过了,接下来你只要在代码中改一个地方&&工厂类&&将返回领域类实例改为返回代理子类(或者是基于接口的代理)的实例。
这是使用抽象工厂模式来实例化所有的领域对象的一个重要理由&&至少使用像Java和C#这样的语言时是这样,这些语言不允许奇异特性,比如不能像C++一样重载(overload)成员取用运算子,不能像ObjectiveC一样改变&new&关键字的行为,也不能像Ruby一样在运行时修改类。
对于代理子类的方法,还有一个问题值得一提。虽然只是一种极端情况,但是它相当隐蔽,如果你陷入这个问题却不知道是什么引起的,会被它狠狠咬上一口。
如果你看一下图9,你会发现Employee继承Person类,这是应该的:无论什么时候,如果一个方法期望Person对象,那么传递给它Employee对象应该也能工作。此外,PersonProxy类继承Person类。这也很好,因为这意味着将一个PersonProxy对象作为参数传递给期望Person对象的方法也是&合法的&。
以同样的方式,EmployeeProxy继承Employee,意味着你能把一个EmployeeProxy对象作为参数传递给任何期望Employee对象的方法。最后,由于EmployeeProxy继承Employee,Employee又继承Person,这表明EmployeeProxy继承Person。因此,任何期望Person对象的方法也可以接受EmployeeProxy对象。
所有一切都符合我们的期望。当我们让抽象工厂开始返回代理对象而不是领域模型类的简单实例时,有一点是很重要的,我们希望客户端代码仍然继续正常运行,而无需重新考虑如何处理我们的类型层次。
换句话说,如果我们原来向一个期望Person对象的方法传递Employee对象给,当我们忽然换成给它传递EmployeeProxy对象的时候,我们的代码必须还能正常编译(并且工作)。幸运的是,由于EmployeeProxy继承Employee,并且Employee继承Person,所以没有问题。
事实上,当开始使用代理来代替领域模型对象时,一切都将如常继续运转,正如我们希望的。只不过,有一个个非常微小的例外。
图9中的红色继承线表示EmployeeProxy不继承PersonProxy。这什么时候会带来问题呢?
好,考虑一下这样一个方法,它接受两个对象,并用反射来确定其中一种对象是否是另一种对象的子类。当我们把一个Person对象和一个Employee对象传递给该方法时,它会返回true。但是当我们把一个PersonProxy对象和一个EmployeeProxy对象传递给该方法时,突然它就返回false了。
除非你有基于反射的、检查继承层次的代码,不然应该是安全的。但是万一你真的有这样的代码,这里先警告一下也没坏处(你可以通过修改反射代码来避开这个问题,让它检测代理类型,并一路向上直到找到一个非代理类型)。
使用混入(Mixin)和拦截器类
我们已经向着可维护的领域模型架构努力了很长时间,让它能容下一个有效率的、可访问的基础架构来实行运行时领域模型管理。
我们是否可以做得更多呢?
让我们开始看一下那些只针对某些领域类、而不是全部领域类的功能&&对于这些功能,我们需要将所需的基础架构成员放在代理子类中,而不是在公用的基类中。在我们的例子中,为懒加载功能设置的加载标记将必须添加到需要支持懒加载的每个类的子类中。
由于我们可能需要在多个子类中放相同的代码,所以我们明显会导致代码重复。只有一个标记的时候情况还好,但是对于需要多个属性、甚至需要多个有复杂实现的方法的功能呢?
在我们的例子中,加载标记是一次写入(write-once),所以在变成true之后不能再切换回来。因此,在setter方法中,我们有一些代码来实施这个规则&&在需要懒加载的每个领域模型类的子类中,我们都要在setter方法中重复的那些代码。
我们会希望创建一个可重用的类来包含这种逻辑。但是,我们已经用了继承(仍然假定我们工作在单继承平台上),那么这个懒加载类该怎样被重用呢?
一个很好的答案就是使用组合模式[GoF Design Patterns],而不是为此采用继承。使用这种模式,EmployeeProxy子类将包含一个内部属性来引用可重用的懒加载类的实例(图10)。
这种可重用的类常常被称为混入(mixin),它反映了这样一个事实,即实现被加入到类型中,而不是成为继承层次的一部分。使用混入的作用是,让单继承语言(甚至是在只支持接口继承的平台上,比如COM+)也能够获得类似多重继承的效果。
有一点可以补充说明一下,使用我们这种组合方式,以混入的形式存在的行为和状态能动态地被加到目标类中(使用依赖注入),而不是静态地绑定到目标上去(如果行为是继承下来的就会如此)。尽管在这篇文章中我们不会更多关注于如何利用它的潜能,但是它为支持一些非常灵活和动态的场景提供了非常大的空间。
通过将子类引入的成员分离到一个可重用的混入类,我们向一个真正模块化、一致的、内聚的架构迈进了一大步,这个架构还是低耦合的、高度代码可重用的。我们能向这个方向再进一步吗?
那么,接下来要做的事就是把拦截代码从子类中分离出来,并放到可重用的类里面。这些拦截器类会像混入一样包含在代理子类里面,如图11.
Person、Employee和DomainBase与代码的上一个版本保持不变,但是PersonProxy和EmployeeProxy发生了变化,并且我们引入了两个新的拦截器类和一个新的混入类。清单5显示了我们进行这些重构(不包括未修改的类)之后代码的样子。
//These proxy subclasses contain only boilerplate//code now. All actual logic has been refactored//out into mixin and interceptor classes.public class PersonProxy : Person{
private DirtyInterceptor dirtyInterceptor = new DirtyInterceptor();
public override string Name
get { return base.N }
dirtyInterceptor.OnPropertySet(this, this.name, value);
base.Name = value;
}}public class EmployeeProxy : Employee, ILazy{
//This mixin contains the implementation
//of the ILazy interface.
private ILazy lazyMixin = new LazyMixin();
private LazyInterceptor lazyInterceptor = new LazyInterceptor();
private DirtyInterceptor dirtyInterceptor = new DirtyInterceptor();
public override string Name
lazyInterceptor.OnPropertyGetSet(this, &Name&);
return base.N
lazyInterceptor.OnPropertyGetSet(this, &Name&);
dirtyInterceptor.OnPropertySet(this, this.name, value);
base.Name = value;
public override decimal Salary
lazyInterceptor.OnPropertyGetSet(this, &Salary&);
return base.S
lazyInterceptor.OnPropertyGetSet(this, &Salary&);
dirtyInterceptor.OnPropertySet(this, this.name, value);
base.Salary = value;
//The ILazy interface is implemented
//by forwarding the calls to the mixin,
//which contains the actual implementation.
bool ILazy.Loaded
get { return lazyMixin.L }
set { lazyMixin.Loaded = value; }
}}//The following mixin and interceptor classes//contain all the actual infrastructural logic//associated with the dirty tracking and//the lazy loading features.public class LazyMixin : ILazy{
private bool
/// The Loaded property is &write-once& -
/// after you have set it to true you can not set
/// it to false again
bool ILazy.Loaded
get { return }
if (loaded)
}}public class DirtyInterceptor{
public void OnPropertySet(
object obj,
object oldValue,
object newValue)
if (!oldValue.Equals(newValue))
IDirty dirty = obj as IDirty;
if (dirty != null)
dirty.Dirty = true;
}}public class LazyInterceptor{
public void OnPropertyGetSet(object obj)
ILazy lazy = obj as ILazy;
if (lazy != null)
if (!lazy.Loaded)
lazy.Loaded = true;
//perform lazy loading...
//(omitted)
通过重构,最终所有实际的基础架构逻辑都被放进了混入和拦截器类,代理子类变得很苗条,变成专注于转发请求到拦截器和混入的轻量级类。事实上,在这一点上,代理子类里面除了由很容易被代码生成工具生成的样板代码之外,再没其它什么内容。
一种选择是在设计时生成代理子类的代码,但是像C#和Java这样的语言能让你在运行时生成、编译、执行代码。因此,我们也可以选择在运行时生成代理子类,这种做法就是俗称的运行时子类化(runtime subclassing)。
花一点儿时间回头想想我们走了多远。在第一步,我们将肥领域模型重构为带有代理子类的POJO/POCO领域模型(还可选择增加一个基类),同时基础架构代码仍然分布在领域模型中。在重构的第二步,所有实际的基础架构逻辑从代理子类分离了出来,并加入到可重用的混入和拦截器类,在代理子类中只留下了样板代码。
在最后一步,我们转向使用运行时代码生成器来为样板代理子类生成全部代码。就这样我们一路跋涉到了面向方面编程的疆界。
使用面向方面编程
很长一段时间,我对AOP的大肆宣传都感到很新奇,每次我尝试研究它是怎么一回事的时候,就会完全迷惑&&这主要因为AOP社区坚持使用他们哪种奇怪的术语。而且常常有一些(不是全部!)AOP倡导者似乎在故意让这个领域看起来那么的不同,让人兴奋并觉得胆怯。
因此,如果说读到这里,实际上你已经掌握了面向方面编程的所有主要概念,你可能会感到惊讶&&不但如此,而且你已经看到了AOP框架在背后是如何运作的,从头到脚。
面向方面编程使用了&方面&这个核心概念,方面简单来说是一组引介(introductions,即混入)和通知(advice,即拦截器)。方面(拦截器和混入)可以通过运行时子类化(runtime subclassing)的方式(以及其他技术)在运行时应用于现有的类。
正如你看到的,你已经理解了大部分怪异但重要的AOP术语。被大量用来描述方面对什么有益的术语横切(crosscutting)关注点,简单意思就是你有一个能应用于大多数(不一定属于同一个继承树的)类的功能&&例如我们的脏跟踪和懒加载功能,这些功能就是横切跨领域模型的关注点。
我们至今还未真正涵盖、唯一真正重要的AOP术语是连接点(join point)和切点(pointcutting)。连接点是目标类中的一个地方,你可以在那里应用拦截器或混入。通常,拦截器所关心的连接点是方法,而混入所关心的连接点则是类本身。简单地说,连接点就是你能加入方面的点。
为了确定哪个方面应该被应用到哪个连接点,你可以使用切点语言。这个语言可以很简单,可以是描述性的语言,比如你可能只是在一个xml文件中命名方面、命名连接点组合;但是也有复杂的可以使用正则表达式、甚至完整的领域特定语言(Domain Specific Languages)来进行更高级的&切点&。
到目前为止,我们还尚未关注切点,因为我们已经在我们需要的地方手工应用了我们的方面(我们的混入和拦截器)。但是,如果我们要想走完最后的一步,为应用我们的方面而使用AOP框架,我们还是需要考虑这个问题。
但在我们做之前,先让我们停下来思索一下这个计划。在我们前面已经完成那么多重构之后,现在加入AOP框架步子其实并不大。它仅仅是很小的一步,因为我们已经有效地重构了我们的应用,使其使用拦截器(通知)和混入(介绍)形式的方面。
事实上,我会认为我们实质上已经在利用AOP了&&我们只是还没有用AOP框架来为我们自动化那些例行公事的部分。但是我们利用着来自AOP的所有基本的概念(拦截和混入),并且我们用一种可重用的方式处理横切关注点。这听起来像极了AOP的定义,所以AOP框架能帮助我们自动化完成之前的一些手工工作是一点儿也不会令人感到意外的。
目前拦截器和混入类中的代码只要轻微地修改一下就是方面了&&它已经被重构为一个泛化的、可重用的形式,这正是方面应该具有的形式。我们需要AOP框架做的只是以某种方式帮助我们将方面应用到领域模型类中。如前所述,AOP框架有许多方式可以做到这一点,其中一种方式是在运行时生成代理子类。
如果用的是运行时子类化框架,生成的代理子类代码看起来会和我们例子中代理子类的样板代码非常近似。换句话说,它很容易生成。如果你认为你可以写一个代码生成器来完成任务,那么你就在实现你自己的AOP框架引擎的半路上了。
重构到方面
在我们最后的重构中,我们将看一下,如果我们使用一个AOP框架来应用我们的拦截器和混入,那么我们的例子代码会怎样。在这个例子中,我将使用NAspect,这是Roger Johansson和我合作完成的一个针对.NET的开源AOP框架。NAspect使用运行时子类来运用方面。
读到这里,至今你应该不难理解NAspect执行的&花招&&&它简单地生成包含样板代码的代理子类,样板代码需要转发请求到混入和拦截器,并且它使用标准的、面向对象的、领域类成员的重写,来提供拦截。
很多人把AOP和最糟糕的巫术联系在一起。奇怪的术语已经令人怯步,开发人员在领域类代码中预见不到的事情忽然并&毫无察觉&地发生在拦截器中,令很多人遭受打击,深深地感到不安。
当然,具体了解这些术语是什么意思会有帮助。能理解至少一种运用方面的方法也是有帮助的,至少能让你看到这不是什么魔术&&只是几个良好的、成熟的、众所周知的、面向对象的设计模式在发挥作用。
AOP就是用我们熟知的面向对象来对付横切关注点。AOP框架只是让你能够更舒服地运用方面,不用自己手写许多代理子类。
所以,现在我们不会再被AOP给吓到了,让我们看看我们如何使用它来使我们的应用向终点线迈出最后一步。
在这个例子里,我们要做的第一件事情是包含一个对NAspect框架程序集的引用。准备好之后,我们就可以开始创建我们的方面、确定我们的切点。我们也可以直接删除PersonProxy和EmployeeProxy类&&从现在开始,NAspect会为我们生成这些类。
如果我们愿意,我们甚至可以删除DomainBase基类&&没有必要使用基类来应用公用于所有基类的混入,我们也可以使用AOP框架来做这些。这意味着,通过使用AOP框架,我们甚至可以无需任何额外工作就能满足POJO/POCO的严格要求。
为了除去基类,我们只需要把功能移到混入中去。就我们的情况而言,我们仅需要再创建一个混入&&DirtyMixin,它持有先前放在基类中的脏标记。
LazyMixin类中的代码完全保持不变,所以这里不再列出。实际上,我们真正需要去做的是创建新的DirtyMixin类,并稍微修改两个拦截器中的代码,让它们使用NAspect传进来的描述被拦截方法的数据结构。
我们还需要修改我们的工厂类,以便它使用NAspect来为我们创建代理子类。这假定我们已经在应用中的所有地方都转为使用抽象工厂模式。如果我们还没有这样做,现在我们一定要汲取教训,因为没有抽象工厂模式,我们将不得不进行另一项庞大的搜索、替换操作,仔细检查代码中每一个代理子类被实例化的地方,改为调用NAspect运行时子类化引擎。
重构后的代码如清单6所示,准备好使用NAspect。
public class DirtyMixin : IDirty{
private bool
bool IDirty.Dirty
get { return }
set { dirty = }
}}public class DirtyInterceptor : IAroundInterceptor{
public object HandleCall(MethodInvocation call)
IDirty dirty = call.Target as IDirty;
if (dirty != null)
//Extract the new value from the call object
object newValue = call.Parameters[0].V
//Extract the current value using reflection
object value = call.Target.GetType().GetProperty(
call.Method.Name.Substring(4)).GetValue(
call.Target, null);
//Mark as dirty if the new value is
//different from the old value
if (!value.Equals(newValue))
dirty.Dirty = true;
return call.Proceed();
}}public class LazyInterceptor : IAroundInterceptor{
public object HandleCall(MethodInvocation call)
ILazy lazy = call.Target as ILazy;
if (lazy != null)
if (!lazy.Loaded)
lazy.Loaded = true;
//perform lazy loading...
//(omitted)
return call.Proceed();
}}public class Factory{
public static IEngine engine = ApplicationContext.Configure();
public static Domain.Person CreatePerson()
return engine.CreateProxyPerson&();
public static Domain.Employee CreateEmployee()
return engine.CreateProxyEmployee&();
为了知道你的方面要应用到哪个类,NAspect在程序的配置文件中使用一个xml配置段(也有其它选择)。现在我们可以在xml文件中定义方面,指出混入和拦截器类的类型,以及我们想应用的类型。大体上,我们准备好了尝试一下我们的AOP应用。
不过,仍然有一点让许多开发人员对使用AOP犹豫不决,甚至当他们理解了AOP的术语和理论,那就是由于切点体系定义得不完善而带来的脆弱性。
虽然我们通过一些巧妙的正则表达式来筛选出打算应用方面的类和成员,但风险是当领域模型变化的时候,你可能会忘了更新切点定义,以致正则表达式忽然筛选到了不应该应用方面的类。就是这类问题给AOP带来了巫术的坏名声。
处理切点不明确的一个方式是创建自定义的.NET属性(Attribute)(Java中的注释(annotations))。用自定义属性装饰领域模型,然后使用属性做为切点目标,这样就可以避免使用正则表达式之类的小花招。方面只应用于我们决定该应用的地方,通过在那里放置属性。
因此根据我们的情况,我们继续创建两个自定义属性&&LazyAttribute和DirtyAttribute&&接下来我们使用它们来装饰我们的类(清单7)。
public class DirtyAttribute : Attribute{}public class LazyAttribute : Attribute{}[Dirty]public class Person : DomainBase{
protected string
public virtual string Name
get { return }
set { name = value; }
}}[Dirty][Lazy]public class Employee : Person{
protected decimal
public virtual decimal Salary
get { return }
set { salary = value; }
public override string Name
get { return }
set { name = value; }
我们接着在应用的配置文件中定义我们的切点(清单8),让方面以我们自定义的属性为目标。
name=&naspect&
type=&Puzzle.NAspect.Framework.Configuration.NAspectConfigurationHandler, Puzzle.NAspect.Framework.NET2&/&
name=&DirtyAspect& target-attribute=&InfoQ.AspectsOfDMM.Attributes.DirtyAttribute, InfoQ.AspectsOfDMM& &
target-attribute=&InfoQ.AspectsOfDMM.Attributes.DirtyAttribute, InfoQ.AspectsOfDMM& &
type=&InfoQ.AspectsOfDMM.Aspects.DirtyInterceptor, InfoQ.AspectsOfDMM& /&
name=&LazyAspect& target-attribute=&InfoQ.AspectsOfDMM.Attributes.LazyAttribute, InfoQ.AspectsOfDMM& &
target-attribute=&InfoQ.AspectsOfDMM.Attributes.LazyAttribute, InfoQ.AspectsOfDMM& &
type=&InfoQ.AspectsOfDMM.Aspects.LazyInterceptor, InfoQ.AspectsOfDMM& /&
现在,我们最后测试一下我们的AOP应用。当我们运行应用时,NAspect会在运行时为我们生成代理子类,并在子类中插入样板代码来将所有的请求转发到拦截器和混入类。
程序最后的架构可参看图12。注意,这个版本和先前非AOP版本之间的唯一实际差别是,两个代理类(使用虚线边框标识)将由AOP框架生成,而之前我们必须手工编码。
与图11比较,我们多了一个DirtyTrackerMixin类和一个新的IDirtyTracker接口,来代替DomainBase基类,但这些东西哪怕我们不使用AOP,只要决定不使用公共基础架构基类(以满足更严格的POJO/POCO需求)就是要有的。换句话说,如果我们不想使用一个公共基类,不管我们是否使用AOP框架,我们最终都会得到一样的架构(图12所示)。
当你添加新的领域模型类时,要想使它们能够懒加载和脏跟踪,只需要用自定义属性装饰它们,就会&变魔术一样地&获得相应功能了。
用属性/注释来作为切点目标,很快你就会注意到,由于每项功能对应一个属性,很快领域模型中每个成员的属性就太多了。因此,为了减少属性的数量,你可能想找到它们之间的抽象性。
如果你适应基于正则表达式的切点方法,并且你觉得属性令领域模型变得杂乱,使得保持领域模型与基础架构关注点完全无关的目标受到了损害,那么另一种选择是,你可以只配置你的xml配置文件来匹配相关的目标类,并且不需要用自定义属性装饰你的领域类。
另外,NAspect的xml切点语言(像许多其它的AOP框架一样)允许你简单地提供一个完整的类型清单,方面会运用于列出的类型,让你不用去找到一个刚好只匹配正确类的正则表达式。这使配置文件变得更长,但是能让你的领域模型保持完全干净。
在使用切点来动态运用方面的地方使用AOP框架,还有一个好处就是,当方面被用在不同的用例中时,把不同的方面应用于相同的类中会变得很容易。如果一个用例要求某个领域类懒加载,而另一个用例不需要,那么只有第一个用例需要使用应用懒加载方面的配置文件。
这个动态的方面应用可以非常强大,比如在测试场景可以额外应用一些测试用的方面,提供mocking之类的功能。
使用面向方面编程的结论
我们是否可以在领域模型中放置任何基础架构代码,这是我们开始时的问题。我们看到,这绝对可取,因为它令许多功能得以更有效率地实现,但是可能使我们的领域模型变得&肥胖&。
为了解决这个问题,我们重构了我们的肥领域模型&&首先是把基础架构代码移到代理子类和一个基类中,然后将实际的逻辑从代理子类中移出、移到混入和拦截器类中。这使我们的领域模型变得更漂亮,但是在代理子类中最终却有很多样板代码需要写。为了解决这个问题,我们转向了面向方面编程。
结果发现迈向AOP框架的步子原来是如此之小&&事实上应用的架构并没有任何改变&&以至它大概使一些读者感到惊奇,这些读者甚至有熟悉的感觉,这种感觉就像当你第一次理解一个设计模式时,你意识到它就是你已经用了很多次的东西,只是不知道它有一个这样的名字。
你很可能在许多应用中有效地使用过AOP,但并没认识到它是AOP,并且好像没有使用过AOP框架来帮助你自动化地生成部分样板代码。
我们现在在这篇文章的结尾处,很希望在这个地方你会同意我的看法:
AOP不像它看上去的那么难以理解和令人迷惑,并且使用AOP框架是很容易的。
不管你是否使用AOP框架,使用AOP的概念和建模技术,对处理你应用基础架构中的横切领域模型管理关注点来说,仍然是一个非常好的方法。
它实际上就是一个通过使用良好的、成熟的面向对象模式,稳步提高应用架构的重构问题。这样做的时候,不要害怕走向一个使用代理来运用拦截器和混入的,可称为面向方面的应用架构。
不管你是否进一步让一个AOP框架来帮助你应用方面,这都不会真正影响你是否在进行AOP。因为,如果你是顺着这篇文章讨论的路线来重构你的应用的,你就是在进行AOP&&不管有没有一个框架的帮助。
通过使用像代理模式、抽象工厂模式、组合模式这些传统的、知名的面向对象设计模式来重构我们的肥领域模型,我们得到了一个极好地结合了精益领域模型和基础架构的应用架构,基础架构充分利用了面向对象的概念和构造。这样,我们巧妙地避免陷入肥领域模型和贫血领域模型反模式的陷阱。
使用这篇文章中讨论的模式,得到了一个可以被形容为面向方面的应用架构。这是因为我们最终用混入和拦截器类解决了横切关注点,混入和拦截器则精密地与面向方面编程中的介绍和通知匹配了起来。
如果你想亲自体验使用AOP是不是真的那么轻松愉快,而且不介意使用不是亲手键入的代码,你可以下载本文随附的Visual Studio 2005工程的所有代码。
总之,在这篇文章中,我尝试去展示面向方面的概念和工具是怎样提供一个伟大的方式去看待和运用许多领域模型管理关注点,以及它们是如何大大减轻了肥领域模型反模式的风险。
Mats Helander是Avanade(荷兰)的一位资深软件开发咨询师。业余时间,他和Roger Johansson一起开发了免费的Puzzle.NET套件,Puzzle.NET是一个开源框架和工具。Puzzle.NET包括NPersist、ObjectMapper(对象/关系映射)、NAspect(面向方面编程)、NFactory(依赖注入)、以及NPath(内存对象查询)。
Mats Helander的博客&&Puzzle.NET&&Avanade(荷兰)&&
[Evans DDD]
《领域驱动设计:软件核心复杂性应对之道》,Evans Eric著。 &&马萨诸塞州,波士顿:Addison-Wesley出版社,2004。
[Fowler贫血领域模型]
Fowler Martin:
[Fowler PoEAA]/bliki/AnemicDomainModel.html《企业应用架构模式》,Fowler Martin著。 &&马萨诸塞州,波士顿:Addison-Wesley出版社,2003。
[GoF设计模式]
《设计模式:可复用面向对象软件的基础》,Gamma Erich、Richard Helm、Ralph Johnson、John M. Vlissides合著。&&马萨诸塞州,里丁镇:Addison-Wesley出版社,1995。
[Helander DMM]Helander Mats:
[Helander肥领域模型]Helander Mats:
[Nilsson ADDDP]《领域驱动设计和模式应用》,Nilsson Jimmy著。&&Addison-Wesley出版社,2006。
查看英文原文:
告诉我们您的想法
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
gao linnchord
DMM的概念很不错
wang jacky
非常感谢Infoq提供这么好的文章
lazyInterceptor.OnPropertyGetSet(this, &Salary&);有问题
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
Ralph Winzinger
赞助商链接
InfoQ每周精要
通过个性化定制的新闻邮件、RSS Feeds和InfoQ业界邮件通知,保持您对感兴趣的社区内容的时刻关注。
<及所有内容,版权所有 &#169;
C4Media Inc.
服务器由 提供, 我们最信赖的ISP伙伴。
北京创新网媒广告有限公司
京ICP备号-7}

我要回帖

更多关于 淘宝上面帮我挑怎么弄 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信