<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">博客园_木-叶</title><subtitle type="text">十载酣游不觉醒，已二更；横刀把盏待天青，还入梦。</subtitle><id>http://feed.cnblogs.com/blog/u/22847/rss</id><updated>2012-06-02T07:36:06Z</updated><author><name>南柯之石</name><uri>http://www.cnblogs.com/nankezhishi/</uri></author><generator>feed.cnblogs.com</generator><link rel="alternate" type="text/html" href="http://www.cnblogs.com/nankezhishi/"/><link rel="self" type="application/atom+xml" href="http://feed.cnblogs.com/blog/u/22847/rss"/><entry><id>http://www.cnblogs.com/nankezhishi/archive/2012/05/31/2529201.html</id><title type="text">小例子背后的大道理&amp;mdash;&amp;mdash;Adapter模式详解</title><summary type="text">上回问题回顾 前文说到一位用户拿着业界标准开关（一个标准的StandardSwitcher，它依赖IStandardSwitchable接口才能工作，然而目前我们的灯并不支持这个接口）出现在我面前，叫嚣着他的“标准开关”应该能打开我们的灯。好吧，这个需求是合理的，的确应该支持。但是该死的是，为什么没有早一点儿知道这个标准的存在呢？这样就不需要花费时间和人力定义这个接口，现在也不会这么纠...</summary><published>2012-05-31T14:52:00Z</published><updated>2012-05-31T14:52:00Z</updated><author><name>南柯之石</name><uri>http://www.cnblogs.com/nankezhishi/</uri></author><link rel="alternate" href="http://www.cnblogs.com/nankezhishi/archive/2012/05/31/2529201.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/nankezhishi/archive/2012/05/31/2529201.html"/><content type="html">&lt;p&gt;&lt;strong&gt;上回问题回顾&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 前文说到一位用户拿着业界标准开关（一个标准的StandardSwitcher，它依赖IStandardSwitchable接口才能工作，然而目前我们的灯并不支持这个接口）出现在我面前，叫嚣着他的“标准开关”应该能打开我们的灯。好吧，这个需求是合理的，的确应该支持。但是该死的是，为什么没有早一点儿知道这个标准的存在呢？这样就不需要花费时间和人力定义这个接口，现在也不会这么纠结。和上次一样，先讲故事、演进方案，再分析背后的思想。&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 这回主要讲解Adapter模式，GoF讲解了这个模式是什么，怎么用，用在什么地方。我想来解释一下Adapter模式的要点是什么，对Adapter模式的延展，以及对Adapter模式的误用。顺便得瑟一下我对面向对象设计的理解。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;两个方案&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 现在有两个选择。&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;     &lt;p&gt;让我们的灯直接支持标准开关。也就是让灯实现IStandardSwitchable接口。 &lt;/p&gt;      &lt;ul&gt;       &lt;li&gt;         &lt;p&gt;好处：成本低，实现方式优雅。&lt;/p&gt;       &lt;/li&gt;        &lt;li&gt;         &lt;p&gt;坏处：相当于放弃了已经买了我们的灯，又想用标准开关的用户。&lt;/p&gt;       &lt;/li&gt;     &lt;/ul&gt;   &lt;/li&gt;    &lt;li&gt;     &lt;p&gt;不改变现在的灯，让标准开关能打开我们的灯。标准接口我们改不了，灯也不能改。好在计算机界有句话，叫“加一层可以解决一切问题”。这让我想到了买外国电器附赠的那个电源接口转换器。现在，我们的灯需要个类似的玩意儿。&lt;/p&gt;      &lt;ul&gt;       &lt;li&gt;         &lt;p&gt;好处：支持所有的灯。&lt;/p&gt;       &lt;/li&gt;        &lt;li&gt;         &lt;p&gt;坏处：这东西都是要附赠的，会降低我们的利润。&lt;/p&gt;       &lt;/li&gt;     &lt;/ul&gt;   &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 第一个方案很简单，就是让Light多实现个接口就OK了。图就不给了。&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 现在分析第二个方案，标准接口依赖IStandardSwitchable接口，那我们必须有一个类来实现它，并完成所需要的功能——操作灯。咱也是学过设计模式的人，这个问题很明显可以用Adapter模式来解释。&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 相关类图很容易就可以画出来。&lt;/p&gt;  &lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/nankezhishi/201205/201205312251342312.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px" title="clip_image002" border="0" alt="clip_image002" src="http://images.cnblogs.com/cnblogs_com/nankezhishi/201205/201205312251345660.png" width="280" height="186" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p align="center"&gt;图1 让灯支持IStandardSwitchable接口的方案&lt;/p&gt;  &lt;p align="center"&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 其对应的代码会是这个样子：&lt;/p&gt;  &lt;div class="csharpcode"&gt;       &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;interface&lt;/span&gt; IStandardSwitchable      {          &lt;span class="kwrd"&gt;void&lt;/span&gt; TurnOn();          &lt;span class="kwrd"&gt;void&lt;/span&gt; TurnOff();      }  &amp;#160;      &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; SwitcherAdapter : IStandardSwitchable      {          &lt;span class="kwrd"&gt;public&lt;/span&gt; Light Switchee { get; set; }  &amp;#160;          &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; TurnOn()          {              Switchee.TurnOn();          }  &amp;#160;          &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; TurnOff()          {              Switchee.TurnOff();          }      }&lt;/div&gt;&lt;style type="text/css"&gt;.csharpcode, .csharpcode pre{font-size: small;color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff;/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt {background-color: #f4f4f4;width: 100%;margin: 0em;}.csharpcode .lnum { color: #606060; }&lt;/style&gt;&lt;p align="center"&gt;代码1&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Job Done。Light通过SwitcherAdapter支持了新的接口，这简直就是应用适配器模式的典范啊。（嗯，这句的确是反话，不过你猜出来为什么这个Adapter不属于适配器模式吗？）&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; “上回真是白跟你说了那么多，平时没觉得你这么不开窍啊。你自己好好想想吧！”背后看着我画UML图的设计Guru好像有点儿生气。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 上回？我冷静下来回想上回的内容和现在的问题。上回讲的DIP，讲不要依赖实现，要依赖抽象。再想想目前的需求，我们有灯，有收音机，如果用户说要用标准开关开收音机，难道还要实现一个RadioAdapter不成？这显然违反了OCP。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 需求是要“通过加一层让灯支持标准开关”，但是并不是说这一层就要使用灯，为了让这个Adapter更加通用，应该让Adapter依赖ISwitchable接口。像下面这个样子。&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/nankezhishi/201205/201205312251348691.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px" title="clip_image004" border="0" alt="clip_image004" src="http://images.cnblogs.com/cnblogs_com/nankezhishi/201205/2012053122513587.png" width="358" height="186" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p align="center"&gt;图2 Adapter模式&lt;/p&gt;&lt;p align="center"&gt;&amp;#160;&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 与代码1的差别，仅仅是SwitcherAdapter里的Switchee属性的类型改成ISwitchable而已。代码就不再贴了。其所体现的原则就是上一篇讲的DIP。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 这个事儿其实任何人静静地想想都能想到。但我绕这个弯子，其实是想顺便表达这样一个意思：一个紧急需求来了的时候，人们更容易倾向于把完成工作放在第一位，从而一时忽视了设计的严谨度，事后又忘了重构，于是Bad Smell就这样产生了。当然，这些大家也都知道。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;面向对象的设计并不是对现实的模拟&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; （这一节算是一个插曲吧，因为这个论点太大，写出来都觉得不自量力，不写又觉得对不起自己爱得瑟的作风。一点拙见，大家多多批评。觉得偏题太远的话可以直接看一下节。）&lt;/p&gt;&lt;p&gt;&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 但是（重点来了），为什么紧张时做出的直观设计更可能是错误的呢？因为人一紧张就容易凭感觉，而使用直觉做设计时，大都会以现实世界为原本，但是良好的面向对象设计，是绝对不能仅仅依靠现实世界的。&lt;/b&gt;其实图1 的设计从直觉上来讲是符合需求，也很符合人们对这个世界的认知的。但是它并不是一个&lt;b&gt;良好的&lt;/b&gt;面向对象设计。图2是相对良好的设计，但是图2显然又没有图1 那么直观，那么好理解，那么符合这个世界的&lt;b&gt;真实状态&lt;/b&gt;。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 图2和图1 的差别仅仅在于Adapter要依赖谁上，Adapter要依赖于ISwitchable接口这个事儿，并不是为了更真实地模拟这个世界，而纯粹地是为了解耦合而出现（或者说，为了依赖抽象）。但是在现实世界中，是不存在解不解耦合的概念的。解耦合是为了保证设计上的灵活性引入的概念。&lt;/p&gt;&lt;p&gt;&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 现实中事物间的依赖都是具体的&lt;/b&gt;，&lt;b&gt;是为了复用、灵活性等才引入的抽象，客观现实是不存在抽象的。抽象是要取决于你是如何看待客观事物的。&lt;/b&gt;举个例子，在动物学家看来，人与动物间有IS-A关系；但是如果你是要开发一款MMORPG游戏，人（NPC和Avatar）和动物（一般会是怪物）应该是不会有IS-A关系的。观察的角度不同，就会得出不同的设计；这些设计没有对错之分，只有是否满足需求之别。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 所以，&lt;a href="http://baike.baidu.com/view/1488767.htm"&gt;有些地方&lt;/a&gt;，把面向对象的设计过程解释为对现实世界的模拟是很片面的。如果仅仅以现实世界的样子对系统进行设计，得出的设计很可能是僵化的，就像图1那样。（有人可能想说我曲解了人家的意思，但是我想说，你写成那个样子明明就是故意给人误解的，至少是很容易引导人误解。容易被误解，就是有问题。没什么好狡辩的。）&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 但是，这并不意味着做设计就要全面地抽象，模拟现实世界的好处是代码容易理解，但是如果全部抽象成图2那样，所有都抽象出个接口，所有都依赖抽象，那代码的可读性显然会下降。所以，&lt;b&gt;好的面向对象设计，会是真实地模拟现实与抽象现实间的取舍的过程。&lt;/b&gt;如果你看过一些功能相似、但实现不同的开源框架，会发现有些好理解，有些不好理解，其根本原因就是其抽象的层次或者说抽象的程度不一样。抽象度过高，灵活性也许上去了，但是并不见得就是好事儿。&lt;b&gt;过度设计，就是因为对现实的抽象度太高，造成可读性差，不好维护&lt;/b&gt;，还没解决问题，就先被问题解决掉了。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 上面的例子可能依然没有什么说服力。我再举一个。上篇文章有人回复说，&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160; &lt;em&gt;“开关里面还包含一个开关接口 ，很奇怪的方式。     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 在我看来应该是灯光有开关”。&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 我想感谢一下这位朋友，因为他提出的这个思路，我一开始就潜意识地无视掉了。经他一提，我才意识这也是设计过程中一类常见的问题。这个设计是一个很真实地反应现实的设计。但是并不是一个可行的类设计。如果你按这个方案写代码，就会发现很多问题。原因我已经回复了。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 总结一下，做面向对象设计的时候，请记得自己要做的是什么？不要让现实世界的“真实”的样子混淆了视听。&lt;b&gt;面向对象设计，是以可复用地、灵活地实现需求为目标的，对现实的抽象，而不是对现实的模拟；抽象的结果很可能在现实中并不存在。&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Adapter模式的关键&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Adapter模式最关键的要求是：Adapter是对&lt;b&gt;两个功能相近的接口&lt;/b&gt;间的适配。如果被适配的对象是个具体类，那么多数情况下，Adapter非但不会带来好处，反而是仅仅增加了维护成本，就像前面说的，有一个新的具体类出现，就要同时添加一个Adapter。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; （如果你非说你见过很多 “适配”具体类的，你是对的，但是那叫Proxy，不叫Adapter，解决的也不是同一种问题，而且多数情况下，Proxy是可以自动生成的，所以不需要担心加一个类，就要自己实现一个对应的Proxy的问题。可以用下面这个图对比一下，来自《敏捷软件开发》）&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/nankezhishi/201205/201205312251351167.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px" title="clip_image006" border="0" alt="clip_image006" src="http://images.cnblogs.com/cnblogs_com/nankezhishi/201205/20120531225135610.png" width="358" height="186" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p align="center"&gt;图3 Proxy模式&lt;/p&gt;&lt;p align="center"&gt;&amp;#160;&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 这不是在死抠Adapter模式的含义。因为只有理解Adapter的目标、适用范围之后，才不会误用这个模式。见过不少人理解力很好或是英文很好，看到Adapter这个词是个模式就想当然地觉得自己“知道”了这个模式的用法（毕竟这个模式也的确不复杂），并“用”了起来。比如图1的那个例子，就是最常见的误用之一。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 这也不是在死抠名词。给模式命名的好处之一就是让两个都懂模式的人沟通起来更顺畅。&lt;b&gt;模式名所表达的，不是一个简单的类关系图，而是对要解决问题的类型的定位和解决问题的策略。&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Adapter，表示遇到的问题是接口不匹配。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Proxy，表示遇到的问题是主体逻辑与附加逻辑（持久化、网络传输等）纠缠。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 名词用错了，就可能会带来不必要的误会。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 如果你就是觉得没必要死抠概念，下面的“广义Adapter模式”可能会比较适合你。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;广义Adapter模式&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 这年头好像什么东西都非要搞出个狭义和广义之分。我个人比较反感这一点，因为狭广之分的存在，本身就是一种对概念的模糊。这导致人们在沟通时，如果遇到问题，常常要想一下对方说的是广义的还是狭义的，而不是把焦点放在问题本身。这像是给自己和对方找借口或是后路。或许是因为大家都想给自己留个后路，这东西才会这么流行。附经典对白一则：&lt;/p&gt;&lt;p&gt;&lt;i&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; “嗯？不对吧，不应该是XXXX&lt;/i&gt;&lt;i&gt;吗？”&lt;/i&gt;&lt;/p&gt;&lt;p&gt;&lt;i&gt;&amp;#160;&amp;#160;&amp;#160; “呃，我说的是一种广义上的XXXX&lt;/i&gt;&lt;i&gt;。”&lt;/i&gt;&lt;/p&gt;&lt;p&gt;&lt;i&gt;&amp;#160;&amp;#160;&amp;#160; “哦。（Shit!&lt;/i&gt;&lt;i&gt;）”&lt;/i&gt;&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 每个人们学习模式，总会有自己的理解，自己的抽象。当理解的角度不同的时候，就会把Adapter模式的内涵延展到不同的地方。这就导致了不同人对广义Adapter的定义是不同的。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 比如《敏捷软件开发》，从逻辑关系出发，把Adapter的概念延展为：使用一个特定的类，实现对方法调用的定向派发（我自己总结的，原文没这话）。从这个概念上讲，Adapter模式可以用于对具体类的适配。因为这个延展的概念实际上已经超出了原有的GoF的定义。这显然不能说是错误的，你甚至会觉得这个人水平真高，能对设计模式进行再抽象，再扩展。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 但是问题是，&lt;b&gt;不同人对同一概念的延展方向是不同的&lt;/b&gt;。你觉得Adapter和Delegate/Event有什么相似之处吗？我相信更多人会觉得Observer模式与Delegate/Event的相似之处更多些。因为无数的人和书都说过C#的Delegate/Event机制就是Observer模式的一种具体实现。如果你面试的时候说，Delegation就是一种Adapter，你的面试可能就直接Pass了。&lt;a href="http://www.cnblogs.com/subwayline13/archive/2012/03/27/2420308.html"&gt;这事儿也的确真实地发生过&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 但是如果去看《Pro Objective-C Design Pattern for iOS》第112页，对Adapter的描述真的是这样的。&lt;/p&gt;&lt;p&gt;&lt;i&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; “The Delegation pattern was once one of the inspirations for cataloging the Adapter&lt;/i&gt;&lt;/p&gt;&lt;p&gt;&lt;i&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; pattern in the “Gang of Four” book.&lt;/i&gt;&lt;i&gt;”&lt;/i&gt;&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 如果你怕我断章取义，可以自己去看。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 这个人是从类与类之间的关系出发，把具有相似结构、交互方式的类的组合都定义为Adapter。你说他的理解错了吗？我只能说：“狭义来讲，是错的，广义来讲，是对的。”但这是这个世界上最操蛋的答案之一。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 像上面链接的博客里描述的那个面试者，显然就成了广义与狭义之分的牺牲品——他说的是广义的Adapter，但是面试官想听到的是狭义的Adapter。（不过从后面的叙述来看，那个面试官也是半瓶子醋，问Delegate的时候居然会顺便问异步，让我不得不怀疑他是不是认为事件是异步触发的。）&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 对Adapter有独特的理解很好，能把Adapter, Observer, Delegation, Proxy全统一起来理解更是NB。但是，其实在多数情况下，越是独到的见解，越可能会给面对面的沟通带来障碍。这些独到的见解在个人顿悟模式的过程中很有用，写到书里也很好，毕竟读者可以细细体味，帮助读者从不同的角度思考问题；但想在面试之类的当面沟通的场合上装逼，然后自己的口才又不咋地。怕只会画虎不成反类犬。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;对Adapter模式的误用&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 学历史的时候，常常见到“左派”、“右派”这样的词，意思是他们走的路线不对。这个词用得很形象，都是走极端。 模式的误用，常见的误用之一也是走极端。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 图2 的Adapter模式，成功的把标准的开关接口适配到了我们的接口上。于是便有了一个顺理成章的思路，ISwitchable和IStandardSwitchable接口都是对开关的定义，我们通过Adapter模式，让支持IStandardSwitchable的开关能够开我们的灯。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 那么我们之前的这个设计：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/nankezhishi/201205/201205312251353642.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px" title="clip_image008" border="0" alt="clip_image008" src="http://images.cnblogs.com/cnblogs_com/nankezhishi/201205/201205312251354722.png" width="204" height="164" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p align="center"&gt;图4. 第一回中提出的开关开灯方案（Abstract Server）&lt;/p&gt;&lt;p align="center"&gt;&amp;#160;&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 是不是应该改成这样？&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/nankezhishi/201205/201205312251354165.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px" title="clip_image010" border="0" alt="clip_image010" src="http://images.cnblogs.com/cnblogs_com/nankezhishi/201205/201205312251353609.png" width="293" height="164" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p align="center"&gt;图5. 试图把Adapter模式用于实现DIP&lt;/p&gt;&lt;p align="center"&gt;&amp;#160;&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 这个设计相比原来的设计方案，抽象度更高、耦合性更低，Light甚至不需要依赖ISwitchable接口就可以工作，这样我们可以很有信心地说，我们可以让一切类都支持ISwitchable接口！&lt;/p&gt;&lt;p&gt;这个想法很丰满，但是现实很骨感。如果你认真看过了前面的内容，应该已经知道这个方案其实很烂的原因了。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 这个世界很微妙，《敏捷软件开发》（P370）的确就把图5称为Adapter模式，不过你应该懂的，他说的是广义的Adapter模式。并不是说对具体类的Adapter就一定是误用，如果没有违反OCP就不是误用，如果那个Light是个Utility类，就不算是误用。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; （如果你想喷Adapter模式本来就有两种，一种是基于类的，一种是基于对象的，你最好先去把Adapter概念回个炉，我们说的根本不是一码事儿。）&lt;/p&gt;&lt;p&gt;&lt;strong&gt;误用的原因&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 我自己总结了一下出现这种误用的原因有三（这些原因会让人出现各种形式的误用，而不针对Adapter模式）：&lt;/p&gt;&lt;ol&gt;  &lt;li&gt;    &lt;p&gt;&lt;strong&gt;想当然地类推。&lt;/strong&gt;像上面那样，从适配IStandardSwitchable可行，直接推出适配ISwitchable也可以，毕竟这是同样功能的接口啊。但是，不能这样类推。&lt;/p&gt;  &lt;/li&gt;  &lt;li&gt;    &lt;p&gt;&lt;strong&gt;妄图用同一个方式解决所有问题的想法或创造出一个work for everything的东西的想法。&lt;/strong&gt;我直觉上就想用热力学第二定律来反驳这想法（和work forever差不多意思），不过“no silver bullet”可能更合适些。但是有些人，尤其是Level越高的，就越容易陷入这个泥潭。可能他们觉得不创造些NB的东西出来，就太对不起大家了。当然，这个想法是很好的，但是也要讲求方法，拿着锤子就看什么都是钉子的做法是要不得的。 参考&lt;a href="http://coolshell.cn/articles/2424.html"&gt;十条不错的编程观点&lt;/a&gt;。&lt;b&gt;第一条就是，独立思考&lt;/b&gt;，妄图通过学习各种模式就可以应对一切设计问题的想法就是要不得的。还有一条让我印象很深的就是关于Google的使用，推荐大家也去看看。&lt;/p&gt;  &lt;/li&gt;  &lt;li&gt;    &lt;p&gt;&lt;strong&gt;对设计原则和设计模式的理解不透彻。&lt;/strong&gt;如果真正理解了Adapter模式的意图、适用范围。是不会犯这样的错误的。但是很可惜，这个世界上的诱惑太多了，哪怕Wikipedia这样看似很权威的地方都在误导着别人（&lt;b&gt;所以，自己思考，自己判断&lt;/b&gt;）。&lt;a href="http://en.wikipedia.org/wiki/Dependency_inversion_principle"&gt;Wikipedia上对DIP的解释&lt;/a&gt;是这样的：“Applying the dependency inversion principle can also be seen as applying the &lt;a href="http://en.wikipedia.org/wiki/Adapter_pattern"&gt;Adapter pattern&lt;/a&gt;, i.e.”直译过来就是“遵循依赖倒置原则可被视同于应用适配器模式”。Oops…用了适配器模式，那的确是DIP了，但是适配器并不用来达到DIP这个目标的，适配器模式虽然DIP，但是如果用来现实DIP，效果却很糟糕，带来了更多 的问题。我猜作者的本意只是想表达：适配器模式本身是符合DIP原则的。这没错。但是我相信有一票人看到这里就去研究适配器模式并计划用它来实现DIP了。（有人嫌我啰嗦，我只是想把问题说清楚，让更多的人无可误解。）&lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 这里说的缺乏经验可能并不是工作年限不足的问题，更可能的是态度的问题，要么是对Adapter模式想当然、觉得自己在字面上的理解就差不多，要么是想对Adapter模式进行所谓的“活用”，结果犯了激进冒险主义错误。&lt;/p&gt;  &lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;strong&gt;下回预告&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 我们的灯卖得好，用户就多了起来，需求也多了起来。这样一下子来了两个用户，一个要求，我要用两个开关控制同一个灯（床头一个，走廊一个，看来这用户晚上常起夜）；另一个要求，我想用一个开关控制屋子里所有的灯（看来这用户不差钱）。&lt;/p&gt;&lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 那么，我们又需要做出怎样的设计来应对这些需求呢？&lt;/p&gt;&lt;img src="http://www.cnblogs.com/nankezhishi/aggbug/2529201.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/archive/2012/05/31/2529201.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/nankezhishi/archive/2012/05/26/dip.html</id><title type="text">小例子背后的大道理——从DIP中“倒置”的含义说接口的正确使用</title><summary type="text">提纲 开灯的例子 暗流涌动 Guru眼中的依赖 DIP（依赖倒置原则） 为什么要解耦合？ 接口的坏味道 同一张类图的不同解释——真假DIP 了解DIP有什么用？DIP用在什么地方？ 下回预告 参考文献 开灯的例子 选开灯做例子，是因为这个例子既常见又简单，而且潜在的需求多样。对于最简单的灯，从功能上讲，按下灯上的开关，灯就开了。 用代码实现这样一个有开关功能的灯，也是一件很容易的事情。 public class Light{ public void TurnOn() { Console.WriteLine("Light Turn On"); } pub...</summary><published>2012-05-26T14:29:00Z</published><updated>2012-05-26T14:29:00Z</updated><author><name>南柯之石</name><uri>http://www.cnblogs.com/nankezhishi/</uri></author><link rel="alternate" href="http://www.cnblogs.com/nankezhishi/archive/2012/05/26/dip.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/nankezhishi/archive/2012/05/26/dip.html"/><content type="html">&lt;p&gt;&lt;strong&gt;提纲&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/admin/EditPosts.aspx?postid=2519621&amp;amp;update=1#sample"&gt;开灯的例子&lt;/a&gt;  &lt;/li&gt;&lt;li&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/admin/EditPosts.aspx?postid=2519621&amp;amp;update=1#issue"&gt;暗流涌动&lt;/a&gt;  &lt;/li&gt;&lt;li&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/admin/EditPosts.aspx?postid=2519621&amp;amp;update=1#guru"&gt;Guru眼中的依赖&lt;/a&gt;  &lt;/li&gt;&lt;li&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/admin/EditPosts.aspx?postid=2519621&amp;amp;update=1#dip"&gt;DIP（依赖倒置原则） &lt;/a&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/admin/EditPosts.aspx?postid=2519621&amp;amp;update=1#why"&gt;为什么要解耦合？&lt;/a&gt;  &lt;/li&gt;&lt;li&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/admin/EditPosts.aspx?postid=2519621&amp;amp;update=1#bad"&gt;接口的坏味道&lt;/a&gt; &lt;/li&gt;&lt;/ul&gt; &lt;/li&gt;&lt;li&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/admin/EditPosts.aspx?postid=2519621&amp;amp;update=1#truedip"&gt;同一张类图的不同解释&amp;#8212;&amp;#8212;真假DIP&lt;/a&gt;  &lt;/li&gt;&lt;li&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/admin/EditPosts.aspx?postid=2519621&amp;amp;update=1#know"&gt;了解DIP有什么用？DIP用在什么地方？&lt;/a&gt;  &lt;/li&gt;&lt;li&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/admin/EditPosts.aspx?postid=2519621&amp;amp;update=1#next"&gt;下回预告&lt;/a&gt;  &lt;/li&gt;&lt;li&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/admin/EditPosts.aspx?postid=2519621&amp;amp;update=1#reference"&gt;参考文献 &lt;/a&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;&lt;strong&gt;&lt;a id="sample"&gt;开灯的例子&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 选开灯做例子，是因为这个例子既常见又简单，而且潜在的需求多样。对于最简单的灯，从功能上讲，按下灯上的开关，灯就开了。&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 用代码实现这样一个有开关功能的灯，也是一件很容易的事情。&lt;/p&gt; &lt;div class="csharpcode"&gt; &lt;div class="csharpcode"&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; Light{    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; TurnOn() { Console.WriteLine(&lt;span class="str"&gt;"Light Turn On"&lt;/span&gt;); }    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; TurnOff() { Console.WriteLine(&lt;span class="str"&gt;"Light Turn Off"&lt;/span&gt;); }}&lt;/div&gt;&lt;style type="text/css"&gt;.csharpcode, .csharpcode pre{font-size: small;color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff;/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt {background-color: #f4f4f4;width: 100%;margin: 0em;}.csharpcode .lnum { color: #606060; }&lt;/style&gt;&lt;/div&gt;&lt;style type="text/css"&gt;.csharpcode, .csharpcode pre{font-size: small;color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff;/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt{background-color: #f4f4f4;width: 100%;margin: 0em;}.csharpcode .lnum { color: #606060; }&lt;/style&gt;&lt;p align="center"&gt;代码1&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 一个具有开关功能的灯就完成了。这个灯，功能完备、也满足当下的需求。一切美好。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 直到有一天，有个客户说，灯上的开关坏了，能不能换一个？我才意识到这个灯的设计有问题&amp;#8212;&amp;#8212;它的开关是换不了的。一面给用户解释，一面考虑着把灯和开关分开。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 咱也是学过设计模式的人，知道要面向接口编程，绝不应该简单地把Light类拆解成Light和Switcher两个类。因为Switcher不应该依赖于具体实现，于是写出了下面的代码。&lt;/p&gt;&lt;div class="csharpcode"&gt;&amp;nbsp;&lt;div class="csharpcode"&gt;&lt;span class="kwrd"&gt;namespace&lt;/span&gt; Me.Lighting{    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;interface&lt;/span&gt; ILightable    {        &lt;span class="kwrd"&gt;void&lt;/span&gt; ShowLight();        &lt;span class="kwrd"&gt;void&lt;/span&gt; HideLight();    }    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; Light : ILightable    {        &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; ShowLight() { Console.WriteLine(&lt;span class="str"&gt;"Light Turn On"&lt;/span&gt;); }        &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; HideLight() { Console.WriteLine(&lt;span class="str"&gt;"Light Turn Off"&lt;/span&gt;); }    }}&lt;span class="kwrd"&gt;namespace&lt;/span&gt; Me.Switch{    &lt;span class="kwrd"&gt;using&lt;/span&gt; Me.Lighting;&amp;nbsp;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; Switcher    {        &lt;span class="kwrd"&gt;public&lt;/span&gt; ILightable Light { get; set; }        &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; TurnOn() { Light.ShowLight(); }        &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; TurnOff() { Light.HideLight(); }    }}&lt;/div&gt;&lt;style type="text/css"&gt;.csharpcode, .csharpcode pre{font-size: small;color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff;/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt {background-color: #f4f4f4;width: 100%;margin: 0em;}.csharpcode .lnum { color: #606060; }&lt;/style&gt;&lt;/div&gt;&lt;style type="text/css"&gt;.csharpcode, .csharpcode pre{font-size: small;color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff;/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt{background-color: #f4f4f4;width: 100%;margin: 0em;}.csharpcode .lnum { color: #606060; }&lt;/style&gt;&lt;p align="center"&gt;代码 2&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这个设计，不仅分离了灯和开关，甚至可以让这个开关灵活地控制要开关哪个灯。只要在开关前设置一下就可以，多方便。我自信满满地迁入了代码。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 事实也证明这样的设计是成功的，产品的灵活设计得到了用户的认可，销量直线上升。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 亲，请看下代码，在不使用什么别的设计模式的前提下，您觉得代码2有什么问题？无论是什么角度的都可以（当然，可能您的角度不是本文讨论的重点），最好先回复下留个底，别事后诸葛。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果您一眼看到了问题，请直接阅读DIP那一节。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a id="issue"&gt;暗流涌动&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 公司壮大之后 ，开始考虑向收音机行业进军。而且公司希望，这种灵活的设计可以沿用下去，收音机和灯的开关应该可以通用，对用户而言，都是拨那么一下。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 我听到这个信息也是相当兴奋，但是当我开始着手写代码时，发现一些坏味道，开关依赖于ILightable 接口，那么我的收音机不得不写成这个样子才能与现有的开关兼容。&lt;/p&gt;&lt;div class="csharpcode"&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; Radio : ILightable{     &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; ShowLight() { Console.WriteLine(&lt;span class="str"&gt;"Play radio"&lt;/span&gt;); }    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; HideLight() { Console.WriteLine(&lt;span class="str"&gt;"Stop radio"&lt;/span&gt;); } }&lt;/div&gt;&lt;style type="text/css"&gt;.csharpcode, .csharpcode pre{font-size: small;color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff;/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt {background-color: #f4f4f4;width: 100%;margin: 0em;}.csharpcode .lnum { color: #606060; }&lt;/style&gt;&lt;style type="text/css"&gt;.csharpcode, .csharpcode pre{font-size: small;color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff;/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt{background-color: #f4f4f4;width: 100%;margin: 0em;}.csharpcode .lnum { color: #606060; }&lt;/style&gt;&lt;p align="center"&gt;代码3&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 虽然可以工作，但是这是严重的坏味道。因为如果有一天，灯的接口变化，我却要连收音机的代码一起改。这种情况绝不应该出现。且不用把LSP（Liskov替换原则）搬出来说教，很显然Radio其实并没有完成ILightable所定义的功能&amp;#8212;&amp;#8212;发光。无论从哪个角度讲都是错的。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 一个可行的设计是，让开关支持收音机的开启和停止。像下面这样。&lt;/p&gt;&lt;div class="csharpcode"&gt;&lt;div class="csharpcode"&gt;&lt;span class="kwrd"&gt;namespace&lt;/span&gt; Me.Radio{    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;interface&lt;/span&gt; IRadio    {        &lt;span class="kwrd"&gt;void&lt;/span&gt; Play();        &lt;span class="kwrd"&gt;void&lt;/span&gt; Stop();    }    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; Radio : IRadio    {        &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Play() { Console.WriteLine(&lt;span class="str"&gt;"Play radio"&lt;/span&gt;); }        &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Stop() { Console.WriteLine(&lt;span class="str"&gt;"Stop radio"&lt;/span&gt;); }    }}&lt;span class="kwrd"&gt;namespace&lt;/span&gt; Me.Switch{    &lt;span class="kwrd"&gt;using&lt;/span&gt; Me.Lighting;    &lt;span class="kwrd"&gt;using&lt;/span&gt; Me.Radio;&amp;nbsp;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; Switcher    {        &lt;span class="kwrd"&gt;public&lt;/span&gt; ILightable Light { get; set; }        &lt;span class="kwrd"&gt;public&lt;/span&gt; IRadio Radio { get; set; }        &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; TurnOn()        {            &lt;span class="kwrd"&gt;if&lt;/span&gt; (Light != &lt;span class="kwrd"&gt;null&lt;/span&gt;) Light.ShowLight();            &lt;span class="kwrd"&gt;else&lt;/span&gt; &lt;span class="kwrd"&gt;if&lt;/span&gt; (Radio != &lt;span class="kwrd"&gt;null&lt;/span&gt;) Radio.Play();        }        &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; TurnOff() { Light.HideLight(); }    }}&lt;/div&gt;&lt;style type="text/css"&gt;.csharpcode, .csharpcode pre{font-size: small;color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff;/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt {background-color: #f4f4f4;width: 100%;margin: 0em;}.csharpcode .lnum { color: #606060; }&lt;/style&gt;&lt;/div&gt;&lt;style type="text/css"&gt;.csharpcode, .csharpcode pre{font-size: small;color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff;/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt{background-color: #f4f4f4;width: 100%;margin: 0em;}.csharpcode .lnum { color: #606060; }&lt;/style&gt;&lt;p align="center"&gt;代码4&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 我看来看去都觉得这个代码太恶心了，因为Switcher的实现方式违反了OCP（开放&amp;#8212;封闭原则），如果这样发展下去，公司的产品越丰富，这坨代码就越难以维护。我的末日也就越近。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 于是我的考虑Switcher的设计是不是有问题，我已经用上面向接口编程了，为什么还是有问题呢？&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a id="guru"&gt;Guru眼中的依赖&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 我把代码发给了我的导师，一个设计Guru，他看完之后哭笑着说，你的基本功很扎实，理论知识也很全面，可惜却缺乏一定的经验。面向接口编程没有错，但是更重要的是模型的建立。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 简单而言，你的开关的依赖关系错了。问你一个问题你就明白了，开关为什么要依赖ILightable呢？但是好在你有一定的设计基础，知道要提取出一个接口，所以要改成正确的设计也非常容易。你只需要把ILightable这个接口的名字改成ISwitchable，再把接口方法名字改下，并把它与Switcher放一起就行了。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 听罢，我恍然大悟。原来接口的&lt;strong&gt;名字和位置&lt;/strong&gt;，也会给使用者带来如此大的困扰。在先进的开发工具的帮助下，瞬间就完成了这个简单的重命名和移动操作。现在的代码像这个样子了。&lt;/p&gt;&lt;div class="csharpcode"&gt;&lt;div class="csharpcode"&gt;&lt;span class="kwrd"&gt;namespace&lt;/span&gt; Me.Lighting{    &lt;span class="kwrd"&gt;using&lt;/span&gt; Me.Switch;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; Light : ISwitchable    {        &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; TurnOn() { Console.WriteLine(&lt;span class="str"&gt;"Light Turn On"&lt;/span&gt;); }        &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; TurnOff() { Console.WriteLine(&lt;span class="str"&gt;"Light Turn Off"&lt;/span&gt;); }    }}&lt;span class="kwrd"&gt;namespace&lt;/span&gt; Me.Radio{    &lt;span class="kwrd"&gt;using&lt;/span&gt; Me.Switch;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; Radio : ISwitchable    {        &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; TurnOn() { Console.WriteLine(&lt;span class="str"&gt;"Play radio"&lt;/span&gt;); }        &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; TurnOff() { Console.WriteLine(&lt;span class="str"&gt;"Stop radio"&lt;/span&gt;); }    }}&lt;span class="kwrd"&gt;namespace&lt;/span&gt; Me.Switch{    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;interface&lt;/span&gt; ISwitchable    {         &lt;span class="kwrd"&gt;void&lt;/span&gt; TurnOn();        &lt;span class="kwrd"&gt;void&lt;/span&gt; TurnOff();    }    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; Switcher    {        &lt;span class="kwrd"&gt;public&lt;/span&gt; ISwitchable Switchee { get; set; }         &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; TurnOn() { Switchee.TurnOn(); }         &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; TurnOff() { Switchee.TurnOff(); }    }}&lt;/div&gt;&lt;style type="text/css"&gt;.csharpcode, .csharpcode pre{font-size: small;color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff;/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt {background-color: #f4f4f4;width: 100%;margin: 0em;}.csharpcode .lnum { color: #606060; }&lt;/style&gt;&lt;/div&gt;&lt;style type="text/css"&gt;.csharpcode, .csharpcode pre{font-size: small;color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff;/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt{background-color: #f4f4f4;width: 100%;margin: 0em;}.csharpcode .lnum { color: #606060; }&lt;/style&gt;&lt;p align="center"&gt;代码5&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 注意：&lt;strong&gt;这个代码与之前有问题的代码&lt;/strong&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;strong&gt;，只是各种名称上的变化。结构上一点儿没变。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 以后有新的产品，也只需要实现ISwitchable接口，就可以支持这个开关了。之前的失败设计，看似与这个设计相差无几，但是其中蕴含的设计思想天差地远，也正是在这种地方，才更能体现出设计师间的差距。这一种设计所体现的，即是DIP（依赖倒置原则），的表现之一，&lt;strong&gt;接口应当被其使用者所拥有，而非其实现者。&lt;/strong&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a id="dip"&gt;DIP（依赖倒置原则）&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 具体问题解决了，还需要把整个问题抽象一下，从本质上了解一下DIP的含义。（我会尽量清楚，可能会有些啰嗦，但这比在回复里争论要舒坦得多。）&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 假设有如下所示的类图。假设我们要把这种关系解耦合。&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/nankezhishi/201205/20120526222846889.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px" title="clip_image002[7]" alt="clip_image002[7]" src="http://images.cnblogs.com/cnblogs_com/nankezhishi/201205/201205262228462284.png" border="0" height="130" width="339" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p align="center"&gt;图1&lt;/p&gt;&lt;p&gt;注：图1中的User表示使用者（调用者），而不是用户的意思。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a id="why"&gt;为什么要解耦合？&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 我说&amp;#8220;假设要解耦合&amp;#8221;，是因为在尝试解耦这种依赖关系之前，应该&lt;strong&gt;先确定有没有解耦的必要&lt;/strong&gt;。这种关系在代码中比比皆是，如果把所有的依赖都解耦，不仅工作量大、带不来任何好处，而且引入了不必要的复杂度，最终演变成了过度设计，增加了编码成本和维护成本。（我已经被人骂怕了，怕不说清楚这一点，总要有人跳出来说我滥用模式，说这种关系要不要解耦要看情况，云云。都是好意，我也心领了，谢谢。但被人假设狗屁不通，总不太舒服。）&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 明确某个依赖关系是否需要被分解，是一件很复杂的事情，个人觉得并没有什么准则能让你轻松地做出这个判断。因为几乎所有的依赖，在一句经典的&amp;#8220;&lt;strong&gt;我以后可能会换一种方式实现它&lt;/strong&gt;&amp;#8221;面前，都变得&lt;strong&gt;似乎需要&lt;/strong&gt;被解耦。这种理由，听上去合理，其实是狗屁。换一种方式实现它，并不意味着要用一个接口来抽象它，&lt;strong&gt;接口是用来抽象并解耦依赖关系的，应该被用在：同时存在多个实现、实现未知或需要模块化的情况下（还有一种情况，是方便多人开发时工作内容的解耦，但我还没有想明白，引入接口来达到这个目的是否合适：因管理需要导致的复杂度上升。所以先不讨论这种情况）&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 具体解释一下，&amp;#8220;同时存在多个实现&amp;#8221;的意思。以IComparable接口为例，很多数据类（比如DTO）大都实现了这个接口，因为上层的功能（比如排序）依赖类的对象有相互比较的能力，同时每个类的实现方式又都不一样，即所谓的同时存在多个实现。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 所以，对于需要&amp;#8220;换一种方式实现它&amp;#8221;的情况，大可以把原来的代码删除然后重新写一个。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 有句话叫&amp;#8220;拿着锤子，看什么都像钉子&amp;#8221;。了解一项技术，不仅仅要了解他能做什么，更要了解这个技术适用在什么地方。所以千万别今天听了解耦的概念觉得很前卫，第二天就去把所有的类都提取出个接口。多数情况当然不会这么夸张，但滥用其实就在一念之间。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a id="bad"&gt;接口的坏味道&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 我承认，上面解释&lt;strong&gt;也许&lt;/strong&gt;正确，但没什么用。懂的人懂，不懂的还是不懂；所以我还是举些接口有问题的坏味道吧。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 最常见的接口坏味儿包括：（注意，总可以找到反例，所以一开始就说了，没有准则，总要具体问题具体分析，但是如果使用接口的原因是如下几种之下，我觉得应该再仔细考虑一下）&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;为了提取出某一个类所提供的Public方法。接口应该用来抽象依赖，而不是抽象实现。后面再解释。你想知道或控制一个类有哪些Method的方法有很多，但是引入一个接口，不仅达不到你的目的，还引入了复杂度&amp;#8212;&amp;#8212;每当你要加一个方法，都要修改两个地方，一个是接口，一个是实现。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;接口抽象出来了，但是和实现放了一起，或者根本没用到这个接口。比如，如果你写出了： &lt;/p&gt;&lt;p&gt;Interface f = new Implementation(); &lt;/p&gt;&lt;p&gt;这样的代码，而且这个接口只被这样用过，那或许需要考虑一下使用这个接口的用法了。我并不是指你需要一个依赖注入的框架。但是这至少看上去不太对劲，像是为了使用接口而提取出了这个接口。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;接口中包含了互不相关的方法。如果某个方法出现在这个接口里会让人觉得惊讶，那这个接口就是有问题的。&lt;strong&gt;不能因为有两个以上的类都有这个方法，所以就提取出来了。要看这两个方法有没有关系，还要看上层是不是一定会同时依赖这两个方法。&lt;/strong&gt;使用者使用接口中的方法时，应该全部都用得到。如果没全用到，可能需要考虑一下这个接口划分的是否合理？的粒度是不是太粗了？还是把接口当成了Common Service Host来用了？&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;strong&gt;&lt;a id="truedip"&gt;同一张类图的不同解释&amp;#8212;&amp;#8212;真假DIP&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 扯得有点儿远了。回来继续正题，考虑如何把User和Implementation解耦合。所有人都知道，解耦的方法是：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;定义接口I &lt;/li&gt;&lt;li&gt;Implementation实现接口I &lt;/li&gt;&lt;li&gt;User使用接口I，则不是Implementation。 &lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这个描述已经很细了，而且画出来的类图也是唯一的。但是很可惜，这个描述是不明确的，有歧义的。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 代码2和代码5都符合这个描述，但是其实是不同的设计。用图来描述会更清楚一些。&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/nankezhishi/201205/201205262228467268.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px" title="clip_image004[4]" alt="clip_image004[4]" src="http://images.cnblogs.com/cnblogs_com/nankezhishi/201205/201205262228468663.png" border="0" height="250" width="328" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p align="center"&gt;图2&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/nankezhishi/201205/201205262228461695.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px" title="clip_image006[4]" alt="clip_image006[4]" src="http://images.cnblogs.com/cnblogs_com/nankezhishi/201205/201205262228471138.png" border="0" height="267" width="303" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p align="center"&gt;图3&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 或许有人一看到学术派的设计图就兴奋起来，一眼就看出有一个设计是有问题的。但是当你看到代码2时，你有一眼看出问题吗？到你自己的项目代码中，你能一眼看出问题吗？问题总是出现在&amp;#8220;混乱&amp;#8221;中，简化成图2、图3这样，只要知道DIP的人，恐怕都能看出问题。但到项目中，那就是另一回事儿了。就像多数人都很鄙视国家组织的&amp;#8220;软考&amp;#8221;，考得再好，也不表示有相当的设计水平。这种简化了的问题和考题一样，也许能明白，但是能在该用的时候记得用，并不是个容易的事儿。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 我来解释一下，其中&lt;strong&gt;根本的区别在于谁依赖谁。至于谁持有接口，只是表象&lt;/strong&gt;。从逻辑上，调用方很明显地依赖着实现方，因为实现方才是功能的实现者，没有实现方，调用方就工作不了。但是在图3的设计中，其设计意图是，&lt;strong&gt;实现方要实现的功能，由调用方来决定&lt;/strong&gt;，而不是实现方实现了什么，调用方就用什么。也就是说，要让实现方依赖调用方。这，就是DIP（依赖倒置原则）的含义。其具体表现就是，调用方定义并持有接口。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 从概念上来讲，DIP的定义如下&lt;sup&gt;2&lt;/sup&gt;：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;高层次的模块不应该依赖于低层次的模块，他们都应该依赖于抽象。 &lt;/li&gt;&lt;li&gt;抽象(Abstractions)不应该依赖于实现(details)，实现应该依赖于抽象。 &lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 目前在网上找到的对DIP的解释，多数都停留在第一项，即模块依赖抽象上，都没有解释清楚&amp;#8220;倒置&amp;#8221;这个词的含义。希望本文中的图2和图3解释清楚了&amp;#8220;倒置&amp;#8221;的含义。从概念上来讲，&amp;#8220;抽象不应该依赖于实现&amp;#8221;，就是要求&amp;#8220;倒置&amp;#8221;。因为如果像图2那种思路，从实现中抽象出接口，那么这个接口就是依赖于实现的。重复一下之前说过的：&lt;strong&gt;接口，应该是对依赖的抽象，而不是对实现（底层功能）的抽象&lt;/strong&gt;，这就是所谓的倒置。（这里的依赖的含义是，调用者所需要的功能，而不是实现者实现了的功能。）&lt;/p&gt;&lt;p&gt;另外，还是这个类图，还有一种常见的组织形式。像下面这样。&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/nankezhishi/201205/201205262228474170.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px" title="clip_image008[4]" alt="clip_image008[4]" src="http://images.cnblogs.com/cnblogs_com/nankezhishi/201205/201205262228479154.png" border="0" height="254" width="286" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p align="center"&gt;图4&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 从箭头的方向上来看，这个更倒置。但是模块的细分，箭头方向的颠倒，并不意味着这个设计真的是倒置的。这要取决于抽象层中的接口，是与图2中的接口定位一致呢？还是与图3中的接口定位一致？单纯地把接口放在抽象层里，就和单纯地定义一个接口，却没有地方用到它一样没有意义。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 所以说，清楚地表达一个设计，并能让人确切地明白你的设计。其实是一件非常不容易的事情。可能把UML的所有功能都用上，才能做到这一点。仅仅画个框框、线线、写俩字儿，是很容易让人误会的。开会的时候有人解释着还好，如果写出的文档如果是这样，对新手而言还不如没有，因为基本上一定会被误解。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a id="know"&gt;了解DIP有什么用？DIP能用在什么地方？&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 我猜不少人看到这里会很想问，知道&amp;#8220;倒置&amp;#8221;到底是什么意思有个鸟用？有好的创意去开发项目才是正经事儿，把项目按时保质地做出来才是正经事儿，老子按时下班才是正经事儿。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 首先，我非常同意！然后，回答这个问题，这个每个人的个性使然。就像天天研究吃什么健康有个鸟用？中国的食品安全都保证不了，还健康？！但是就是有人就好这口，不是么？而且，我在这里只是解释DIP，也并没有说做的项目里，都要符合DIP啊。项目管理和架构是很灵活的，不是几个P就可以规范的起来的。有时候，直接找个开源的产品一搭，多快好省，一个P也用不着。如果非要给出个理由，我想恬不知耻地说句，追求卓越。（好吧，根本原因是，我喜欢得瑟，但是又不喜欢被明白人骂成猪头，所以我选择先搞明白了再去得瑟。）&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 但是我还是要说说了解这个原则的好处，不然写这文章不是打自己脸么？&lt;strong&gt;了解依赖倒置的意义，并不限于设计，还在于思想上的转变。&lt;/strong&gt;理解这个原则之后，你会发现自己明明已经把这个原则用上了，比如做需求分析的时候，肯定是问用户想要什么，而不是我们能做到什么。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这个原则在协作上也有用处。请回想一下，&lt;strong&gt;在工作中，是否遇到过上层开发人员等下层开发接口的情况呢&lt;/strong&gt;？如果遇到过，当时有没有想过，这个依赖关系是不是反了呢？其实，应该是下层模块的开发者依赖上层开发者呀。上层开发者定义好他依赖的接口，下层开发者来实现，同时，因为接口已经定义好了，上层也不用等下层开发者，完全可以用些Mock框架进行测试嘛。但是，如果让下层开发者定义接口，显然上层开发者就必须等，Mock类也写不了。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 关于这个原则，我还见到过&lt;strong&gt;更广义，更天下大同的解释&lt;/strong&gt;。在客户关系上，我们常见的依赖是开发者依赖客户，客户说什么我们就得做什么，一点主动权都没有。于是有人就把依赖倒置的原则拿来，说，应该让客户依赖开发者！大有，&amp;#8220;我们说什么，客户就听什么！&amp;#8221;的派头。到底哪个依赖是倒置的我就不在这儿争了，因为我觉得这完全不是依赖的方向性问题。而是店大欺客还是客大欺店的问题。如果你在IBM、在SAP、在四大，你可以让客户听你的。如果你在一个小屁公司，或者客户是政府部门，你倒置个试试？&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a id="next"&gt;下回预告&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 自此之后，一切安好。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 直到有一天，又有一个用户，他的灯上的开关也坏了，然后他试着把另外一家厂商的开关装了上去，却发现打不开灯。用户抱怨道，他的这个开关可是按国际标准实现的，我们的灯具应该支持这种标准开关。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果有可能，我们一定会让这个灯支持这个国际标准。可是灯已经卖出去了，出厂的千千万万个灯都召回的代价也很大。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这个灯的设计，又要做出怎样的变化呢？&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a id="reference"&gt;参考文献：&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;1. 《敏捷软件开发 原则、模式与实践（C#版）》 第117页11.1.1节&lt;/p&gt;&lt;p&gt;2. 《敏捷软件开发 原则、模式与实践（C#版）》 第115页&lt;/p&gt;&lt;img src="http://www.cnblogs.com/nankezhishi/aggbug/2519621.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/archive/2012/05/26/dip.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/nankezhishi/archive/2012/05/12/serializationcompare.html</id><title type="text">XmlSerializer, DataContractSerializer 和 BinaryFormatter区别与用法分析</title><summary type="text">.NET提供了很多序列化对象的方法，了解他们之间的区别才能更好地确定使用哪一种序列化方式并正确地使用。本文从下面几个方面对标题中的三种序列化方法进行了分析。 范围：Property Or Field Or Both 可见性：Public or Private Or All 可访问性：Readonly Property 回调：指OnSerializing, OnSerialized, OnDeserializing, OnDeserialized这些回调。 包含循环引用的对象 包含Dictionary的对象 乱序读（XML） 自定义序列化结果。...</summary><published>2012-05-12T14:06:00Z</published><updated>2012-05-12T14:06:00Z</updated><author><name>南柯之石</name><uri>http://www.cnblogs.com/nankezhishi/</uri></author><link rel="alternate" href="http://www.cnblogs.com/nankezhishi/archive/2012/05/12/serializationcompare.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/nankezhishi/archive/2012/05/12/serializationcompare.html"/><content type="html">&lt;p&gt;.NET提供了很多序列化对象的方法，了解他们之间的区别才能更好地确定使用哪一种序列化方式并正确地使用。本文从下面几个方面对标题中的三种序列化方法进行了分析。&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;strong&gt;范围：&lt;/strong&gt;Property Or Field Or Both&lt;/li&gt;    &lt;li&gt;&lt;strong&gt;可见性：&lt;/strong&gt;Public or Private Or All &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;可访问性：&lt;/strong&gt;Readonly Property&lt;/li&gt;    &lt;li&gt;&lt;strong&gt;回调：&lt;/strong&gt;指OnSerializing, OnSerialized, OnDeserializing, OnDeserialized这些回调。&lt;/li&gt;    &lt;li&gt;包含循环引用的对象&lt;/li&gt;    &lt;li&gt;包含Dictionary的对象&lt;/li&gt;    &lt;li&gt;乱序读（XML）&lt;/li&gt;    &lt;li&gt;自定义序列化结果。&lt;/li&gt;    &lt;li&gt;对被序列化的类有没有什么Attribute要求。有时被序列化的对象位于第三方库中，无法修改源代码，如果要序列化这样的对象，就不能使用一定要有Attribute的Serializer。 &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;先把结果整理出来再分析：代码可以&lt;a href="http://files.cnblogs.com/nankezhishi/SerializationTest.zip"&gt;从这里下载&lt;/a&gt;，所写测试可能并不全面，所以如果有什么错误，欢迎大家指出，不然要误人子弟了。不过有&lt;a href="http://www.google.com/url?sa=t&amp;amp;rct=j&amp;amp;q=&amp;amp;esrc=s&amp;amp;source=web&amp;amp;cd=1&amp;amp;ved=0CGgQFjAA&amp;amp;url=http%3A%2F%2Fwww.danrigsby.com%2Fblog%2Findex.php%2F2008%2F03%2F07%2Fxmlserializer-vs-datacontractserializer-serialization-in-wcfhttp://www.danrigsby.com/blog/index.php/2008/03/07/xmlserializer-vs-datacontractserializer-serialization-in-wcf/"&gt;这篇更误人子弟的文章殿底&lt;/a&gt;（Google top 1），我相信这个表不会更糟。&lt;/p&gt;  &lt;table width="533" border="0" cellpadding="2" cellspacing="0"&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;td valign="top" width="100"&gt;&amp;nbsp;&lt;/td&gt;        &lt;td valign="top" width="135"&gt;XmlSerializer&lt;/td&gt;        &lt;td valign="top" width="167"&gt;DataContractSerializer&lt;/td&gt;        &lt;td valign="top" width="129"&gt;BinaryFormatter&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="100"&gt;范围&lt;/td&gt;        &lt;td valign="top" width="135"&gt;Both&lt;/td&gt;        &lt;td valign="top" width="167"&gt;Both&lt;/td&gt;        &lt;td valign="top" width="129"&gt;Field Only&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="100"&gt;可见性&lt;/td&gt;        &lt;td valign="top" width="135"&gt;Public Only&lt;/td&gt;        &lt;td valign="top" width="167"&gt;All&lt;/td&gt;        &lt;td valign="top" width="129"&gt;All&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="100"&gt;只读属性&lt;/td&gt;        &lt;td valign="top" width="135"&gt;不支持&lt;/td&gt;        &lt;td valign="top" width="167"&gt;支持，但要加Attribute&lt;/td&gt;        &lt;td valign="top" width="129"&gt;N/A&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="100"&gt;回调支持&lt;/td&gt;        &lt;td valign="top" width="135"&gt;不支持&lt;/td&gt;        &lt;td valign="top" width="167"&gt;支持&lt;/td&gt;        &lt;td valign="top" width="129"&gt;支持&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="100"&gt;循环引用&lt;/td&gt;        &lt;td valign="top" width="135"&gt;不支持&lt;/td&gt;        &lt;td valign="top" width="167"&gt;支持，但要加Attribute&lt;/td&gt;        &lt;td valign="top" width="129"&gt;支持&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="100"&gt;Dictionary&lt;/td&gt;        &lt;td valign="top" width="135"&gt;不支持&lt;/td&gt;        &lt;td valign="top" width="167"&gt;支持，专有格式&lt;/td&gt;        &lt;td valign="top" width="129"&gt;支持&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="100"&gt;乱序读&lt;/td&gt;        &lt;td valign="top" width="135"&gt;支持&lt;/td&gt;        &lt;td valign="top" width="167"&gt;不支持&lt;/td&gt;        &lt;td valign="top" width="129"&gt;不支持&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="100"&gt;自定义格式&lt;/td&gt;        &lt;td valign="top" width="135"&gt;支持，要加Attribute&lt;/td&gt;        &lt;td valign="top" width="167"&gt;不支持Xml Attribute&lt;/td&gt;        &lt;td valign="top" width="129"&gt;通过接口&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="100"&gt;Attribute要求&lt;/td&gt;        &lt;td valign="top" width="135"&gt;不必要&lt;/td&gt;        &lt;td valign="top" width="167"&gt;不必要&lt;/td&gt;        &lt;td valign="top" width="129"&gt;必须有Serializable&lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;有几点需要解释一下。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;性能？大小？&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;在我写的几个测试用例中，无论对于简单对象，还是嵌套对象，还是数组重复引用对象，这几个Serializer的性能和大小基本在一个数量级上。他们之间功能上的差异远大于他们性能上的差异。这个时候，使用哪个方法的决策应该取决于功能及其约束，而不是性能。如果你在意性能，应该用第三方库，如Google的&lt;a href="http://code.google.com/p/protobuf-net/"&gt;protobuf-net&lt;/a&gt;。无论从性能、序列化结果的大小还是兼容性上，protobuf-net比这三者都要优秀，当然，not human readable。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Attribute要求&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;不少&lt;a href="http://www.danrigsby.com/blog/index.php/2008/03/07/xmlserializer-vs-datacontractserializer-serialization-in-wcf/"&gt;同类文章&lt;/a&gt;都声称XmlSerializer和DataContractSerializer都要求在类上加相应的Attribute。但事实上并不是这样。只有当你想要改变默认的序列化结果时，Attribute才是必要的。&lt;/p&gt;  &lt;p&gt;最常见的一个错误就是，给XmlSerializer要用到的类加Serializable Attribute。这个Attribute其实是给BinaryFormatter和SoapFormatter用的，一般用在Remoting和Messaging上。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;乱序读&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;乱序读的意思是，Deserialize的过程中，不对Xml节点的顺序做要求。Xml本身就是一种标记性的，带有声明性的语言，多数基于Xml的网络协议都不会对Xml节点的顺序做要求。比如常见的RSS，ATOM，OPDS等。&lt;/p&gt;  &lt;p&gt;WCF使用的DataContractSerializer要求Xml节点顺序要么严格指定，要么按字母顺序排列，或许有性能上考虑，但是WCF的定位才是允许DataContractSerializer严格要求Xml节点顺序的根本原因。WCF本身是用于通信的，其对序列化的定位，仅仅只是消息传递的手段，消息格式的严格定义并没有什么不好，而且调用方的代码都可以通过WSDL来生成，自然也就更不会出现解析时的问题。所以不支持乱序读完全是由其功能设定决定的。&lt;/p&gt;  &lt;p&gt;XmlSerializer，在我看来可以做为Object Xml Mapping的工具，当然要尽可能支持所有Xml的标准。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;自定义的支持&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;XmlSerializer和DataContractSerializer都支持IXmlSerializer接口。通过这个接口DataContractSerializer甚至可以支持Attribute，但是，这其实已经不是在使用DataContractSerializer的序列化机制了。而且在实际项目中也不具备可操作性。所以在上表中，还是把DataContractSerializer标记为不支持Xml Attribute。&lt;/p&gt;  &lt;p&gt;还有一些常见的接口，如ISerializable和IDeserializationCallback是给BinaryFormatter用的，以支持自定义序列化的过程。这里我很奇怪为了BinaryFormatter已经支持了OnDeserialized Attribute，为什么还有这么个接口来提供相似的功能。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;一些小的细节&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;两个Xml序列化方法还有一些小的挺有意思的差别，我试图猜测产生这种差异的原因，却没想出来，或许是一些微软员工不为人知的设计理念，也许只是写代码时的顺心而为罢了。&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;XmlSerializer不序列化Null值，DataContractSerializer默认会序列化Null值。&lt;/li&gt;    &lt;li&gt;DataContractSerializer与Attribute：对没有任何Attribute的类，序列化所有Public的可读可写Property和Field；对仅仅加了Serializable Attribute的类，序列化所有可见性的Field（为了和BinaryFormatter行为一致吗）；如果仅仅加了DataContract Attribute，则什么都不会序列化出来，一定要加DataMember。&lt;/li&gt;    &lt;li&gt;DataContract支持序列只读属性，但是属性上要加DataMember.&lt;/li&gt;    &lt;li&gt;DataContractSerializer，仅仅加DataMember而不加DataContract会出异常。&lt;/li&gt;    &lt;li&gt;DataContractSerializer与ISerializable接口不兼容，直接抛异常。&lt;/li&gt;    &lt;li&gt;DataContractSerializer支持IXmlSerializer接口，但是实现了这个接口，就不能加DataContract了。否则抛异常。这是什么道理？&lt;/li&gt;    &lt;li&gt;XmlSerializer支持乱序读的代价是，你不能控制你自己的生成的Xml的节点的顺序。也就是说ElementName Attribute中不能指定Order。&lt;/li&gt;    &lt;li&gt;BinaryFormatter要求被序列化的类必须&amp;#8220;满城尽带Serializable&amp;#8221;。所以如果你改不了源代码，序列化不了就是序列化不了。而XmlSerializable，你尽可以通过继承的方式，把Internal和Protected的Property序列化出来。 &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;&lt;strong&gt;小结&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;基于上面的客观结果，相信你已经有了自己的判断。我个人意见，DataContractSerializable就用在它应该用的地方吧，如果不是用WCF，还是不要用它了，它的序列化结果有一些微软专属的东西。对于来自网络的松散Xml接口数据，XmlSerializer是不二之选。如果想把对象完整地保存下来（数据与状态），同时又不需要被人看。那就用BinaryFormatter吧。如果对性能或是数据大小要求比较高，那这三个就都能用，用protobuff吧。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;补充&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;这些信息算是一种经验，但是绝对不适合作面试或是笔试题用。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;2012年6月2日：&lt;/p&gt;&lt;p&gt;发现一个不错的总结：&lt;/p&gt;&lt;p&gt;&lt;div&gt;http://www.codeproject.com/Articles/255684/Create-and-Consume-RESTFul-Service-in-NET-Framewor&lt;/div&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/nankezhishi/aggbug/2497586.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/archive/2012/05/12/serializationcompare.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/nankezhishi/archive/2012/03/18/clearmine.html</id><title type="text">使用WPF开发的扫雷游戏，双系统主题复刻版</title><summary type="text">平时有人问我业余时间会做点儿什么？ 我说：用WPF写个游戏玩玩。 ：写的什么？ 我：扫雷。 ：…… 我：想想都写了快两年了。 ：orz 单纯地讲扫雷游戏都会觉得很简单，都不好意思当个事儿，我一开始也是这样觉得。写着写着才发现，事情其实没有想象中的那么简单。先给大家上个图看看效果吧。 写这个扫雷程序的主要目的，并不是练习WPF，也不是用WPF复刻Windows自带的这个游戏。这两件事儿其实都不至于做两年。（当然不是持续的两年，连续讲的话，是三个月的晚上吧。） 写这个扫雷程序，是想探究桌面应用程序开发的最佳实践和模式，也能成为自己这几年WPF工作的一个沉淀。而扫雷，只因为其...</summary><published>2012-03-18T11:47:00Z</published><updated>2012-03-18T11:47:00Z</updated><author><name>南柯之石</name><uri>http://www.cnblogs.com/nankezhishi/</uri></author><link rel="alternate" href="http://www.cnblogs.com/nankezhishi/archive/2012/03/18/clearmine.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/nankezhishi/archive/2012/03/18/clearmine.html"/><content type="html">&lt;p&gt;平时有人问我业余时间会做点儿什么？&lt;/p&gt;  &lt;p&gt;我说：用WPF写个游戏玩玩。&lt;/p&gt;  &lt;p&gt;：写的什么？&lt;/p&gt;  &lt;p&gt;我：扫雷。&lt;/p&gt;  &lt;p&gt;：&amp;#8230;&amp;#8230;&lt;/p&gt;  &lt;p&gt;我：想想都写了快两年了。&lt;/p&gt;  &lt;p&gt;：orz&lt;/p&gt;  &lt;p&gt;单纯地讲扫雷游戏都会觉得很简单，都不好意思当个事儿，我一开始也是这样觉得。写着写着才发现，事情其实没有想象中的那么简单。先给大家上个图看看效果吧。&lt;/p&gt;  &lt;p&gt;&lt;img src="http://download.codeplex.com/Download?ProjectName=clearmine&amp;amp;DownloadId=349589"  alt="" /&gt;&amp;nbsp; &lt;img src="http://download.codeplex.com/Download?ProjectName=clearmine&amp;amp;DownloadId=349591"  alt="" /&gt;&lt;/p&gt;  &lt;p&gt;写这个扫雷程序的主要目的，并不是练习WPF，也不是用WPF复刻Windows自带的这个游戏。这两件事儿其实都不至于做两年。（当然不是持续的两年，连续讲的话，是三个月的晚上吧。）&lt;/p&gt;  &lt;p&gt;写这个扫雷程序，是想探究桌面应用程序开发的最佳实践和模式，也能成为自己这几年WPF工作的一个沉淀。而扫雷，只因为其功能相对简单却比较齐全而被我选为一个载体。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;想通过这个程序达到的目标&lt;/strong&gt;&lt;/p&gt;  &lt;ul&gt;&lt;li&gt;设计方案成长历程的记录      &lt;p&gt;回想起自己刚刚学习WPF第一个年头，单是日历控件就先后做了三遍。从第一遍的拿Button当日期，翻月都卡；到最后考虑使用者的便利度、主题的支持、模板的使用。真的是一个很明显的成长的过程。这个扫雷程序，虽然是我工作的沉淀，但是我并不追求一开始的完美，反而我会希望他一开始并不完美，从而能展现出越变越好的一个过程。而这个过程，才是最宝贵的。如果有时间，也许我会写个系统博客，讲述自己开发这个游戏的心路历程。&lt;/p&gt;   &lt;/li&gt;&lt;li&gt;自行发现、创造、实践设计模式的游乐场      &lt;p&gt;虽然从大学开始就开始看设计模式这样的书，但是我不得不承认，那个时候看了也是白看。没有直面过严峻的问题的考验，没有自己摸索着真正使用模式，是不可能真正理解模式的。也许能认识这个模式，但是不知道这个模式应该在什么时候用，用在什么地方，用的合适不合适。而开始开发扫雷这个程序的时候，我排空了自己所有的模式、设计上的知识，基本靠感觉写。希望可以再次体验那种，发现问题、解决问题、抽象成模式的美妙感觉。所以在这个项目里，你看不到任何第三方的库，第三方库在帮我们解决问题同时，也带走了我们自己学习、发现的机会。这个扫雷做到现在，已经先后两年时间，都是在期间有了新的灵感，又重新开始了这个项目的开发。比如学习完Android开发之后，觉得里面的Intent和Broadcast都是不错的设计，就引入了这个扫雷项目里。&lt;/p&gt;   &lt;/li&gt;&lt;li&gt;WPF程序开发技术特性的展示台      &lt;p&gt;既然把这个扫雷当成是工作的沉淀，当然还要保留它应有的功能。在这个程序里，我尽可能多地引入一些WPF的特性或功能。比如自定义Effect什么的。希望自己在以后有机会再用到WPF的时候，在这个扫雷游戏的帮助下，能够回忆起这些细节，并能把里面的代码或是框架进行征用。&lt;/p&gt;   &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;&lt;strong&gt;目前的现状&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;这个游戏还没有写完，按这个进度，要完成我想象中的功能可能还要5年时间吧。目前已经实现的功能包括。&lt;/p&gt;  &lt;ul&gt;&lt;li&gt;基本游戏逻辑和用户交互。你可以理解为XP版的扫雷复刻。什么英雄榜啦，选项界面啦。 不过有改进。&lt;/li&gt;&lt;li&gt;主题支持。现在已经完成的主题是Ubuntu风格的扫雷，你可以通过一个XAML，完全自定义一个。 &lt;/li&gt;&lt;li&gt;多语言支持。 &lt;/li&gt;&lt;li&gt;模块化。 &lt;/li&gt;&lt;li&gt;插件框架。目前实现了一个简单的插件系统，里面就一个配音插件。由于我目前并没有去了解过别的插件系统是如何实现的，所以可能你会觉得这完全不是插件。而这就是我想要的，自己学习、发现的过程。&lt;/li&gt;&lt;li&gt;帮助文档，还没有写完。&lt;/li&gt;&lt;li&gt;安装包。使用NSIS脚本编写，还很原始。&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;下面是插件对话框的截图。&lt;/p&gt;  &lt;p&gt;&lt;img src="http://download.codeplex.com/Download?ProjectName=clearmine&amp;amp;DownloadId=349595"  alt="" /&gt;&lt;/p&gt;  &lt;p&gt;下面是改进的英雄榜。&lt;/p&gt;  &lt;p&gt;&lt;img src="http://download.codeplex.com/Download?ProjectName=clearmine&amp;amp;DownloadId=349593"  alt="" /&gt;&lt;/p&gt;  &lt;p&gt;在这个英雄榜上可以看到每次游戏的结果。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Roadmap&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;以后如果有时间，我是希望这个扫雷可以支持如下的几个功能。&lt;/p&gt;  &lt;ul&gt;&lt;li&gt;AI SDK，我知道扫雷是个NP问题，所以这才比较有挑战嘛。况且Codeplex上已经有人在做了。&lt;/li&gt;&lt;li&gt;AI对战模式。有了AI，就可以让两个AI一起解一个游戏。技术宅们的最爱。&lt;/li&gt;&lt;li&gt;录制游戏视频。扫雷可是正儿八经的国际赛事，从网上可以找到不少玩家录制的游戏视频，比如26秒中级什么的。有需求就要有功能。&lt;/li&gt;&lt;li&gt;联网对战。MSN就有扫雷游戏。&lt;/li&gt;&lt;li&gt;&amp;#8230;&amp;#8230;&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;&lt;a href="http://clearmine.codeplex.com/"&gt;这个项目现在放在了codeplex上&lt;/a&gt;，大家有兴趣可以玩玩看啊。不过编译起来可能会比较麻烦，里面的自定义Effect可能会默认Build不过，除非你安装了Shader Build Task。不过Effect里面有Readme，介绍了两种Build的方法，一步步来应该没有问题的。&lt;/p&gt; &lt;img src="http://www.cnblogs.com/nankezhishi/aggbug/2404993.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/archive/2012/03/18/clearmine.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/nankezhishi/archive/2012/03/18/2404639.html</id><title type="text">技术误用带来的技术偏见及所谓的&amp;ldquo;经验&amp;rdquo;</title><summary type="text">从触发器说起 之前写了一篇NoSQL和MemeryCache的出现意味着传统数据库使用方式的变革吗?，里面描述了一下目前所在项目的数据库使用的情况————基本就是把SQL Server当成一个可自定义结构的文件在用。没有使用到任何数据库自身的特性，比如外键、约束、存储过程、触发器、自定义函数等。原因是我们的领导根据自己的经验，认为这些东西带来的麻烦大于好处，所以干脆就不要用。 不少人回复的意思...</summary><published>2012-03-18T06:22:00Z</published><updated>2012-03-18T06:22:00Z</updated><author><name>南柯之石</name><uri>http://www.cnblogs.com/nankezhishi/</uri></author><link rel="alternate" href="http://www.cnblogs.com/nankezhishi/archive/2012/03/18/2404639.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/nankezhishi/archive/2012/03/18/2404639.html"/><content type="html">&lt;p&gt;&lt;strong&gt;从触发器说起&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;之前写了一篇&lt;a href="http://www.cnblogs.com/nankezhishi/archive/2012/02/12/2348091.html"&gt;NoSQL和MemeryCache的出现意味着传统数据库使用方式的变革吗?&lt;/a&gt;，里面描述了一下目前所在项目的数据库使用的情况————基本就是把SQL Server当成一个可自定义结构的文件在用。没有使用到任何数据库自身的特性，比如外键、约束、存储过程、触发器、自定义函数等。原因是我们的领导根据自己的经验，认为这些东西带来的麻烦大于好处，所以干脆就不要用。&lt;/p&gt;  &lt;p&gt;不少人回复的意思都是，这是经验之谈，我应该Follow。其实我很震惊，怎么会有人觉得这样的经验是正确的？如果这个经验是正确的，那数据库系统早就没有这些东西了。然而事实却是，一些原本没有存储过程，没有触发器机制的数据库系统，却也在引入这些东西。&lt;/p&gt;  &lt;p&gt;我不想在这里纠结应该不应该用触发器，这个问题讨论清楚了，还会有其它同样的问题出现。我想探究一下人们产生这种“经验”的原因，以及这种经验的误用。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;技术特性的适用范畴&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;每项技术特性的出现都不会是为了娱乐，都是为了解决&lt;strong&gt;特定的&lt;/strong&gt;问题。而且在设计、实现这项新特性的时候，设计者一般都会尽量地让这个特性更加地通用，更加地灵活，可扩展性更好。但是这些灵活性也会带来一系列的麻烦，其中之一就是对特性的误用。引用一句名言，&lt;strong&gt;一个API，如果有可能被误用，就一定会被误用。&lt;/strong&gt;而且现实中这种误用的例子太多了。误用分两种，下面分别解释一下。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;功能误用&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;一项技术，和这个技术可以解决的问题，会存在下图所示的关系。&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/nankezhishi/201203/201203181421326149.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/nankezhishi/201203/201203181421533693.png" width="725" height="364" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;如果开发者用一个技术去解决本不应该用这个技术解决的问题，结果可能会是处处碰壁，不是编码复杂就是性能差什么的。然后开发者就回来否定了这项技术，认为这技术一无是处。这种误用基本天天都在发生，并误导着一群人。如果某个大牛给出的什么建议，依赖到了自己这样的经验，而读者又存在盲目的个人崇拜心理，那这个误导人的建议，就很容易蔓延开来。&lt;/p&gt;  &lt;p&gt;还是触发器为例，就连教科书上，都可以看到这样误用的例子。&lt;/p&gt;  &lt;p&gt;&lt;em&gt;有一个学生选课情况表，字段有学号、课程号、平时成绩、考试成绩、总评成绩。需要设计一个触发器，当学生的平时成绩或考试成绩修改时，就自动按照平时成绩20%和考试成绩80%的比例计算出总评成绩。&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;关于这个误用的解释，可以参考&lt;a href="http://deltamaster.is-programmer.com/posts/28296.html"&gt;审慎而明智地使用SQL触发器&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;牛人高纳德的建议带来的误导&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;如果各位觉得国内教科书很山寨，我再举一个我个人觉得是误导的例子。著名计算机科学家Donald Ervin Knuth，在他的《计算机程序设计艺术》中提倡函数单入单出。有人就会拿来奉为金科玉律。却无视其提出这个建议时的社会环境、编程语言、编程思想的约束，要求C#，Java这样的语言写的函数也只能有一个return语句，我觉得就是很可笑的事情了。类似的带有误导性的建议还有很多，诸如要求源代码只能有80列，类名以DAL、BLL结尾之类。&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;实现误用&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;还有一种误用，就是这个技术功能用对了地方，但是实现方式不正确，结果也没有达到预期的目的。&lt;/p&gt;  &lt;p&gt;以C#的Expression Tree和反射的使用方式为例，我也是刚看到又一个误用的例子。&lt;a href="http://www.cnblogs.com/andyliu/archive/2012/03/17/2402865.html"&gt;颠覆常识？？Reflection、Delegate、Expression 生成实体类，实测结果，反射最快。&lt;/a&gt;显然就是没有正确使用Expression Tree的例子，这篇文章一度是博客园的评论头条，却也一直没有人指出问题的症结在什么地方。&lt;/p&gt;  &lt;p&gt;最要命的当属各类这种建立在“实现误用” 之上的测评。网上各种Java、C#甚至C++程序员写的抨击对方性能差的文章中，测试结果彼此冲突的情况屡见不鲜。盖其原因，多数不过是对对方技术的“实现误用”。&lt;/p&gt;  &lt;p&gt;2012年5月4日更新：&lt;/p&gt;  &lt;p&gt;刚才又看到一个例子，&lt;a href="http://www.cnblogs.com/jacklondon/archive/2012/05/04/2482659.html"&gt;&lt;font size="2"&gt;编程经验点滴----避免在数据库访问函数中使用 try catch&lt;/font&gt;&lt;/a&gt;，LZ陈述了在DAL层使用try catch时存在的潜在问题，并提出了解决方案A。方案A的要点是不能在DAL中使用try catch，于是LZ就得出结论说所有的DAL中都不能使用try catch。我且不说他的结论是否正确，单纯的这种以偏概全的推理逻辑就是不合理的。因为可能存在方案B，在解决了问题的同时，并不要求DAL不能使用try catch。    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;经验带来技术偏见&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;再回来讨论之前触发器的问题。很多人抱怨触发器的原因就是性能、可移植性、复杂性。从他的抱怨其实就可以看出，他已经误用了触发器。而他失败的误用经历，让他对触发器本身产生了偏见。虽然其中的可移植性的确是个问题，但是如果你的产品从一开始就能决定就使用SQL Server，而且不会更换，那这也不是什么问题。如果你们的产品的确要做数据库兼容，那当然另当别论。&lt;/p&gt;  &lt;p&gt;有人会说，我不用触发器也能达到我想要结果，为什么要用触发器？甚至还引经据典地用敏捷开发的理念来解释，说这是简化开发流程，简化开发环境。我有点儿想笑了，当有工具和环境专门给你解决你要解决的这个问题，你不去用，非要找一个不是解决这个问题的技术来Workaround一把，还说这是简单，我看是懒得学习或是技术歧视主义者罢了。&lt;/p&gt;  &lt;p&gt;我的意思当然也不是某个系统自带了解决了X问题的技术，就一定要用这个技术去解决X问题。但是在否定这个技术前，请先确认你的技术方案比原有更好（更好编码，性能更好，可维护性高，任何一条都可以），千万不要“简单地试用了一下”就下结论说，我们重要自己做一套。这是对资源的极大浪费。   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;面对技术特性应当有理性的态度&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;各种技术特性就好比瑞士军刀，你当然可以用里面的&lt;strong&gt;主刀&lt;/strong&gt;开瓶盖或是拧螺丝，但是不要因此抱怨瑞士军刀不好用，因为你根本没有用对地方。更糟糕的情况是，如果你是瑞士军刀的老板，希望你不要因此决定停止生产瑞士军刀。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;所以当你发现一个技术功能没有达到你想要结果时，请先不要否则这项技术，请先问问自己下面这两个问题。&lt;/strong&gt;&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;&lt;strong&gt;你所选择的技术是否适用于解决这个问题。（不要把某项技术用在不恰当的地方） &lt;/strong&gt;&lt;/li&gt;    &lt;li&gt;&lt;strong&gt;你的实现方式是否正确。（用某项技术，要用正确的方式使用。用对。） &lt;/strong&gt;&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;完全地否定或是完全地肯定某项技术都是不理智的。都是一种类似种族歧视般的偏见。（请不要误解这句话，如果你说Asp.NET在各个方面都好过CGI，我也没有任何意见，这是技术进步的结果，而不是你对CGI有偏见。希望大家也能正确理解文章的主旨，不要纠结于字词的表面意思，一句句这样解释很累。）&lt;/p&gt;  &lt;p&gt;针对你所喜欢的技术方案也是这样。&lt;strong&gt;也不要片面地觉得什么方案是万灵丹，在上一个项目成功了，在一个新的项目上就不会出问题。&lt;/strong&gt;比如，你可能觉得在代码里拼SQL已经可以解决所有问题，这的确也没什么错。给足够多的时间，无论用什么方法都可以实现需求。但是技术进步的源动力是寻求更好的方案，不是吗？（可能又要有人拿技术的商业目的来说事儿，说赚钱就行，技术是屎，反正做出来就行，云云。那其实你就不应该进来来看这篇文章，不是吗？）&lt;/p&gt;&lt;img src="http://www.cnblogs.com/nankezhishi/aggbug/2404639.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/archive/2012/03/18/2404639.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/nankezhishi/archive/2012/03/10/2389134.html</id><title type="text">从一个UI交互设计师的讲座说开去</title><summary type="text">下面是苹果UI交互设计师Bret Victor的一个讲座视频。主要讲解了如何通过工具上的创新，更高效地写代码、调试代码、测试代码、设计电路图和创作动画。 视频中所带来的设计理念、工具和视觉效果都令我非常地震撼。然而，看视频的时候我就在考虑另一个问题。如此优秀的交互设计师，不仅仅需要有相当的艺术水准（主要指交互，不是指视觉），更需要有相当的专业水平。毕业以来工作的几家公司，都有自...</summary><published>2012-03-10T07:12:00Z</published><updated>2012-03-10T07:12:00Z</updated><author><name>南柯之石</name><uri>http://www.cnblogs.com/nankezhishi/</uri></author><link rel="alternate" href="http://www.cnblogs.com/nankezhishi/archive/2012/03/10/2389134.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/nankezhishi/archive/2012/03/10/2389134.html"/><content type="html">&lt;p&gt;下面是苹果UI交互设计师&lt;a href="http://worrydream.com/"&gt;Bret Victor&lt;/a&gt;的一个讲座视频。主要讲解了如何通过工具上的创新，更高效地写代码、调试代码、测试代码、设计电路图和创作动画。&lt;/p&gt;  &lt;p&gt;&lt;embed width="480" height="400" type="application/x-shockwave-flash" wmode="transparent" allowfullscreen="true" allowscriptaccess="never" src="http://player.youku.com/player.php/sid/XMzUyOTIyNzg0/v.swf"&gt;&lt;/p&gt;&lt;P&gt;&lt;/P&gt;&lt;/embed&gt;     &lt;/p&gt;    &lt;p&gt;视频中所带来的设计理念、工具和视觉效果都令我非常地震撼。然而，看视频的时候我就在考虑另一个问题。如此优秀的交互设计师，不仅仅需要有相当的艺术水准（主要指交互，不是指视觉），更需要有相当的专业水平。毕业以来工作的几家公司，都有自己的交互设计师，但是能达到这个要求的，说凤毛麟角都算是夸张。虽然视频中的产品一定是一个团队贡献的结果，但是我相信在这个团队中，一定存在这种人，才能做得出这种产品。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;现实不是理想国&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;当然，每个人的标准不一样。有人可能会觉得这东西不过而而。不过现实不是理想国，不应该把别人实在东西与自己美好的理想去比较。所以高标准应该是留给自己的，衡量别人的东西的时候，应该用现实做标准。实现就是现有有开发工具，微软的Visual Studio做得很好，IE的Develop Tool做得也还可以。但是和视频里的工具比起来让我感觉已经落后了一个时代。微软的VS发展了多年，每次版本都有大量的更新，为什么在易用性上，在苹果的一个原型产品的面前直接就扑街了呢？&lt;/p&gt;  &lt;p&gt;我不了解微软，但是我还比较了解我毕业以来工作过的几家公司。多数公司里，多数交互设计师需要同时兼负美工的责任，所以在人员招聘的时候，主要会选择美术相关专业的人员。这对于一般产品没有问题，但是如果是设计一些专业产品，这些交互设计师可能就会觉得力不从心或是交互出来的产品仅仅是差强人意。最好的解决办法倒也很简单，就是沟通。让交互设计师真切地了解到专业的用户会怎么样使用产品，使用产品的最终目标是什么？而不仅仅是了解用户希望有什么功能。有时候用户所希望的使用方式，由于其思维方式的限制，可能并不是最佳的方式。而这才是交互设计师最能，也最应该发挥价值的地方。而只有了解用户的最终目的，才能让交互设计的方案更直接更高效，让最终的产品高于用户的期望，让人觉得耳目一新。&lt;/p&gt;  &lt;p&gt;然而，现实不是理想国。如果一个交互设计师的人生理想，不是类似“让生活更美好，更简单”之类，而是“周游世界”之类。相信在公司的设计工作就只是为了赚钱，赚得多到达到“周游世界”这个理想而已。他会为了赚更多的钱而努力工作，但是他所创造出来，还不足以达到“更美好，更简单”的“理想国”。最美好的东西，多半只会诞生于一个恪守自己人生理想的人的手中。&lt;/p&gt;  &lt;p&gt;这就是现实，现实不是理想国。理想的东西，也不会多。&lt;/p&gt;  &lt;p&gt;边想边写，写到这里，我觉得我快成果粉了，因为我开始觉得，Steven Jobs创立的不是一个苹果，而是一个理想国。（BTW，但我依然不觉得iPhone比Android好用）&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;分工带来的桎梏&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;我本来是想在这一部分里借题发挥，吐槽一下我经历的几个公司里见过的一些，工分好了，协作没了，甚至出现任务推诿的情况。不过想想这实在没什么意义，哪家公司都有这种人这种事儿，也没什么稀罕的，而且也改变不了什么。还不如把这时间用来写写代码，创造自己的理想国去呢。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/nankezhishi/aggbug/2389134.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/archive/2012/03/10/2389134.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/nankezhishi/archive/2012/02/12/2348091.html</id><title type="text">NoSQL和MemeryCache的出现意味着传统数据库使用方式的变革吗？</title><summary type="text">故事的起源 作为软件工程专业出身的程序员，之前所接受的关于数据库的教育都是基于关系型数据库。对key-value based数据库和document-based数据库的都只是仅仅了解而已。 最近公司要做一个类似电商的系统，我来负责数据库的设计和接口的提供。当然，我们使用的数据库也是传统的关系型数据库SQL SERVER 2005，所以我也并没有什么太大压力。 但是当头儿看到我设计的分类系统的...</summary><published>2012-02-12T09:20:00Z</published><updated>2012-02-12T09:20:00Z</updated><author><name>南柯之石</name><uri>http://www.cnblogs.com/nankezhishi/</uri></author><link rel="alternate" href="http://www.cnblogs.com/nankezhishi/archive/2012/02/12/2348091.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/nankezhishi/archive/2012/02/12/2348091.html"/><content type="html">&lt;p&gt;&lt;strong&gt;故事的起源&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;作为软件工程专业出身的程序员，之前所接受的关于数据库的教育都是基于关系型数据库。对key-value based数据库和document-based数据库的都只是仅仅了解而已。&lt;/p&gt;  &lt;p&gt;最近公司要做一个类似电商的系统，我来负责数据库的设计和接口的提供。当然，我们使用的数据库也是传统的关系型数据库SQL SERVER 2005，所以我也并没有什么太大压力。&lt;/p&gt;  &lt;p&gt;但是当头儿看到我设计的分类系统的数据库表结果时意见很大，一翻沟通下来，我基本上就崩溃了。他的一翻话基本上推翻了我对数据库的基本认识。&lt;/p&gt;  &lt;hr /&gt;  &lt;p&gt;&lt;strong&gt;我的设计&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;要实现的分类是书籍分类体系，会至少有4层，总共大约有几百个分类。我很自然地想到了邻接表（Adjacency List）模式。然后设计了的表结构大体如下：&lt;/p&gt;  &lt;table border="1" cellspacing="0" cellpadding="2" width="400"&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;td valign="top" width="100"&gt;列名&lt;/td&gt;        &lt;td valign="top" width="100"&gt;CategoryId&lt;/td&gt;        &lt;td valign="top" width="100"&gt;ParentId&lt;/td&gt;        &lt;td valign="top" width="100"&gt;Name&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="100"&gt;描述&lt;/td&gt;        &lt;td valign="top" width="100"&gt;主键&lt;/td&gt;        &lt;td valign="top" width="100"&gt;父键&lt;/td&gt;        &lt;td valign="top" width="100"&gt;分类名&lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;当然还有别的字段，与问题无关就不列了。&lt;/p&gt;  &lt;p&gt;使用CTE递归的方式来获取一个分类下的子分类树。我考虑数据量并不大，所以性能上应该不成什么问题。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;和头儿的争论&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff"&gt;头儿看到我的设计，直接说，不用这么麻烦了，不是有4层吗？那就用4列，一列表示一级。&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;我说国图法分类可能有7层以上。&lt;font color="#0000ff"&gt;他说那就7列好了。&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;我说这样数据量会有大量冗余，更新起来会很麻烦。&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff"&gt;头儿说，我之前做过很多树型结构，一开始也和你一样。但是做着做着就发现查询继承关系好麻烦啊，还不如直接用列存储下来。&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;我说查询继承的代码我已经写好了，用CTE递归我并不觉得麻烦啊。&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff"&gt;头儿说，不要用递归，我们要为读优化，而不是为写优化。&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;这个我完全赞同，但是这个数据量并不大，但是我还是觉得这个做法很山寨。不符合数据库的基本范式啊。&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff"&gt;他说你知道范式，也要了解反范式。而且这个数据库本身不要包含业务逻辑。你看现在出来的Redis和MemeryCached，存的都是名值对儿，没有逻辑。逻辑是给业务处理的。什么是程序？程序就是算法加数据，数据和逻辑是要分开的。&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;我说从属关系也是逻辑吗？数据一致性也是逻辑吗？&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff"&gt;应该算啊，数据库就是负责存储数据的就够了，你自己好好想想吧。&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;之后我还是坚持了自己的做法，头儿勉强接受，但是&lt;font color="#0000ff"&gt;要多加一个Path列，存储这个分类的所有父节点。&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;这个我倒是同意加上，这属于为特定查询做的优化。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;背景和现状&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;我进这个项目的时候，这个项目的数据库基本上已经建立完了。现在只是由于业务扩展需要新建数据库表。刚进公司的时候，我就觉得这里的数据库用法很诡异。&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;只有主键，没有外键，也就是数据库基本无从保证数据的一致性。&lt;/li&gt;    &lt;li&gt;没有使用ORM，需要在C#代码中拼SQL来实现业务对象的读取和写入。&lt;/li&gt;    &lt;li&gt;不允许使用存储过程。这会引入过多的业务逻辑。&lt;/li&gt;    &lt;li&gt;我也没见过任何View，任何Trigger，任何自定义函数。&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;现在发现这些现状与头儿的想法其实是比较一致的。但是给人的感觉就是把SQL SERVER当作一个Key-Value的Database来实用。自然也就不需要那些SQL SERVER的功能。&lt;/p&gt;  &lt;p&gt;但是我很奇怪，这样的用法，为什么不直接用No SQL方案呢？不用也就算了，更让&lt;strong&gt;我困惑的地方就在于，把No SQL的理念用在关系型数据库上合适吗？&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;我在这里也没有看到过任何数据库性能Profiling的代码，对于数据库性能的考量，基本上依赖数据库服务器的CPU占用。（也许有测试人员做过我不知道而已吧。希望是这样。）我们仅有的一名DBA也于年前离职了。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;我的想法&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;我一直觉得数据库设计是一项很重要很复杂的工作，需要了解业务需求，需要了解用户的使用方式，需要能够遇见到基本的可变性，需要对数据库底层机制有深入的了解，等等等等。也听过很多数据库高手改写一段SQL，就让数据库查询变得飞快的故事。而且一切优化都应该有实际的数据来说话。也许我们头儿在数据库设计上有相当的功力，很有感觉。但是真的是这样的吗？我有我自己的想法，不知道是不是正确，但希望能和大家讨论一下。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;关于反范式和优化&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;反范式作为一种数据库优化方案，我并觉得和其它的优化有什么根本上的不同。但是从优化的次序上来讲，我个人觉得应该先在查询的SQL上做足功夫，不行了再祭出反范式这个以空间换时间的杀手锏。不应该是从数据库设计一开始就惦记着的事情，尤其是这还是在关系型数据库上。当然，资深人员一眼就能感觉出哪里需要这么干当然更好。不过对于上面的情况，我个人觉得不需要。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;关于数据库不能有逻辑&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;这句话首先需要澄清什么属于逻辑。由于这是我们头的观点，我也没有跟他详细讨论过这个问题。我来讲下我的理解吧。数据库在设计之初就是为某个业务设计的，脱离了这个业务，这个设计本身也没有了意义。所以我不认为逻辑从数据库分离能让数据库可以被重用。但是数据库包含了过多的逻辑会增加数据库服务器的负担，所以逻辑还是少放在数据库为好。所以我基本同意这句话。&lt;/p&gt;  &lt;p&gt;但是，我认为。数据关系（如外键）不属于逻辑，数据完全性不属于逻辑。而且这些就应该主是由数据库来保证的。Web 服务器可以去检查外键，检查唯一约束，但是即使Web服务器不做这些，数据库也应该做到这些。&lt;/p&gt;  &lt;p&gt;最常见的就是数据插入和更新，如果涉及到多张表，那这些SQL写入一个存储过程以方便外界调用是很好的。同时，存储过程或是View也可以把数据库表设计与程序之间的依赖解除，在一些简单的情况下实现不修改程序而对数据库表结构进行变更。使用存储过程也许会给数据迁移带来麻烦，但是有谁没事儿换数据库呢？&lt;/p&gt;  &lt;p&gt;我们目前这种在程序中拼SQL去操作数据库的做法，我没有看到什么优势。纯粹就是为了“数据库不能有‘逻辑’”而拆分出来的东西。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;关于数据库系统的选用&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;我并没有反对NoSQL或MemeryCached的理念，毕竟是用于处理不同的情况问题的。&lt;/p&gt;  &lt;p&gt;像微博、社交、互动相关的系统。因为数据量大，访问量大，数据结构相对简单，用户访问具有时效性、地域性，使用这种NoSQL+MemeryCache方案是合理甚至是唯一的选择。但是任何产品或是理念都有它适用的范围，没有银弹。把 MemoryCache用在员工作业平台或是项目管理系统上我觉得就相当的不靠谱。&lt;/p&gt;  &lt;p&gt;但是电商系统似乎是处于两者之间的一种模式，而且我们的业务量在可预见的未来也不会增长到淘宝的那个级别。我和头的争论也就源于这些理念上的差别，他像是在建立大统一理论，把各种先进思想整合在一起，而我又不确定这是创新呢？还是不伦不类的方式？于是有了题目上的疑问。而按头的说法，也许这就是思考问题层次上的差别。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;我在数据库设计上并没有什么经验，却又做不到别人说什么我就听什么。上面只是我根据对于数据库自身的理解产生的一些想法，还很不成熟，希望大家能说说自己的见解。如果实在看不下去了，也欢迎吐槽。&lt;/strong&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/nankezhishi/aggbug/2348091.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/archive/2012/02/12/2348091.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/nankezhishi/archive/2012/02/11/dynamicaccess.html</id><title type="text">不使用反射进行C#属性的运行时动态访问</title><summary type="text">摘要 问题的抽象 没有优化的反射 使用晚绑定优化的反射 公平的竞赛 公平的实现方式 换个思路，最直白的实现方式 运行时生成代码 性能比拼 摘要 单纯的反射带来灵活性的同时，也大大降低了应用程序的效率...</summary><published>2012-02-11T11:43:00Z</published><updated>2012-02-11T11:43:00Z</updated><author><name>南柯之石</name><uri>http://www.cnblogs.com/nankezhishi/</uri></author><link rel="alternate" href="http://www.cnblogs.com/nankezhishi/archive/2012/02/11/dynamicaccess.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/nankezhishi/archive/2012/02/11/dynamicaccess.html"/><content type="html">&lt;ol&gt;   &lt;li&gt;     &lt;p&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/archive/2012/02/11/dynamicaccess.html#summary"&gt;摘要&lt;/a&gt;&lt;/p&gt;   &lt;/li&gt;    &lt;li&gt;     &lt;p&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/archive/2012/02/11/dynamicaccess.html#abs"&gt;问题的抽象&lt;/a&gt;&lt;/p&gt;   &lt;/li&gt;    &lt;li&gt;     &lt;p&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/archive/2012/02/11/dynamicaccess.html#re"&gt;没有优化的反射&lt;/a&gt;&lt;/p&gt;   &lt;/li&gt;    &lt;li&gt;     &lt;p&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/archive/2012/02/11/dynamicaccess.html#la"&gt;使用晚绑定优化的反射&lt;/a&gt;&lt;/p&gt;      &lt;ul&gt;       &lt;li&gt;公平的竞赛 &lt;/li&gt;        &lt;li&gt;公平的实现方式 &lt;/li&gt;     &lt;/ul&gt;   &lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/archive/2012/02/11/dynamicaccess.html#di"&gt;换个思路，最直白的实现方式&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;     &lt;p&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/archive/2012/02/11/dynamicaccess.html#g"&gt;运行时生成代码&lt;/a&gt;&lt;/p&gt;   &lt;/li&gt;    &lt;li&gt;     &lt;p&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/archive/2012/02/11/dynamicaccess.html#p"&gt;性能比拼&lt;/a&gt;&lt;/p&gt;   &lt;/li&gt; &lt;/ol&gt;  &lt;hr /&gt;  &lt;p&gt;&lt;strong&gt;&lt;a name="summary"&gt;&lt;/a&gt;摘要&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;单纯的反射带来灵活性的同时，也大大降低了应用程序的效率。本文将利用C#的各种技术，就如何实现动态的方法调用或属性访问做一些初步的研究。希望可以给同样需要提高反射性能的朋友一些帮助。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;a name="abs"&gt;&lt;/a&gt;问题的抽象&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;反射可以用在很多的情景中，但是抽象来看就是用来访问编译时无法确定的成员。这成员可以是方法，也可以是属性。为了简化问题，我们把问题限定在属性的访问上。那么反射这个功能就可以抽象成下面这个接口。&lt;/p&gt;  &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="rem"&gt;/// Abstraction of the function of accessing member of a object at runtime.&lt;/span&gt;&lt;br/&gt;&lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;interface&lt;/span&gt; IMemberAccessor&lt;br/&gt;{&lt;br/&gt;    &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br/&gt;    &lt;span class="rem"&gt;/// Get the member value of an object.&lt;/span&gt;&lt;br/&gt;    &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br/&gt;    &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;instance&amp;quot;&amp;gt;The object to get the member value from.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br/&gt;    &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;memberName&amp;quot;&amp;gt;The member name, could be the name of a property of field. Must be public member.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br/&gt;    &lt;span class="rem"&gt;/// &amp;lt;returns&amp;gt;The member value&amp;lt;/returns&amp;gt;&lt;/span&gt;&lt;br/&gt;    &lt;span class="kwrd"&gt;object&lt;/span&gt; GetValue(&lt;span class="kwrd"&gt;object&lt;/span&gt; instance, &lt;span class="kwrd"&gt;string&lt;/span&gt; memberName);&lt;br/&gt;&lt;br/&gt;    &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br/&gt;    &lt;span class="rem"&gt;/// Set the member value of an object.&lt;/span&gt;&lt;br/&gt;    &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br/&gt;    &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;instance&amp;quot;&amp;gt;The object to get the member value from.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br/&gt;    &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;memberName&amp;quot;&amp;gt;The member name, could be the name of a property of field. Must be public member.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br/&gt;    &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;newValue&amp;quot;&amp;gt;The new value of the property for the object instance.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br/&gt;    &lt;span class="kwrd"&gt;void&lt;/span&gt; SetValue(&lt;span class="kwrd"&gt;object&lt;/span&gt; instance, &lt;span class="kwrd"&gt;string&lt;/span&gt; memberName, &lt;span class="kwrd"&gt;object&lt;/span&gt; newValue);&lt;br/&gt;}&lt;style type="text/css"&gt;.csharpcode, .csharpcode pre{font-size: small;color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff;/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt {background-color: #f4f4f4;width: 100%;margin: 0em;}.csharpcode .lnum { color: #606060; }&lt;/style&gt;&lt;p&gt;下面我们就来探讨这个接口怎么实现才能达到最高效率。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a name="re"&gt;&lt;/a&gt;没有优化的反射&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;使用反射是实现上面接口的最直观最简单的方式。代码如下：&lt;/p&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; ReflectionMemberAccessor : IMemberAccessor&lt;br/&gt;{&lt;br/&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;object&lt;/span&gt; GetValue(&lt;span class="kwrd"&gt;object&lt;/span&gt; instance, &lt;span class="kwrd"&gt;string&lt;/span&gt; memberName)&lt;br/&gt;    {&lt;br/&gt;        var propertyInfo = instance.GetType().GetProperty(memberName);&lt;br/&gt;        &lt;span class="kwrd"&gt;if&lt;/span&gt; (propertyInfo != &lt;span class="kwrd"&gt;null&lt;/span&gt;)&lt;br/&gt;        {&lt;br/&gt;            &lt;span class="kwrd"&gt;return&lt;/span&gt; propertyInfo.GetValue(instance, &lt;span class="kwrd"&gt;null&lt;/span&gt;);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;null&lt;/span&gt;;&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; SetValue(&lt;span class="kwrd"&gt;object&lt;/span&gt; instance, &lt;span class="kwrd"&gt;string&lt;/span&gt; memberName, &lt;span class="kwrd"&gt;object&lt;/span&gt; newValue)&lt;br/&gt;    {&lt;br/&gt;        var propertyInfo = instance.GetType().GetProperty(memberName);&lt;br/&gt;        &lt;span class="kwrd"&gt;if&lt;/span&gt; (propertyInfo != &lt;span class="kwrd"&gt;null&lt;/span&gt;)&lt;br/&gt;        {&lt;br/&gt;            propertyInfo.SetValue(instance, newValue, &lt;span class="kwrd"&gt;null&lt;/span&gt;);&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;}&lt;style type="text/css"&gt;.csharpcode, .csharpcode pre{font-size: small;color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff;/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt {background-color: #f4f4f4;width: 100%;margin: 0em;}.csharpcode .lnum { color: #606060; }&lt;/style&gt;&lt;p&gt;但是这种方式的效率让人望而却步。经过分析我们可以发现最慢的部分就是GetValue和SetValue这两个调用。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a name="la"&gt;&lt;/a&gt;使用Delegate优化的反射&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;将PropertyInfo的XetValue代理起来是最简单的提高性能方法。而且也已经有很多人介绍了这种方式，&lt;/p&gt;&lt;p&gt;1. &lt;a href="http://www.codeproject.com/Articles/14560/Fast-Dynamic-Property-Field-Accessors"&gt;Fast Dynamic Property Field Accessors&lt;/a&gt;&lt;/p&gt;&lt;p&gt;2. &lt;a href="http://www.cnblogs.com/artech/archive/2011/03/24/PropertyAccessor.html"&gt;晚绑定场景下对象属性赋值和取值可以不需要PropertyInfo&lt;/a&gt;&lt;/p&gt;&lt;p&gt;如果仅仅是看到他们的测试结果，会以为晚绑定就可以让属性的动态访问的速度达到和直接取值一样的速度，会觉得这生活多么美好啊。但是如果你真的把这个技术用在个什么地方会发现根本不是这么回事儿。真实的生活会如老赵写的&lt;a href="http://www.cnblogs.com/JeffreyZhao/archive/2009/02/01/Fast-Reflection-Library.html"&gt;Fast Reflection Library&lt;/a&gt;中给出的测试结果一般。你会发现即使是晚绑定了或是Emit了，速度也是要比直接访问慢5-20倍。是老赵的实现方式有问题吗？当然不是。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;font style="font-weight: bold"&gt;公平的竞赛&lt;/font&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;这里明确一下我们要实现的功能是什么？&lt;strong&gt;我们要实现的功能是，用一组方法或是模式，动态地访问任何一个对象上的任何一个属性。而前面那些看些美好的测试，都只是在测试晚绑定后的委托调用的性能，而那测试用的晚绑定委托调用都是针对某个类的某个属性的。这不是明摆着欺负反射吗？&lt;/strong&gt;虽然测试用的反射Invoke也是针对一个属性，但是反射的通用版本的性能也是差不多的，Invoke才是消耗的大头。这也是数据统计蒙人的最常见的手法，用自己最好的&lt;strong&gt;一部分&lt;/strong&gt;和对方的最差的&lt;strong&gt;一部分&lt;/strong&gt;去比较。但是&lt;strong&gt;我们真正关心的是整体。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;用晚绑定这个特性去实现类似反射能实现的功能，是需要把每个类的每个属性都缓存起来，并且在使用的时候，根据当前对象的类型和所取的属性名查找对应的缓存好的晚绑定委托。这些功能在那些美好的测试结果中都完全没有体现出来。而老赵的Fast Reflection Libary实现了这些功能，所以测试结果看上去要差很多。但是这才是真实的数据。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;font style="font-weight: bold"&gt;公平的实现方式&lt;/font&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;为了文章的完整起见，Delegate反射的实现方式如下。（我这里为了简单起见，没有过多优化，如果你要用这个方法，还是有很大的优化空间的。）&lt;/p&gt;&lt;p&gt;方法有两种，一种是使用Delegate.CreateDelegate函数。一种是&lt;a href="http://finite.mikeandcorinne.com/2007/07/c-30-expression-trees-and-reflectionemit/"&gt;使用Expression Tree&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;使用Delegate的核心代码分别如下所示：&lt;/p&gt;&lt;font size="2"&gt;&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PropertyAccessor&lt;/span&gt;&amp;lt;T, P&amp;gt; : &lt;/font&gt;&lt;font size="2"&gt;&lt;span style="color: #2b91af"&gt;INamedMemberAccessor&lt;br/&gt;&lt;/span&gt;{&lt;br/&gt;    &lt;span style="color: blue"&gt;private &lt;/span&gt;Func&amp;lt;T, P&amp;gt; GetValueDelegate;&lt;br/&gt;    &lt;span style="color: blue"&gt;private &lt;/span&gt;Action&amp;lt;T, P&amp;gt; SetValueDelegate;&lt;br/&gt;&lt;br/&gt;    &lt;span style="color: blue"&gt;public &lt;/span&gt;PropertyAccessor(&lt;span style="color: #2b91af"&gt;Type &lt;/span&gt;type, &lt;span style="color: blue"&gt;string &lt;/span&gt;propertyName)&lt;br/&gt;    {&lt;br/&gt;        &lt;span style="color: blue"&gt;var &lt;/span&gt;propertyInfo = type.GetProperty(propertyName);&lt;br/&gt;        &lt;span style="color: blue"&gt;if &lt;/span&gt;(propertyInfo != &lt;span style="color: blue"&gt;null&lt;/span&gt;)&lt;br/&gt;        {&lt;br/&gt;            GetValueDelegate = (Func&amp;lt;T, P&amp;gt;)&lt;span style="color: #2b91af"&gt;Delegate&lt;/span&gt;.CreateDelegate(&lt;span style="color: blue"&gt;typeof&lt;/span&gt;(Func&amp;lt;T, P&amp;gt;), propertyInfo.GetGetMethod());&lt;br/&gt;            SetValueDelegate = (Action&amp;lt;T, P&amp;gt;)&lt;span style="color: #2b91af"&gt;Delegate&lt;/span&gt;.CreateDelegate(&lt;span style="color: blue"&gt;typeof&lt;/span&gt;(Action&amp;lt;T, P&amp;gt;), propertyInfo.GetSetMethod());&lt;br/&gt;        }&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    &lt;span style="color: blue"&gt;public object &lt;/span&gt;GetValue(&lt;span style="color: blue"&gt;object &lt;/span&gt;instance)&lt;br/&gt;    {&lt;br/&gt;        &lt;span style="color: blue"&gt;return &lt;/span&gt;GetValueDelegate((T)instance);&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    &lt;span style="color: blue"&gt;public void &lt;/span&gt;SetValue(&lt;span style="color: blue"&gt;object &lt;/span&gt;instance, &lt;span style="color: blue"&gt;object &lt;/span&gt;newValue)&lt;br/&gt;    {&lt;br/&gt;        SetValueDelegate((T)instance, (P)newValue);&lt;br/&gt;    }&lt;br/&gt;}&lt;/font&gt;&lt;p&gt;Delegate.CreateDelegate在使用上有一个要求，其生成的Delegate的签名必须与Method的声明一致。所以就有了上面使用泛型的方式。每个PropertyAccessor是针对特定属性的，要真正用起来还要用Dictionary做下Mapping。如下所示：&lt;/p&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; DelegatedReflectionMemberAccessor : IMemberAccessor&lt;br/&gt;{&lt;br/&gt;    &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;static&lt;/span&gt; Dictionary&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;, INamedMemberAccessor&amp;gt; accessorCache = &lt;span class="kwrd"&gt;new&lt;/span&gt; Dictionary&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;, INamedMemberAccessor&amp;gt;();&lt;br/&gt;&lt;br/&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;object&lt;/span&gt; GetValue(&lt;span class="kwrd"&gt;object&lt;/span&gt; instance, &lt;span class="kwrd"&gt;string&lt;/span&gt; memberName)&lt;br/&gt;    {&lt;br/&gt;        &lt;span class="kwrd"&gt;return&lt;/span&gt;  FindAccessor(instance, memberName).GetValue(instance);&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; SetValue(&lt;span class="kwrd"&gt;object&lt;/span&gt; instance, &lt;span class="kwrd"&gt;string&lt;/span&gt; memberName, &lt;span class="kwrd"&gt;object&lt;/span&gt; newValue)&lt;br/&gt;    {&lt;br/&gt;        FindAccessor(instance, memberName).SetValue(instance, newValue);&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    &lt;span class="kwrd"&gt;private&lt;/span&gt; INamedMemberAccessor FindAccessor(&lt;span class="kwrd"&gt;object&lt;/span&gt; instance, &lt;span class="kwrd"&gt;string&lt;/span&gt; memberName)&lt;br/&gt;    {&lt;br/&gt;        var type = instance.GetType();&lt;br/&gt;        var key = type.FullName + memberName;&lt;br/&gt;        INamedMemberAccessor accessor;&lt;br/&gt;        accessorCache.TryGetValue(key, &lt;span class="kwrd"&gt;out&lt;/span&gt; accessor);&lt;br/&gt;        &lt;span class="kwrd"&gt;if&lt;/span&gt; (accessor == &lt;span class="kwrd"&gt;null&lt;/span&gt;)&lt;br/&gt;        {&lt;br/&gt;            var propertyInfo = type.GetProperty(memberName);&lt;br/&gt;            accessor = Activator.CreateInstance(&lt;span class="kwrd"&gt;typeof&lt;/span&gt;(PropertyAccessor&amp;lt;,&amp;gt;).MakeGenericType(type, propertyInfo.PropertyType), type, memberName) &lt;span class="kwrd"&gt;as&lt;/span&gt; INamedMemberAccessor;&lt;br/&gt;            accessorCache.Add(key, accessor);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        &lt;span class="kwrd"&gt;return&lt;/span&gt; accessor;&lt;br/&gt;    }&lt;br/&gt;}&lt;style type="text/css"&gt;.csharpcode, .csharpcode pre{font-size: small;color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff;/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt {background-color: #f4f4f4;width: 100%;margin: 0em;}.csharpcode .lnum { color: #606060; }&lt;/style&gt;&lt;p&gt;用ExpressionTree的生成委托的时候，也会遇到类型的问题，但是我们可以在ExpressionTree中对参数和返回值的类型进行处理，这样就不需要泛型的实现方式了。代码如下：&lt;/p&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; DelegatedExpressionMemberAccessor : IMemberAccessor&lt;br/&gt;{&lt;br/&gt;    &lt;span class="kwrd"&gt;private&lt;/span&gt; Dictionary&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;, Func&amp;lt;&lt;span class="kwrd"&gt;object&lt;/span&gt;, &lt;span class="kwrd"&gt;object&lt;/span&gt;&amp;gt;&amp;gt; getValueDelegates = &lt;span class="kwrd"&gt;new&lt;/span&gt; Dictionary&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;, Func&amp;lt;&lt;span class="kwrd"&gt;object&lt;/span&gt;, &lt;span class="kwrd"&gt;object&lt;/span&gt;&amp;gt;&amp;gt;();&lt;br/&gt;    &lt;span class="kwrd"&gt;private&lt;/span&gt; Dictionary&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;, Action&amp;lt;&lt;span class="kwrd"&gt;object&lt;/span&gt;, &lt;span class="kwrd"&gt;object&lt;/span&gt;&amp;gt;&amp;gt; setValueDelegates = &lt;span class="kwrd"&gt;new&lt;/span&gt; Dictionary&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;, Action&amp;lt;&lt;span class="kwrd"&gt;object&lt;/span&gt;, &lt;span class="kwrd"&gt;object&lt;/span&gt;&amp;gt;&amp;gt;();&lt;br/&gt;&lt;br/&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;object&lt;/span&gt; GetValue(&lt;span class="kwrd"&gt;object&lt;/span&gt; instance, &lt;span class="kwrd"&gt;string&lt;/span&gt; memberName)&lt;br/&gt;    {&lt;br/&gt;        var type = instance.GetType();&lt;br/&gt;        var key = type.FullName + memberName;&lt;br/&gt;        Func&amp;lt;&lt;span class="kwrd"&gt;object&lt;/span&gt;, &lt;span class="kwrd"&gt;object&lt;/span&gt;&amp;gt; getValueDelegate;&lt;br/&gt;        getValueDelegates.TryGetValue(key, &lt;span class="kwrd"&gt;out&lt;/span&gt; getValueDelegate);&lt;br/&gt;        &lt;span class="kwrd"&gt;if&lt;/span&gt; (getValueDelegate == &lt;span class="kwrd"&gt;null&lt;/span&gt;)&lt;br/&gt;        {&lt;br/&gt;            var info = type.GetProperty(memberName);&lt;br/&gt;            var target = Expression.Parameter(&lt;span class="kwrd"&gt;typeof&lt;/span&gt;(&lt;span class="kwrd"&gt;object&lt;/span&gt;), &lt;span class="str"&gt;&amp;quot;target&amp;quot;&lt;/span&gt;);&lt;br/&gt;&lt;br/&gt;            var getter = Expression.Lambda(&lt;span class="kwrd"&gt;typeof&lt;/span&gt;(Func&amp;lt;&lt;span class="kwrd"&gt;object&lt;/span&gt;, &lt;span class="kwrd"&gt;object&lt;/span&gt;&amp;gt;),&lt;br/&gt;                Expression.Convert(Expression.Property(Expression.Convert(target, type), info), &lt;span class="kwrd"&gt;typeof&lt;/span&gt;(&lt;span class="kwrd"&gt;object&lt;/span&gt;)),&lt;br/&gt;                target&lt;br/&gt;                );&lt;br/&gt;&lt;br/&gt;            getValueDelegate = (Func&amp;lt;&lt;span class="kwrd"&gt;object&lt;/span&gt;, &lt;span class="kwrd"&gt;object&lt;/span&gt;&amp;gt;)getter.Compile();&lt;br/&gt;            getValueDelegates.Add(key, getValueDelegate);&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        &lt;span class="kwrd"&gt;return&lt;/span&gt; getValueDelegate(instance);&lt;br/&gt;    }&lt;br/&gt;}&lt;style type="text/css"&gt;.csharpcode, .csharpcode pre{font-size: small;color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff;/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt {background-color: #f4f4f4;width: 100%;margin: 0em;}.csharpcode .lnum { color: #606060; }&lt;/style&gt;&lt;p&gt;一个优化方式是，把这个类做成泛型类，那么key就可以只是memberName，这样就少去了type.FullName及一次字符串拼接操作。性能可以提高不少。但是这种委托式的访问就是性能上的极限了吗？如果是我就不用来写这篇文章了。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a name="di"&gt;&lt;/a&gt;虽然山寨却更直接的方法&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;我们的目标是动态的访问一个对象的一个属性，一谈到动态总是会自然而然地想到反射。其实还有一个比较质朴的方式。就是让这个类自己去处理。还记得一开始定义的IMemberAccessor接口吗？如果我们所有的类的都实现了这个接口，那么就直接调用这个方法就是了。方式如下。&lt;/p&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; Man : IMemberAccessor&lt;br/&gt;{&lt;br/&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;string&lt;/span&gt; Name { get; set; }&lt;br/&gt;&lt;br/&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;int&lt;/span&gt; Age { get; set; }&lt;br/&gt;&lt;br/&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; DateTime Birthday { get; set; }&lt;br/&gt;&lt;br/&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;double&lt;/span&gt; Weight { get; set; }&lt;br/&gt;&lt;br/&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;double&lt;/span&gt; Height { get; set; }&lt;br/&gt;&lt;br/&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;decimal&lt;/span&gt; Salary { get; set; }&lt;br/&gt;&lt;br/&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;bool&lt;/span&gt; Married { get; set; }&lt;br/&gt;&lt;br/&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;object&lt;/span&gt; GetValue(&lt;span class="kwrd"&gt;object&lt;/span&gt; instance, &lt;span class="kwrd"&gt;string&lt;/span&gt; memberName)&lt;br/&gt;    {&lt;br/&gt;        var man = instance &lt;span class="kwrd"&gt;as&lt;/span&gt; Man;&lt;br/&gt;        &lt;span class="kwrd"&gt;if&lt;/span&gt; (man != &lt;span class="kwrd"&gt;null&lt;/span&gt;)&lt;br/&gt;        {&lt;br/&gt;            &lt;span class="kwrd"&gt;switch&lt;/span&gt; (memberName)&lt;br/&gt;            {&lt;br/&gt;                &lt;span class="kwrd"&gt;case&lt;/span&gt; &lt;span class="str"&gt;&amp;quot;Name&amp;quot;&lt;/span&gt;: &lt;span class="kwrd"&gt;return&lt;/span&gt; man.Name;&lt;br/&gt;                &lt;span class="kwrd"&gt;case&lt;/span&gt; &lt;span class="str"&gt;&amp;quot;Age&amp;quot;&lt;/span&gt;: &lt;span class="kwrd"&gt;return&lt;/span&gt; man.Age;&lt;br/&gt;                &lt;span class="kwrd"&gt;case&lt;/span&gt; &lt;span class="str"&gt;&amp;quot;Birthday&amp;quot;&lt;/span&gt;: &lt;span class="kwrd"&gt;return&lt;/span&gt; man.Birthday;&lt;br/&gt;                &lt;span class="kwrd"&gt;case&lt;/span&gt; &lt;span class="str"&gt;&amp;quot;Weight&amp;quot;&lt;/span&gt;: &lt;span class="kwrd"&gt;return&lt;/span&gt; man.Weight;&lt;br/&gt;                &lt;span class="kwrd"&gt;case&lt;/span&gt; &lt;span class="str"&gt;&amp;quot;Height&amp;quot;&lt;/span&gt;: &lt;span class="kwrd"&gt;return&lt;/span&gt; man.Height;&lt;br/&gt;                &lt;span class="kwrd"&gt;case&lt;/span&gt; &lt;span class="str"&gt;&amp;quot;Salary&amp;quot;&lt;/span&gt;: &lt;span class="kwrd"&gt;return&lt;/span&gt; man.Salary;&lt;br/&gt;                &lt;span class="kwrd"&gt;case&lt;/span&gt; &lt;span class="str"&gt;&amp;quot;Married&amp;quot;&lt;/span&gt;: &lt;span class="kwrd"&gt;return&lt;/span&gt; man.Married;&lt;br/&gt;                &lt;span class="kwrd"&gt;default&lt;/span&gt;:&lt;br/&gt;                    &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;null&lt;/span&gt;;&lt;br/&gt;            }&lt;br/&gt;        }&lt;br/&gt;        &lt;span class="kwrd"&gt;else&lt;/span&gt;&lt;br/&gt;            &lt;span class="kwrd"&gt;throw&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; InvalidProgramException();&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; SetValue(&lt;span class="kwrd"&gt;object&lt;/span&gt; instance, &lt;span class="kwrd"&gt;string&lt;/span&gt; memberName, &lt;span class="kwrd"&gt;object&lt;/span&gt; newValue)&lt;br/&gt;    {&lt;br/&gt;        var man = instance &lt;span class="kwrd"&gt;as&lt;/span&gt; Man;&lt;br/&gt;        &lt;span class="kwrd"&gt;if&lt;/span&gt; (man != &lt;span class="kwrd"&gt;null&lt;/span&gt;)&lt;br/&gt;        {&lt;br/&gt;            &lt;span class="kwrd"&gt;switch&lt;/span&gt; (memberName)&lt;br/&gt;            {&lt;br/&gt;                &lt;span class="kwrd"&gt;case&lt;/span&gt; &lt;span class="str"&gt;&amp;quot;Name&amp;quot;&lt;/span&gt;: man.Name = newValue &lt;span class="kwrd"&gt;as&lt;/span&gt; &lt;span class="kwrd"&gt;string&lt;/span&gt;; &lt;span class="kwrd"&gt;break&lt;/span&gt;;&lt;br/&gt;                &lt;span class="kwrd"&gt;case&lt;/span&gt; &lt;span class="str"&gt;&amp;quot;Age&amp;quot;&lt;/span&gt;: man.Age = Convert.ToInt32(newValue); &lt;span class="kwrd"&gt;break&lt;/span&gt;;&lt;br/&gt;                &lt;span class="kwrd"&gt;case&lt;/span&gt; &lt;span class="str"&gt;&amp;quot;Birthday&amp;quot;&lt;/span&gt;: man.Birthday = Convert.ToDateTime(newValue); &lt;span class="kwrd"&gt;break&lt;/span&gt;;&lt;br/&gt;                &lt;span class="kwrd"&gt;case&lt;/span&gt; &lt;span class="str"&gt;&amp;quot;Weight&amp;quot;&lt;/span&gt;: man.Weight = Convert.ToDouble(newValue); &lt;span class="kwrd"&gt;break&lt;/span&gt;;&lt;br/&gt;                &lt;span class="kwrd"&gt;case&lt;/span&gt; &lt;span class="str"&gt;&amp;quot;Height&amp;quot;&lt;/span&gt;: man.Height = Convert.ToDouble(newValue); &lt;span class="kwrd"&gt;break&lt;/span&gt;;&lt;br/&gt;                &lt;span class="kwrd"&gt;case&lt;/span&gt; &lt;span class="str"&gt;&amp;quot;Salary&amp;quot;&lt;/span&gt;: man.Salary = Convert.ToDecimal(newValue); &lt;span class="kwrd"&gt;break&lt;/span&gt;;&lt;br/&gt;                &lt;span class="kwrd"&gt;case&lt;/span&gt; &lt;span class="str"&gt;&amp;quot;Married&amp;quot;&lt;/span&gt;: man.Married = Convert.ToBoolean(newValue); &lt;span class="kwrd"&gt;break&lt;/span&gt;;&lt;br/&gt;            }&lt;br/&gt;        }&lt;br/&gt;        &lt;span class="kwrd"&gt;else&lt;/span&gt;&lt;br/&gt;            &lt;span class="kwrd"&gt;throw&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; InvalidProgramException();&lt;br/&gt;    }&lt;br/&gt;}&lt;style type="text/css"&gt;.csharpcode, .csharpcode pre{font-size: small;color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff;/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt {background-color: #f4f4f4;width: 100%;margin: 0em;}.csharpcode .lnum { color: #606060; }&lt;/style&gt;&lt;p&gt;有人可能会担心用这种方式，属性多了之后性能会下降。如果你用Reflector之类的工具反编译一下生成的DLL，你就不会有这种顾虑了。C#对于 switch语句有相当力度的优化。简略地讲，当属性少时会将switch生成为一堆if else。对于字段类型为string，也会自动地转成dictionary + int。&lt;/p&gt;&lt;p&gt;经过测试这种方式比上面的缓存晚绑定的方式要快一倍。但是劣势也很明显，就是代码量太大了，而且不是一个好的设计，也不优雅。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a name="g"&gt;&lt;/a&gt;用动态生成的工具函数让动态属性访问更快一些&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;上面的方法速度上其实是最有优势的，但是缺乏可操作性。但是如果我们能为每个类动态地生成两个Get/Set方法，那么这个方法就实际可用了。注意，这时的动态调用并不是反射调用了。生成的方式就是使用Expression Tree编译出函数。&lt;/p&gt;&lt;p&gt;又因为这个方式是每个类一个函数，不像之前的方式都是一个属性一个访问对象。我们就可以利用C#的另一个特性来避免Dictionary的使用——泛型类中的静态成员：如果GenericClass&amp;lt;T&amp;gt;中定义的静态成员staticMember，那么GenericClass&amp;lt;A&amp;gt;中的staticMember和GenericClass&amp;lt;B&amp;gt;中的staticMember是不共享的。虽然查找泛型类也需要额外的运行时工作，但是代价比Dictionary查询要低。&lt;/p&gt;&lt;p&gt;在这个方法中，既没有用到反射，也没有用到缓存Dictionary。能更好地保证与手工代码性能的一致度。&lt;/p&gt;&lt;p&gt;实现的代码如下，鉴于代码量，只列出了get方法的代码：&lt;/p&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; DynamicMethod&amp;lt;T&amp;gt; : IMemberAccessor&lt;br/&gt;{&lt;br/&gt;    &lt;span class="kwrd"&gt;internal&lt;/span&gt; &lt;span class="kwrd"&gt;static&lt;/span&gt; Func&amp;lt;&lt;span class="kwrd"&gt;object&lt;/span&gt;, &lt;span class="kwrd"&gt;string&lt;/span&gt;, &lt;span class="kwrd"&gt;object&lt;/span&gt;&amp;gt; GetValueDelegate;&lt;br/&gt;&lt;br/&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;object&lt;/span&gt; GetValue(&lt;span class="kwrd"&gt;object&lt;/span&gt; instance, &lt;span class="kwrd"&gt;string&lt;/span&gt; memberName)&lt;br/&gt;    {&lt;br/&gt;        &lt;span class="kwrd"&gt;return&lt;/span&gt; GetValueDelegate(instance, memberName);&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    &lt;span class="kwrd"&gt;static&lt;/span&gt; DynamicMethod()&lt;br/&gt;    {&lt;br/&gt;        GetValueDelegate = GenerateGetValue();&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;static&lt;/span&gt; Func&amp;lt;&lt;span class="kwrd"&gt;object&lt;/span&gt;, &lt;span class="kwrd"&gt;string&lt;/span&gt;, &lt;span class="kwrd"&gt;object&lt;/span&gt;&amp;gt; GenerateGetValue()&lt;br/&gt;    {&lt;br/&gt;        var type = &lt;span class="kwrd"&gt;typeof&lt;/span&gt;(T);&lt;br/&gt;        var instance = Expression.Parameter(&lt;span class="kwrd"&gt;typeof&lt;/span&gt;(&lt;span class="kwrd"&gt;object&lt;/span&gt;), &lt;span class="str"&gt;&amp;quot;instance&amp;quot;&lt;/span&gt;);&lt;br/&gt;        var memberName = Expression.Parameter(&lt;span class="kwrd"&gt;typeof&lt;/span&gt;(&lt;span class="kwrd"&gt;string&lt;/span&gt;), &lt;span class="str"&gt;&amp;quot;memberName&amp;quot;&lt;/span&gt;);&lt;br/&gt;        var nameHash = Expression.Variable(&lt;span class="kwrd"&gt;typeof&lt;/span&gt;(&lt;span class="kwrd"&gt;int&lt;/span&gt;), &lt;span class="str"&gt;&amp;quot;nameHash&amp;quot;&lt;/span&gt;);&lt;br/&gt;        var calHash = Expression.Assign(nameHash, Expression.Call(memberName, &lt;span class="kwrd"&gt;typeof&lt;/span&gt;(&lt;span class="kwrd"&gt;object&lt;/span&gt;).GetMethod(&lt;span class="str"&gt;&amp;quot;GetHashCode&amp;quot;&lt;/span&gt;)));&lt;br/&gt;        var cases = &lt;span class="kwrd"&gt;new&lt;/span&gt; List&amp;lt;SwitchCase&amp;gt;();&lt;br/&gt;        &lt;span class="kwrd"&gt;foreach&lt;/span&gt; (var propertyInfo &lt;span class="kwrd"&gt;in&lt;/span&gt; type.GetProperties())&lt;br/&gt;        {&lt;br/&gt;            var property = Expression.Property(Expression.Convert(instance, &lt;span class="kwrd"&gt;typeof&lt;/span&gt;(T)), propertyInfo.Name);&lt;br/&gt;            var propertyHash = Expression.Constant(propertyInfo.Name.GetHashCode(), &lt;span class="kwrd"&gt;typeof&lt;/span&gt;(&lt;span class="kwrd"&gt;int&lt;/span&gt;));&lt;br/&gt;&lt;br/&gt;            cases.Add(Expression.SwitchCase(Expression.Convert(property, &lt;span class="kwrd"&gt;typeof&lt;/span&gt;(&lt;span class="kwrd"&gt;object&lt;/span&gt;)), propertyHash));&lt;br/&gt;        }&lt;br/&gt;        var switchEx = Expression.Switch(nameHash, Expression.Constant(&lt;span class="kwrd"&gt;null&lt;/span&gt;), cases.ToArray());&lt;br/&gt;        var methodBody = Expression.Block(&lt;span class="kwrd"&gt;typeof&lt;/span&gt;(&lt;span class="kwrd"&gt;object&lt;/span&gt;), &lt;span class="kwrd"&gt;new&lt;/span&gt;[] { nameHash }, calHash, switchEx);&lt;br/&gt;&lt;br/&gt;        &lt;span class="kwrd"&gt;return&lt;/span&gt; Expression.Lambda&amp;lt;Func&amp;lt;&lt;span class="kwrd"&gt;object&lt;/span&gt;, &lt;span class="kwrd"&gt;string&lt;/span&gt;, &lt;span class="kwrd"&gt;object&lt;/span&gt;&amp;gt;&amp;gt;(methodBody, instance, memberName).Compile();&lt;br/&gt;    }&lt;br/&gt;}&lt;style type="text/css"&gt;.csharpcode, .csharpcode pre{font-size: small;color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff;/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt {background-color: #f4f4f4;width: 100%;margin: 0em;}.csharpcode .lnum { color: #606060; }&lt;/style&gt;&lt;p&gt;但是，好吧，问题来了。泛型类就意味着需要在写代码的时候，或者说编译时知道对象的类型。这样也不符合我们一开始定义的目标。当然解决方案也是有的，就是再把那个Dictionary缓存请回来。具体方式参考上面的给Delegate做缓存的代码。&lt;/p&gt;&lt;p&gt;还有一个问题就是，这种Switch代码的性能会随着Property数量的增长而呈现大致为线性的下降。会最终差于Delegate缓存方式的调用。但是好在这个临界点比较高，大致在40个到60个属性左右。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a name="p"&gt;&lt;/a&gt;性能测试&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;我们先把所有的方式列一下。&lt;/p&gt;&lt;ol&gt;  &lt;li&gt;直接的对象属性读写 &lt;/li&gt;  &lt;li&gt;单纯的反射 &lt;/li&gt;  &lt;li&gt;使用Delegate.CreateDelegate生成委托并缓存 &lt;/li&gt;  &lt;li&gt;使用Expression Tree生成属性访问委托并缓存 &lt;/li&gt;  &lt;li&gt;让对象自己实现IMemberAccessor接口，使用Switch Case。 &lt;/li&gt;  &lt;li&gt;为每个类生成IMemberAcessor接口所定义的函数。（非泛型方式调用） &lt;/li&gt;  &lt;li&gt;为每个类生成IMemberAcessor接口所定义的函数。（泛型方式调用） &lt;/li&gt;&lt;/ol&gt;&lt;p&gt;我们来看一下这6种实现对应的7种使用方式的性能。&lt;/p&gt;&lt;p&gt;Debug：执行1000万次&lt;/p&gt;&lt;table border="1" cellspacing="0" cellpadding="2" width="399"&gt;&lt;tbody&gt;    &lt;tr&gt;      &lt;td valign="top" width="171"&gt;方法&lt;/td&gt;      &lt;td valign="top" width="113"&gt;第一次结果&lt;/td&gt;      &lt;td valign="top" width="113"&gt;第二次结果&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td valign="top" width="171"&gt;直接调用&lt;/td&gt;      &lt;td valign="top" width="113"&gt;208ms &lt;/td&gt;      &lt;td valign="top" width="113"&gt;227ms&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td valign="top" width="171"&gt;反射调用&lt;/td&gt;      &lt;td valign="top" width="113"&gt;21376ms &lt;/td&gt;      &lt;td valign="top" width="113"&gt;21802ms&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td valign="top" width="171"&gt;Expression委托&lt;/td&gt;      &lt;td valign="top" width="113"&gt;4341ms &lt;/td&gt;      &lt;td valign="top" width="113"&gt;4176ms&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td valign="top" width="171"&gt;CreateDelegate委托&lt;/td&gt;      &lt;td valign="top" width="113"&gt;4204ms&lt;/td&gt;      &lt;td valign="top" width="113"&gt;4111ms&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td valign="top" width="171"&gt;对象自身Switch&lt;/td&gt;      &lt;td valign="top" width="113"&gt;1653ms&lt;/td&gt;      &lt;td valign="top" width="113"&gt;1338ms&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td valign="top" width="171"&gt;动态生成函数&lt;/td&gt;      &lt;td valign="top" width="113"&gt;2123ms &lt;/td&gt;      &lt;td valign="top" width="113"&gt;2051ms&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td valign="top" width="171"&gt;（泛型）动态生成函数&lt;/td&gt;      &lt;td valign="top" width="113"&gt;1167ms&lt;/td&gt;      &lt;td valign="top" width="113"&gt;1157ms&lt;/td&gt;    &lt;/tr&gt;  &lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;Release：执行1000万次&lt;/p&gt;&lt;table border="1" cellspacing="0" cellpadding="2" width="400"&gt;&lt;tbody&gt;    &lt;tr&gt;      &lt;td valign="top" width="170"&gt;方法&lt;/td&gt;      &lt;td valign="top" width="113"&gt;第一次结果&lt;/td&gt;      &lt;td valign="top" width="116"&gt;第二次结果&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td valign="top" width="170"&gt;直接调用&lt;/td&gt;      &lt;td valign="top" width="113"&gt;73ms &lt;/td&gt;      &lt;td valign="top" width="116"&gt;77ms&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td valign="top" width="170"&gt;反射调用&lt;/td&gt;      &lt;td valign="top" width="113"&gt;20693ms&lt;/td&gt;      &lt;td valign="top" width="116"&gt;21229ms&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td valign="top" width="170"&gt;Expression委托&lt;/td&gt;      &lt;td valign="top" width="113"&gt;3852ms&lt;/td&gt;      &lt;td valign="top" width="116"&gt;3853ms&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td valign="top" width="170"&gt;CreateDelegate委托&lt;/td&gt;      &lt;td valign="top" width="113"&gt;3704ms&lt;/td&gt;      &lt;td valign="top" width="116"&gt;3748ms&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td valign="top" width="170"&gt;对象自身Switch&lt;/td&gt;      &lt;td valign="top" width="113"&gt;1105ms&lt;/td&gt;      &lt;td valign="top" width="116"&gt;1116ms&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td valign="top" width="170"&gt;动态生成函数&lt;/td&gt;      &lt;td valign="top" width="113"&gt;1678ms&lt;/td&gt;      &lt;td valign="top" width="116"&gt;1722ms&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td valign="top" width="170"&gt;（泛型）动态生成函数&lt;/td&gt;      &lt;td valign="top" width="113"&gt;843ms&lt;/td&gt;      &lt;td valign="top" width="116"&gt;862ms&lt;/td&gt;    &lt;/tr&gt;  &lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;动态生成的函数比手写的Switch还要快的原因是手写的Switch要使用到Dictionary来将String类型的字段名映射到int值。而我们生成的Switch是直接使用属性的hashcode值进行的。所以会更快。完整的代码可以从&lt;a href="http://files.cnblogs.com/nankezhishi/DynamicAccess.zip"&gt;这里&lt;/a&gt;下载到。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/nankezhishi/aggbug/2346973.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/archive/2012/02/11/dynamicaccess.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/nankezhishi/archive/2012/01/10/2318643.html</id><title type="text">一次模块划分的争论及其结局</title><summary type="text">公司最近正在对整个产品进行大规模的重构，把原先基于Web的产品线全部转向Android平台。随之而来的就是产品整体架构设计上的大讨论。作为其中一项最为旷日持久的争论的发起者，我觉得有必要把这个事件记下来。无论现在的思路或是观点是成熟的还是幼稚的。以后都可以引以为鉴。 先来描述一下我们要做什么。简单而言就是一个横跨各个内容源的书籍阅读平台。这个平台的目标不仅仅是方便用户在一个终端上，以一种统一的方...</summary><published>2012-01-10T15:57:00Z</published><updated>2012-01-10T15:57:00Z</updated><author><name>南柯之石</name><uri>http://www.cnblogs.com/nankezhishi/</uri></author><link rel="alternate" href="http://www.cnblogs.com/nankezhishi/archive/2012/01/10/2318643.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/nankezhishi/archive/2012/01/10/2318643.html"/><content type="html">&lt;p&gt;公司最近正在对整个产品进行大规模的重构，把原先基于Web的产品线全部转向Android平台。随之而来的就是产品整体架构设计上的大讨论。作为其中一项最为旷日持久的争论的发起者，我觉得有必要把这个事件记下来。无论现在的思路或是观点是成熟的还是幼稚的。以后都可以引以为鉴。&lt;/p&gt;  &lt;p&gt;先来描述一下我们要做什么。简单而言就是一个横跨各个内容源的书籍阅读平台。这个平台的目标不仅仅是方便用户在一个终端上，以一种统一的方式购买、阅读到所有内容源的书。同时，这个平台作为一个开放式的平台，也以方便内容源的接入为目标，这个平台就是起着这样一台读者与内容源之间的通道的作用。&lt;/p&gt;  &lt;p&gt;这里的内容源是指持有内容版权的内容提供商，比如全国各家出版社、网络小说站点（如起点、红袖、纵横天下等）及其它数字出版资源提供商（如当当、淘花、云中书城、方正Apabi等）&lt;/p&gt;  &lt;p&gt;这次改造的目标主要有这样几点：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;开放平台：提供Apple Strore这样的机制，让其它内容源的开发者可以有机会自主地接入这个平台。比如当当网可以用我们给的SDK自己做一个Android APK，在里面卖当当的书。&lt;/li&gt;    &lt;li&gt;升级管理：将原来一整个系统分拆成各个模块，各个模块可以独立地发布、测试。并使用强制升级的方式回避向后兼容的问题。&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;争论问题的背景是：不同的内容源所提供的内容的格式不尽相同。有EPUB，有PDF，有TXT，有SNB，也有DOC的。我们显然要尽量多地去支持这些书籍的格式。&lt;/p&gt;  &lt;p&gt;争论的焦点在于：对内容的阅读这个功能，应该与每个内容源自身的APK合并还是分开实现？很多人觉得当然是要分开，但是其实现实中的问题没有这么简单。比如如下的几个问题：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;有的书不是整本购买，而是一章章地买。阅读器看到一半儿，发现有一章没有买，是不是发起购买呢？如果是，应该向谁发起呢？&lt;/li&gt;    &lt;li&gt;有版权的书，都是经过加密的，想解析这种加密好的文件，需要有Key，阅读器发现某一章没有Key了，找谁要Key呢？&lt;/li&gt;    &lt;li&gt;TXT是整个的，没有章节，如果想为TXT附加章节信息，可能就要额外的实现，但是不同内容源的实现方式不同。如果章节列表在阅读中显示，阅读器又怎么知道不同内容源对于章节列表的定义方式呢？&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;诸如此类的问题还有很多。即便如此，我个人自始至终都坚持分开。但我的上级领导坚持认为应该合并在一起，一个APK应当包括分类浏览、购买、阅读等一整套的功能，自成体系。如果仅仅是我一个人坚持分开，显然不会有争论，级别在这儿摆着，他一句：“这个事情不要讨论了，就是这个样子的。”我就直接歇了。好在主导这次重构的两个架构师的意见和我比较一致。于是我们几个人在大庭广众之下（一时找不到会议室）争论了多次。最后上级的上级看不下去。把我们一票人拉去群体PK。&lt;/p&gt;  &lt;p&gt;最后BOSS给的解决方案是这样的：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;如果讨论不清楚就合一块。以后有必要了再分开。&lt;/li&gt;    &lt;li&gt;这两种方式没有实质上的冲突，可以并存，也应该并存。只是先实现哪种方式的问题。&lt;/li&gt;    &lt;li&gt;讨论哪一种方式更好的时间，还不如直接让做的人自己选一个自己觉得爽的方试直接做了。&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;虽然我就是那个做的人，BOSS说我爽就行。但是对于这个解决方案我目前是相当的不满意。&lt;/p&gt;  &lt;p&gt;从技术的角度，我觉得合比分要简单得多，先分着做，出现无法解决的问题，再合起来所用的时间应该比把一个系统拆分成不同模块所用的时间更少。先分着做，也能把层次关系尽早理清。&lt;/p&gt;  &lt;p&gt;从管理的角度，做正确的事情，应该比做事的效率重要吧？&lt;/p&gt;&lt;img src="http://www.cnblogs.com/nankezhishi/aggbug/2318643.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/archive/2012/01/10/2318643.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/nankezhishi/archive/2011/12/22/2298729.html</id><title type="text">电子书籍质量保证事件分析</title><summary type="text">在产品的验收阶段，正式发布前一周，紧急动员全公司的人，对1万多本电子书进行人肉测试。我觉得这事儿真是有点儿意思。不知道各位怎么看？也许所有的公司或产品都有可能出现类似的状况，但是我想分析一下：为什么会出现这样的事件，有没有什么办法尽量避免出现类似的事情？ 故事的背景是，公司新研发的电子书设备发布在即；同时，为了丰富在设备上可以阅读的书籍的来源，也在接入一些其它公司的内容，其格式为...</summary><published>2011-12-22T15:30:00Z</published><updated>2011-12-22T15:30:00Z</updated><author><name>南柯之石</name><uri>http://www.cnblogs.com/nankezhishi/</uri></author><link rel="alternate" href="http://www.cnblogs.com/nankezhishi/archive/2011/12/22/2298729.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/nankezhishi/archive/2011/12/22/2298729.html"/><content type="html">&lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 在产品的验收阶段，正式发布前一周，紧急动员全公司的人，对1万多本电子书进行人肉测试。我觉得这事儿真是有点儿意思。不知道各位怎么看？也许所有的公司或产品都有可能出现类似的状况，但是我想分析一下：为什么会出现这样的事件，有没有什么办法尽量避免出现类似的事情？&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 故事的背景是，公司新研发的电子书设备发布在即；同时，为了丰富在设备上可以阅读的书籍的来源，也在接入一些其它公司的内容，其格式为PDF，而我们公司的电子书本来就是支持PDF阅读。&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 然而，当最近一周这些内容全部上线的时候，测试人员发现一些很严重的问题。比如：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;部分书籍太大，有40-100M，设备表示压力很大，打开就需要1分钟，翻一页要10秒，或者干脆就死掉了。&lt;/li&gt;    &lt;li&gt;PDF的排版是固定的，不像EPUB一样是流式的，可以运行时自动调整。而且多数PDF的排版目标是为了打印效果。所以在800*600分辨率的设备上阅读的时候相当吃力。（设备目前不支持触屏，不支持缩放。）&lt;/li&gt;    &lt;li&gt;部分书籍含有乱码。&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 显然上述情况的存在，会严重影响新产品质量。甚至会影响新产品发布。像一开始说的，我们的做法是逐本人肉测试。做这个决定的时候，是一个周五下午5点。我们甚至连完整的书单都还没有。而计划是下一个周二时，第一轮验收要结束。再一个周二产品就要发布了。&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 动员全公司的人帮忙做测试的时候，很多人都想问事情怎么会发展到这种地步，其实我也想问这个问题。从接入其它公司内容这个想法出来，到找其它公司洽谈，到我们技术上实现接入。所有人似乎都只看到了一个美好的愿景，却对其中潜在的问题视而不见。当然，一开始也有人提到要找一些样书来看看，但是“看看”就够了吗？&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;什么样的书可以作为接入可行性的样书？各种类型、大小、编码、章节细分度、字体大小、图片多少、原生/扫描…等等…的组合？&lt;/li&gt;    &lt;li&gt;样书拿来之后，用什么样的标准来判定？有多少指标？应该要有文档化吧。&lt;/li&gt;    &lt;li&gt;样书判定不合格就不接入了吗？&lt;/li&gt;    &lt;li&gt;设备还有没有优化的空间？让更多的书合格？&lt;/li&gt;    &lt;li&gt;我们觉得合格了，最终用户觉得就是无法接受怎么办？&lt;/li&gt;    &lt;li&gt;部分样书不合格，但是决定接入，那如何在所有书中找到所有不合格的书？&lt;/li&gt;    &lt;li&gt;书籍合格性判定，能不能自动化？还是一定要人肉。&lt;/li&gt;    &lt;li&gt;如果可以自动化，是不是要开发自动化工具，需要多少资源？&lt;/li&gt;    &lt;li&gt;如果不能自动化，需要以什么样的方法判定？找实习生？临时工？他们的判定结果的质量如何控制？&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 其实上面这些问题我也是事后总结的。但是事前详细想想也是可以想到很多问题。但是在下面的形势面前：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;接入的事情迫在眉睫&lt;/li&gt;    &lt;li&gt;我们的设备本来就是支持PDF阅读的&lt;/li&gt;    &lt;li&gt;公司战略目标就是要丰富内容。人家谈判那么久，你这那那这的，为什么不先花时间接入进来再说？&lt;/li&gt;    &lt;li&gt;我们同时接入的第三方有很多。&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 当时的工作重点，是不惜一切代价地把内容接入进来。一切问题都可以认为是我们的问题。在这样的大环境下，内容接入以外的事情都是次要的。到了验收阶段，产品质量就成了主要矛盾，就需要花大力气解决了。听起来很合情合理。细想起来真是屎一样的逻辑，无脊椎动物的逻辑。&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 产品质量监控应当贯穿于产品的每个阶段，每个环节。问题暴露得越早越好。这样做事情才像是发现问题，然后解决问题；而不是遇到问题，然后打补丁。理想状况下，二者最后结果可能会一样，但是过程却是完全不同的体验。在第一种环境下，只要你喜欢这份工作，你会乐在其中，享受过程；在第二种环境下，即使你喜欢这份工作，也会被搞得焦头烂额，身心俱疲，最后被补丁埋葬。&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 相对于写代码而言，尤其是已经有了设计的问题，想问题才真正属于脑力活。这里说的问题并不是指设计问题，软件设计也是脑力活，但是依然局限在某技术框架之中，有了经验，设计也是体力活。这里指的问题是整个产品的设计方向，整个公司的做事方式，市场永远是变化的，永远都有新的问题出现，永远是优胜劣汰。有人觉得这不是技术人员做的事情，此言差矣。像上面列出的一些问题，可以从技术上解决的就从技术上解决，但是很多问题不是单纯的技术问题，甚至可能会为公司引入一个新的部门，这不就是一个问题引发的工作机会吗。谁想到了这些问题，谁自然就有发言权（当然可能没有决策权）。&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 重要的是，是事前把问题提出来，并文艺地解决掉。还是等到用户投诉电话打来了，才发现自己要被领导骂成2B了？现实可能比较普通一些，也最普遍，问题非要到大规模测试的时候才能暴露出来，虽然能保证用户看不到问题了，我们也得累得和2B一样。但是这样的状态谁想要？&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 所以可能一些刚入行的程序员，常常抱怨自己的老大P事儿不干，就喜欢说大话。当然，我不否认很多老大的确是这样，但是一个称职的老大的确应该是P事儿都不干，代码一行不写。因为他有更重要的事情要做。他要想，应该做什么，不应该做什么，先做什么，后做什么，做到什么样是OK了，把什么事儿分给什么人做，做的过程中可能会出现什么问题，有什么应对的策略。总之一句话，能保证项目按时保质地完成，就是合格的老大；同时能让手下很文艺地工作，天天很Happy地回家，那他就是一个NB的老大。&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 所以老大的一个重要职责就应该是把工作给手下讲清楚，做什么，不做什么……，当然，老大如果充分信任自己的小弟，也可以让小弟自己全权负责。但是其实这个时候很容易出样下面这样的情况。&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;老大：我手上有200张票，明天的，你发一下。&lt;/li&gt;    &lt;li&gt;小弟：好。&lt;/li&gt;    &lt;li&gt;小弟想了想，为了让更多的人享受到这个福利，同时也做推广，决定以家庭为单位，一家一张。于是发给了200个人家。&lt;/li&gt;    &lt;li&gt;小弟回来向老大汇报工作。&lt;/li&gt;    &lt;li&gt;老大：我X，你为人家想想好不好，想去还再掏钱，你应该一家发三张，一般家庭都是三口之家。&lt;/li&gt;    &lt;li&gt;小弟：……&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 大家也不用考虑应该怎么发票，这完全是我YY的情节，有些事情只关于做事的方法和理念，没有对错。每个人关注点不同，方法自然不同。也许所有事情都有个最佳方案，但是一般都需要一定的调研才能得出结论。比如上面的例子，如果想要知道哪种发法收益最大，恐怕要调研一家人拿到一张票时，有多少比例会去买，有多少直接放弃。和一般和是什么票有关系。但是首先要判断调研值不值？时间够不够？要效率还是要效果？多数情况下二者是难以兼得的。老大有自己的考虑，小弟也有自己的考量。谁对谁错？Who knows. I don’t care. 但是这时候，你觉得最重要的是什么呢？&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 从项目和公司的角度而言，最重要的就是事情有没有搞定。所以无论票怎么发，事情最终是搞定了，小弟也体验了把自己做主的感觉，过程也不错。只是老大觉得搞得不漂亮，然后小弟估计也开心不起来。说到这里，我觉得我的意思已经表达清楚了。&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 感觉说得有点儿远，其实说的是同一个事儿——为什么事情会发展到这种地步。当然，上面只是我个人的一点儿想法。我也还没有真正做过Leader，也许我做Leader之后会有更加不同的理解吧。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/nankezhishi/aggbug/2298729.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/nankezhishi/archive/2011/12/22/2298729.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry></feed>
