JGTM'2008 [MVP]

Thinking in WPF...
随笔 - 36, 评论 - 453, 引用 - 16

导航

标签

每月存档

最新留言

广告

【第1页/共3页,44条】
首页
前页
1
2006年10月29日

[DRAFT] Ease API Designing with .NET Interception Technology

There will be another series of articles discussing solving real problem (during our R&D process) with .NET's interception technology (specifically, RealProxy/TransparentProxy for intercepting method invocation for mocking API implementation).

With technology discussed in this installation, you will be assisted a lot during API designing phases, eliminating thousands lines of codes in effect to achieve the same runtime behavior (not counting performance) and that allowing you to incrementally implement your library.

Phase O: Initiative/Goals

1. get bored writing tedious fields, property getters n' setters, etc?

class X
{

   private int _a;

   public int A

   {

     get { return _a; }

     set { if (_a != value) _a = value; }

   }
}

Sure we could define interfaces, like this:

interface X
{
  int A { get; set; }
}

But without at least one concrete implementation, this code just cannot run and use.

2. Getting bored writing trival constructors merely to initialize various properties/fields?

class X
{

   public X() { _a = 0; }
   public X(int a) { _a = a; }
}

3. In an AJAX world, everything's changing incrementally. how to track object's changes and deal with only changed parts?

X x = new X();

DetectChanges(x); // -> nothing

x.A = 123;

DetectChanges(x); // -> x.A changed to 123

CommitChanges(x);

DetectChanges(x); // -> nothing

x.A = 123;

DetectChanges(x); // -> nothing!

x.A = 1234;

DetectChanges(x); // -> x.A changed to 1234

4. And to serialize such changes as XML/JSON, XmlSerializer is just no-use in such situation.

X x = new X(); // we want: <X />

x.A = 123; // we want: <X a="123" /> or X {a:123;}

x.B = 456; // we want: <X a="123" b="456" /> or X {a:123; b:456;}

Phase I: A Mock Factory

1. Simple Type Implementation

interface M

{

  int A { get; set; } 

}

void Test()

   M m = MockFactory.Create<M>();

   Debug.Assert(m!=null);

   m.A = 123;

   Debug.Assert(m.A==123);

}

2. Nested Type

interface N

{

  M B { get; set; }
}

void Test()

{

   N n = MockFactory.Create<N>();

   Debug.Assert(n!=null);

   M b = n.B;

   Debug.Assert(b!=null);

}

3. Improvement and Refactor

a) Get unassigned ValueType/ReferenceType?

b) DefaultValue?

Q. Constraint while set value? CannotBeNull(), Within(min, max), etc.

4. Implement of GetType(), ToString(), GetHashCode() and Equals()

5. Delegate inner method to external extension concrete class?

interface X

{

  void F();

}

class ExtensionX: X
{

   void F() { Debug.Assert(true); }  
}

void Test()

{

   X x = MockFactory.Create<X>();

   x.F(); // -> NotImplementedException

   MockFactory.RegisterExtenstionClass<X, ExtensionX>();

   x.F(); // -> Okay now

}

6. Refactor General-4 method to ExtensionBase:

class ExtensionBase
{

   Type GetType() {}
   string ToString() {}
   int GetHashCode() {}
   bool Equals() {}
}

How an extension instance could get "context" information about target instance?

Phase II: A Mocked Factory

1. Getting bored with MockFactory.Create<T>()? Let's define our own factory interface!

interface Factory

{

  M CreateM();
}

void Test()
{

   Factory factory = MockedFactory.Create<Factory>();

   Debug.Assert(factory!=null);

   M m = factory.CreateM();

   Debug.Assert(m!=null);
}

2. How about non-default constructor?

interface Factory
{

   M CreateM(int A);
}

void Test()
{

   Factory factory = MockedFactory.Create<Factory>();

   Debug.Assert(factory!=null);

   M m = factory.CreateM(123);

   Debug.Assert(m!=null);

   Debug.Assert(m.A==123);
}

Main challenge here is to locate property member for setting value when there is multiple interface inheritance. For instance,

interface X
{

  int A { get; set; }
}

interface Y: X
{

  int B { get; set; }
}

From typeof(Y) you can only get a member information (more specifically, PropertyInfo) for name "B" but not "A", because "A" is declared in interface X. So, you need to find a property info from a combination of Y and its interfaces implemented, in this case, X.

Q.

interface M

{

  int A { get; set; }
  int B { get; set; }
}

interface Factory
{

   M CreateM(int A);
   M CreateM(int B); // <-- conflict with CreateM(int A)
   M CreateM(int A, int B);
}

Phase III: Solve a Real Problem

1. Get changeset during object operation:

interface M

{

  int A { get; set; }

interface Factory
{

  M CreateM();
  M CreateM(int A);
}

void Test()

{

   Factory factory = MockedFactory.Create<Factory>();

   M m;

   // create "clean" instance

   m = factory.CreateM();

   PropertyInfo PropA = m.GetType().GetProperty("A");

   PropertyInfo PropN = m.GetType().GetProperty("N");

   Debug.Assert(ChangeDetector.HasChanged<M>(PropA)==false);

   m.A = 123;

   Debug.Assert(ChangeDetector.HasChanged<M>(PropA)==true);

   // create instance with

   m = factory.CreateM(123);

   Debug.Assert(ChangeDetector.HasChanged<M>(PropA)==true);

 }

posted on 2006-10-29 20:44:00 by jgtm2000  评论(0) 阅读(1136)

 
2006年01月01日
新文章的骨架素材,请大家预览并提出宝贵建议(这是素材,还没有串接成文)。

文章的标题拟为:Thinking in PPP: DIP, Adapter and TDD

首先的问题是,DIP是什么?

依赖反转原则:高层模块不应该依赖底层模块,两者都应该依赖抽象;而抽象不应该依赖细节,反之细节应该依赖于抽象。

引用针对包的DIP原则:SDP+SAP 也即朝着稳定和抽象的方向进行依赖。

如果X->Y,那么我们需要评估Y的稳定性以确定该依赖的稳定程度:

class X
{
?public void F()
?{
? ?Y y = new Y();
? ?y.G();
?}
}

class Y
{
?public void G() {}
}


因为依赖者的稳定性不可能超过被依赖者的稳定性,如果被依赖者不太稳定,则依赖他的其他类也就不可能太稳定,这不是我们希望得到的结构。

如果被依赖者的稳定程度非常之高,则这个依赖也是可以接受的;否则,有必要提取一个相对稳定的抽象以改善这个依赖。

通过运用重构的提取接口技术,从Y提取出I,并让Y实现I——此时,Y通过realize关系静态依赖于I,其稳定程度不会高于I;

class Y: I
{
?void I.G() {}
}

interface I
{
?void G();
}

接下来,改变X以使之从直接依赖于Y变为通过I间接的依赖Y:

class X
{
?public void F()
?{
? ?I i = new Y();
? ?i.G();
?}
}

为什么说X是间接的依赖Y?因为如果没有Y,X连编译都不能通过

怎么办?通过使用运行时动态对象工厂来消除X对Y的静态依赖(这里图省事,我们直接使用.NET
Framework中的Activator来创建对象——实践中一般都是引入一个专用的组件工厂类):

class X
{
?public void F()
?{
? ?I i = (I)Activator.CreateInstance("Y, Demo");
? ?i.G();
?}
}

现在就算你把整个Y注释掉,程序的静态结构也是完整的——也就是说,我们已经消除了X和Y之间的静态依赖,降低了X和Y之间的耦合。

现在的结构是:X->I<-Y。被依赖的核心变成了I——再次评估此结构,我们发现X和Y的稳定性都不会高于I,因此如果I不稳定,一切都是徒劳。那么I到底稳定吗?要看I是如何而来。因为I是从原本不稳定的Y中提取的,如果抽象的合理,抽象出来的I确实代表了Y的稳定的本质,那么I就有一定的稳定性,那么该结构就相对合理了。可是如何能够保证I一定是合理的抽象呢?这就引出一个问题,I到底是属于谁的?

请问X->I<-Y到底是(X->I)<-Y,还是X->(I<-Y)呢?

事实上,对抽象的定义实际上是由对抽象的依赖者来确定的,该抽象的实现者只能去无条件的接受这个抽象,提供对这个抽象的实现——比较经典的说法也就是:接口是属于它的客户而不是具体实现的。

从包的层次讲,如果你需要分离X和Y到两个包中,那么你会把X和I放在一个包中呢,还是把Y和I放在一个包中?显然 ,X应该是和I在一个包中,也只有这样才能保证即便没有Y的存在X也是可以编译通过而独立存在的。此时,Y的包需要通过引用X(实际上只是I)所在的包即可为之提供一个具体的实现。

问题又来了,如果Y并不在我的控制内,也就是说即便我抽象出了I,Y也不可能实现这个接口(比如说Y是一个第三方组件)——这时候ADAPTER就是你的救星了。通过编写一个适配器A,使之对X提供I的实现,并利用Y来提供这个实现,我们可以达到这样的结构:X->I<-A->Y。如果要分离包,原理和前面是一样的。

很多的ORM工具,可以帮你由数据库结构导出一个对应的对象结构,然后你的应用程序就去依赖这个对象结构进行开发了。问题是,本来数据库结构的稳定性就不好,由这个不稳定的东西"直接"映射出一个静态结构,其稳定程度不可能高于它的源,因此你去依赖这样一个同样不稳定的对象结构,除了编程上面可能要略为方便一些以外,从结构的角度看简直就是一塌糊涂——因为这违反了DIP原则。

从大的架构来看,一个企业级应用程序是依赖于特定的数据库的(这里的特定有两层含意:一是提供数据库功能的服务器;而是数据库本身的数据结构和功能)。而大量的开发经验告诉我们,数据库是非常不稳定的环节之一。如果整个应用程序都依赖这个不稳定的数据库,则整个应用程序的稳定性就好不到哪儿去。怎么办?通过引入稳定的抽象反转这种依赖关系。我们将应用程序对数据的所有需求都封装到一个稳定抽象的接口中,然后令数据库去提供这个抽象接口的一个具体实现。可是数据库是一个外部软件实体,我们不能让它实现我们提供的抽象接口,于是我们写了一系列的所谓数据访问组件,里应外合,对应用程序提供一个满足其所指定的抽象接口的具体实现,而这里的实现是委托给后面的特定数据库来完成的。结构:(App->DAI)<-DAC->DB。

再来看应用程序使用WS的例子:VS.NET可以帮你从WSDL生成一个PROXY(在应用架构中也即Service
Agent,SA)。众所周知,每当WSDL变化的时候(你说了,不是说服务接口发布之后就不许变了吗?!那当然是比较理想了,然而现实就是这么无奈啊!),你需要Refresh
Web Reference,那么VS.NET就会把这些变化重新反映到为你生成的SA中,而你的应用程序可是直接依赖于这个SA的!因此,你不得不去更改你的客户代码。

应用本文所述原理,应用程序应该首先确定为实现自身功能所需要的抽象,然后让VS.NET从WSDL中创建一个PROXY出来。但是这个PROXY并不能以应用程序所定义的抽象来提供具体实现,于是我们可以写一个Service
Agent,让他来适配两边不一致的接口。

OOAD就是通过分析一个可以工作的动态逻辑(dynamic
logic,也可以称之为一个算法)所需的超越本质逻辑的软件特性(如可维护性、可扩展性、灵活性等等)通过重构将其转化为一个可以达到逻辑等价的动态逻辑的静态对象结构。

无论结构变得有多复杂,其运行时的动态逻辑不应该发生超乎本质的改变;

通过重构而带来的结构复杂性一般而言都会在不改变本质动态逻辑的前提下增加开销,因此软件设计人员的主要能力也是去正确的评估增加的开销和带来的利益的关系;

DIP带来很多好处

首先,它帮助上层模块满足了OCP(开闭原则),这样上层逻辑就能够避免受到具体实现细节变动的侵扰而保持更高的稳定性。

DIP在软件组件之间引入稳定的抽象从而降低模块之间的耦合,大大提高了软件组件的可测试性;

考虑DIP使得需求抽象先于实现细节而确定,有助于实现并行开发,有效的增强了团队的协作程度,提到了开发效率。

TDD帮助开发满足DIP要求的软件

因为TDD强调从客户的角度考虑代码的编写,这和DIP中由分析客户代码需求而提取其所依赖的核心抽象是一致的。

posted on 2006-01-01 01:04:00 by jgtm2000  评论(2) 阅读(1570)

 
2005年的最后一天,北京。一大早,雪下得好大——这该是2005年的最后一场雪吧,或许也是2005年第一场真正意义上的雪。晚上,夹在滚滚车流中,来到首体听齐秦黄金20年的演唱会。一首首熟悉的老歌唱起,我的脑中也随之浮现出历历往事——好一个黄金20年啊……
 
而2005年对于我和我们团队来说,则是非常具有戏剧性、充满转折点的一年。
 
自从2004年7月开始创业以来,身边的朋友们都十分关注我们项目的进展。创业之路确实崎岖,然而我们仍然坚信,只要坚守信念和理想、坚持到底就是胜利。事实证明:只要精神不滑坡,办法总比问题多。
 
2005年的新春佳节的前几天,团队的投资人之一突然变故,团队一夜之间竟连个“窝”都没有了,频临破碎。幸好canyue家有间两居室的新房,花了几百块钱简单装弄了一番,我们开始了一穷二白的第二阶段创业。如果说只是物质上的苦,对我们来说也真的不算什么,可屋漏偏逢连阴雨,sumtec退守观望,原本坚定的musicland也因为家里的压力不得已而退出——深深地,我被打击了一下。博文视点的周老师说过,有些人并不像看上去那么坚强,有些人却比看上去要坚强得多。是爷们儿就得坚强不是,谁让咱们认准要走创业这条荆棘路呢!
 
挺到五月份,终于迎来转机——不过也不是天上掉馅饼,而是需要权衡得失利弊,决定是否与农业部系统内的一家公司在深层面上战略合作。我带着兄弟几个把局势仔细分析了一番,考虑了若干种可能性,最后大家终于统一了思路——在坚守信念的大前提下,放弃一些可以放弃的利益,去争取一些需要争取的资源。还好,自此之后,事情一直如愿发展。我们用了大半年的时间来建设公司的团队,帮助公司打开了局面,也同时借助几大项目来运作共同的资源。就这样,我们双方在优势互补、资源共享的大前提下都得以和谐发展。
  • 这一年,我们和电视媒体相关的3G无线增值项目的理念被越来越多的业内人士所认同,而大环境、市场及相关政策也给我们留出了足够的成熟与完善的时间与空间,阿弥陀佛;
  • 这一年,我们依托农业部的项目研发了集影像识读(包括条码与二维码的识读以及图像拍摄)、智能卡读写、全球卫星定位以及无线数据传输等四大功能于一身的掌上电脑,为日后的移动应用拓展奠定了坚实的基础,也在这个过程中积淀了必要的人脉和网络;
  • 这一年,我们把创新精神和前沿技术用在国家大事上(禽流感监测、预警与防控指挥决策),确立了总投资约1.3亿的部级科技攻关项目和相关工程,同时拓展了我们的合作伙伴网络,为各位伙伴也带来了更多的机遇和收益;
  • 这一年,我们还为明年的互联网事业奠定了项目基础(有谁知道国内有多少Google Earth玩家?)……
总之,2005年对我和我们团队而言,都是充满曲折、变数、挑战和戏剧色彩的一年。而2006年已经到来,期望在这一年里大家都能脚踏实地,把计划中的事情一项项落实,惟有这样,再大的事情、再难的问题也终归会被解决,胜利总是属于内心坚定、敢想敢做的人们的!加油!
 
另外号一个外:不出意外的话,微软出版社的《代码大全》(第二版)将由电子工业出版社博文视点公司在春节前后发布上市,我作为第一译者有幸代表全书所有译者为本书作序。这是一本经久不衰的好书,它影响了十几年前的一批程序员,也必定会对今天广大的程序员有所帮助。真心期望每一名真正的程序员都读过这本书(无论是译本还是原版),并将书中的实用技术应用于每日的编程工作中,真正地体会到编程的艺术性和科学性。
 
最后,祝大家2006年顺利、快乐、梦想成真!旺旺旺~~  

posted on 2006-01-01 00:46:00 by jgtm2000  评论(28) 阅读(7908)

 
2005年05月20日

随着中国日渐成为世界最大的移动通讯与应用市场,一大批中小型移动增值应用软件商也迅速崛起,并奋力挖掘着也许是继互联网之后最大的商机。然而,移动应用市场并不简单,软件开发商所处的是一个十分复杂的产业生态环境。如何在这个环境中寻求商机,稳步发展,并在这个竞争激烈的巨大市场中站稳脚跟,是每个移动应用开发商必须要想清楚的事情。

让我们首先来看移动应用产业生态环境中的各个角色吧。

首先是移动网络运营商。通过运营商定制手机的现象我们可以看到,移动网络运营商正逐渐成为产业链的主导者,因此移动应用开发商首先要考虑移动网络运营商的根本利益。在移动市场刚刚形成的时期,用户主要以高端人群为主,体现在每用户平均收益(ARPU)上是比较乐观的。但是随着市场规模的日益扩大,手机终端设备的成本急剧下降,再加上人们对低廉实用的短信业务的认同,市场中涌入大量低端用户,这些用户极大的拉低了对运用商意义重大的ARPU值。因此运营商现在考虑的一件十分重要的事情,就是如何能够在这些低端用户身上挖掘更多的利润,这也正是对于运营商而言移动数据增值应用的意义所在。那么作为移动应用开发商,我们如何能够帮助运营商提供最终用户更好的数据增值业务,从而提升其ARPU值呢?

手机设备制造商在这种引导力的作用下,也必然会主动地考虑如何量身定制符合运营商思路的终端设备。彩屏手机的迅速普及就是为了迎合彩信这一无线增值应用,而智能手机(包括2.5G/3G的手机)作为未来最重要的数据增值业务的运行平台,也正逐渐成为引领市场潮流的重要力量。然而,随着智能手机软硬件平台的逐渐统一,各制造商的产品的差异性越来越小,因此增值应用软件就成为手机差异性优势的一个重要来源。那么作为移动应用开发商,我们如何为手机设备制造商提供更多的卖点,从而提升其差异性竞争优势呢?

手机平台开发商,大多也同时作为软件开发工具提供商,是提供手机设备制造商以及移动应用开发商一个统一、开放的软硬件应用开发与运行平台的厂商。他们考虑的当然是平台的普及性,越普及的平台就会产生越多的授权销售,而越好的开发工具将越能促进移动应用的诞生,反过来近一步巩固平台的市场占有地位。那么作为移动应用开发商,我们如何最大限度的利用软件开发工具提供商提供的平台应用开发工具,在适当的平台上开发出更好的应用,从而促进手机平台开发商的市场地位呢?

简单的分析之后,我们开始考虑how-to在这样一个复杂的、环环相扣的产业链中找到自己合适的位置,并逐渐融入到这个产业链中生根发芽发展。结合我们自身创业摸索的经验和一些心得体会,我们想可能主要有以下这样几点:

首先是积极地创新,这是非常重要的。单纯的模仿别人已有的应用模式是不够的,一定要能够深入挖掘最终用户的需求,想在用户前面、做在对手前面,成为市场的开拓者和引领者。这一点而言,并不是只有大公司才可以办得到。相反,倒是只有三、五个人,但又极具创造力的团队更容易做到这一点。我们有一个很实用、也很顺理成章的建议,那就是每一个从事移动应用产品设计与研发的人都应该自己首先拥有一台智能移动终端设备,这样子才能够随时随地的更有效的发现生活中、工作中、娱乐中的实际需求。

第二个体会就是,要注重积累和培养和产业伙伴之间有序、高效的沟通,利用价值链自身的利益驱动力借力打力,从而达到四两拨千斤的效果。很多像我们一样刚起步的移动应用软件开发创业公司可能都有这样的体会,那就是经常干使劲儿却不见效,就好像开车时候猛轰油门却不见车走——因为没有挂上档。和产业伙伴的沟通就是“挂档”的过程,通过找到彼此的利益结合点,按部就班的找到一档、二档乃至更高档的位置,从而实现顺利起步、逐渐加速的效果。

第三点,一定要选择正确的时机进入市场。俗话说:赶得早不如赶得巧。我们想到前面、做到前面,目的是更好的把握未来的市场机遇。但是,这并不是说要不顾一切的赶进度、拼市场,因为大多数中小应用开发商并不具备这样的实力来抵御大规模挺进市场的风险。比如说即将到来的3G大潮,我们现在为之做好一切准备,但并不意味着一定要就此下海——国家还没确定的事情,你着急也没有意义。我们能做的,就只有令万事俱备,这样就可只待东风了。

再一点,在市场机会初步形成后,可以在适当的时机寻求资本市场的支持,加速市场推进,从而为投资人和公司获得更大的收益。这就要求移动应用开发商能够具备资本市场的意识,将自身的核心价值通过资本市场转化为实在的市场价值,这也是一个很重要的手段。

总结一下,我们认为,对于移动应用领域的软件开发商而言,最为重要的一点,就是要在充分理解和研究产业价值链的基础上,利用自身的优势和想法,形成与产业共嬴的商业模型,和谐的、深入地融入产业的价值链中,并主动地成为产业发展的一分子。

posted on 2005-05-20 01:44:00 by jgtm2000  评论(1) 阅读(1704)

 
2005年01月01日

2004年对于我来讲,是一个既平凡又重要的一年。说平凡,是因为这样的人生道路已经走了十好几年,已经习以为常了;说重要,则是这一年来发生了许多意料之外但也情理之中的事情,而这些事情也许正在改变着我未来的道路。

2004年,是我编程序的第18个年头了——这在我身边大多数程序员眼里看来,已经是一个不可思议的跨度了。大概也是不怎么值得骄傲,就像谁也不好意思说自己“小学上了10年”一样,明摆着是屡战屡败、毕业不得啊!不过,也许微软公司就很喜欢像我这样执着的程序员吧,今年初我荣幸的被微软公司授予最有价值专家(MVP)荣誉。然而对我而言,这并不是什么大不了的事情(所谓情理之中)。真正能谈得上也许会改变我未来之路的还是——创业。

我的创业之梦做了已经不是一年两年了。我相信,早在互联网热潮席卷全球的时候,创业的理念就已经走进了每个有志向、有能力也有激情的年轻人的心中。而当时,虽然我没有任何创业资本可言,但我还是在职业与事业之间开始了曲折的摸索。在这期间,我涌出了五个创业计划(包括其中一款在海外成绩还算不错的共享软件),也学习了和创业相关的许多知识——虽然没有一个算得上真正成功,但是成功本也不是我既定的目标。按照做软件的说法——这些都叫原型,都是为了探索和积累。直到今年,也就是开始创业梦想后的第四个年头,我终于带着我的第五套创业计划和一帮激情兄弟正式扬帆启程了……我们的船名叫“无线视通”。而我,正是它的舵手——首席技术官(CTO)。

CTO是什么?CTO做什么?CTO怎么做?这是我在这几年来一直学习、思考和探索的问题。以我现在的理解比喻一下:CTO就像是船的舵手;CTO要在启航前确定船的目标并时刻保持船的航向;当然,在前方发现暗礁的时候也要灵活应变、化险为夷;光靠舵手而没有动力的船也是不可能前行的,因此CTO要集结更多人的力量、燃烧激情、共同奋斗抵达胜利的彼岸。然而这里面最重要、最根本的一点,还是CTO要兼具远见卓识和脚踏实地的双重作风。这就要靠所谓CTO的“第三只眼”了。

CTO的“第三只眼”既是可以高瞻远瞩的战略望远镜,也是可以明辨秋毫的战术显微镜。通过这只眼,CTO既要看清远景进而制定适宜的战略目标,也要因地制宜、审时度势的看清眼前的情况,并依此制定具体行动方案,最终达到既定战略目标——可见这只眼睛对于CTO而言事关重大,但这只眼睛也并非与生俱来。回想起来,这么多年我做程序员的经历的确让我的这“第三只眼”看得更远、更清楚。因为做一个程序员,一定要具备几项最基本、也是十分重要的能力。

首先是学习能力。程序员需要在实践中不断完成一项项具体的编程任务,比如说访问个网络啦、打印个图像啦、等等。谁也不可能在一开始就学会所有用得到或用不到的编程技术,因此程序员要有快速、持久的学习能力。这些能力可以让你在需要的时候获得你所需要的能力,而这些能力可以让你走得更远、也看得更远。试想,一个程序员,只是学会一些片断皮毛,怎么可能会有更宽广的见识,甚至为自己指引前进方向呢?方向一定是在不断的、广泛的学习过程中摸索、感触而来的。在这18年的程序员生涯中,我的学习能力让我能够积累足够的知识攻克一项又一项的难关,也让我把这些知识贯穿起来,将全局、远景看得越来越清楚。

接下来,程序员要有分析问题的能力。在编程的时候,为了解决同一个问题,经常会有多个选择摆在你的眼前——尤其是对于具有良好学习能力的人而言。这就面临一个选择的问题,这就要求程序员不断地去从各种各样的可能性中结合自己要完成的任务目标选择一条最佳的途径——长期的磨练增强了程序员分析问题的能力,而这个能力对于CTO的第三只眼而言,就是能够从无数的机会中选择出最佳战略方向的能力。战略是什么?战略就是要做什么、不能做什么。没有分析问题的能力,就极有可能犯下“做不该做的事情”、或“没做该做的事情”这样重大的战略错误。

但有了分析问题的能力还不够,程序员需要扎实的动手能力,也就是解决问题的能力。这可能也是我最突出的能力之一了。俗话说,做事情不能眼高手低,光有第三只眼高高在上、指引方向,而不能够把战略意图和战术思想实践,那么实在是没有太大意义。反思这些年我一路踏着键盘走过来,无论遇到什么问题,总是动手、动脑相结合,在实践中思考、在思考中实践,最终解决一个又一个问题。前面说过,CTO也不可能妄想一帆风顺走直线到达目标,如果真是这样的话也太幸运了。在实现战略目标的过程中,一定会有一个又一个现实的问题横在我们前进的道路上,等着我们去解决。CTO固然要有领导能力,但能有实际解决问题的能力则更是如虎添翼——毕竟到了关键时刻还是要亲自披挂上阵的嘛——尤其在创业阶段。

最后,但也是很重要的一点,我可能是一个比较爱与人沟通、喜欢与人交流的积极乐观型的程序员。这种沟通与交流的能力对于一个担任企业决策与执行核心的CTO而言,同样是至关重要的。对外,我要向合作伙伴与客户传达企业理念,这需要积极与平等的沟通;对内,我要把企业的战略设想与战术部署渗透给每一个成员,这需要广泛而深入的交流。想想看,作程序员的时候与老板沟通、与同事交流时候的经验和技巧也都派上了用处啊!

每当回想起自己过去写程序的激情时光,我总是暗自庆幸,庆幸自己能够在很小的年纪就对这样一个有趣味也有意义的职业如此热爱。我甚至没有想到,就是这样一股发自内心的热爱,让我一编就是18年。更让我庆幸的是,这18年的程序员生涯已经对我一生的发展起到了如此重要的作用,让我的路越走越宽、越走越远。而看着一些做过两、三年就开始寻求其他道路的程序员朋友,我感觉很遗憾——不过也许他们就是不喜欢吧,那么不喜欢就不要做,做也做不长、做不好的。一些做了四、五年的程序员也在抱怨了:累啊,没意思啊,没前途啊,也没钱赚啊……真的吗?如果你还热爱程序员这个工作,我建议你不妨从我们这些经历还算丰富的程序员这里得到一些启示和鼓舞,在你接下来的程序员生涯中有意识的去培养自己的学习能力、分析能力、动手能力以及沟通与交流的能力,相信大家都会有宽广深远的发展机会。

看看国外那些花白胡子的程序员吧,真是让人感慨和羡慕。虽然我现在有了全新的事业与角色,但程序员仍然是我最本质的角色,哪怕再过上十年、二十年……到那时候,花白胡子可能不太现实,但估计秃顶是没问题了。到那时候,我希望我已经站在CTO那第三只眼所锁定的胜利彼岸,自豪的向大家汇报:我,为自己曾经是、现在也仍旧是一名优秀的程序员而感到自豪!

(本文原载《程序员》杂志2004年第12期第88页,此处是未经修编的原文

posted on 2005-01-01 19:34:00 by jgtm2000  评论(4) 阅读(2968)

 
2004年12月30日
不管怎么说,智能手机是一个非常好的智能客户端应用平台(广义上说,并不是只有.NET可以实现智能客户端应用——只不过在目前它确实有一些优势——尤其是站在开发者的立场上来说)。就这一点,就足够让智能手机做很多令我们生活发生变化的事情了——当然,核心问题还是“应用”。

 

什么样的应用可能会改变我们的生活呢?比如说——移动电子商务应用(M-Commerce)。

 

表面上看,从电子商务到移动电子商务,无非是借助了智能移动终端的移动性和网络通讯能力而已。然而,移动电子商务在很多情形下对于传统商务的冲击,要比当年基于互联网的电子商务要猛烈得多——比如说购书。

 

昨天的北京现代商报的报头文章题为“过半购买数冲动消费”。文中是这样说的:我国的冲动型消费所占比重呈现逐年上升的趋势,随着人均可支配收入的大幅度提升,冲动型消费已经成为大众消费中的主流。

 

回想一下过去的一年中,有没有人因为冲动而购买过图书?你有没有向自己的朋友们推荐过什么自己钟情的图书?有没有过在不经意间随手翻开一本陌生的书而由此产生购买的欲望?然而,又有多少这样的欲望真正转化为了购物行为呢?

 

国内目前从事图书类电子商务业务的电子商务服务提供商已经达到六十余家,其中既有老牌的当当网卓越网贝塔斯曼,也有日益成熟的互动出版网China-Pub)、第二书店,当然还有不记名数的初出茅庐的小字辈。在非典期间,由于人们闭关自守、足不出户,很多网上书店都迎来了一阵销售高峰——然而,毕竟这只是非常时期的非常现象。回想一下你或者你身边的人,当购物冲动产生的时候,有多少次其实是当书在手中、而身边又没有互联网让你去享受电子商务的便利和优惠的时候呀!

 

而如果此时此刻,你手中的手机可以让你在几十秒之内把这种购书冲动转化为消费行为呢?如果你手中的手机可以让你以更好的用户体验享受这一过程呢?我相信,只要不会为此付出过多的代价,而又有足够好的用户体验,人们是会慢慢接受并习惯甚至依赖于这一全新购物形式的。

 

没错,移动电子商务是传统电子商务销售渠道和用户体验的延展,然而其移动性带来的体验变化将很有可能对传统商务形式带来远比当年电子商务出现时更大的冲击!试想一下,如果在所有的地面书店、朋友家中、公交车上,每当人们看到可心的图书产生购物冲动时,都会习惯性的掏出身上的智能手机查查网上能买到的最低价、看看别人写的书评、甚至就此把这本书放入购物车或者下单的话——也许以后传统书店都会打出“本书店谢绝使用智能手机购书”的提示呢!

 

由此可以看出,有些时候,一些技术的出现将可能对整个产业、人们的生活习惯等等产生意想不到的、甚至是颠覆性的影响和变化——就如鼠标的出现使得人们使用计算机的方式以及应用程序的交互设计完全变了个样,就如MP3这么一种音频压缩与回放技术的出现几乎颠覆基于磁带和光盘的随身听一样——说不定,在特定业务领域中,移动电子商务的出现也将改变很多事物。

 

那么,谁将是为市场带来这一体验的先行者呢?什么时候我们真正可以用自己手中的智能手机踏踏实实、舒舒服服的随时随地的购书呢?嘿,等着看吧,也许用不了个把月大家就会看到了!先问一句,如果你就有智能手机的话,你希望是哪一家先觉悟并行动起来把这个想法付诸现实呢?;)

 

P.S: 想先获得一些感性认识的朋友,可以在微软中国智能客户端的案例研究中找到我们公司(无线视通)为微软做的一个demo(仅是demo而已,真正的原型kaneboy见过,你可以来说说你的感受,呵呵),其中就有和本文内容相关的一个演示,感谢开心哈达。另一个概念演示也很有意思,代表了我们眼中所谓智能客户端应该具备的关键特性。

 

BTW: 本文标题借mvm的光,实属荣幸。;)

posted on 2004-12-30 04:43:00 by jgtm2000  评论(26) 阅读(11805)

 
2004年09月10日
昨天晚上我们的团队在饭后展开了一个小小的话题,这个话题原自我这些天来的一个设想。对于这个想法,我已经思考了一段时间,感觉应该拿出来供大家头脑风暴一下了,于是我向大家说明想法的来龙去脉,并希望大家调动思维,想想看这个创意是否有潜在价值——请注意,这件事情并没有提到议事日程,我仅是拿出来一个概念让大家集思广益,想想看这个东西的价值可能在那里,还是根本没有价值。

 

请问如果我希望有效率、有效果的达成这个目标,我们应该采用什么思考方法或工具呢?

 

Positive Thinking!

 

正面思考是什么呢?正面思考意味着只去想好的事情、对事情成功有益的因素,而不考虑任何会引起事情没有价值、无法实施、导致失败的因素。

 

你觉得这是考虑问题不全面?其实不然,因为人的思维是渐进式的,人不会在瞬间凝固对一件事物的认知。比如说,你的朋友跟你说他新买的手机多好多好,这时候你对他的手机会产生正面的看法(即产生概念),然而这并不意味着你就会头脑发热的也跑去买一部(即付诸实施)。因为当他给你介绍这款手机的时候,这只是一个和你利益暂无关系的信息;而当你要掏钱买这部手机的时候,这必然需要进一步考量是否有这个必要、是否合时宜——如果这部手机的价格高于你的预期很多倍,那么多少正面的信息也不足以让你掏钱去买它。也就是说,在不同的阶段,其实应该以不同的方法去考虑问题,这样才不会错失太多的机会。比如说:大卖场上经常在促销手机,宣讲各种各样好的功能特性,而你首先想到的是他在“吹牛”、又来骗钱、反正我已经有一个了跟我没关系了……然后你就扬长而去了——结果没想到这是一次拍卖,最终拍到这款手机的人才花了10块钱。(什么?10块钱还觉得被骗了?噢!原来买了个坏的!——见后文“实施前不做负面思考则会增加风险”:)

 

尤其对于产品研发阶段而言,正面思考绝对是一个有效的思维工具。它能让你在一种积极、愉悦的模式中发现埋藏在事物深层中的价值。我举个例子,任何两件事物,可能从来也没有过关系,你把它们放在一起,问问大家我们从中能够有什么机会。如果不是正面思考,可能有人上来就说出一大堆两者结合毫无价值或者困难重重的理由——这其实真的是很容易的一件事情,以至于人人都会(原因很简单,任何事物都是有利有弊,而人们可能更容易从自卫的角度出发去否定事物——包括否定自己、否定别人以及否定未来)。你觉得这样思考有益于发现机会吗?显然不会。退一步说,在确立机会之前就考虑那些负面因素根本就是毫无效率!可不是吗?既然这件事情还不是机会,肯定不会有人去做,那么把脑子用在考虑那些会引起失败的原因上面可不是没有任何意义也缺乏效率吗?

 

因此,我强调强调再强调,我们是创造新事物的团队,我们一定要使用“正面思考”这一强大的工具!尤其是团队的领袖和领导,更是要如此。试想,一个人刚刚提出一个新念头,你就提出一堆不可以、不太好的话,先不说直接打击思考者的积极性,就算别人想出来了正面的理由,谁还敢说呢?(因此在贯彻“正面思考”之前首先也要培养“平等交流”的意识,以便在极端情况下团队仍然能够积极的不受任何阻碍的思考……)况且如果是一个聪明人提出来的想法,他自然已经在正面思考之后做过负面的分析,当他仍觉得机会存在时才会拿到大家面前来探讨,这时候你也去提一些谁都能想得到的负面因素,首先不会表明你更聪明,反过来还会让提出想法的人感觉受到轻视。这种文化在团队中形成的话,对于开拓思想、发现机会而言就是一个灾难。

 

也许你已经猜到了,本来我是希望让大家对我的想法brainstorm一下,来共同挖掘一下由这个创意带来的潜在机会,结果变成了我来重新强化团队要首先“正面思考”的思维方法——也许有人会否认思维方法对解决问题的效果,然而我多年来的切身体会早就让我感受到了正面思考的价值,甚至于我偏执的认为团队中的负面思考者对整个团队都是有害无益的。为什么这么说?因为人的思维是容易受潜意识的左右的。试想某一件事情客观的讲有5个有利因素和5个不利因素。如果团队中的一个人首先提出来几个不利因素(尤其是持领导者地位的高层人士或其他有威望的人士),其他大部分人的潜意识就会受此影响以至于对有利因素更加的不敏感。反过来,如果上来就会有一两个有利的因素提出来,其他人的思维就会变得主动、活跃,因为大家会受价值的驱使而转向从好的角度思考问题。因此在领导团队的思考过程中,领导者能否积极的合时宜的正面思考对于整个团队而言都是意义重大的。

 

当然,我们做事情也不能只看有利因素而不看不利因素,问题的关键在于思考问题的阶段。我的原则是,在形成机会的阶段一定要“正面思考”,而在实施之前强调“负面思考”。如果用反了,肯定只会压抑对新机会、新思路的发现;而如果不重视实施前的“负面思考”,则可能导致更高的风险、把好事办砸。

 

总之,我是一个会善用positive thinking的人,这让我发现了比别人更多的机会;我也要求我的团队在适宜的时机保持positive thinking。这实在是一个强大的思维工具!

 

有兴趣的朋友,可以继续来看两篇不错的文字:The Power of Positive/Negative Thinking.

 

如果你也是一个positive thinker,不妨也来激发一下灵感,把一些毫不相关的事情放在一起,看看有什么机会?比如说,电视和博客?比如说,DIPAdapter?:)

posted on 2004-09-10 03:40:00 by jgtm2000  评论(18) 阅读(7409)

 
2004年09月01日

刚刚上MSDN Subscriber Downloads扫了一圈,发现已经有Visual Studio 2005 Beta 1 Refresh with Team System(TS)的下载了(3.39G Full DVD Image)。跟上次不一样的是,这次RSS却没有更新——不知道MSDN的人在搞什么搞啊。而且更恶劣的是,到现在这个大家伙还不能正确下载,FTM总是报internal error,我严重faint……

其实对这个Team System一直比较期待,因为我最近也在组建团队中,肯定是需要把团队开发的基础打好、环境建舒服了,才能最大程度的发挥人的价值。所以等下载回来体验一下,再回来向大家报告一下心得体会啥的。

这两天sumtec渐入佳境,思维明显开始活跃起来了。由于这个月这次迭代是要形成初步的架构设计(上个月花了一整个月来形成用户体验原型、而这之前的时间则都是在分析、挖掘用户需求),因此大家讨论的主要内容都是和OOAD、Patterns、Principles相关的,而我也十分关心Practices方面的东西。我一直和大家强调的一件事情,就是说光有OOAD/Patterns相关的理论知识实际上是远远不够的,一定要结合具体的practices才能够凑效,而这里面的大背景则是一系列软件开发所提炼出来的principles(如OCP、DIP、SRP、LSP等等)——这也就是为什么我十分推崇微软的Patterns & Practices项目以及Robert C. Martin所著的经典的Agile Software Development, Principles, Patterns, and Practices的原因之一。

另外,本着与大家交流、向大家学习的principles,如果您对我们的团队建设以及我们的项目开发感兴趣的话,不妨留言给我,我会尽可能的与大家交流——Yes, we're inspired people, and we shall be inspiring the community! Thanks! :-)

posted on 2004-09-01 15:03:00 by jgtm2000  评论(9) 阅读(7237)

 
2004年08月31日

各位MSDN Subscribers,最新一期的MSDN Library October 2004 DVD/CD已经可以从MSDN Subscriber Downloads中下载了(其实早在26日我就从RSS中看到它了,可就是一直没有出现在TOC中——看来MSDN的信息化工作还有待加强:)。

这期DVD中除了收录了截至7月中旬的MSDN Library Online中的内容外,还有一贯精彩的.NET Shows、MSDN TV、MSDN Webcasts以及.NET Rocks等多媒体资源(CD版本的就只有遗憾啦——还好基本网上都可以下载到)。看得出,我是一直十分推崇保持应用最新版本的MSDN Library的,因为这是优秀的IT工作人的基本秘籍之一——随时保持知识更新!你可以不精通每一项新知识,但是你至少应该对业界的各种动态有所了解——因为没有一定的知识面,很难在技术领域有所突破。

对了,如果你对Robert C. Martin也就是Uncle Bob有所熟悉的话,本期收录的MSDN Webcasts中有他主讲的一次Patterns & Practices Live for MSDN Architecture,还在从事企业级架构设计与开发的朋友们不妨聆听一下大师的声音。:)

另外,对SQL Server 2000 Reporting Services感兴趣的朋友也不要错过那两集近三个小时的在线课程录像,内容十分翔实。

听说Visual Studio 2005 beta 1 Refresh with Team System也就快能够从MSDN Subscriber Downloads下载了,这倒是对我有些吸引力——这一块儿东西也该有点儿新气象了吧?!如果你也感兴趣,可以到MSDN Channel 9去搜索一下“team system”,有一个4 parts的video十分精彩——Late Night with the Burton Team。

Well, so much for today's late night blogging! Enjoy.

posted on 2004-08-31 02:21:00 by jgtm2000  评论(9) 阅读(5186)

 
2004年08月29日

20天的等待,我们团队终于迎来了千里迢迢从南京北上的sumtec同学——希望我们在一起会创造一段值得回忆的创业时光!

20周的阔别,我又回到了久违的博客堂——却不知道还有多少人记得我呢?:)

20年的卧薪尝胆,祝贺中国女排又一次让五星红旗飘扬在奥运五环的光芒下!

 

我代表我们的团队给博客堂的朋友们问好啦!希望随后团队成员会有精彩的内容与大家分享,与朋友们共同成长!:)

posted on 2004-08-29 04:31:00 by jgtm2000  评论(22) 阅读(4102)

 
2004年04月07日

记得以前思归曾指导我们凡事都该多求一种alternative,看了ghj1976关于在.NET中利用XmlNamespaceManager来处理XPath名称空间引用的方法,又看了Zee关于直接在XPath过滤表达式中利用XPath内置节点函数来处理类似问题的另一种方法,那么我也来贡献一种非常有用的解决办法——利用MSXML中DOM的SelectionNamespaces二级属性(second-level properties)。

如果你还在基于ActiveScript脚本引擎或者COM的环境中操作XML的话,一般你都是使用Microsoft XML Parser(目前主要是用3.0或4.0)这套API,而且大部分时候你会与DOM打交道。在DOM对象上有很多的native properties,如validateOnLoad、async等等,这些属性通常会影响DOM运行时的表现。为了能够更方便的扩展一些新运行时的新属性,MSXML的DOM上提供了setProperty/getProperty的方法,用它们可以为DOM运行时提供基于key/value的额外属性,也即二级属性。

在MSXML 3.0中,为更方便的应用XPath等相关技术,DOM开始支持SelectionLanguage和SelectionNamespaces这两个二级属性。这个SelectNamespaces就是用来指定额外的名称空间映射的,如下:

dom.setProperty("SelectionNamespaces", "xmlns:x='urn:mine' xmlns:y='urn:yours'")
dom.selectSingleNode("/x:foo/y:bar")

则如果dom节点的XML内容是这样的:

<foo xmlns="urn:mine">
  <yours:bar xmlns:yours="urn:yours" />
</foo>

便可以正确的选取到中间一行的urn:yours名称空间中的bar元素(注意本例中XML默认名称空间及名称空间等价的概念)。

这个技术在功能上等价于使用.NET中的XmlNamespaceManager,只是在非.NET环境中(包括在开发InfoPath表单中使用的Microsoft Script脚本环境或者IE的脚本环境中)这还是非常正式的解决方法(效率也会比使用XPath过滤表达式要高效一些)。

希望在你使用MSXML的DOM对象遇到类似问题时有所帮助吧。

posted on 2004-04-07 21:18:00 by jgtm2000  评论(5) 阅读(8601)

 
2004年04月05日

周末熬夜终于搞定的AOP尝鲜系列文章第三篇一出,就收到了很多朋友的反馈和邮件。首先感谢大家的关注!也衷心谢谢给我指出文章错误、文字疏忽的朋友们!

有一位细心朋友今早给我发来邮件,“批评”我又在简单问题上面犯了糊涂,他所指的是文章中出现的这个语句:

Console.WriteLine("Add: " + Thread.CurrentContext);

其中Thread.CurrentContext属性将返回当前线程所处的执行环境,类型是System.Runtime.Remoting.Contexts名称空间中的Context。一个字串常量怎么可以和一个对象直接相加呢!肯定是我忘了写ToString()了吧?他对此的评语也很有意思,“老兄以为在写JScript呐!”……嘿嘿!一针见血啊!

我回复他,感谢他的热心和仔细(再次:),顺便问了一句:我文中的代码你动手编译过吗?因为我清楚的记得像这样的代码我已经不是写一次两次了——偶的实践经验告诉我:这个语句是可以在Visual C# .NET中编译并正确运行的!不仅如此,我还经常写这样的语句:

string tempFilename = null;
tempFilename += Environment.TickCount + ".tmp"; // 啊?string += int?

或者:

string guidKey = "{" + Guid.NewGuid() + "}"; // 奋特!

再或者:

string logLine = "Log: " + DateTime.Now; // 咣当……

……就像读者所说:这是C#还是JScript啊!?实际上,我也没有在C#语言规范中找到能解释这个现象的理论依据(如果你知道的话希望能告诉我:)。所以我只能说这是Visual C# .NET编译器的一种编译器优化手段造成的side effect(将静态字串相加转化为String.Concat()调用)——不过事实证明,这种写法的效率比起String.Format()还要高(虽然没有人家灵活),甚至有时候比每一个用ToString()还要快!原因是什么?也很容易分析出来,留给聪明的你做个练习吧(可以把你的想法用反馈告诉我)!:)

最后还是按照惯例,做个练习——想想下面这个表达式中对quiz求值将是什么结果?

string quiz = null;
quiz += 1 + 2 + "3" + 4 + 5;

P.S: USE THIS TRICK AT YOUR OWN RISK! AND IT IS NOT RECOMMENDED AT ALL (THOUGH IT'S OFTEN USEFUL AND HANDY DURING DEBUGGING.)

posted on 2004-04-05 21:31:00 by jgtm2000  评论(18) 阅读(6870)

 
2004年04月04日
 Part III: Context-based Interception Towards a Mini AOP Framework

 

摘要:在本文的上一篇中,我们利用.NET Remoting基础架构中的真实代理/透明代理技术实现了不针对具体类型、具体方法的通用方法调用拦截机制。在本篇文字中,我们介绍可以用于在.NET中实现基本AOP(面向方面编程)的更深入的基于ContextBoundObject机制的相关技术,并结合该技术的优势和劣势提出了一个已经在我们设计的项目中局部应用的一个AOP框架原型思路。

 

BTW: 由于工作调动等各方面的原因,偶的文章也像本年度偶最期待的产品Microsoft Visual Studio 2005一样频繁跳票……不过就在今晚,我终于完成了本系列文章最后一部分的写作。实际上,最近手头的工作已经离所写的内容越来越远,不过回头再鼓弄起这些技术,我脑中仍可激发出阵阵的奇思妙想……我想,这就是技术的魅力吧!希望您读过我的文章之后也能够同我一样被这些美妙的事物激发灵感,感悟个中快乐——只是,不要像我这样熬夜。:)

posted on 2004-04-04 03:12:00 by jgtm2000  评论(0) 阅读(5637)

 

在本文的上一篇中,我们利用.NET Remoting基础架构中的真实代理/透明代理技术实现了不针对具体类型、具体方法的通用方法调用拦截机制。由于技术内容太多,本来想一同写在上一篇中的最后一大块内容就新作一篇吧。在本篇文字中,我们介绍可以用于在.NET中实现基本AOP(面向方面编程)的更深入的技术,并结合该技术的优势和劣势提出了一个已经在我们设计的项目中应用的一个AOP框架原型思路。

 

在前一篇文字的结尾,我们提出了几点技术方案中感觉不爽的环节,其中群众呼声最大的就是觉得对象的构造不够intuitive:一方面需要引入FACTORY METHOD(如CreateInstance()),另一方面在该方法中的构造过程也不够simpleproxied real objectreal proxyGetTransparentProxy……),还有,由于我们是在构造proxied对象之后才将方法拦截代理(即真实代理)施加在真实对象上,因此我们自然无法拦截到对对象构造器的方法调用——这个不足对于很多功能实现场合都是致命的(比如说需要监视系统中各类应用对象的创建频率,我们就需要在构造器调用时进行相应计数逻辑;另外对对象构造参数的记录和分析也是很常用到的)。在本文中,我们就引入新的技术来一一解决这些问题吧!

 

首先,有没有更简单的办法来构造被代理的对象呢?有没有办法在构造对象之前就设置好消息拦截器呢?我们来考虑这行代码:

 

Calc calc = new Calc();

 

这可以算是最简单的创建对象的写法了吧!如果我们能够覆盖new指令的默认行为使之利用我们自己的FACTORY就好了——可惜,CLR/C#都不支持覆盖(overridenew操作符。看来此路不通……可是——我突然想到了在.NET Remoting中是可以用new操作符直接构造远程对象的(而远程对象不正是一个经过代理的对象吗)!只要CalcMarshalByRefObject类型且在Remoting中正确的配置为wellknown type的话,则当CLR遇到C#中的new指令(也即IL中的newobj指令)时,它将把对象激活的任务交给.NET Remoting来完成。比如这样:

 

public class Calc: MarshalByRefObject {…}

 

RemotingConfiguration.RegisterWellKnownClientType(typeof(Calc),
  "tcp://remoteServer:8080/calcService");

Calc calc = new Calc();

Assert.IsTrue(RemotingServices.IsTransparentProxy(calc));

 

你可以自己试试这种写法,看样子还真的有戏——只要我们能够实现一个和对象激活地址(URL)配套的channel在指定位置监听由Remoting发来的方法调用消息并处理就可以!只是……这不是有点儿添乱吗?

 

再想想有什么线索……System.EnterpriseServices里面的ServicedComponent!因为它是为了让.NET的对象可以使用COM+提供的组件服务,那么它也应该应用了透明代理技术。让我们来验证一下(记得Add Reference to System.EnterpriseServices assembly and use this namespace!):

 

public class Calc: ServicedComponent {…}

 

Calc calc = new Calc();

Assert.IsTrue(RemotingServices.IsTransparentProxy(calc));

Console.WriteLine(RemotingServices.GetRealProxy(calc).GetType());

 

当然,为了在一个assembly里面实现ServicedComponent,我们首先要给assembly一个strong name,原理就不多说,只要简单的为所在assembly施加这么一个自定义属性即可:

 

[assembly: System.Reflection.AssemblyKeyFile(@"C:\xxx.snk")]

 

其中这个C:\xxx.snk文件是事先用.NET SDKsn工具生成的(如:sn -k C:\xxx.snk)。编译执行,发现果然如此!此时新创建的calc实例确实是一个经过透明代理包装的对象,而且这个透明代理所对应的真实代理是System.EnterpriseServices里面的一个叫ServicedComponentProxy的类(注意看Console.WriteLine()方法的输出结果)。意思对了,可是感觉有点儿“过”,还是先来研究一下ServicedComponent到底有什么名堂吧——为什么一个类从它派生出来以后再new的时候就被套上了一个代理呢?

 

原来,ServicedComponent是从System.ContextBoundObject派生而来的,而这个类又是从MarshalByRefObject派生而来。看来,MarshalByRefObject不能满足我们的需要,而ServicedComponent又是为了一个特定的开发需求而设置的基类,那么其中夹着的这个ContextBoundObject就应该是一个提供某种公共特性的基类啦!赶快来印证一下,把Calc的基类变成这个ContextBoundObject

 

public class Calc: ContextBoundObject {…}

 

再运行前面的同样的测试代码……噻!就是它了!看来只要一个类是从ContextBoundObject派生而来(以后我们也用CBO来简称这类对象),那么当它被new的时候,就会被套上一个透明代理——而这个代理背后干活的真实代理类(再次注意看Console.WriteLine()输出的透明代理的真实代理的类型!)就是System.Runtime.Remoting.Proxies名称空间中的RemotingProxy(这是一个internal的类型,所以既没有文档也不能直接被使用——事实上,这个真实代理就是实现.NET Remoting所有核心机制与扩展机制的类!)。

 

关于CBO的详细用途和使用方法,还请读者自己查一下MSDN文档(趁有得看还是看看吧——再往后一些的内容可就想看也没有喽!),我这里只是大概讲解一下。ContextBoundObject,顾名思义,object that will be bound with a context。这里的所谓context是指一个逻辑上的执行环境,每一个AppDomain中都有一个或多个这样的context,就好比每一个托管进程都可以有一个或多个AppDomain一样。AppDomain中的第一个context也叫做default context,所有不是CBO的对象(也称为context-agile object)默认都是在这个context中创建和运行。而CBO对象都将创建并运行在自己所需要或认可的context中。跨越context的方法调用都是通过透明代理转发的,因为context所设立的执行环境是由若干个对象用法规则所保障的,只有通过代理机制才可以通过设置方法拦截器来对进出context的方法进行规则保障(这和COM/COM+中的概念是相似的,COM+的所有服务也都是通过类似的方法拦截代理来实现的——其实更准确的说应该是.NET CLR大量借鉴了COM+的设计与实现机制)。

 

执行线程所在的context可以通过System.Threading名称空间中的Thread.CurrentContext取得,它的类型是System.Runtime.Remoting.Contexts名称空间中的Context类,其中每个Context实例都有一个唯一的IDContextID)。前面说的default context也是这个Context类上面的一个静态属性(DefaultContext)。我们可以通过它来观察在一个线程上跨越多个对象时每个方法调用所发生在的逻辑context。比如这样(记得using System.Threading!):

 

public class Calc: ContextBoundObject
{
  public Calc()
  {
    Console.WriteLine("Calc(): " + Thread.CurrentContext);
  }

  public int Add(int x, int y)
  {
    Console.WriteLine("Add: " + Thread.CurrentContext);
    return x + y;
  }
}

 

public class Application
{
  public static void Main()
  {

    Console.WriteLine("Main: " + Thread.CurrentContext);

    Assert.AreEqual(3, new Calc().Add(1, 2));
  }

}

 

你可以看到,对Calc实例的方法调用是发生在另一个context内的(作为对比,你可以把Calc声明为从MarshalByRefObjectObject再看看)。(最好找个开发环境动手实验一下——笔者四月一日注;)

 

除了ContextID之外,Context对象上的另一个重要概念是context property。在Context类中就有一个叫ContextProperties的属性,它的类型是一个IContextProperty[]。那么到底什么是context property呢?这可以是任何一个实现了IContextProperty接口的对象,这个对象可以为其相关的context提供一些特性(这里我把property称为“特性”,因为如果叫成“属性”有可能会和attribute混淆——而还真就有一个叫context attribute的东西!这个我们稍后就会遇到)。而IContextProperty接口中只定义了一个Name属性,还有几个方法,我们稍后再说。除了提供只读的数组之外,可以看到Context对象还提供了GetProperty()SetProperty()两个方法。如果感兴趣可以动手试试,你会发现一上来每个context就有一个叫LeaseLifeTimeServiceProperty的东东,而用它的名字去GetProperty()也能取得,但是一SetProperty()就会出错——就像对象的property一样,contextproperty虽然不是类型的一部分,但是也只能在context构造的时期形成并在context的生存期内一直存在。如何为context指定一些特性呢?我们需要用context attribute(在.NET中一般说到attribute都是指可以通过反射获取的attribute)来把所需的context property设置在context中,而因为context是为ContextBoundObject来提供合适的执行环境的,所以这些context attribute就自然是附着在CBO派生类型身上了,就像这样:

 

[SomeContextAttribute]
public class AnyContextBoundObject: ContextBoundObject {…}

 

那么context attribute如何来将指定的context property提供给context呢?实际上,一个context attribute是一个实现了IContextAttribute接口的Attribute(什么?属性也可以实现接口?以后你将越来越多的看到这种机制在.NET中的灵活运用),它将在CBO对象被构造的阶段被反射实例化。随后,CLR将通过这个IContextAttribute接口与context attribute进行交互,以获知对象所需要的合适的执行环境(也就是由context properties所确定的一个具体的context)。为了做到这一点,IContextAttribute中定义了下面这两个方法:

 

public interface IContextAttribute
{
  bool IsContextOK(Context ctx, IConstructionCallMessage ctorMsg);
  void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg);

}

 

每个context attributecontext的构造阶段(通常是由CBO对象构造动作引发的)会被首先问到IsContextOK,就是说新创建的这个CBO(通过ctorMsg可以知道是哪个对象的哪个构造方法被用来构造CBO对象的)能不能在给定的ctx中存在呢?这个目的主要是减少AppDomain中潜在的context的数量,如果某些CBO类型可以共用一个有所需特性的执行环境的话,就可以不用再创建新的环境,而只要在已有的环境中构造并执行就好了。

 

如果CBO类型上设置的所有context attributes都认同给定的context(也即调用代码所处的context)是okay的(即IsContextOK均返回true),那么新的CBO就会被绑定到这个context上。否则,只有有一个属性投了否决票,就马上会有一个新的context被创建出来。紧接着CLR会再一次询问每一个context attribute这次新构造的context是否okay,这时候大部分都会说no了——毕竟是重新开始了嘛。因此在这个阶段,context构造程序将对每一个认为新的ctxokaycontext attributes调用其GetPropertiesForNewContext()方法——其实这个方法叫做SetPropertiesForNewContext()可能会更贴切,context attribute可以用这个方法传入的构造器方法调用信息(ctorMsg)中的context properties列表(ContextProperties)来为新建的context增加所需的context properties

 

话说多了,还是写些代码来看看吧!假如说我们要求Calc对象所在的Context都必须有一个叫CallLogger的特性(先不用管这个特性到底能够干什么),我们会这样把它指定在Calc类上:

 

[CallLogger]
public class Calc: ContextBoundObject {…}

 

而这样我们就要有一个叫CallLoggerAttributecontext attribute

 

public class CallLoggerAttribute: Attribute, IContextAttribute
{
  public void IsContextOK(Context ctx, IConstructionCallMessage ctorMsg)
  {
    return false;
  }

  public void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg)
  {
    ctorMsg.ContextProperties.Add(new CallLoggerProperty());
  }
}

 

注意这里我们首先让IsContextOK()总返回false,使得Calc总会被放在一个新的环境中(不过在编写自己的context attribute的时候还是应该仔细考虑尽可能让多个对象共享已有的环境以减少创建新环境以及跨环境调用的开销)。随后,我们在GetPropertiesForNewContext()方法中使用传入的代表着CBO构造方法调用的消息(ctorMsg)的ContextProperties属性(这个属性的类型是IList,所以是可以Add/Remove的)来添加我们所需的环境特性,如CallLoggerProperty

 

public class CallLoggerProperty: IContextProperty
{
  public string Name { return "CALLLOGGER"; }
  public bool IsNewContextOK(Context newCtx) { return true; }
  public void Freeze(Context newCtx) {}
}

 

实现IContextProperty的时候主要就是要提供特性的Name(这个名字必须在整个context中是唯一的!),其他两个方法一个是用来在所有的环境特性都到位后再次确认彼此没有冲突的情况发生(IsNewContextOK),另一个则是通知context property:新的context即将构造完成,请进入冻结状态(Freeze——通常情况下并不需要采取什么动作)。

 

有了这些代码,我们来看看新构造的Calc实例是否执行在具有我们所需的环境特性的执行环境中:

 

[CallLogger]
public class Calc: ContextBoundObject
{
  public int Add(int x, int y)
  {
    IContextProperty property = Thread.CurrentContext.GetProperty("CALLLOGGER");
    Assert.IsNotNull(property);
    Assert.IsTrue(property is CallLoggerProperty);
   
  }
}

 

确实如我们所愿,通过为CBO派生类施加context attribute,我们就可以控制CBO类型构造和运行所需的执行环境,也就是设置该环境中的context properties,进而在该CBO实例的内部方法调用中通过Context.GetProperty()取得由context attribute设置好的context properties。挺好,都通了——可以有什么用呢?现在我们来忽悠一下想象的翅膀——context property不就是一个对象吗?如果它有自己的数据和方法,我们不就可以用它做点儿什么了吗?比如说:

 

public class CallLoggerProperty: IContextProperty
{
  public void Log(string message)
  {
    Console.WriteLine(message);
  }
 
}

那么我们在Calc中就可以使用环境中的这个特性啦:

 

[CallLogger]
public class Calc: ContextBoundObject
{
  public int Add(int x, int y)
  {
    IContextProperty property = Thread.CurrentContext.GetProperty("CALLLOGGER");
    CallLoggerProperty logger = property as CallLoggerProperty;

    if (logger!=null) logger.Log(String.Format("Add({0},{1})", x, y));
    int result = x + y;
    if (logger!=null) logger.Log(String.Format("={0}", result));   
  }
}

 

靠!这不折腾吗?原来写一句话就能做的事情现在要绕这么大一个圈?!嘿嘿,不过至少我们现在可以自由的开关这个Log方法了吧?把[CallLogger]注释掉,程序照样转,就是没有Log功能了(因为logger==null)——可是毕竟Calc这个具体类要了解(或者说依赖)这个具体的CallLoggerProperty才可以用它进行Log操作,这种依赖性是很愚昧的(就像progame同学指出的那样——不过请注意,无论如何类的内部是不依赖于context attribute的!),如果.NET真的设计成这个样子那可真的是不可救药了……还好,在愤怒之前先冷静一下,因为我发现大部分事情都比我们想象的要科学的多(尤其在没有透彻理解之前),让我们来设想一下理想的情形:Calc根本不需要了解任何context attribute或者context property,它只需要完成它所需要做的;而既然Calc已经在一个context内部运行,跨越context的方法调用又是自动通过透明代理来进行的,那么应该有某种机制可以与透明代理合作的话,原本这些显式的代码就可以隐藏在后面了——而且我们在前面的文章中也反复强调了,透明代理这种基于消息的拦截机制是对类型和具体方法不敏感的,这也是它的一个重要特点,我们应该合理的加以利用。

 

这种机制是什么呢?还记得我们上一篇中讲过的IMessageSink吗?还记得我们利用一个链式代理将一串儿方法消息拦截器串接起来拦截并处理方法调用消息吗?没错,这正是.NET内部使用的链式方法拦截机制!在继续之前,让我们先来写一个CallLoggerSink,顺便复习一下:

 

public class CallLoggerSink: IMessageSink
{
 
private readonly IMessageSink _nextSink;

 
public CallLoggerSink(IMessageSink nextSink)
  {
    _nextSink = nextSink;
  }

 
public IMessageSink NextSink
  {
   
get { return _nextSink; }
  }

 
public IMessageCtrl AsyncProcessMessage() {}

 
public IMessage SyncProcessMessage(IMessage msg)
  {
    IMethodCallMessage callMsg = msg
as IMethodCallMessage;
    msg = NextSink.SyncProcessMessage(msg);
    IMethodReturnMessage returnMsg = msg
as IMethodReturnMessage;

    LogMessageProcessing(callMsg, returnMsg);
  }

 
protected virtual void LogMessageProcessing(IMethodCallMessage callMsg,
                                              IMethodReturnMessage returnMsg)
  {
   
string methodName = callMsg.MethodBase.Name;

   
string[] methodArgs = new string[callMsg.InArgCount];
   
for (int i=0; i<methodArgs.Length; i++)
    {
      methodArgs[i] = callMsg.InArgs[i].ToString();
    }

   
string returnValue = returnMsg.ReturnValue;

    Console.WriteLine("{0}({1})={2}", methodName,
                                      String.Join(", ", methodArgs),
                                      returnValue);
  }
}

 

这一部分不多解释了,如果有不明白的地方可以参考前一篇文章中的相关内容。现在我们想做的事情是把这个通用的方法消息拦截器植入到任意context的透明代理中(实际上是被相应的真实代理所调用的),以形成一个具有特定功能的执行环境——这个工作正是由context property来做的!

 

实际上,一个context property类如果光实现IContextProperty的话基本上没有什么大用,它只是提供了作为一个环境特性的最基本的信息。要想通过context property向所在contextTP/RP中植入message sinks的话,还需要实现一个或多个IContributeXXXSink接口——这里的XXX可以有四种可能:EnvoyClientContextServerContextObject(其实还有一种是DynamicSink,不过跟着四个还不太一样,以后用得到的时候再说吧)。复杂了,看文档吧……更复杂了,文档说这些都是为了支持.NET框架基础结构而设计的,不是让用户代码来使用的。还是跟着我一起来研究吧!:)

 

首先一点,从名称上揣测:IContributeSomething,应该是“贡献”个什么东西。看看接口中定义的方法,还好,都只有一个GetXXXSink(),返回值都是IMessageSink,而参数也很简单,无外乎就是nextSink(显然是为了我们构造IMessageSink用的)或者外加一个MarshalByRefObject(是不是以前讲过的target呢?:)。实际上,这些都是所谓的FACTORY METHODS,而context property就是用来创建这一系列IMessageSink实现的工厂!可是何来这四种sink呢?不妨利用已有的知识实际研究一下吧!让我们来写一个简单的sink,并在每一个工厂方法中都返回这个类的新实例,然后来分析每一个工厂方法构造的sink都是什么用途:

 

public class ResearchSink: IMessageSink
{
 
private readonly IMessageSink _nextSink;
 
private readonly string _sinkKind;

 
public ResearchSink(string sinkKind, MarshalByRefObject target, IMessageSink nextSink)
  {
    _nextSink = nextSink;
    _sinkKind = sinkKind;
    Console.WriteLine("{0}Sink is created on {1}...", sinkKind, Thread.CurrentContext);
    Console.WriteLine("\tTarget is {0}", target==null ? "<null>" : target);
    Console.WriteLine("\tNextSink's type is {0}", nextSink.GetType());
  }

 
public IMessageSink NextSink
  {
   
get { return _nextSink; }
  }

 
public IMessage SyncProcessMessage(IMessage msg)
  {
    IMethodCallMessage callMsg = msg
as IMethodCallMessage;
    Console.WriteLine("{0}Sink is being called to process msg for {1}.{2}()", _sinkKind, callMsg.MethodBase.DeclaringType, callMsg.MethodBase.Name);
    return _nextSink.SyncProcessMessage(msg);
  }

 
public IMessageCtrl AsyncProcessMessage() {}
}

 

然后我们利用一个ResearchProperty贡献所有四种sink

 

public class ResearchProperty: IContextProperty,
                               IContributeEnvoySink,
                               IContributeClientContextSink,
                               IContributeServerContextSink,
                               IContributeObjectSink
{
  public string Name { get { return "RESEARCH"; } }
  public bool IsNewContextOK(Context newCtx) { return true; }
  public void Freeze(Context newCtx) {}

  public IMessageSink GetEnvoySink(MarshalByRefObject obj, IMessageSink nextSink)
  {
    return new ResearchSink("Envoy", obj, nextSink);
  }

  public IMessageSink GetClientContextSink(IMessageSink nextSink)
  {
    return new ResearchSink("ClientContext", null, nextSink);
  }

  public IMessageSink GetServerContextSink(IMessageSink nextSink)
  {
    return new ResearchSink("ServerContext", null, nextSink);
  }

  public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink nextSink)
  {
    return new ResearchSink("Object", obj, nextSink);
  }
}

 

再写一个对应的context attribute以便可以将这个特性施加到任何一个CBO类型的定义上:

 

public class ResearchAttribute: Attribute, IContextAttribute
{
  public bool IsContextOK(Context ctx, IConstructionCallMessage ctorMsg)
  {
    return false;
  }

  public void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg)
  {
    ctorMsg.ContextProperties.Add(new ResearchProperty());
  }
}

 

现在在Calc类型上面施加[Research]属性,并调用其Add()方法……看到了一连串的输出信息?感觉还不错吧!我们可以看到,除了ClientContext之外,所有其他的sink都被创建并调用过,而且在ServerContextSink的方法消息处理器中我们甚至收到了对Calc构造器的方法调用消息!实际上,上面提到的四种sink对应于跨环境调用对象方法的四个不同阶段:当一个CBO对象实例收到一个来自于其他环境的方法调用时,我们称这个对象为server object,而其所处的环境也即server context,那么.NET Remoting将在server context中先后调用已注册的ServerContextSinkObjectSink的方法消息处理器(即SyncProcessMessage()方法);同理,当一个CBO对象向另一个环境中的CBO对象发出方法调用时,我们称这个发起调用的CBO对象为client object,而其所处的环境也即client context,那么.NET Remoting将先后调用已注册的EnvoySinkClientContextSink的方法消息处理器——不过此时调用的EnvoySink并不是由client context贡献的,而是由server context贡献的,所以对于发起调用的client context而言它是server context的特使(envoy)!当然,ClientContextSink都是在client context中被调用的。

 

透彻理解.NET Remoting中提供的这些sink机制并不是本文要讲的重点,如果感兴趣的话可以参考MSDN中的一些文章(如果要求强烈我也会考虑在以后的文章中详细讲解几种sink的设计意图和应用场合:)。实际上,对这些sink的支持都是通过前面说到的RemotingProxy这个.NET Remoting中枢代理来实现的,所以对这些内容的探讨放在跟.NET Remoting相关的文章中会更合适。

 

不过利用我给出的研究工具,不难亲自体会这其中的设计奥秘,并想象如何利用这些技术手段来实现之前提出的若干关于AOP方面的问题。不过在本文,我还是想继续揭露更多的技术细节。在进行之前,我们先来回顾一下已经可以实现的特性。

 

首先,现在我们可以直接用C#语言提供的new指令构造一个CBO对象,并返回一个使用RemotingProxy作为真实代理的透明代理对象——这解决了前文中提出的构造麻烦的问题;其次,利用RemotingProxy中设立的种种机制,我们可以在定制CBO类型的执行环境,利用context attribute植入context property,并利用context property向执行环境中注入方法拦截器(IMessageSink)——这其实可以做到前文中的MessageChainProxy的功效;最后,因为建立执行环境是在构造访对象实例之前完成的,而方法拦截器又是在建立执行环境期间注入的,因此我们已经可以拦截到用来构造对象实例的构造器方法调用了——这也解决了前文中提到的功能缺憾。那么我们还可以做些什么呢?

 

经过一段时间的实践,我们发现如果只是需要通过拦截方法调用来实现AOP的一些基本开发任务的话,需要在创建和特定执行环境绑定的CBO对象的同时也创建很多的执行环境。同时,在不同执行环境之间进行方法调用也涉及大量的环境切换。这些开销实际上都是由于RemotingProxy为支持.NET Remoting而实现的复杂逻辑造成的。有没有办法能够用我们自己的真实代理替换默认的这个RemotingProxy以支持最小的用于实现AOP功能的基础机制呢?

 

回想当时对ServicedComponent的研究可以发现,当创建一个ServicedComponent派生对象的时候后,我们得到的透明代理的真实代理其实是一个叫ServicedComponentProxy的类(而不是RemotingProxy!)。这说明.NET肯定提供了某种可以替换真实代理的机制!利用.NET Reflector等工具,我们发现在ServicedComponent这个派生于ContextBoundObject的类型上被设置了一个ServicedComponentProxyAttribute自定义属性。这个属性又是派生于System.Runtime.Remoting.Proxy中的ProxyAttribute。在这个自定义属性类中,我们发现了一个virtualCreateInstance()方法,看来这就是真正构造CBO对象实例的工厂方法喽!

 

原来,当我们构造一个CBO对象实例的时候,CLR将首先检查该类上是否被施加了从ProxyAttribute派生的自定义属性。如果有的话,CLR将实例化该自定义属性并调用其CreateInstance()方法获得CBO对象实例(MarshalByRefObject)。在这个方法中,我们就可以利用自己的真实代理生成透明代理,而不是用系统默认的过于强大的RemotingProxy!为了印证这个逻辑,我们可以用一个什么都不干的真实代理换上默认的RemotingProxy

 

public class BypassProxy: RealProxy
{
  private readonly MarshalByRefObject _target;

  public BypassProxy(MarshalByRefObject target): base(target.GetType())
  {
    _target = target;
  }

  public override IMessage Invoke(IMessage msg)
  {
    return RemotingServices.ExecuteMessage(_target, (IMethodCallMessage)msg);
  }
}

 

再写一个从ProxyAttribute派生的自定义代理属性,通过覆盖其CreateInstance()方法来构造给定的类型的实例,并套上由我们自己指定的BypassProxy生成的透明代理返回:

 

[AttributeUsage(AttributeTargets.Class)] // whose hack to force this!?
public class BypassProxyAttribute: ProxyAttribute
{
  public override MarshalByRefObject CreateInstance(Type serverType)
  {
    MarshalByRefObject rawInstance = base.CreateInstance(serverType);
    RealProxy realProxy = new BypassProxy(rawInstance);
    return (MarshalByRefObject)realProxy.GetTransparentProxy();
  }
}

 

在这段代码中,我们首先使用基类的CreateInstance()方法来构造所需的serverType的“实例”(为什么不能用Activator.CreateInstance()呢?请自己想一想,或者动手试一试!),然后一如既往的构造我们自己的真实代理,传入刚刚构建好的目标对象实例,然后从中生成真实代理,并将其返回。再继续之前请您先想一想这段代码是否可以工作——注意new BypassProxy(rawInstance)这句话!如果运行这段代码,你将得到一个RemotingException,说“Trying to call proxy while constructor call is in progress.”。这是为什么呢?原来,我们在BypassProxy的构造器中调用了传入的target对象的GetType()方法来得到其运行时类型。这在以前一直工作的很好,然而在此时,我们正处于BypassProxyAttributeCreateInstance()方法内部,虽然已经用base.CreateInstance()构造了对象实例,但是整个构造过程还没有完成!因此这时候还不允许调用对象的任何实例方法。换句话说,刚刚得到的rawInstance对象只是一个刚刚内部初始化完成但还尚未执行构造方法调用的萌芽状态(所以我叫它rawInstance嘛)。解决这个问题很简单,只需要在构造真实代理的时候显式传入serverType即可:

 

public class BypassProxy: RealProxy
{
 
  public BypassProxy(MarshalByRefObject target, Type targetType):
    base(targetType)
  {
    _target = target;
  }
}

 

[AttributeUsage(AttributeTargets.Class)]
public class BypassProxyAttribute: ProxyAttribute
{
  public override MarshalByRefObject CreateInstance(Type serverType)
  {
   
    RealProxy realProxy = new BypassProxy(rawInstance, serverType);
   
  }
}

 

这次再运行,我们又将遇到一个新的异常(仍旧是RemotingException):“ExecuteMessage can be called only from the native context of the object.”。这又是什么鸟?调试一下发现,原来是已经走到了BypassProxy.Invoke(),而此时我们收到了一个ConstructorCallMessage(实现的接口则是IConstructionCallMessage——要是没有IntelliSense这日子还真不好过的说)!这不正是对象构造器方法调用消息嘛!这是我们一直希望能够拦截到的一个关键事件。然而真的拦到了我们又该如何处理它呢?注意到RealProxy基类中有一个叫做InitializeServerObject()的方法,它可以接受一个IConstructionCallMessage并返回一个IConstructionReturnMessage,我们可以用它来处理构造器方法调用消息:

 

public class BypassProxy: RealProxy
{
  public override IMessage Invoke(IMessage msg)
  {
    if (msg is IConstructionCallMessage)
      return base.InitializeServerObject((IConstructionCallMessage)msg);

   
  }
}

 

好了,有了这个秘密武器,我们期待的结果就可以被断言了:

 

[BypassProxyAttribute]
public class AnyCBO: ContextBoundObject {}

 

object instance = new AnyCBO();
Assert.IsTrue(RemotingServices.IsTransparentProxy(instance));
Assert.AreEqual(typeof(BypassProxy), RemotingServices.GetRealProxy(instance));

 

不过不要高兴得太早,离通关还远着呢(学习和研究真的就像玩游戏一样的说)!虽然目前一切看着都很顺利,我们也能够用最简洁的办法拿到一个被代理的对象,可是除了构造阶段我们能够拦截到构造器方法调用之外(爽),以前能够拦截到的普通方法却怎么也无法正常拦截到了(衰)!真是岂有此理!莫名其妙!这也是我在研究这套技术时候遇到的最匪夷所思的事情。这里我真的希望你也自己动手找找解决方法,分析一下个中原因——也算对得起我的付出吧(兄弟!怎么着也花个半天儿一天儿的吧!我写这几篇文章可是用了两个多月的零散时间了!)。

 

所谓以毒攻毒,为了对付这个怪物,我们要用到另一个怪物:System.Runtime.Remoting.Services名称空间中的EnterpriseServicesHelper。说它是怪物一点儿也不过分:就三个静态方法,却忘了藏起自己的默认构造器(这样的类在C# 2.0中都应该标记为static才符合编码准则);三个静态方法也都没做什么事儿——都是把一些internal的事情public出来而已;最烦的是还把自己放到一个冠冕堂皇的名称空间中……不过很多时候就是靠怪物的伎俩才能通关的说:我们得用它的CreateConstructionReturnMessage()方法来构造一个实现了IConstructionReturnMessageinternalConstructorReturnMessage类。为什么呢?原因是前面用到的RealProxyInitializeServerObject()方法返回的IConstructionReturnMessage里面包含的仍是未经过代理的原始对象实例——也就是说,虽然我们在ProxyAttributeCreateInstance()中把一个处于萌芽状态的原始对象实例传给了RealProxy,也因此拦截到了第一手构造器方法调用,然而我们在处理这个构造器方法调用的时候却还是返回了一个赤裸裸的原始对象实例给构造对象的调用代码!只是直到现在我还在奇怪为什么我们利用RemotingServicesGetRealProxy()方法还是能够得到这个赤裸裸的原始对象实例的真实代理……废话少说,来看看我们需要写哪些代码来最终实现我们的理想:

 

public class BypassProxy: RealProxy
{
  public override IMessage Invoke(IMessage msg)
  {
    if (msg is IConstructionCallMessage)
    {
      IConstructionCallMessage ctorMsg = (IConstructionCallMessage)msg;

      RemotingServices.GetRealProxy(_target).InitializeServerObject(ctorMsg);

     
return EnterpriseServicesHelper.CreateConstructionReturnMessage
        (ctorMsg, (MarshalByRefObject)
this.GetTransparentProxy());
    }
   
  }
}

 

怎么看怎么变态:我们首先需要使用之前ProxyAttribute.CreateInstance()创建的处于萌芽状态的目标对象的真实代理来按照构造器方法调用消息(ctorMsg)初始化;然后我们利用EnterpriseServicesHelperCreateConstructionReturnMessage()方法构造一个构造器返回消息,而消息中用于返回给调用代码的对象则是我们自己的真实代理生成的透明代理(本可以不写this,这里我是希望强调这两步中真实代理主体的差异)。

 

无论如何,现在我们已经基本上走通了所有的技术难点:我们可以用new来直接构造一个经过代理的对象(只要对象派生自ContextBoundObject);我们可以利用ProxyAttribute派生属性来为任意CBO类指派自定义真实代理(覆盖ProxyAttribute基类中的CreateInstance()方法);我们继而可以在自定义的真实代理中拦截到包括构造器方法调用在内的所有发往目标对象上的方法调用消息——而最重要的是,我们现在无需切换执行环境即可为目标对象添加方法级的服务代码(这是相比使用context property机制最大的优势)——现在我们只需要提供一个同样可以利用IMessageSink机制的定制真实代理就可以完成一个最基本的AOP开发框架了!

 

这个真实代理就是前文中讲过的MessageChainProxy,只是我们需要为其增加处理IConstructionCallMessage的处理分支就可以了。再结合一些简单的自定义属性,我们就可以编写这样的调用代码,来利用这个通用的AOP框架:

 

[AopProxy]
public abstract class ObjectWithAspects {}

 

[CallLogger, ExceptionPublisher, PerformanceMoniter, PersistManager]
public class AnyBusinessObject: ObjectWithAspects {…}

 

AnyBusinessObject instance = new AnyBusinessObject();
instance.DoBusiness(…);

 

这里,AopProxyAttribute就是一个提供自定义真实代理的ProxyAttribute,基本代码示意如下:

 

[AttributeUsage(AttributeTargets.Class)]
public class AopProxyAttribute: ProxyAttribute
{
  private Attribute[] GetAspectAttributes(Type classType)
  {
    return classType.GetCustomAttributes(typeof(AspectAttribute), true);
  }

  public override MarshalByRefObject CreateInstance(Type serverType)
  {
    MarshalByRefObject rawInstance = base.CreateInstance(serverType);

    IMessageSink headSink = new TerminatorSink(rawInstance);
    foreach (AspectAttribute aspectAttribute in GetAspectAttributes(serverType))
    {
      headSink = aspectAttribute.CreateAspectSink(headSink);
    }

    RealProxy aopProxy = new MessageChainProxy(serverType, rawInstance, headSink);
    return (MarshalByRefObject)aopProxy.GetTransparentProxy();
  }
}

 

而以CallLoggerAttribute为例,它其实是一个拦截器工厂,通过从一个公共的基类派生并实现工厂方法向AopProxyAttributeCreateInstance()方法提供拦截器实例:

 

public abstract class AspectAttribute: Attribute
{
  public abstract IMessageSink CreateAspectSink(IMessageSink nextSink);
}

public class CallLoggerAttribute: AspectAttribute
{
  public override IMessageSink CreateAspectSink(IMessageSink nextSink)
  {
    return new CallLoggerSink(nextSink);
  }
}

 

CallLoggerSink则是一个标准的完全可以重用的IMessageSink实现——这种结构的美在于我们是在针对公共接口编程,所以同样的实现可以应用于了解这个公共接口的所有机制中(比如这些IMessageSink也可以不加改动直接用context property将其contribute到任意的context中)。

 

当然,如果你需要在代码被部署之后可以灵活通过配置文件来改变施加在不同对象上的aspect对象,比如:

 

<aopSetting>
  <objectPattern typeNameRegex="…">
    <applyAspect type="My.Aspects.CallLogger" />
  </objectPattern>
  <objectPattern fromNamespace="My.Business.Services">
    <applyAspect type="My.Aspects.PerformanceMoniter" />
  </objectPattern>
  <objectPattern fromNamespace="My.Business.Entities">
    <applyAspect type="My.Aspects.PersistManager" />
  </objectPattern>
</aopSetting>

 

则只需要在AopProxyAttribute中加入相应的逻辑即可。本文中提到的机制已经以某种形式运用在我最近设计的企业级项目中,其可行性、可用性已经得到了初步的检验,不过距离一个完美的AOP框架肯定还有很长的路要走。不过也正如本文题目所言,这里我只是希望让大家尝尝鲜(a taste of而已),先接触一下AOP的概念,并对在.NET中实现AOP的几种基本技术有一个初步的了解。从这个层面上说,我想我的目的已经达到了——很多很多朋友仔细的读过这些文章之后给我发来邮件探讨相关的技术问题,有些朋友主动的利用我介绍的这些技术解决一些以前不能很好解决的问题,更有朋友已经在考虑利用各种技术来实现一个面向应用的AOP框架for .NET……这正是我非常希望看到的局面:技术要运用起来才能体现出价值。

 

事实上,为什么要写这一系列文章呢?其实本来就是希望写一些与这些技术相关的具体运用,可是发现了解这些技术的朋友实在是太少,有必要先铺垫一下,所以就引出来这一旷日持久的AOP尝鲜系列文章。聪明的你一定已经意识到,本文中介绍的几种技术完全还有更广阔的应用领域——比如我之前提过的mock objectElegantDAL interpreter等等……这些话题我稍后就会涉及,希望到时候能够把我的心得与大家分享,并期望听到更多的反馈。

 

按照惯例,我的文章都不配代码(甚至文中的代码我都不是copy-and-paste来的,所以你也不要copy-and-paste回去——我不能保证完全正确,但我还是会尽量写出最贴切的示意性代码),我鼓励大家动手实践,并期待听到你的反馈(比如说:几种机制在性能方面你有什么发现?在功能运用上你有什么体会?感觉还有哪些不妥之处?当然,文章中如果有拼写错误、语法错误、逻辑错误、概念错误的话也一定要告诉我!不能让我糊涂一辈子不是?:)

 

最后,再次感谢您阅读我的文章(不知道有多少朋友真的会读到这里:)……

posted on 2004-04-04 03:06:00 by jgtm2000  评论(63) 阅读(10112)

 
2004年03月10日

抛开技术细节不说,我今天很想记一种精神、一种态度,请来看看博客堂的“弟弟”博客园上面的一个话题:

关于.Text如何过滤恶意脚本的思考
小题大作:.Text中恶意脚本过滤的新方法

应该是受我文章的“误导”,dudu同学坚定不移的要用些(我已暗示多次并不合适的)新技术、新思路去解决一个很多中手、高手都不屑于解决的问题。他自己钻研文章,多次通过邮件向我提出自己的质疑,与我讨论相关的技术,最后终于成功的从完全不同的角度解决了问题,还发信来让我批评他写的程序……很多朋友问我如何可以成长为高手,这下我可找到教材了。:)

星星之火,可以燎原。一个社区的力量终归还是出自每个人的身上。所以我想,对外的广告是要这么打:Inspired community, inspiring people! 但对内的口号应该这样说:Inspired people, inspiring community!

posted on 2004-03-10 23:01:00 by jgtm2000  评论(9) 阅读(5112)

 
2004年03月02日

前面用透明代理机制实现简单AOP的文章一发,很快收到很多朋友的反馈,其中就有一些朋友提出关于文中所述的透明代理等技术还能够用于什么应用场合的疑问。说实在的,本来我就是想写一些关于这些技术在实际项目中应用的随笔的,可是想到可能会有很多朋友对这些技术的基本原理还不熟悉,这才萌生了先写一系列文章作铺垫的念头(可惜由于工作比较忙的缘故,至今还欠一篇没有写完,还请大家见谅啦,我抓紧时间补上——这篇内容更精彩噢!呵呵)。

 

今天我们又一次把我前面这两篇文字中提到的技术应用在项目开发中,初步反馈还是非常positive的,这里我不妨把问题先说出来,请看过文章的朋友们好好想想,看看如何利用已经学到的知识来解决这个实际问题吧——稍后我会回来说说我的想法(当然如果大家看过偶的文章以后都能够想出这种解决方案的话那当然就最好了:),当然如果你有其他更好的办法,更是欢迎你通过发表评论和大家分享!

 

这个问题和单元测试(Unit Testing)有密切关系(虽然我们的项目还不能算是完全的TDD,但是充分、必要的单元测试还是不可或缺),这里我们还是以简单的例子来说明,比如这里有一个银行服务的接口:

 

interface IBankService
{
  void Withdraw(Account account, int amount);
  void Deposit(Account account, int amount);
  int QueryBalance(Account account);
}

 

这个接口的三个方法的作用很简单:取款、存款、查询余额。我们肯定要开发一个实现这个接口的具体业务操作类;与此同时,由于该服务接口对安全性的严格要求,项目组中专门负责安全规则的部门将以DECORATOR的方式针对该接口的每一个方法编写负责安全保障的修饰类(最终在运行时由组件工厂动态组合)。以这种设计方式,只要确定了服务接口,则两个类的开发就可以充分的并行进行了——真的是这样吗?我们写写看:

 

class BankServiceSecurityGuard: ComponentDecorator, IBankService
{
  void IBankService.Withdraw(Account account, int amount)
  {
    if ( … )
      throw new DailyAmountExceedsException( … );
    if ( … )
      throw new AccountBlockedException( … );
    if ( … )
      throw new PermissionNotGrantedException( … );
   

    ((IBankService)Decoratee).Withdraw(account, amount);   
  }
 
}

 

这个方法的实现就是一个把门儿的逻辑:结合方法调用参数使尽各种必要手段确保只有在符合所有安全规则的前提下才能够把调用转发到被修饰的(即decoratee)对象(也就是IBankService的核心实现类上)。也就是说,开发并测试这个安全保障修饰类的时候最终还是要依赖于一个能够工作的核心实现类,否则就算可以编译通过(还好这种依赖已经被接口隔离开了),你也无法让单元测试通过。我们能不能用什么办法来独立的测试这个修饰对象使之不依赖于一个正确工作的核心实现类呢?换句话说,有没有办法在没有可工作的核心实现类的情况下(这种情况常见于核心类开发周期较长的时候),让下面针对安全保障修饰类的单元测试代码正常起到单元测试的作用(一是驱动开发过程、二是形成软件规格说明、三是跟踪软件缺陷……)呢(我们先假设Account类是已经存在且经过充分测试的——其实对该类的隔离测试也是类似的道理)?

 

[TestFixture]
public class BankServiceSecurityTest
{
  private IBankService bankService;

  [SetUp]
  public void Initialization()
  {
    bankService = new BankServiceSecurityGuard(???);
  }

  [ExpectedException(typeof(DailyAmountExceedsException))]
  public void TestForExceedingDailyAmount()
  {
    AccountState state = AccountState.DailyAmountReached;
    Account account = new MockAccount(state);
    bankService.Withdraw(10);
  }

  public void TestForNormalWithdraw()
  {
    AccountState state = AccountState.Normal;
    int initialBalance = 1000;
    Account account = new MockAccount(state, initialBalance);
    try
    {
      bankService.Withdraw(10);
      Assert.IsTrue(coreBankService's been called for Withdraw(10));
    }
    catch(Exception ex)
    {
      Assert.Fail("this call should not throw exception");
    }   
  }
}

 

简单说,我们要测试两类情况:一类是应该抛出安全异常的情况(TestForExceedingDailyAmount),在这种情况下,我们需要确信在给定的前提下必将产生指定的后果(比如特定类型的安全异常);另一类则是不该抛出异常的情况喽,在这种情况下我们不光要确信没有安全异常抛出,还要进一步确保方法调用确实被正确的(包括传入参数的值)转发给内部的指定方法了(否则一是有可能放过人为的安全后门,也有可能漏过修饰类忘记或错误调用被修饰类的情况)!

 

我已经听见有人高呼“简单简单”啦……写个mock对象不就结了!好,现在请你来为系统中数以百计的安全修饰类以及其他功能修饰类编写一一对应的mock对象好啦(幸好我们的接口都不是很fat,每个里面平均也就不到10个方法吧:)!反正我可真是一个超懒的程序员啊……你能给个省事儿又简单的方案吗?

posted on 2004-03-02 21:09:00 by jgtm2000  评论(11) 阅读(6289)

 
2004年02月23日
在很多朋友的一再催促下,偶关于.NET中实现基本AOP编程的第二篇文章终于在上个周末出炉了。这里首先多谢在大周末帮我做review的朋友们,多谢你们的火眼金睛,群众的眼睛真的是够靓啊!!:)

 

在写这篇文章的时候,我从MS的e-CompanyStore新收到的微软无线桌面套装精英版给我添了不少乐趣,可也带来了不少烦恼。键盘的手感虽比不上我那台老的IBM金属键盘,却仍是舒适、便捷得很(尤其是那条皮质的腕托,还有左面的纵横滚轮——可惜不能按!);鼠标虽然不是想象中的皮革版(明明在买它的时候看到图上照片是皮的嘛!上当!),不过仍是十分的漂亮且舒服。不过要命的是,无线的键盘经常会“批发”多余的按键,让人防不胜防(我按一下DELETE键没准给我发出6个!十分钟至少一次的说——怀疑是不是买到了次品?别的用微软无线键盘的朋友有遇到这个问题吗?);另外F/Lock锁定键的默认状态是快捷功能而不是功能键,且没有直观显示,难道F键要下岗了吗?这一点实在有些自作聪明了(其实这些双态键不妨设计为机械式的锁定按键,用键位的高低来显示状态,既不费电,也很直观,而且可以保持用户的习惯——顶多就是内部走线复杂一些,可也对得起这价钱啊)。还有,F功能键从4键x3组变成了3键x4组,更是让我头疼不已(所以没有把它拿回家用——回家我就是开开Flight Simulator 2004,它的键位可是按照4个一组来设计的,所以基本上是没法用了——不过Visual Studio受影响也不小啊)!看来这款“新颖的”产品应该更适合于那些从来没有用过传统键盘布局的朋友吧——不然你且得适应一下呢(而且适应以后就更用不惯不是这样设计的键盘了,还真是微软的一贯风格嘿)。

 

嗯,总之呢,写文章还是挺辛苦的,不过只要能有几个朋友读过文章后有所收获的话,我也就心满意足了。当然,如果你觉得有什么不吐不快的意见和建议啥的,也可以通过博客堂随时与我交流(写评论或者用联系链接),多谢啦!:)

posted on 2004-02-23 19:45:00 by jgtm2000  评论(8) 阅读(5378)

 
2004年02月20日

继续文章的第一部分,我们在这一篇文字中将从另一个角度考虑原文中提出的问题,并深入探索.NET/CLR中提供的相关技术机制,最终以一种AOP的形式达成同样的结果。为了让你能够尽快进入状态,我们先简要回顾一下前文中已经探讨的内容:

 

在文章的第一部分,我们从一个非常简单的虚构的业务操作类(Calculator)开始,结合企业开发中经常会面临的种种非功能性需求(操作日志、权限控制、性能监测等等),用面向对象的思路,结合DECORATOR设计模式,将业务操作对象的核心功能和对它的其他服务性功能代码分离,实现了更高内聚、松散耦合的软件组件。

 

不过在解决了对象代码职责混淆的问题(也就是提高了代码的内聚性)以后,我们却引入了很多的代码,包括为了解决问题而引入的更多的对象及更复杂的结构(用jjx的话说就是“过度设计”——虽然我强调这是一种能够解决我们必须要解决的问题的相对更合理的设计,但我也正是希望读者能在这样的感觉中体验本篇中讨论的方法相对的优越性——看来jjx果然中了偶的这招欲擒故纵:)

 

新引入的问题在相似的业务操作类的数量持续增长的时候变得愈加突出。在我们当前的企业级应用项目中,业务组件的数量已经是数十个了。而我们对每个组件都有几个基本的非业务性需求:基于方法调用颗粒度的权限控制、方法调用日志、业务操作审核、翔实的性能监测、可配置的缓冲策略……这些需求在一次又一次的企业级项目中开发了一次又一次。你说了不是有Application Building Block吗?别忘了,那只是你能用的工具而已,就算每个功能的实现只需要一条语句,想想看,50个组件乘上20个方法……怎么样?受够了吗?结果技术总监说了一句“能不能把我们现在用得自己写的缓冲控制器换成MS新发布的缓冲控制应用构建块?”……

 

可是这么多面向对象的设计方法还有设计模式难道还不能解决这些问题吗?为什么给这个组件的每个方法施加的缓冲控制逻辑不能在另一个组件的另一个方法上重用呢?为什么在一个组件的两个方法上写的逻辑几乎一模一样的方法日志逻辑不能合并呢?我想答案可能就是面向对象中的封装机制——方法,这已经是对象封装在内部的一个实现细节了,在这个层次上你已经回到了结构化编程的世界——你可以调用另一个方法,传入你想传递的参数,但是这个调用就再也不能够省却或合并。既然OOP这种生产力已经不适应新的生产关系,势必产生对新的生产技术的需求——这个新的产物就是所谓的AOP。抽象点儿说,AOP是一种在对象内部层次元素上(主要是构造器和方法)横切的功能植入机制;简单说,AOP允许你拦截方法并植入新的代码(不过现在技术的演变已经朝着越来越复杂的方向发展了),而最关键的是,这种横切是跨越对象类型、甚至与对象类型无关的。我们在本文中就来研究如何利用.NET/CLR中提供的技术机制来用一个类就实现为所有的50个组件的1000个方法拦截并植入我们的非业务性需求代码。

 

好,废话少说,我们切入正题。还是从最简单的例子说起(还是那句话:希望你能够将其想象为更复杂、更真实的情形——不然对于这样简单的事情而言任何设计技术都难逃过度设计之嫌了)

 

public class Calculator
{
  public int Add(int x, int y)
  {
    return x+y;
  }
}

 

这里是基于NUnit的单元测试代码:

 

[TestFixture]
public class UnitTest
{
  public void TestCalculator()
  {
    Calculator calc = new Calculator();
    Assert.IsNotNull(calc);

    Assert.AreEqual(8, calc.Add(3,5));
  }
}

 

还是同前一部分一样的需求,我们先来为这个类添加方法调用日志。这一次我们用一个新的设计模式PROXY来进行思考。其实,PROXY的结构和DECORATOR基本上是一样的,这两个模式的主要区别在于其意图DECORATOR主要用于为对象添加职责;而PROXY则主要用于控制/掌握对对象的访问。现在,我们希望有一个PROXY在调用代码和真实对象之间负责掌握/控制对对象的访问,同时还要客户代码无需了解其存在。为了应用该模式,我们还是逃不开抽象基类或接口、引入工厂等步骤,那么我们首先用工厂方法把对象的创建过程封装起来:

 

public class Calculator
{
  private Calculator() {}

  public static Calculator CreateInstance()
  {
    return new Calculator();
  }


  public int Add(int x, int y)
  {
    return x+y;
  }
}

 

因为默认的无参数构造器已经被修饰为内部可见性private了,所以原来使用new语句的测试代码就无法编译通过了,我们将测试代码相应调整到使用新提供的静态工厂方法调用上:

 

public class UnitTest
{
  public void TestCalculator()
  {
    Calculator calc = Calculator.CreateInstance();
   
  }
}

 

现在我们看看如何可以将一个代理嵌入到调用代码和真实对象之间,显然我们应该在对象创建的过程中动动手脚,比如这样:

 

public class Calculator
{
 
  public static Calculator CreateInstance()
  {
    return (Calculator)new LoggingProxy(new Calculator());
  }
}

 

在上面假想的代码中,我们希望把一个真实对象的新建实例(new Calculator())作为构造参数传入代理对象的构造器,因为最终真正干活的还是我们的真实对象,肯定要把这个家伙传给代理对象。然后我们希望创建好的代理对象应该能够以真实对象的身份(即Calculator类)返回给调用代码。然而,以我们已有的对C#面向对象编程的知识而言,只有当LoggerProxyCalculator的派生类的时候,上面的类型转换代码才可能在运行期成立。而Calculator本身已经是具体类了,让LoggerProxy从中派生恐怕没有道理,所有为了能够有一个能够与之平行兼容的代理类,我们只能为他们提取公共基类或抽象接口(如ICalculator),然后分别派生,再想办法用工厂组合起来……如此一来就等于回到了用DECORATOR模式解决问题的老路上,不是吗?:)

 

不过,如果能有办法让LoggerProxy类具备“模仿”其他类的能力,或者说——使其对于调用代码而言看上去和被代理的类毫无二致的话,前面的代码就能够成立啦!所以我们需要一个所谓的透明代理transparent proxy,也简称TP)!好消息:CLR里面还真有这么个透明代理的类(__TransparantProxy);不幸的是:我们既不能让自己的代理类从透明代理类派生以获得这种能力(正如大多数人希望的那样),也不能通过自定义属性、实现标志性接口等等方法让CLR认为我们的一个类能够透明的“模仿”另一个类。要想在CLR中获取一个透明代理,我们实际上需要提供一个真实代理(real proxy,下简称RP)。

 

一个真实代理是一个从System.Runtime.Remoting.Proxies.RealProxy派生而来的类。这个RealProxy类的首要功能就是帮我们在运行期动态生成一个可以透明兼容于某一个指定类的透明代理类实例。怎么告诉它我们想要“模仿”的类呢?你需要在从RealProxy类派生出来的真实代理类的构造器中显式调用该类中的一个protected RealProxy(Type classToProxy)构造器,传入我们需要透明代理去模仿的那个类型,如下代码所示:

 

using System.Runtime.Remoting.Proxies;

public class MyRealProxy: RealProxy
{
  public MyRealProxy(Type classToProxy): base(classToProxy)
  {
   
  }
}

 

这样,当构造MyRealProxy类的新实例时,RealProxy就会帮我们在内部构造好一个能够透明的模拟classToProxy类的透明代理!而当你得到这个新的真实代理的实例后,你就可以使用其GetTransparentProxy()方法取得其内部已经构造好的透明代理了。为了验证透明代理模仿可以模仿任何类型的超凡能力,请在单元测试中添加并运行这段测试代码:

 

public void TestTransparentProxy()
{
  Type classToProxy = typeof(Calculator);
  RealProxy realProxy = new MyRealProxy(classToProxy);
  object transparentProxy = realProxy.GetTransparentProxy();

  Assert.IsNotNull(transparentProxy);
  Assert.IsTrue(classToProxy.IsInstanceOfType(transparentProxy));
}

 

我们首先选择一个要代理的类型(classToProxy),然后为它构造我们真实代理(realProxy),再从创建好的真实代理实例中取出内部已经动态生成的能够模仿要代理类型的透明代理实例(transparentProxy)。接下来我们验证两件事:首先我们的透明代理不是空引用(说明确实成功的构造出了一个透明代理);然后用Type.IsInstanceOfType()方法验证该对象的类型确实是之前希望模仿的类型(当然你也可以写成检测静态类型的形式,即Assert.IsTrue(transparentProxy is Calculator),不过用代码中的这个方法是可以动态测试类型的)……

 

(靠……蒙我!编译不过去!)嘿嘿,想学习又懒得动手的朋友还是活动活动,把上面的代码实际验证一下,这样印象才深噢!:)

 

其实有问题才好,新的问题恰恰是引领我们学习新事物的动力嘛@#$#%$&*&%……还是让我们先来解决编译不过的问题吧。看看出错信息就知道:我们自己定义的真实代理类(MyRealProxy)没有实现一个叫做Invoke()的方法。翻翻文档,发现这个方法接一个类型为System.Runtime.Remoting.Messaging.IMessage的参数,并返回一个同样类型的对象——这是什么东东?先不管啦,实现了再说(稍后我马上会仔细解释这个的)!谁让RealProxy是一个抽象的基类呢,无论如何你也要记得在从该类派生时实现这个方法才行:

 

using System.Runtime.Remoting.Messaging;

public class MyRealProxy: RealProxy
{
 
  public override IMessage Invoke(IMessage msg)
  {
   
return null;
  }

}

 

使用Visual C#.NET 2003版本的朋友有福了,如果你背不下来也懒得自己去查要override的方法的定义的话,只要在编辑器中先打入“override”,然后在你按下空格之后——“噻,早不告诉我……”

 

这次编译肯定没有问题了,运行NUnit执行TestTransparentProxy测试节点,你马上将发现第二个问题了(够狠——一次下俩套儿):在RealProxy(Type classToProxy)构造器执行时产生了一个异常,说classToProxy必须是一个从MarshalByRefObject类型派生而来的类型。这又是个什么东东?我们还是暂且放下留待以后再说。其实熟悉或者用过.NET Remoting的朋友都了解,要想让一个对象能走出AppDomain周游天下的话,它或者要是MBVmarshal by value),或者要是MBRmarshal by reference)——而成为MBR的方法就是让类型从这个古怪的MarshalByRefObject派生出来(至于成为MBV对象的方法,有两种,请顺便复习一下啦!)

 

因此我们遵循CLR的抗议,将我们的Calculator类的基类指定为MarshalByRefObject(或其派生类):

 

public class Calculator: MarshalByRefObject
{
 
}

 

这次再运行测试,你将见到绿色的通过标志,这就验证了我们对于TP/RP的基本认识(注:如果你没有使用NUnit也无妨,将项目创建或修改成Console类型,然后用Console.WriteLine()输出我们在Assert后面需要验证的结果就行了——比如Console.WriteLine(transparentProxy!=null)然后看它是不是True也是可以的——不过你真的还不想装个NUnit吗?:)

 

现在我们回过头来看看刚才说过的IMessage是什么东西。查一下该接口的定义,发现里面就有一个Properties属性,类型是IDictionary,这说明IMessage只是一个用字典提供数据的消息。想知道这到底是个什么消息,我们就要研究一下这个字典里面都有什么数据。那就让我们来看看吧——怎么看呢?我们注意到这个IMessage是在我们真实代理类的Invoke()方法被调用的时候传入的,显然我们应该在这个方法里面来检查传入的消息。可是谁将会调用这个方法呢?它又是在什么时候调用这个方法呢?让我们首先来回忆一下对方法的调用是如何进行的吧(画面逐渐淡去颜色)……

 

在冯氏计算机体系中,调用方法都是透过堆栈进行的。堆栈是调用代码和被调用代码之间传递参数数据和执行结果的一个数据区。即使是在今天面向对象编程的世界中(乃至延展到今天的.NET世界中),普通的方法调用仍然是经由堆栈进行的。然而我们的高级代码对这一底层机制是毫不知情的,我们只是在进入方法后获得了方法传入的所有参数,并在返回的时候把返回值return给调用者(当然还有所有的ref/out参数的值)就万事大吉。换言之,因为我们的高级代码无法直接操纵堆栈,我们只能在方法的层次上解释参数并返回结果,这样就很难为现有方法嵌入额外代码。还记得我们是怎样利用DECORATOR模式解决这一问题的吗?当调用代码将输入参数传递给某个DECORATOR的某个方法时,我们可以在该方法内部检查甚至修改这些参数,然后再次利用方法调用的机制将调用转发给后一个DECORATOR的某个方法,直到方法调用抵达内部的核心对象再原路返回。这个过程实际上是一系列的构造/解析方法调用堆栈的过程。而利用.NET/CLR中的透明代理机制,情况发生了根本性的改变(逐渐恢复到彩色画面)……

 

当调用代码取得了一个透明代理实例并将其视为一个真实对象发出任何方法调用时,这个透明代理将利用内部机制拦截到该方法调用以及堆栈上所有与之相关的数据(传入的参数值、参数地址等),并将这些数据存储到一个高级代码能够处理的数据结构中,并将这个数据结构转发到能够处理它的代码上。正如你所想象的,这里所谓的“高级代码能够处理的数据结构”就是前面我们看到的IMessage(更具体的说——其中提供的数据字典);而那个“能够处理它的代码”自然就是我们真实代理对象内部的代码咯!也就是说,透明代理帮我们截获了来自调用代码基于堆栈的所有方法调用,并将其打包成数据字典以方法调用消息的形式转发给我们的真实代理进行在高级语言层次上的处理——这就是本篇文字要讲述的核心问题,即利用CLRTP/RP机制拦截方法调用,实现基本的AOP编程任务——通过这里初步的介绍,想必你已经对这种机制与基于传统面向对象(包括前文所述的DECORATOR设计模式)所采用的机制的区别有了初步的感觉。

 

初步了解了这些理论知识,我们不妨来看看透明代理都给我们打包了关于调用方法的什么数据。首先,我们改改Calculator类的CreateInstance()工厂方法,使其返回一个能够模仿Calculator类的透明代理,而这个透明代理所依赖的真实代理不妨就是刚才我们写的那个什么活都不干(其实是还干不了)的MyRealProxy吧!

 

public class Calculator: MarshalByRefObject
{
  public static Calculator CreateInstance()
  {
    RealProxy realProxy = new MyRealProxy(typeof(Calculator));
    object transparentProxy = realProxy.GetTransparentProxy();
    return (Calculator)transparentProxy;

  }
}

 

现在这段代码对你而言应该已经很容易理解了吧(不然还是我没写清楚喽)!编译后,运行最开始的TestCalculator测试,唔……出错喽!看看出错时的调用堆栈就发现其实原因很简单,我们在真实代理的Invoke()方法中什么也没干直接扔回去一个null——这要能干活才怪!不过出错也不要紧,我们还是可以先来检查一下到底传进来的IMessage的属性字典里面都有啥子名堂:

 

public class MyRealProxy: RealProxy
{
 
  public override IMessage Invoke(IMessage msg)
  {
    IDictionary properties = msg.Properties;
    foreach (object key in properties.Keys)
    {
      Console.WriteLine("{0} = {1}", key, properties[key]);
    }


    return null;
  }
}

 

我们知道,一个IDictionary数据字典其实是一个key/value值对数组。在这段新加入的代码中我们枚举字典中的每一个键值,并打印出它和它在字典中的值(你说了我傻了吧,字典中的每一个条目都是一个DictionaryEntry呀,应该foreach (DictionaryEntry entry in msg.Properties)entry.Keyentry.Value访问才正点啊……可惜,这个字典并不是Hashtable那个分支上面的,所以比较古怪,有兴趣你可以去看看它的源码:)。再次运行测试TestCalculator节点,仍然出错!也是,我们是还没解决问题呢。不过即使出错我们也已经在Console.Out窗口中偷窥到下面这样的输出结果了:

 

__Uri=
__MethodName=Add
__MethodSignature=System.Type[]
__TypeName=AOP.Part.II.Calculator, AOP.Part.II, Version=1.0…

__
Args=System.Object[]
__CallContext=System.Runtime.Remoting.Messaging.LogicalCallContext

 

果然,这个字典中包含有关于本次方法调用的一些信息,猜也差得差不多了:__MethodName显然就是被调用的方法的名字,__TypeName则是这个方法所在的类型的全称,__Args是一个object[],它应该是方法调用时候传进来的参数值吧?那__Uri又是什么东东呢?__MethodSignature这个Type[]又是什么呢?还有一个__CallContext,看上去有点儿像前面在DECORATOR中引入的Context,是不是呢?再写两行代码分析一下:

 

public class MyRealProxy: RealProxy
{
 
  public override IMessage Invoke(IMessage msg)
  {
   
    foreach (object arg in (object[])msg.Properties["__Args"])
    {
      Console.WriteLine("Arg: {0}", arg);
    }

    foreach (Type type in (Type[])msg.Properties["__MethodSignature"])
    {
      Console.WriteLine("Signature: {0}", type);
    }


    return null;
  }
}

 

运行测试,果然看到了期望的结果:


Arg
: 3
Arg: 5
Signature: System.Int32
Signature: System.Int32

 

也就是说,在传入的IMessage中的数据字典中,__Args这一项中包含了所有传入参数的数值序列,而__MethodSignature则是对应的参数的类型序列(method signature在很多书中被译为方法签名,其实它的定义很简单:就是方法的参数列表中参数类型的序列,它最初的用途大概是用来结合方法名称识别特定的方法重载的)。

 

现在我们希望能够让测试代码再次正确的运行,这就需要我们能够从Invoke()方法返回时将该方法在真实对象上调用时同样的返回值返回给调用代码。我们写return 8……恐怕不行。因为Invoke()方法的返回类型也是一个IMessage,也就是说透明代理希望我们把返回的结果也包装在一个消息对象中返回——可是我怎么知道如何包装这么个数据字典呢?还好,发现一个叫ReturnMessage的类,看样子是干这个的。我们可以构造一个ReturnMessage的实例,让它将我们的返回值通过透明代理带回调用代码去!这个类有两个看上去截然不同的构造器(自己翻一下文档啦),一个是用来处理正常返回情况的(就是带ret参数的这个,它应该就是实际的返回值喽),而另一个则可以处理异常情况(就是那个e)。outArgs/outArgsCount不用说,应该是用来返回输出参数的。LogicalCallContext先不管它,先给个null试试吧!那IMethodCallMessage是什么?顾名思义,一个代表着方法调用的消息——原来,它就是一个源于IMessage(更确切的说——IMethodMessage)的接口,一看定义就明白,原来它将IMessage中的属性字典中的很多项用属性和方法的形式发布出来了,这样我们就可以更直观的访问传入的代表着方法调用的消息了。那么现在我们就让Invoke()中返回测试代码所期待的“正确结果”吧:

 

public class MyRealProxy: RealProxy
{
  public override IMessage Invoke(IMessage msg)
  {
   
    IMethodCallMessage callMsg = msg as IMethodCallMessage;

    int x = (int)callMsg.InArgs[0];
    int y = (int)callMsg.InArgs[1];
    int result = x+y;

    return new ReturnMessage(
result, null, 0, null, callMsg);
  }
}

 

编译并运行测试,爽——又绿了。咦,等等——好像现在我们算这个加法的时候并没用到Calculator这个干活的类啊?幸亏只是个加法,要是算个圆周率什么的就有好看了!你说了,这个还不好办,我创建一个Calculator来干活不就行了:

 

public class MyRealProxy: RealProxy
{
  public override IMessage Invoke(IMessage msg)
  {
   
    Calculator calc = Calculator.CreateInstance();
    int result = calc.Add(x, y);

   
  }
}

 

嗯?好像不行——肯定要死循环了,因为我们正在处理这个Add()方法调用呢,而CreateInstance()返回的实例还会是一个转发给这个真实代理的透明代理(虽然不是同一个实例)。这里如果不用透明代理就好了,我们需要的其实是真正干活的那个核心实现,这个好办,在构造真实代理的时候就传进来一个能干活的真实对象不就行啦:

 

public class Calculator: MarshalByRefObject
{
  public static Calculator CreateInstance()
  {
    Calculator realCalculator = new Calculator();
    RealProxy realProxy = new MyRealProxy(realCalculator);
    object transparentProxy = realProxy.GetTransparentProxy();
    return (Calculator)transparentProxy;
  }
}

 

这样的话我们得为MyRealProxy添加一个相应的构造器:

 

public class MyRealProxy: RealProxy
{
 
  private MarshalByRefObject target;

  public MyRealProxy(MarshalByRefObject target): base(target.GetType())
  {
    this.target = target;
  }

}

 

经过这样的改造,当我们再遇到Invoke()调用时,应该就可以访问到最开始传入的真正干活的Calculator对象并用它进行真正的操作了吧?可是……等等,我们该怎么把从透明代理那里得到的方法调用消息转发给这个对象呢?我们可不会操作堆栈呀!总不能写int result = ((Calculator)target).Add(x, y)吧!我们可是打算让这个真实代理为50个组件的1000个方法服务呢啊……我们的答案就是RemotingServices.ExecuteMessage()方法。RemotingServices是一个位于System.Runtime.Remoting名称空间中的工具类,它提供了很多实用的辅助方法用来帮助我们实现包括真实代理在内的很多底层类。其ExecuteMessage()方法用法超级简单,作用也一目了然——就是将方法调用消息转发给指定的目标对象上执行,最后将返回的结果再打包成消息返回。有了它的帮助,我们就不用再自己去碰那些InArgs什么的啦:

 

public class MyRealProxy: RealProxy
{
  public override IMessage Invoke(IMessage msg)
  {
   
    IMethodCallMessage callMsg = msg as IMethodCallMessage;

    IMessage returnMsg = RemotingServices.ExecuteMessage(target, callMsg);

    return returnMsg;
  }
}

 

编译并运行测试,可以发现一切正常。不过背后发生的事情才是最重要的:我们已经拥有了拦截任意MarshalByRefObject对象上任意方法的基本手段,那么剩下来的事情就简单多了!这里插一句,就像你已经知道的这样,透明代理是负责把方法调用的堆栈转换成消息并转发给一开始构造它的那个真实代理的Invoke()方法,可又是谁把方法返回的消息转换回堆栈并发送给真实对象的呢?又是谁把真实对象方法执行的结果从堆栈上再次打包为消息返回Invoke()方法的呢?这个家伙其实是StackBuilderSink,我们后面还会再提到它的,现在先打个照面的说。

 

回过头来仔细观察上面的代码,可以发现真正对核心真实对象(target)方法的执行就是发生在调用RemotingServicesExecuteMessage()方法之时。在它之前,我们可以通过callMsg取得(甚至修改)所有的关于方法调用的信息(就是AOP基本操作之pre-processing啦);在它之后,我们又可以通过returnMsg取得(甚至修改)所有关于方法返回的信息(也就是AOP基本操作之post-processing啦!还记得两种不同的情形吗——正常返回与抛出异常)——这里提示你去看一下与IMethodCallMessage对应的IMethodReturnMessage接口,我们从ExecuteMessage()方法得到的IMessage是可以转换为这个接口进行直观访问的。

 

好了,现在就请您按照目前的得到的信息自己写一个真实代理类,比如说就叫LoggingProxy吧。你可以让这个真实代理为所代理的对象上的每一次方法调用都打印出一行记录,比如:

 

[2004-02-16 12:34:56] Calculator.Add(3, 5) -(37ns)-> 8

 

应该不难吧?写好了再继续往下看啦……

 

现在我们已经有了两个真实代理类(什么?还没写好?不要偷懒啊:),一个是MyRealProxy,它简单的在方法调用进入时输出msg.Properties中的内容;一个是你刚写的LoggingProxy,它应该可以记录下每次方法调用的日志信息。现在我们希望能够像组合DECORATOR那样将这两个真实代理所提供的功能叠加组合起来,我们该怎么办呢?有了前一篇文字的知识和思路,我们首先可以再次尝试使用DECORATOR模式将几个真实代理所构造的透明代理彼此连接起来(啊?可以吗?——怎么不可以?要记住透明代理的魔力——它对于调用代码来说跟你所代理的类型的实例毫无二致)。就像这样:

 

public class Calculator: MarshalByRefObject
{
  public static Calculator CreateInstance()
  {
    Calculator realCalculator = new Calculator();
    RealProxy realProxy = new MyRealProxy(realCalculator);

    Calculator
fakeCalculator = (Calculator)realProxy.GetTransparentProxy();
    RealProxy loggingProxy = new LoggingProxy(
fakeCalculator);

    Calculator result = (Calculator)loggingProxy.GetTransparentProxy();

    return result;
  }
 
}

 

现在CreateInstance()返回的透明代理其实是由LoggingProxy的实例构造的,而该实例内部真正干活的对象又是由之前MyRealProxy构造并返回的透明代理,最终干活的对象则是最开始构造的realCalculator。当调用代码向这个透明代理发出方法调用时,loggingProxy的透明代理首先将堆栈上的信息打包传给LoggingProxyInvoke()方法;然后你在LoggingProxyInvoke()内调用RemotingServices.ExecuteMessage()将这个消息(通过StackBuilderSink转换回堆栈——还记得前面提到的这位仁兄吗?:)转发给目标对象——也就是由MyRealProxy构造的那个透明代理——然而这次转发将是一次由消息到堆栈再到消息的过程(另外别忘了,无论如何最终从Invoke()方法返回的消息还会被透明代理转换回基于堆栈的方法调用结果的——调用代码对这一路上发生的这一串儿事情真是毫不知情)!

 

一串儿事情?这不禁让我们想起了另一个设计模式,也就是CHAIN OF RESPONSIBILITY(职责链)。我们可以把一系列对方法调用消息感兴趣的处理代码封装到一个个独立的、高度内聚的消息处理对象中,并通过将其串接成链表形成一个职责链,让方法调用消息沿着这条职责链一路走下去,直到抵达真实对象——而后方法返回消息再沿原路返回依次途径职责链上的每一个参与者。通过这样的机制,我们避免了反复在堆栈与消息之间进行转换所带来的额外开销,从而把所有的方法拦截处理活动在一个统一的基于消息的世界中搞定——也就是说,我们需要一个链式反应的场所——显然,它也应该是一个真实代理,我们不妨把它叫做MessageChainProxy,即消息链代理吧:

 

public class MessageChainProxy: RealProxy
{
  private MarshalByRefObject target;

  public MessageChainProxy(MarshalByRefObject target): base(target.GetType())
  {
    this.target = target;
  }

  public override IMessage Invoke(IMessage msg)
  {
    return null;
  }
}

 

怎么样,这六行实现一个不干活的RealProxy的骨架代码现在你也已经可以轻车熟路的写出来了吧?现在我们开始编写实质性的代码。正如前面所说,如果有多个需要串连起来的对方法调用消息进行链式处理的代码,我们希望将它们封装起来,成为一个消息处理与转发器——正好.NET Remoting里面已经定义了这样一个语义,我们不妨直接拿来用用,这就是IMessageSink(消息接收器)接口(同样在System.Runtime.Remoting.Messaging名称空间中):

 

public interface IMessageSink
{
  IMessageSink NextSink { get; }
  IMessage SyncProcessMessage(IMessage msg);
  IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink);
}

 

仔细一看其实这个接口很简单:NextSink顾名思义,肯定是指向消息处理链中的下一个接受器喽;SyncProcessMessage肯定就是真正处理方法消息的方法啦(这一个是用于同步场合,而另一个A开头的则是用于异步场合——异步方法调用在.NET中还算是一级支持的)。考虑到我们可能将需要编写越来越多的消息处理器,不妨为这个接口提供一个基础实现吧:

 

public abstract class MessageSinkBase: IMessageSink
{
  private readonly IMessageSink nextSink;

  public MessageSinkBase(IMessageSink nextSink)
  {
    this.nextSink = nextSink;
  }

  public IMessageSink NextSink
  {
    get { return nextSink; }
  }

  public abstract IMessage SyncProcessMessage(IMessage msg);

  public virtual IMessageCtrl AsyncProcessMessage(IMessage msg, IMesssageSink replySink)
  {
    return nextSink.AsyncProcessMessage(msg, replySink);
  }
}

 

在这个抽象的基类中,我们实现了IMessageSink接口中的两个方法:NextSinkgetter因为觉得不太可能需要灵活改写,所以实现为默认的非虚方法了(嗯?怎么成方法了,不是属性吗?别忘了属性其实就是一对或一个方法噢!);SyncProcessMessage正是我们要求派生类必须提供且没有默认实现的逻辑(这不是个TEMPLATE METHOD嘛),所以声明为抽象的还是很自然的吧;而AsyncProcessMessage呢,考虑到不是所有的消息接收器都支持异步方法的拦截处理,不如提供一个默认的实现将其转发到nextSink上去(或者实现为抛出NotSupportedException,这也是很常见的做法),也就是说派生类可以根据需要可选实现或覆盖这个逻辑。现在我们就可以将原来的两个RealProxy中的Invoke()方法提取出来作为IMessageSink中的SyncProcessMessage()来实现啦,因为实在没什么可说的,这里就不再浪费网络带宽喽(%$^%%$#)。我们还是来看MessageChainProxy如何实现职责链吧,因为这一块也并不是本文着重要讲的话题,所以我也就不再推导了,相信你可以很容易的看懂这部分代码:

 

public class MessageChainProxy: RealProxy
{
  private MarshalByRefObject target;
  private IMessageSink headSink;

  public MessageChainProxy(MarshalByRefObject target): base(target.GetType())
  {
    this.target = target;
    this.headSink = new TerminatorSink(target);
  }

  public override IMessage Invoke(IMessage msg)
  {
    return headSink.SyncProcessMessage(msg);
  }

  public void AppendSinkType(Type sinkType)
  {
    object[] ctorArgs = new object[] { headSink };
    IMessageSink newSink = (MessageSinkBase)Activator.CreateInstance(sinkType, ctorArgs);
    headSink = newSink;
  }

}

 

代码中,headSink就是整个消息处理链的头结点,而整个调用链的驱动乃是由每个sink在自己的xxxProcessMessage()方法中显式调用NextSink.xxxProcessMessage(msg)来形成的(这与我们在DECORATOR模式中每个DECORATOR中调用Decoratee.Method()是异曲同工的)。值得注意的是一个边界情况,即MessageChainProxy在刚被构造完成后,即没有调用任何AppendSinkType()添加处理节点前,我们希望它也能正常工作(就是什么都不用做只要别抛出空引用异常就好),所以我们要引入一个TerminatorSink的概念——这个和SCSI设备链中的terminator概念是一样的,就是一个终结器。我们将其实现如下(注意看一下粗体的部分就行了,我就不再详细分析了——如果你这里还是看不明白,那我文章算是白写了:)

 

private class TerminatorSink: IMessageSink
{
  private MarshalByRefObject target;

  public TerminatorSink(MarshalByRefObject target)
  {
    this.target = target;
  }

  IMessageSink IMessageSink.NextSink
  {
    get { return null; }
  }

  IMessage IMessageSink.SyncProcessMessage(IMessage msg)
  {
    return RemotingServices.ExecuteMessage(target, msg as IMethodCallMessage);
  }

  IMessage IMessageSink.AsyncProcessMessage(IMessage msg, IMessageSink replySink)
  {
    throw new NotSupportedException();
  }
}

 

想想看,道理其实很简单,这也同样是一种很常见的面向对象设计模式,有的地方管它叫做NULL OBJECT,总之就是为可能出现空引用的场合也提供一种替代对象,从而简化复杂的条件判断逻辑——写到这里恐怕又有高手要抗议说过度设计了,其实偶就是多给大家一种解决问题的思路而已,具体怎么选择还是要放到具体的环境中考虑——不过多一种选择总比没得选择要好吧(何况这里提出这个概念也是为下篇中的一些内容作铺垫呢:)

 

好了继续往下写……因为这个TerminatorSink仅在我们自己的MessageChainProxy里面用到,所以将其定义为私有的内嵌类是再合适不过的了:

 

public class MessageChainProxy: RealProxy
{
  private class TerminatorSink: IMessageSink
  {
   
  }

 
}

 

现在我们就可以在CalculatorFACTORY METHOD里面使用这个具有可扩展能力的消息处理链式代理类了:

 

public class Calculator: MarshalByRefObject
{
  public static Calculator CreateInstance()
  {
    Calculator realCalculator = new Calculator();

    MessageChainProxy chainedProxy = new MessageChainProxy(realCalculator);

    chainedProxy.AppendSinkType(typeof(MyMessageSink1));
    chainedProxy.AppendSinkType(typeof(
MyMessageSink2));
   


    return (Calculator)chainedProxy.GetTransparentProxy();
  }
 
}

 

这里你需要注意一下,由于是一个有序的调用链,因此添加消息处理器结点的顺序对于最终的执行逻辑是有很大影响的。比如说你有两个消息处理器:一个是做方法日志的,第一个添加到处理链中(其NextSink将指向TerminatorSink);第二个是做访问控制的(就是根据代码调用者的安全身份和权限决定是否允许执行该方法),在其后添加到处理链中(NextSink即指向前面的方法日志处理器)。这时候,当调用代码发出一个对目标对象上方法的调用时,该基于堆栈的调用首先被CLR的透明代理截获并转换为方法调用消息,随后被发送给对应的真实代理也就是我们的MessageChainProxyInvoke()方法处理,而该方法立即把该消息传递给headSink也就是最后被添加到处理链上的访问控制处理器的SyncProcessMessage()方法上。此时,如果访问控制器允许方法被继续执行下去,则同样的方法调用消息被转发给NextSink所指向的方法日志处理器处理,最终方法消息到达TerminatorSink并被转发给RemotingServicesExecuteMessage()方法——还记得前面讲过的这个方法的执行逻辑吧:一个透明代理的“反透明代理”也就是StackBuilderSink(你看,它的名称已经暗示了它其实也是一个IMessageSink!)最终利用传入的方法调用消息重建调用堆栈,并发往真正干活的我们的Calculator类……但是如果在访问控制器这一关没有过去,而是被抛出了安全异常的话(要记得用ReturnMessage构造一个代表方法调用异常的返回消息噢!不然的话透明代理也就不再透明了……),显然方法日志这一环节根本就不会被执行到了(更甭说随后的TerminatorSinkStackBuilderSink了,当然我们对权限控制完全无知的Calculator也就永远不会看到这个越权的方法调用了)。所以说,如果你希望记录下所有的方法调用请求,就应该把方法日志处理器放到权限控制的前面来,也就是说——在添加完访问控制器以后再添加日志处理器就行了。这个道理还是比较简单的,和前面说的DECORATOR模式的实现是一样的——我这里再捋一遍也就当作本篇文字的再回首吧。

 

行文至此(好拽),我们已经对基于TP/RPIMessage的方法拦截机制有了初步的了解,利用这些知识应该已经可以解决很多问题了。那么前文中描写的基于DECORATOR的机制就没有任何用场了吗?其实不然。这两种机制的截然不同本身也暗示了它们将在不同的场合和情况下发挥不同的优势。首先,不可否认的是,DECORATOR基于堆栈的方法转发机制肯定要比基于IMessage的转换传递机制效率高的多,当然对于很多涉及远程方法调用的企业级应用或者在本地运行的桌面应用而言损失的性能可能算不上什么主要矛盾,然而你仍然不得不时刻记着这个潜在的陷阱——尤其是在选择技术的起始阶段。然而,通过基于透明代理的机制我们可以把一些与具体对象类型甚至方法都无关的通用逻辑集中在一个真实代理中实现,并利用对象构建模式将其动态的(即运行时)“缝合”到目标对象的每一个方法上去,这个机制对于为大量对象添加n通用行为的场合是很有诱惑力的——甚至很容易让你为之妥协因此可能损失的些许性能!不过,既然是通用性很强,那么对于需要针对性的场合,比如说根据方法传入的参数的值来具体判断访问权限等场合,还是基于DECORATOR接口实现的方法转发更为灵活——让一个为通用性而设计的机制去处理针对性的场合本身肯定会得不偿失的。因此,最好的结局恐怕还是需要把两种机制无缝的整合在一起,也就是说把一些需要针对性处理的功能叠加通过DECORATOR的机制施加到核心对象上,而把一些通用的功能利用透明代理的魔力作用到对象上——这个机制事实上已经在我们当前的项目开发中设计并采用了,确实给我们的项目开发带来了很多的好处(减少数千行代码、节省很多的人月、提交易于维护且灵活的软件系统——这还不够吗?:)

 

最后,把这部分内容相关的一些小技巧贴着这里,也许对你有用的说:

  • IMethodMessage这个接口中引入了MethodBase这个属性,透过它你可以进入Reflection的世界检视方法上的类型信息包括自定义属性。用它你可以为真实代理的Invoke()方法提供参数,从而增加系统的灵活——记住:这只是增加一种灵活性的机制,并没有取代其他的机制——你还是可以从配置文件中读入有关的参数化信息,对吧progame?:)
  • 真实代理不仅仅可以为MarshalByRefObject构造透明代理,它也可以给一个接口构造透明代理。所以你完全可以用一个真实代理为很多不同的接口提供基于方法调用消息的实现——就像我们在ElegantDAL里面使用的方法一样,只要定义接口、获取透明代理,不需要提供接口的实现类,就可以完成原本需要一一实现的繁琐功能。具体做法请参考我们一开始直接用ReturnMessage返回执行结果的实现,原理是一样的。
  • 不过当一个透明代理所代理的目标为接口时,你将不能再用RemotingServices.ExecuteMessage()将方法调用消息转发给它了。这里你需要这两个帮手:RemotingServices.IsTransparentProxy()RemotingServices.GetRealProxy()。一旦你手里有了真实代理,怎么样转发消息就不言而喻了吧?
  • 更多的小技巧还是留给你自己在实践中摸索吧,我都说了就没意思了!:)

到这里,本篇的内容已经基本完成,但是挑剔的读者肯定还是有很多不爽的感觉,比如说:

  • 想拦截构造器调用,可是真实对象已经先于真实代理创建好了,怎么办呢?
  • 要想用TP/RP包装对象,就要写FACTORY METHOD封装整个的构造过程,能不能直接用更直观的new命令来直接创建一个代理好的对象呢?
  • 如果你在Calculator类的某些方法中将代表对象实例的this传递给对象外部的时候,这个引用是直接指向真实对象的引用!所以你精心设置的关键的比如说访问控制器等机制就将被绕过了!怎么样才能保证外部调用总是经过我设置的重重关卡呢?
  • 更多的不爽请你通过反馈发送给我 :)

其实这些内容本来也是计划在本文中一并完成的,可是篇幅所限(借口!)我想还是留到下一篇文字中继续分析并解决吧!其间还是一如既往的请你把与本文相关的意见、建议、收获、疑惑等等通过留言或者邮件告诉我,您们的支持是我写作的最大动力!再次谢谢大家的阅读!:)

posted on 2004-02-20 23:29:00 by jgtm2000  评论(44) 阅读(13213)

 
2004年02月18日

先来看这段NUnit测试代码,我们希望用反射机制在运行时访问一个对象的枚举类型的域或属性:

 

[TestFixture]
public class PaymentInfo
{
  public enum PaymentType
  {
    Cash, CreditCard, Check
  }

  public PaymentType Type;

  public void Test()
  {
    PaymentInfo payment = new PaymentInfo();
    payment.Type = PaymentType.Cash;

    System.Reflection.FieldInfo enumField = GetType().GetField("Type");

    int paymentTypeInt32;

    paymentTypeInt32 = (int)enumField.GetValue(payment);
    Assert.AreEqual((int)PaymentType.Cash, paymentTypeInt32);

    enumField.
SetValue(payment, paymentTypeInt32);
    Assert.AreEqual(PaymentType.Cash, payment.Type);
  }
}

 

实际上运行测试时发现在标红的这行上抛出一个异常:“对象类型无法转换为目标类型”。究其原因,原来是因为CLR的反射机制不允许枚举类型与整数类型之间隐式转换。不过C#编译器还是允许我们通过强制类型转换的语法来进行两者间的显式转换。

 

在这个测试中,使之通过的办法其实非常简单:把划线部分强制转换为枚举类型即可,如:(PaymentType)paymentTypeInt32。可问题是:在运行时如何动态转换类型呢?比如说我在写ElegantDAL的时候,需要将从数据库读出的一个类型为int的数值写入到要返回的对象的一个枚举型字段中,此时我只有fieldInfocolumnValueresultObject,然而写成fieldInfo.SetValue(resultObject, columnValue)就会出现前面提到的错误,可是我又只有一个运行时的Type信息(fieldInfo.FieldType),我又不能写成fieldInfo.SetValue(resultObject, (fieldInfo.FieldType)columnValue)……

 

只好将这种情况列为一个特例处理,而我们的救兵则是Enum.ToObject()方法——你知道有更好的方法解决这个问题吗?

 

BTW: 很多朋友写信来问偶承诺的下一篇文章什么时候才能搞定,其实我也不想拖,可最近工作确实比较紧张,而我的最佳写作时间又都在深更半夜(之前还要热身进入状态),所以迟迟不能结稿。目前其实已经写了好多(已经和第一篇长度差不多了),可是发现要写的内容还太多(也许是我写得太细了,因为我的目标不仅是讲how,更多的是whatwhy),所以现在决定把本篇文字再细分为两部分(上篇只讲TP/RP+MBR对象;把CBO相关的内容放到下篇再写),以便能够尽快让大家看到新的内容——嗯,争取周五release吧。至于还有朋友希望我能够深入写些关于ElegantDAL的设计与实现细节,这个留待稍后吧,正好我们将要整理的项目文档中也要有这一块内容(现在在我们这个企业级项目中用得还真挺好),到时候一起写啦(希望能够和canyue尽快完成第三版本的重写工作,目前使用的是第二版)。

posted on 2004-02-18 20:16:00 by jgtm2000  评论(5) 阅读(6544)

 
2004年02月07日
 

昨天,我第一次在实际开发工作中体验到了结对开发的魔力(以前也就是两个人一起研究问题而已),那确实是一种令人神怡且有效的工作方式——至少it works extremely well for me!

 

在下午三四个小时的开发工作中,我和我的搭档一起解决了已经困扰了我很久的几个问题,并令人激动不已的在NUnit中点绿了单元测试中所有的测试项(就这几个问题我已经拖了半个月了还没能解决)——这意味着我可以放心的开始对代码进行重构(而不是重写)了(本来一直没有信心这样做,因为到处都有红灯,而每一个红灯都牵扯到很多的问题)……体会一:结对编程消除了最影响开发人员情绪的孤独感,进而重新迸发工作激情,大大提升工作效率和质量。

 

其他同事都下班以后,我们简直是进入了一种turbo的开发状态——我们先是一起总结了前面开发中最不爽的几个环节,并一致认为一定有好办法能够让开发变得更简单一些——于是我的灵感突然涌出,我告诉他“我将把让我们的开发变得繁琐的这些类都删掉,用一个custom proxy搞定所有的类”!“听上去不错,可是……有这么好的事吗?”“看我的——你就帮我瞧着那些绿灯吧!”当然,我在下手之前还是像唐僧一样把改造的思路说了几遍,当我确信搭档已经明白我的意图后,我开始动手了:三下五除二,我先把一个private成员提为internal(本来它只是被自己的一系列public gateway methods访问的,现在我要让它能被custom proxy使用),并马上写出了这个custom proxy,用它替代了以前那些繁琐的method gateway和那个接口实现类,最后,我到组件工厂中把实现接口的类换为由custom proxy构造的transparent proxy——NUnit.Run! Bingo! We got it! 体会二:两个人的智慧确实大于一个人,尤其当两个人从两个方向共同考虑同一个问题的时候——当然,彼此持续、默契的交流是必不可少的。

 

现在回头想想,这一下午的进展真的是匪夷所思,我又一次亲身体会到了结对编程的魔力和乐趣——当然,坐在我对面的项目经理估计也体验了他工作以来强度最大、持续时间最长的人造噪音。不过我想说的不是sorry,而是thanks!谢谢你给我们这样的机会,这是在其他团队中难以想象的事情。我相信你也将亲眼看到我曾向你描述的关于结对编程的种种好处。另外,thanks to my partner canyue!和你一起工作真的很开心,我在你谨慎的约束下变得更专注也更有效率,而你也能在看我开发的时候学到更多的东西。Cool!

 

如果你也有类似的敏捷开发经历,不妨写在评论中与我们分享——无论是成功的经历还是失败的经历,我想都会很有参考价值。其实关于敏捷方法论(尤其是其中最著名的极限编程技术),我也有很多实践经验和想法,希望以后有时间能够在这方面也写些心得体会吧——不过当务之急,还是先把腾苦不堪的颈椎疼痛消灭掉吧!现在开车都成问题了……

posted on 2004-02-07 16:44:00 by jgtm2000  评论(18) 阅读(5867)

 
【第1页/共3页,44条】
首页
前页
1

Powered by: Joycode.MVC引擎 0.5.2.0