<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>冒号空间 &#187; 框架</title>
	<atom:link href="http://blog.zhenghui.org/tag/framework/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.zhenghui.org</link>
	<description>自然、人类、机器</description>
	<lastBuildDate>Fri, 16 Jul 2010 18:33:48 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>冒号课堂§3.4：事件驱动</title>
		<link>http://blog.zhenghui.org/2009/09/11/colon-class-3_4/</link>
		<comments>http://blog.zhenghui.org/2009/09/11/colon-class-3_4/#comments</comments>
		<pubDate>Fri, 11 Sep 2009 12:37:48 +0000</pubDate>
		<dc:creator>hui</dc:creator>
				<category><![CDATA[冒号课堂]]></category>
		<category><![CDATA[事件驱动式编程]]></category>
		<category><![CDATA[回调函数]]></category>
		<category><![CDATA[控制反转]]></category>
		<category><![CDATA[框架]]></category>
		<category><![CDATA[编程范式]]></category>
		<category><![CDATA[观察者模式]]></category>

		<guid isPermaLink="false">http://blog.zhenghui.org/?p=393</guid>
		<description><![CDATA[<b>事件驱动</b>——有事我叫你，没事别烦我（<em>事件驱动式编程简谈</em>）<br/>
•	它们（同步回调和异步回调）都使调用者不再依赖被调者，将二者从代码上解耦，异步调用更将二者从时间上解耦<br/>
•	它们（控制反转、依赖反转和依赖注射）的主题是控制与依赖，目的是解耦，方法是反转，而实现这一切的关键是抽象接口<br/>
•	“回调”强调的是行为方式——低层反调高层，而“抽象接口”强调的是实现方式——正是由于接口具有抽象性，低层才能在调用它时无需虑及高层的具体细节，从而实现控制反转<br/>
•	控制反转导致了事件驱动式编程的被动性<br/>
•	事件驱动式还具有异步性的特征，这是由事件的不可预测性与随机性决定的<br/>
•	独立是异步的前提，耗时是异步的理由<br/>
•	发行/订阅模式正是观察者模式的别名，一方面可看作简化或退化的事件驱动式，另一方面可看作事件驱动式的核心思想]]></description>
			<content:encoded><![CDATA[<h1 style="text-align: center"><span style="font-family: 宋体">冒号课堂</span></h1>
<strong><span style="font-size: 13pt; font-family: 宋体">第三课 常用范式(4)</span></strong>

<!-- below comes from generated html -->
<head><link rel="stylesheet" href="http://blog.zhenghui.org/css/colonclass.css" type="text/css"></head>

<div lang="zh-CN" class="article" title="事件驱动"><div class="titlepage"><div><div><h1 class="title"><a name="id578923"></a>3.4 事件驱动——有事我叫你，没事别烦我</h1></div><div><div class="author"><h3 class="author">郑晖</h3></div></div><div><div class="abstract" title="摘要"><p class="title"><b>摘要</b></p><p>事件驱动式编程简谈</p></div></div></div><hr /></div><div class="toc"><p><b>目录</b></p><dl><dt><span class="section"><a href="#preview">！预览</a></span></dt><dt><span class="section"><a href="#question">？提问</a></span></dt><dt><span class="section"><a href="#explaination">：讲解</a></span></dt><dt><span class="section"><a href="#note">，插语</a></span></dt><dt><span class="section"><a href="#summary">。总结</a></span></dt><dt><span class="section"><a href="#reference">“”参考</a></span></dt></dl></div><div class="epigraph"><div class="literallayout"><p>劳心者治人,劳力者治于人 </p></div><div class="attribution"><span>—<span class="attribution">《孟子•滕文公上》</span></span></div></div><div class="section" title="！预览"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="preview"></a>！预览</h2></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>
                    它们（同步回调和异步回调）都使调用者不再依赖被调者，将二者从代码上解耦，异步调用更将二者从时间上解耦
                </p></li><li class="listitem"><p>
                    它们（控制反转、依赖反转和依赖注射）的主题是控制与依赖，目的是解耦，方法是反转，而实现这一切的关键是抽象接口
                </p></li><li class="listitem"><p>
                    “回调”强调的是行为方式——低层反调高层，而“抽象接口”强调的是实现方式——正是由于接口具有抽象性，低层才能在调用它时无需虑及高层的具体细节，从而实现控制反转
                </p></li><li class="listitem"><p>
                    控制反转导致了事件驱动式编程的被动性
                </p></li><li class="listitem"><p>
                    事件驱动式还具有异步性的特征，这是由事件的不可预测性与随机性决定的
                </p></li><li class="listitem"><p>
                    独立是异步的前提，耗时是异步的理由
                </p></li><li class="listitem"><p>
                    发行/订阅模式正是观察者模式的别名，一方面可看作简化或退化的事件驱动式，另一方面可看作事件驱动式的核心思想
                </p></li></ul></div></div><div class="section" title="？提问"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="question"></a>？提问</h2></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>什么是事件？有哪些不同类型的事件？</p></li><li class="listitem"><p>什么是回调函数？什么是异步同调？它们有什么用处？</p></li><li class="listitem"><p>控制反转的目的是什么？它是如何实现的？在框架设计中起什么作用？</p></li><li class="listitem"><p>控制反转、依赖反转原则和依赖注射的共同点是什么？</p></li><li class="listitem"><p>事件驱动式编程有哪些关键步骤？</p></li><li class="listitem"><p>异步过程特点和作用是什么？</p></li><li class="listitem"><p>事件驱动式编程最重要的特征是什么？它们是如何实现的？</p></li><li class="listitem"><p>事件驱动式与观察者模式、MVC架构有何关系？</p></li></ul></div></div><div class="section" title="：讲解"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="explaination"></a>：讲解</h2></div></div></div><p>
            逗号渐觉睡虫上脑，开始闭目点头。正神游之际，忽觉腰间一阵酥麻。惺眼微睁，原是被引号的胳膊肘给捅的，顿时警醒。抬头见讲台上的老冒正目光灼灼地盯着自己，不禁脸颊微烫，嗫嚅道：“不好意思，昨晚睡得太晚了。”
        </p><p>
            冒号却不以为意：“正愁找不到新话题呢，你倒启发我了。话说课堂上睡觉大抵有三种方式——”
        </p><p>
            话音未落，有人已笑不自禁。
        </p><p>
            “第一种是警觉式：想睡可又担心被老师发现，不时睁眼查看周围的变化。同时双耳保持警戒，一有异动立刻挺直身板。”冒号有板有眼地形容，“第二种是宽心式：俯桌酣睡，如处无人之境。境界至高者或可雷打不动，或可鼾声如雷。”
        </p><p>
            “总之是很雷人。”叹号的网络新语再度引发笑声。
        </p><p>
            冒号继续分析：“第三种是托付式：请人放哨，非急勿扰。遂再无顾忌，大可封目垂耳，安心入眠。请问你们乐意采用哪种方式？”
        </p><p>
            “第一种方式睡不踏实，不得已而为之。敢用第二种方式的人多半没心没肺，估计IT人都达不到那种境界。只要有同伴在身旁，我想大家都会选第三种方式的。”句号的回答获得一致认同。
        </p><p>
            冒号续问：“好，抛开第二种方式不谈，为什么第三种要比第一种优越呢？”
        </p><p>
            句号回答：“犯困者既要打盹又要警戒，必然苦不堪言。如果把警戒的任务委托同伴，两人分工合作，自然愉快得多。”
        </p><p>
            冒号再问：“他们是如何合作的呢？”
        </p><p>
            “放哨者一旦发现有情况，立即通知犯困者采取行动——睁眼坐直，作认真听讲状。”句号说得是绘声绘色。
        </p><p>
            除了两位当事人略显尴尬外，其他人均乐不可支。
        </p><p>
            眼见时机成熟，冒号不再兜圈：“采用警觉式者<span class="emphasis"><em>主动去</em></span><span class="strong"><strong>轮询</strong></span>（polling），行为取决于自身的观察判断，是<span class="emphasis"><em>流程驱动</em></span>的，符合常规的<span class="term">流程驱动式编程</span>（Flow-Driven Programming）的模式。采用托付式者<span class="emphasis"><em>被动等</em></span><span class="strong"><strong>通知</strong></span>（notification），行为取决于外来的突发事件，是<span class="emphasis"><em>事件驱动</em></span>的，符合<span class="term">事件驱动式编程</span>（Event-Driven　Programming，简称EDP）的模式。下面我们就来说说这种编程范式。”
        </p><p>
            逗号瓮声瓮气道：“没想到打瞌睡打出了个范式。”
        </p><p>
            冒号瞥了他一眼，继续说下去：“为完成一样事，既可以采用流程驱动式，也可以采用事件驱动式。这样的例子在生活中可谓俯拾即是，刚才逗号同学为大家现场示范了一个，谁还能举出其他范例？”
        </p><p>
            叹号抢先举例：“与客户打交道，推销员主动打电话或登门拜访，他的工作是流程驱动的；接线员坐等电话，他的工作是事件驱动的。”
        </p><p>
            问号也说：“同样是交通工具，公共汽车主要是流程驱动的，它的路线已预先设定；出租车主要是事件驱动的，它的路线基本上由随机搭载的乘客所决定。” 
        </p><p>
            引号以个人经验作例：“购买喜爱的杂志可以选择频繁光顾报刊亭，也可以选择一次性订阅。浏览关注的新闻网站或博客，可以直接访问站点，也可以订阅相应的RSS。主动检查所关心的内容是否更新是流程驱动的，用订阅的方式是事件驱动的。”
        </p><p>
            句号回到本行：“Windows下的许多工作既可以在DOS下用批处理程序实现，也可以在图形界面下完成。前者不需人工干预，显然是流程驱动的；后者毫无疑问是事件驱动的。”
        </p><p>
            “看来你们对这种范式很熟悉嘛。不过，它原理虽简单，威力却无穷。看似一招，实则暗藏百式，甚可幻化千招。个中精妙之处，断非一时可以尽述。”冒号不知不觉中又走进了武侠的世界。
        </p><p>
            众人听了，暗疑老冒有些言过其实。
        </p><p>
            冒号正式入题：“首当其冲的问题是：何谓事件？通俗地说，它是已经发生的某种令人关注的事情。在软件中，它一般表现为一个程序的某些信息状态上的变化。基于事件驱动的系统一般提供两类的<span class="term">内建事件</span>（built-in event）：一类是<span class="term">底层事件</span>（low-level event）或称<span class="term">原生事件</span>（native event），在用户图形界面（GUI）系统中这类事件直接由鼠标、键盘等硬件设备触发；一类是<span class="term">语义事件</span>（semantic event），一般代表用户的行为逻辑，是若干底层事件的组合。比如鼠标拖放（drag-and-drop）多表示移动被拖放的对象，由鼠标按下、鼠标移动和鼠标释放三个底层事件组成。”
        </p><p>
            问号推想：“编程人员应该还能创造新的事件类型吧？”
        </p><p>
            “那是当然。”冒号点点头，“还有一类<span class="term">用户自定义事件</span>（user-defined event）。它们可以是在原有的内建事件的基础上进行的包装，也可以是纯粹的<span class="term">虚拟事件</span>（virtual event）。除此之外，编程者不但能定义事件，还能产生事件。虽然大部分事件是由外界激发的<span class="term">自然事件</span>（natural event），但有时程序员需要主动激发一些事件，比如模拟用户鼠标点击或键盘输入等，这类事件被称为<span class="term">合成事件</span>（synthetic event）<a class="link" href="#note1"><sup>[1]</sup></a>。这些都进一步丰富完善了事件体系和事件机制，使得事件驱动式编程更具渗透性。”
        </p><p>
            叹号嘟哝了一句：“看来这里边还有点名堂。”
        </p><p>
            “名堂多着呢！”冒号回应，“事件固然是事件驱动式编程的核心概念，但一个编程范式的独特之处绝不仅仅是一些概念，更重要的是建立于这些概念之上的思维模式。为了了解这种范式与众不同的特点，我们先看看如何利用win32的API在windows下创建一个简单的窗口——”
        </p><div class="informalexample"><pre class="programlisting">
/** 一个win32窗口程序 */
…WinMain(...) // windows应用程序的主函数
{
    // 第一步——注册窗口类别
    ...;
    windowClass.lpfnWndProc = WndProc; // 指定该类窗口的回调函数
    windowClass.lpszClassName = windowClassName; // 指定该类窗口的名字
    RegisterClassEx(&amp;windowClass);

    //第二步——创建一个上述类别的窗口
    CreateWindowEx(…, windowClassName, ...);
    …;

    //  第三步——消息循环
    while (GetMessage(&amp;msg, NULL, 0, 0)  &gt; 0) // 获取消息
    {
        TranslateMessage(&amp;msg); // 翻译键盘消息
        DispatchMessage(&amp;msg);  // 分派消息
    }
}

// 第四步——窗口过程（处理消息）
…WndProc(…, msg,...)
{
    switch (msg)
    {
        case WM_SIZE:   …;   // 用户改变窗口尺寸
        case WM_MOVE: …; // 用户移动窗口
        case WM_CLOSE: …; // 用户关闭窗口
        …;
    }
}</pre></div><p>
            “没有选用Java、Visual C++、C#、VB或者Delphi来实现窗口，是因为它们高度的封装和强大的IDE掩盖了部分事件机制。如果你们对win32 API不太熟悉，没有关系。为了减少语言和API上的障碍，同时突出重点，这里最大限度地省略了次要的过程和参数等，仅保留脉络主干。”冒号解释，“从中看出到，创建一个能响应用户操作的win32窗口共分四步：注册窗口类别、创建窗口、消息循环和窗口过程。”
        </p><p>
            问号对概念很敏感：“消息与事件是一回事吗？”
        </p><p>
            “严格说来它们不是一回事，但如果你不想深究，不加区分也无大碍。概略地说，消息是Windows内部最基本的通讯方式，事件需要通过消息来传递，是消息的主要来源。每当用户触发一个事件，如移动鼠标或敲击键盘，系统都会将其转化为消息并放入相应程序的消息队列（message queue）中<a class="link" href="#note2"><sup>[2]</sup></a>。”冒号解答着，“明白了这一点，上面的代码就不难理解了——在<span class="emphasis"><em>消息循环</em></span>中，程序通过GetMessage不断地从消息队列中获取消息，经过TranslateMessage预处理后再通过DispatchMessage将消息送交<span class="emphasis"><em>窗口过程</em></span>WndProc处理。”
        </p><p>
            逗号琢磨了一会，不解地问：“窗口过程应该是在分派消息时被调用的，但我怎么想不出DispatchMessage是如何联系到WndProc的？”
        </p><p>
            冒号为其解惑：“DispatchMessage的消息参数含有事发窗口的<span class="term">句柄</span>（handle），从而可以得到窗口过程WndProc<a class="link" href="#note3"><sup>[3]</sup></a>。至于窗口与窗口过程之间是如何建立联系的，回看前面两步就一目了然了：当初在创建窗口时指明了窗口类别名windowClassName，而窗口类别windowClass又绑定了窗口过程。”
        </p><p>
            叹号有点纳闷：“干嘛要绕这么大的弯子，直接调用WndProc不就得了？”
        </p><p>
            “对于这个简单的程序来说，的确区别不大。但假如再增添其他菜单、按钮、文本框之类的控件，每个控件都可绑定自己的窗口过程，那么到底该调用哪个才对呢？”冒号反问。
        </p><p>
            叹号虽有所悟，但仍有心结：“总觉得窗口过程的用法有些怪怪的。”
        </p><p>
            冒号一敲桌案：“没错！怪就怪在编程者自己写了一个应用层的函数，却不直接调用它，而是通过库函数<span class="emphasis"><em>间接调用</em></span>。这类函数有个专用名称：<span class="term">回调函数</span>（callback）。”
        </p><p>
            引号忍不住插话：“回调函数我知道，在C和C++中就是函数指针嘛。”
        </p><p>
            “确切地说，函数指针是C和C++用来实现callback的一种方式。此外，抽象类（abstract class）、接口（interface）、C++中的泛型函子（generic functor）和C#中的委托（delegate）都可实现callback。我们先图解一下回调机制。”冒号调出一张图示——
        </p><div class="figure"><a name="id619557"></a><p class="title"><b>图3-4. 普通函数与回调函数的对比</b></p><div class="figure-contents"><div class="mediaobject"><img src="http://blog.zhenghui.org/img/colonclass/figure3-4.jpg" alt="普通函数与回调函数的对比"></div></div></div><br class="figure-break"><p>
            “如果我们把系统划分为两层<a class="link" href="#note4"><sup>[4]</sup></a>：低层的函数库和高层的应用程序。同样作为主函数的辅助函数，左图中的普通函数直接被主函数调用，然而右图中的回调函数却是通过库函数间接被主函数调用的。”冒号的手影在幻灯下上下翻飞。
        </p><p>
            句号点出要害：“一般都是高层代码调用低层代码，callback反其道而行之，因此显得与众不同。”
        </p><p>
             “所言极是。一方面，在软件模块分层中，低层模块为高层模块提供服务，并且不能依赖高层模块，以保证其可重用性；另一方面，通常被调者（callee）为调用者（caller）提供服务，调用者依赖被调者。两相结合，决定了低层模块多为被调者，高层模块多为调用者。但这种惯例并不总是合适的——低层模块为了追求更强的普适性和可扩展性，有时也有调用高层模块的需求，于是便邀callback前来相助。我们看一个简单的例子。”冒号写下一段Java代码——
        </p><div class="informalexample"><pre class="programlisting">
String[] strings = {"Please", "sort", "the", "strings", "in", "REVERSE", "order"};
Arrays.sort(strings, new Comparator&lt;String&gt;() {
    public int compare(String a, String b){ return -a.compareToIgnoreCase(b); }
    });</pre></div><p>
            引号很快读懂了代码：“这是将字符串组不区分大小写地逆序排列。其中Comparator的匿名类实现了callback，因为它的方法compare是在类库中被调用的。”
        </p><p>
            “此处callback的好处是显而易见的——它使得Arrays.sort不再局限于自然排序，允许用户自行定制排序规则，大大提高了算法的重用性。”冒号说着将幻灯片又翻到前页，“回头再看win32窗口程序的例子，其中第三步消息循环那段代码不依赖应用程序代码，完全可以提炼出来作为library的一部分。事实上，在Visual C++里这段代码就‘下放’到MFC类库中去了。假设窗口过程由应用程序直接调用，那么消息循环中的代码将不再具有独立性，无法作为公因子分解出来。”
        </p><p>
            叹号块垒顿消，畅然无比：“终于搞清那个怪异的窗口过程了！每个窗口在创建时就携带了一个callback，以后每当系统侦查到事件，都能轻易地从事发窗口身上找到它的callback，然后调用它以响应事件。”
        </p><p>
             “这等于将侦查事件与响应事件两项任务进行了正交分解，降低了软件的耦合度和复杂度。”句号言犹未尽，又加了一句，“就像刚才，引号负责侦查事件——警戒，逗号负责响应事件——警醒。想法很好，可惜配合不够默契，还是给人逮住了。”
        </p><p>
            逗、引二人大窘，余者大笑。
        </p><p>
            “仔细比较，以上两个callback的用法还是稍有不同的。在字符串组排序中，callback在作为参数传入低层的函数后，很快就在该函数体中被调用；在窗口程序中，callback则先被储存起来，至于何时被调用完全是未定之数。用一句话概括：前者属<span class="emphasis"><em>同步</em></span>（synchronous）回调，后者属<span class="emphasis"><em>异步</em></span>（asynchronous）回调。它们都<span class="strong"><strong>使调用者不再依赖被调者</strong></span>，将二者<span class="strong"><strong>从代码上解耦</strong></span>，异步调用更将二者<span class="strong"><strong>从时间上解耦</strong></span>。”冒号显示出一副新图—— 
        </p><div class="figure"><a name="id619643"></a><p class="title"><b>图3-5. 异步回调</b></p><div class="figure-contents"><div class="mediaobject"><img src="http://blog.zhenghui.org/img/colonclass/figure3-5.jpg" alt="异步回调"></div></div></div><br class="figure-break"><p>
            “图中处于低层的软件平台是在win32 API的基础上的改进。不仅把主循环从应用程序中<span class="emphasis"><em>沉淀</em></span>下来，而且将储存callback的过程封装在一个注册函数中，使得应用程序代码变得更简洁、健壮。同时我们看到，整个流程的控制权已经从应用程序的主程序转移到底层平台的主循环中，符合好莱坞原则。”冒号。
        </p><p>
            逗号好奇地问：“什么是好莱坞原则？”
        </p><p>
             “don&#8217;t call us, we&#8217;ll call you.”冒号难得甩出一句洋文，“我很想画蛇添足地在末尾加上单词‘back’，这样更容易理解callback的含义：‘call you back’。此话的背景大约是这样的：一个艺人要想演出，需与好莱坞的经纪公司联系。由于幻想一朝成名的人太多，经纪人总是牛气十足，他们的口头禅是：‘别打电话给我们，留下你的电话，有活干我们会打给你的’。”
        </p><p>
            引号认真地解析：“好莱坞经纪公司相当于一个背后运作的软件平台，艺人相当于一个callback，‘留下你的电话’就是注册callback，‘我们会打给你的’就是异步调用callback。”
        </p><p>
            冒号接着补充：“‘别打电话给我们’意味着经纪公司处于主导地位，艺人们处于受控状态，这便是<span class="term">控制反转</span>（Inversion of Control，简称IoC）。”
        </p><p>
            问号听着耳熟：“控制反转？第一课谈到框架时似乎提到过。”
        </p><p>
             “没错，正是它！”冒号谈兴愈浓，“一般library中用到callback只是局部的控制反转，而framework将IoC机制用到全局。程序员牺牲了对应用程序流程的主导权，换来的是更简洁的代码和更高的生产效率。如果将编程譬比命题作文，不用framework的程序是一张可以自由写作的白纸，library是作文素材库；采用framework的程序是一篇成型的作文，作者只需填写空白的词语和段落即可。”
        </p><p>
            叹号为之一叹：“唉，编程序变成了做填空题，真没劲！ ”
        </p><p>
             “那你就多努力，争取以后出填空题吧。”冒号笑着鼓励他，“控制反转不仅增强了framework在代码和设计上的重用性，还极大地提高了framework的可扩展性。这是因为framework的内部运转机制虽是封闭的，但也开放了不少与外部相连的扩展接口点，类似插件（plugin）体系。如下图所示——”
        </p><div class="figure"><a name="id619702"></a><p class="title"><b>图3-6. 框架的IoC机制</b></p><div class="figure-contents"><div class="mediaobject"><img src="http://blog.zhenghui.org/img/colonclass/figure3-6.jpg" alt="框架的IoC机制"></div></div></div><br class="figure-break"><p>
            引号联想到另一个名词：“我知道有个依赖反转，与控制反转是一回事吗？”
        </p><p>
            冒号简答：“虽然不少人把它们看成同义词，但<span class="term">依赖反转原则</span>（Dependency-Inversion Principle，简称DIP）更加具体——高层模块不应依赖低层模块，它们都应依赖抽象；抽象不应依赖细节，细节应依赖抽象。经常相提并论的还有<span class="term">依赖注射</span>（Dependency Injection，简称DI）——动态地为一个软件组件提供外部依赖。由于时间关系，它们之间的区别容后再叙。有一点可以看出，它们的<span class="strong"><strong>主题是控制与依赖，目的是解耦，方法是反转，而实现这一切的关键是抽象接口</strong></span>。”
        </p><p>
            “为什么说是抽象接口而不是前面所说的回调函数？”打过瞌睡的逗号现在似乎变得特别清醒。
        </p><p>
            冒号予以说明：“回调函数的提法较为古老，多出现于过程式编程，抽象接口是更现代、更OO的说法。另外从字面上看，‘回调’强调的是<span class="emphasis"><em>行为方式</em></span>——低层反调高层，而‘抽象接口’强调的是<span class="emphasis"><em>实现方式</em></span>——正是由于接口具有抽象性，低层才能在调用它时无需虑及高层的具体细节，从而实现控制反转。”
        </p><p>
            众人细细品味着冒号的这番话。
        </p><p>
            问号忽然惊觉：“我们是不是跑题了？本来是谈事件驱动式编程的，结果从callback谈到控制反转，再到框架，现在又说起了抽象接口。”
        </p><p>
            “事物是普遍联系的嘛。”冒号扯了句哲学套话，“不谙熟callback和IoC机制，就不可能真正领会事件驱动式编程的精髓。不过，也该回到中心主题了。我们通过win32 API用四步实现了一个简单的窗口程序，与事件直接相关的有三步：实现<span class="term">事件处理器</span>（event handler）或<span class="term">事件监听器</span>（event listener）；注册事件处理器；实现<span class="term">事件循环</span>（event loop）。具体上，事件处理器负责处理事件，经注册方能在事发时收到通知；事件循环负责侦查事件、预处理事件、管理事件队列和分派事件等，无事时默默等待，有事时立即响应，生命不息工作不止。在整个事件机制中，主循环好比心脏，事件处理器好比大脑，是最重要的两类模块。”
        </p><p>
            句号指出：“在支持事件驱动的开发环境中，主循环是现成的。许多IDE的图形编辑器在程序员点击控件后，还能自动生成事件处理器的骨架代码，连注册的步骤也免除了。”
        </p><p>
            冒号提醒他：“并不是总有这样的好事，要知道事件驱动式并不局限于GUI应用，支持事件驱动的开发环境也未必唾手可得。程序员有时必须自行设计整个事件系统，他需要决定：采用事件驱动式是否合适？如果合适，如何设计事件机制？其中包括事件定义、事件触发、事件侦查、事件转化、事件合并、事件调度、事件传播、事件处理、事件连带（event cascade）<a class="link" href="#note5"><sup>[5]</sup></a>等等一系列问题。”
        </p><p>
            逗号扮着苦相说：“我的脑袋就是一个事件监听器，在听到要面临这么多的事件后，迅速作出反应——大了一圈。”
        </p><p>
            众皆弯腰捧腹。
        </p><p>
            “脑袋能变大是件好事啊，说明它伸缩性强，相信用它来编的程序也是一样。”冒号打着哈哈，“事件驱动式的程序可伸缩性就很强，知道为什么吗？”
        </p><p>
            叹号随口说道：“不是因为利用回调函数实现了控制反转吗？”
        </p><p>
            “非也非也。”冒号文绉绉地说，“软件的<span class="term">可伸缩性</span>（scalability）一般指从容应对工作量增长的能力，常与性能（performance）等指标一并被考量。而控制反转的主要作用是降低模块之间的依赖性，从而降低模块的耦合度和复杂度，提高软件的可重用性、柔韧性和可扩展性，但对可伸缩性并无太大帮助。我们已经看到，控制反转导致了事件驱动式编程的<span class="strong"><strong>被动性（passivity）</strong></span>。此外，事件驱动式还具有<span class="strong"><strong>异步性（asynchrony）</strong></span>的特征，这是由事件的不可预测性与随机性决定的。如果一个应用中存在一些该类特质的因素，比如频繁出现<span class="term">堵塞呼叫</span>（blocking call），不妨考虑将其包装为事件。”
        </p><p>
            问号打岔道：“什么是堵塞呼叫？”
        </p><p>
            冒号作了个比方：“在高速公路上一辆车突然出故障停在路途，急调维修人员。如果现场修理，在修好之前所在车道是堵塞的，后面车辆无法通行。类似地，在程序中一些函数需要等待某些数据而不能立即返回<a class="link" href="#note6"><sup>[6]</sup></a>，从而堵塞整个进程。”
        </p><p>
            引号道出常识：“显然更可取的修车做法是：先把车拖到路边，修完后向其他车辆发出信号，以便重回车道。”
        </p><p>
            冒号趁热打铁：“同理，我们可以让堵塞呼叫暂时脱离主进程，事成之后再利用事件机制申请重返原进程。相比第一种同步流程式的方案，这种异步事件式将连续的进程中<span class="emphasis"><em>独立且耗时</em></span>的部分抽取出来，从而减少随机因素造成的资源浪费，提高系统的性能和可伸缩性。”
        </p><p>
            问号听得仔细：“为什么抽取的部分是‘独立且耗时’，而不是‘随机且耗时’？”
        </p><p>
            “问得好！”冒号很欣赏他严谨的学风，“再拿修车来说，第二种方案之所以可行有两方面原因：一是修车耗时，二是修车独立。所谓独立又有两层含义：与车道独立——修车时不必占用车道；与后车独立——后面车辆不必恭候该车。如果一分钟内能修好，或者路边没有足够空位，再或者后面车辆是故障车的随行车，那么拖车方案均不成立。大家可以自己类比堵塞呼叫的情形，我就不再饶舌了。总之，<span class="strong"><strong>独立是异步的前提，耗时是异步的理由</strong></span>。至于随机嘛，只是副产品，一个独立且耗时的子过程，通常结束时间也是不可预期的。”
        </p><p>
            眼见天色已晚，冒号赶忙换上最后一页幻灯片——
        </p><div class="figure"><a name="id619872"></a><p class="title"><b>图3-7. 事件驱动式模型</b></p><div class="figure-contents"><div class="mediaobject"><img src="http://blog.zhenghui.org/img/colonclass/figure3-7.jpg" alt="事件驱动式模型"></div></div></div><br class="figure-break"><p>
            “上图为一个典型的事件驱动式模型。事件处理器事先在关注的<span class="term">事件源</span>上注册，后者不定期地发表<span class="term">事件对象</span>，经过<span class="term">事件管理器</span>的转化（translate）、合并（coalesce）、排队（enqueue）、分派（dispatch）等集中处理后，事件处理器接收到事件并对其进行相应处理。请注意事件处理器随时可以注册或注销事件源，意味着二者之间的关系是<span class="emphasis"><em>动态</em></span>建立和解除的。”冒号在幻灯屏上指指点点，“通过事件机制，事件源与事件处理器之间建立了<span class="emphasis"><em>松耦合</em></span>的<span class="emphasis"><em>多对多关系</em></span>：一个事件源可以有多个处理器，一个处理器可以监听多个事件源。再换个角度，把事件处理器视为服务方，事件源视为客户方，便是一个client-server模式。每个服务方与其客户方之间的会话（session）是异步的，即在处理完一个客户的请求后不必等待下一请求，随时可切换（switch）到对其他客户的服务。更有甚者，事件处理器也能产生事件，实现处理器接口的事件源也能处理事件，它们可以角色换位，于是又演化为peer-to-peer模式。”
        </p><p>
            叹号抱怨：“有点眼花缭乱了。”
        </p><p>
            为湿润枯燥的理论，冒号再次举例：“你们不是很喜欢在QQ上聊天吗？QQ服务器是事件管理器，每个聊天者既是事件源又是事件处理器，这正是事件驱动式的P2P模式啊<a class="link" href="#note7"><sup>[7]</sup></a>。此外，聊天时不等对方回答，就可与另一网友交谈，这就是<span class="emphasis"><em>会话切换</em></span>带来的异步效果。不过同样是聊天，改用电话就稍有不同了。”
        </p><p>
            冒号扫了 众人一眼，果见有人皱起了眉头。
        </p><p>
            “当你正用座机通话时，手机响了。你会怎么做？”冒号提示。
        </p><p>
            逗号本能地回答：“要么挂掉电话再接手机，要么让打手机的人迟些打来。”
        </p><p>
            句号听出了门道：“这说明电话的通话过程是同步而非异步的，原因是打电话双方的交流是连贯的、非堵塞式的（non-blocking），与QQ聊天正好相反。”
        </p><p>
            冒号点头称许。
        </p><p>
            虽然早已过了下课时间，引号仍是好学不倦：“我觉得观察者模式与事件驱动式很像啊。”
        </p><p>
            “你开始不是还举了订阅杂志和RSS的例子吗？<span class="term">发行/订阅模式</span>（publish-subscribe pattern）<a class="link" href="#note8"><sup>[8]</sup></a>正是<span class="term">观察者模式</span>（observer pattern）的别名，一方面可看作<span class="strong"><strong>简化或退化的事件驱动式</strong></span>，另一方面可看作<span class="strong"><strong>事件驱动式的核心思想</strong></span>。该模式省略了事件管理器部分，由事件源直接调用事件处理器的接口。这样更加简明易用，但威力有所削弱，缺少事件管理、事件连带等机制。著名的MVC（Model-View-Controller）架构正是它在架构设计上的一个应用<a class="link" href="#note9"><sup>[9]</sup></a>。”冒号长舒了一口气，准备收工，“事件驱动式的应用极广，变化极多，还涉及到框架、设计模式、架构、以及其他的编程范式，本身也可作为一种架构模型。今天我们仅仅是蜻蜓点水，更深入更具体的内容只能留后探讨了。时候不早，你们也该饿了，赶快回家吧！范式可不能当饭吃哦。”
        </p><p>
            众人笑作鸟兽散。
        </p></div><div class="section" title="，插语"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="note"></a>，插语</h2></div></div></div><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><p><a name="note1"></a>
                    许多基于事件驱动的系统都提供了createEvent之类的API，授权编程者自行产生事件。
                </p></li><li class="listitem"><p><a name="note2"></a>
                    更准确地说，Windows先把所有的硬件事件存入系统消息队列（system message queue），然后再放入应用程序消息队列（application message queue）。
                </p></li><li class="listitem"><p><a name="note3"></a>
                    比如可以这样从msg中得到窗口过程： (WNDPROC)GetWindowLong(msg.hwnd, GWL_WNDPROC)。
                </p></li><li class="listitem"><p><a name="note4"></a>
                    后面的论述同样适用于其他形式的软件分层结构。
                </p></li><li class="listitem"><p><a name="note5"></a>
                    指事件处理器在处理过程中又产生新的事件，从而再次触发事件处理器。
                </p></li><li class="listitem"><p><a name="note6"></a>
                    比如套接字（socket）中的accept函数。
                </p></li><li class="listitem"><p><a name="note7"></a>
                    真正的P2P网络是不需要中心服务器的，此处P2P指聊天双方是不分主客的对等关系。
                </p></li><li class="listitem"><p><a name="note8"></a>
                    有人将发行-订阅模式视为事件驱动设计的同义词，这是有道理的：在实际生活中，处于出版商与订阅者之间的邮局可作为事件管理器。
                </p></li><li class="listitem"><p><a name="note9"></a>
                    MVC也可作为一种设计模式，同样是观察者模式的应用。
                </p></li></ol></div></div><div class="section" title="。总结"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="summary"></a>。总结</h2></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>
                    事件是程序中令人关注的信息状态上变化。在基于事件驱动的系统中，事件包括内建事件与用户自定义事件，其中内建事件又分为底层事件和语义事件。此外，事件还有自然事件与合成事件之分。
                </p></li><li class="listitem"><p>
                    Callback指能作为参数传递的函数或代码，它允许低层模块调用高层模块，使调用者与被调者从代码上解耦。异步callback在传入后并不立即被调用，使调用者与被调者从时间上解耦。
                </p></li><li class="listitem"><p>
                    控制反转一般通过callback来实现，其目的是降低模块之间的依赖性，从而降低模块的耦合度和复杂度。
                </p></li><li class="listitem"><p>
                    在框架设计中，控制反转增强了软件的可重用性、柔韧性和可扩展性，减少了用户的负担，简化了用户的代码。
                </p></li><li class="listitem"><p>
                    控制反转、依赖反转原则和依赖注射是近义词，它们的主题是控制与依赖，目的是解耦，方法是反转，而实现这一切的关键是抽象接口（包括函数指针、抽象类、接口、C++中的泛型函子和C#中的委托）。
                </p></li><li class="listitem"><p>
                    事件驱动式编程的三个步骤：实现事件处理器；注册事件处理器；实现事件循环。
                </p></li><li class="listitem"><p>
                    异步过程在主程序中以非堵塞的机制运行，即主程序不必等待该过程的返回就能继续下一步。异步机制能减少随机因素造成的资源浪费，提高系统的性能和可伸缩性。
                </p></li><li class="listitem"><p>
                    独立是异步的前提，耗时是异步的理由。
                </p></li><li class="listitem"><p>
                    事件驱动式最重要的两个特征是被动性和异步性。被动性来自控制反转，异步性来自会话切换。
                </p></li><li class="listitem"><p>
                    观察者模式又名发行/订阅模式，既是事件驱动式的简化，也是事件驱动式的核心思想。MVC架构是观察者模式在架构设计上的一个应用。
                </p></li></ul></div></div><div class="section" title="“”参考"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="reference"></a>“”参考</h2></div></div></div><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><p>
                    Wikipedia．Event-driven programming．<a class="link" href="http://en.wikipedia.org/wiki/Event-driven" target="_top">http://en.wikipedia.org/wiki/Event-driven</a>
                </p></li><li class="listitem"><p>
                    Wikipedia．Callback (computer science)．<a class="link" href="http://en.wikipedia.org/wiki/Callback_(computer_science)" target="_top">http://en.wikipedia.org/wiki/Callback_(computer_science)</a>
                </p></li><li class="listitem"><p>
                    Charles Petzold．Programming Windows, 5<sup>th</sup> ed.．Redmond：Microsoft Press，1999．41-70
                </p></li><li class="listitem"><p>
                    Robert C. Martin．Agile Software Development: Principles, Patterns, and Practices（影印版）．北京：中国电力出版社，2003．127-134
                </p></li><li class="listitem"><p>
                    Martin Fowler．Inversion of Control Containers and the Dependency Injection pattern．<a class="link" href="http://martinfowler.com/articles/injection.html" target="_top">http://martinfowler.com/articles/injection.html</a>
                </p></li><li class="listitem"><p>
                    Erich Gamma，Richard Helm，Ralph Johnson，John Vlissides．Design Patterns: Elements of Reusable Object-Oriented Software．Boston：Addison-Wesley，1994．293-299
                </p></li></ol></div></div></div>

<!-- below is edited manually -->
<strong><span style="font-family: 宋体">课后思考</span></strong>
<ul style="margin-top: 0cm; list-style-type: none">
    <li>03-01 了解C++中的STL、Java中的 Collections Framework和C#中的Collection Classes。</li>
    <li>03-02 了解C++、Java和C#中的泛型机制，比较它们之间的异同以及各自在集合（collection）中的应用。</li>
    <li>03-03 当你成功构想地并实现了一个算法，是否考虑过利用泛型编程来扩大其适用范围以提高其重用性？</li>
    <li>03-04 当你发觉几个模块中有类似的算法，是否考虑过利用泛型思想进行重构？</li>
    <li>03-05 当你发觉程序中有大量类似的代码，是否考虑过用产生式编程来自动生成它们？</li>
    <li>03-06 试着利用编译器生成器（如ANTLR）自定义一种DSL，并用它来解决问题。</li>
    <li>03-07 你采用过AOP吗？它有哪些优缺点？</li>
    <li>03-08 如何合理地抽象出系统的横切关注点？</li>
    <li>03-09 请对比流程驱动式编程与事件驱动式编程之间的差异，它们各自适合哪些应用？</li>
    <li>03-10 你编写的代码是否有足够的灵活性和可扩展性？能否利用控制反转原理？</li>
    <li>03-11 你在程序中是如何处理堵塞呼叫的？是否考虑过引入异步机制？</li>
</ul><a class="a2a_dd addtoany_share_save" href="http://www.addtoany.com/share_save?linkurl=http%3A%2F%2Fblog.zhenghui.org%2F2009%2F09%2F11%2Fcolon-class-3_4%2F&amp;linkname=%E5%86%92%E5%8F%B7%E8%AF%BE%E5%A0%82%C2%A73.4%EF%BC%9A%E4%BA%8B%E4%BB%B6%E9%A9%B1%E5%8A%A8">分享/保存</a>]]></content:encoded>
			<wfw:commentRss>http://blog.zhenghui.org/2009/09/11/colon-class-3_4/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>冒号课堂§1.5：开发技术</title>
		<link>http://blog.zhenghui.org/2009/08/31/colon-class-1_5/</link>
		<comments>http://blog.zhenghui.org/2009/08/31/colon-class-1_5/#comments</comments>
		<pubDate>Mon, 31 Aug 2009 02:58:29 +0000</pubDate>
		<dc:creator>hui</dc:creator>
				<category><![CDATA[冒号课堂]]></category>
		<category><![CDATA[工具包]]></category>
		<category><![CDATA[开发技术]]></category>
		<category><![CDATA[惯用法]]></category>
		<category><![CDATA[架构]]></category>
		<category><![CDATA[框架]]></category>
		<category><![CDATA[编程范式]]></category>
		<category><![CDATA[设计模式]]></category>

		<guid isPermaLink="false">http://blog.zhenghui.org/?p=308</guid>
		<description><![CDATA[<b>开发技术</b>——实用还是时髦？（<em>关于框架、设计模式、架构和编程范式等开发技术的讨论</em>）<br/>
•	任何概念和技术都不是孤立的，如果不能在纵向的时间和横向的联系中找准坐标，便似那群摸象的盲人，各执一端却又自以为是<br/>
•	库和工具包是为程序员带来自由的，框架是为程序员带来约束的<br/>
•	设计模式是软件的战术思想，架构是软件的战略决策<br/>
•	知识的学习有几种方式：一种靠记忆，一种靠练习，一种靠培养<br/>
•	学习编程范式能增强编程语言的语感]]></description>
			<content:encoded><![CDATA[<h1 style="text-align: center"><span style="font-family: 宋体">冒号课堂</span></h1>
<strong><span style="font-size: 13pt; font-family: 宋体">第一课 开班导言(5)</span></strong>

<!-- below comes from generated html -->
<head><link rel="stylesheet" href="http://blog.zhenghui.org/css/colonclass.css" type="text/css"></head>

<div lang="zh-CN" class="article" title="开发技术"><div class="titlepage"><div><div><h1 class="title"><a name="id627012"></a>1.5 开发技术——实用还是时髦？</h1></div><div><div class="author"><h3 class="author">郑晖</h3></div></div><div><div class="abstract" title="摘要"><p class="title"><b>摘要</b></p><p>关于框架、设计模式、架构和编程范式等开发技术的讨论</p></div></div></div><hr /></div><div class="toc"><p><b>目录</b></p><dl><dt><span class="section"><a href="#preview">！预览</a></span></dt><dt><span class="section"><a href="#question">？提问</a></span></dt><dt><span class="section"><a href="#explaination">：讲解</a></span></dt><dt><span class="section"><a href="#note">，插语</a></span></dt><dt><span class="section"><a href="#summary">。总结</a></span></dt><dt><span class="section"><a href="#reference">“”参考</a></span></dt></dl></div><div class="epigraph"><div class="literallayout"><p>借我借我一双慧眼吧，让我把这纷扰看得清清楚楚明明白白真真切切</p></div><div class="attribution"><span>—<span class="attribution">《雾里看花》</span></span></div></div><div class="section" title="！预览"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="preview"></a>！预览</h2></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>
                    任何概念和技术都不是孤立的，如果不能在纵向的时间和横向的联系中找准坐标，便似那群摸象的盲人，各执一端却又自以为是
                </p></li><li class="listitem"><p>
                    库和工具包是为程序员带来自由的，框架是为程序员带来约束的
                </p></li><li class="listitem"><p>
                    设计模式是软件的战术思想，架构是软件的战略决策
                </p></li><li class="listitem"><p>
                    知识的学习有几种方式：一种靠记忆，一种靠练习，一种靠培养
                </p></li><li class="listitem"><p>
                    学习编程范式能增强编程语言的语感
                </p></li></ul></div></div><div class="section" title="？提问"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="question"></a>？提问</h2></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>库和工具包与框架有何不同？</p></li><li class="listitem"><p>什么是设计模式、惯用法和架构？</p></li><li class="listitem"><p>为什么要谈编程范式，而不是框架、设计模式或者架构？</p></li></ul></div></div><div class="section" title="：讲解"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="explaination"></a>：讲解</h2></div></div></div><p>
            “现在我们具体介绍一下编程范式。”冒号忽然顿住，隐觉一抹失望从众人脸上掠过，问号更是欲言又止，便鼓励他开口。
        </p><p>
            问号略显迟疑：“您说编程范式是一种心法，那框架、设计模式还有架构呢？”
        </p><p>
             “原来如此！”冒号心下了然，“让我说说你们最想听些什么吧。”
        </p><p>
            众现不信之色。
        </p><p>
            冒号说道：“一种是具体而实用的，最好能立马解决学习和工作中的问题；一种是时髦而花哨的，管它有用没用，不学点心里就是不踏实。”
        </p><p>
            众人虽觉此话有些尖刻，细想起来也有几分道理，但老冒明知而不为，不走群众路线，偏去扯什么劳什子的范式——当然，直接谈OOP倒是不错的。
        </p><p>
            “<span class="strong"><strong>自以为懂的未必真的懂，自以为不懂的未必真的不懂</strong></span>。” 冒号玩起了玄学，“有些概念和技术即使背得烂熟，甚至用得烂熟，那也不代表真正掌握；有些概念和技术看起来很新奇，却不过是新瓶装旧酒。”
        </p><p>
            叹号颇不服气：“用得烂熟都不算掌握，难不成只有发明概念和技术才算掌握？”
        </p><p>
            “哈哈，那倒不必。”冒号笑道，“用得烂熟不等于用得恰到好处，能解决问题不等于没有后顾之忧。”
        </p><p>
            逗号问道：“那掌握的标准是什么？”
        </p><p>
             “许多应聘者喜欢在简历中言必称精通某某语言、某某技术云云，大多不必面试即知其大言炎炎——倘若真的精通，他自当应聘更高的职位。”冒号有感而发却又似不着边际，“任何概念和技术都不是孤立的，如果不能在纵向的时间和横向的联系中找准坐标，便似那群摸象的盲人，各执一端却又自以为是。”
        </p><p>
            众人心想，老冒虽言辞旦旦却有凿空之嫌，一节课下来，天马行空的扯了不少，真刀真枪的一个也无，该不是只会纸上谈兵吧？
        </p><p>
            句号紧扣主题：“您为何选择谈编程范式，而不是框架、设计模式还有架构呢？难道它们真如您所说只是时髦而花哨的东西吗？”
        </p><p>
            “我可没这么说。”冒号矢口否认，“但在弄清一样东西存在的意义之前就随众跟风，早晚会跟丢的。我先问问你们：什么是<span class="term">框架</span>（framework）？它与一般的<span class="term">库</span>（library）和<span class="term">工具包</span>（toolkit）有何不同？”
        </p><p>
            引号应答：“框架就是一组协同工作的类，它们为特定类型的软件构筑了一个可重用的设计。与库和工具包不同之处在于前者侧重<span class="emphasis"><em>设计重用</em></span>而后两者侧重<span class="emphasis"><em>代码重用</em></span>。”
        </p><p>
            “嗯，有点标准答案的味道。”冒号夸道，“如果吹毛求疵的话，框架并不限于OOP，可以是协同工作的<span class="emphasis"><em>类</em></span>，也可以是协同工作的<span class="emphasis"><em>函数</em></span>。一个足够复杂的应用软件开发，为确保快速有效，通常采取的方式是：在<span class="emphasis"><em>宏观管理</em></span>上选取一些框架以控制整体的结构和流程；在<span class="emphasis"><em>微观实现</em></span>上利用库和工具包来解决具体的细节问题。框架的意义在于使<span class="emphasis"><em>设计者</em></span>在特定领域的整体设计上不必重新发明轮子；库和工具包的意义在于使<span class="emphasis"><em>开发者</em></span>摆脱底层编码，专注特定问题和业务逻辑。”
        </p><p>
            问号提出问题：“框架与库和工具包看起来很相似——都是一些代码集合，都提供一些API（应用编程接口），是什么使得它们不同呢？”
        </p><p>
            “问得好！”冒号赞言，“框架与工具包最大的差别在截然相反的设计理念上：<span class="strong"><strong>库和工具包是为程序员带来自由的，框架是为程序员带来约束的</strong></span>。具体地说，库和工具包是为程序员提供武器装备的，框架则利用<span class="emphasis"><em>控制反转</em></span>（IoC）<a class="link" href="#note1"><sup>[1]</sup></a>机制实现对各模块的统一调度从而剥夺了程序员对全局的掌控权，使他们成为手执编程武器、随时听候调遣的士兵。”
        </p><p>
            叹号苦着脸：“程序员原来就是一小卒子啊！”
        </p><p>
             “哪个将军不是从小卒做起的？”冒号反问道，“不错，框架是在语言的语法规则之外施加于程序员的又一层枷锁，但没有规矩不成方圆。正如行军打仗，讲究排兵布阵，程序员就是那兵，框架就是那阵。”
        </p><p>
            句号说：“可不可以这么理解，框架就是一些人——也就是框架设计者，把一个软件开发中最甜的部分啃掉了，剩下部分留给下面的人？”
        </p><p>
             “从某种意义上说，是这样。”冒号点点头。
        </p><p>
            逗号很不甘心：“我就想啃最甜的部分。”
        </p><p>
             “当心别把牙给崩掉。”冒号笑道，“不是打击你，首先你还没那本事；其次即使你有本事也未必有机会；最后即使有本事也有机会，重新设计框架也未必是好的选择。就说大名鼎鼎的Struts吧，哪怕你设计出比它更高明的框架也难以被采用，因为前者早已成为Java平台上网络开发的事实（de facto）标准，公司很容易从市场上招到懂Struts的程序员，不必培训即可上手，成本低见效快。过去许多公司都有自己的网络框架，但最后大多都放弃了，并不是因为Struts更优秀，而是因为它更普及。毕竟大多数软件开发是以金钱而不是技术为中心的。”
        </p><p>
            问号提议：“您能谈谈设计模式和架构吗？”
        </p><p>
            冒号侃侃而谈：“与框架与库和工具包不同，<span class="term">设计模式</span>（design pattern）和<span class="term">架构</span>（architecture）不是<span class="emphasis"><em>软件产品</em></span>，而是<span class="emphasis"><em>软件思想</em></span>。<span class="strong"><strong>设计模式是软件的战术思想，架构是软件的战略决策</strong></span>。设计模式是针对某些经常出现的问题而提出的行之有效的设计解决方案，它侧重<span class="emphasis"><em>思想重用</em></span>，因此比框架更抽象、更普适，但多限于局部解决方案，没有框架的整体性。与之相似的还有<span class="term">惯用法</span>（idiom），也是针对常发问题的解决方案，但偏重实现而非设计，与实现语言密切相关，是一种更底层更具体的编程技巧。至于架构，一般指一个软件系统的最高层次的整体结构和规划，一个架构可能包含多个框架，而一个框架可能包含多个设计模式。”
        </p><p>
            引号愈发疑惑：“这些不是都很重要吗？”
        </p><p>
            “当然都很重要。不过——”冒号话锋一转，“在没有打好基础前，架构只是空中楼阁，因此不可能现在谈它。至于框架，不同的应用领域有不同的框架，如表现层的Struts、业务层的Spring、持久层的Hibernate等等，即使相同领域的框架也有多个选择，更不用说不同的语言框架还不一样，从何谈起？再说框架其实一点也不高深，完全可以无师自通，关键是领会思想，多学习多实践。说到设计模式，一共就那么几十个，一本‘四人帮’（GoF）<a class="link" href="#note2"><sup>[2]</sup></a>的书足矣，自己慢慢去啃，又何须多谈？简言之，一个谈之过早，一个无从谈起，一个不必多谈。”
        </p><p>
            下面开始交头接耳窃窃私语起来。
        </p><p>
            “知识的学习有几种方式：一种靠<span class="emphasis"><em>记忆</em></span>，一种靠<span class="emphasis"><em>练习</em></span>，一种靠<span class="emphasis"><em>培养</em></span>。就拿英语学习来说吧，学单词，单靠记忆即可；学句型、语法，光记忆是不够的，需要勤加练习方可熟能生巧；而要讲出地道的英语，光记忆和练习是远远不够的。从小学到大学，甚至博士毕业，除了英语类专业的学生外，大多数人英语练了一二十年，水平如何？不客气但很客观地说：一个字，烂；两个字，很烂；三个字，相当烂！口语甚至连一个英语国家的三岁小孩都不如。”冒号越说越激动，“原因只有一个，那就是国内的英语教学方式严重失策。教学总是围绕单词、词组、句型、语法转，缺乏对<span class="strong"><strong>语感</strong></span>的重视和培养，导致学生只会‘中式英语’。同样道理，一个惯用C语言编程的人也许很快就能写一些C++程序，但如果他只注重C++的<span class="emphasis"><em>语法</em></span>而不注重培养OOP的<span class="emphasis"><em>语感</em></span>，那么写出的程序一定是‘C式C++’。与其如此，倒不如直接用C呢。”
        </p><p>
            句号悟道：“您是想告诉我们，<span class="strong"><strong>学习编程范式能增强编程语言的语感</strong></span>？”
        </p><p>
            “一语中的！”冒号庆幸总算没有白费口舌，“语感是一个人对语言的敏锐感知力，反映了他在语言方面的整体上的直觉把握能力。语感强者，能听弦外之音，能说双关之语，能读隽永之作，能写晓畅之文。这是一种综合的素质和修养，其重要性是不言而喻的。那么如何培养语感呢？普通的学习和训练固不可少，但如果忽视语言背后的文化背景和思维方式，终究只是缘木求鱼。编程范式正体现了编程的思维方式，因而是培养编程语言的语感的关键。现在如果我开始介绍范式，你们还有意见吗？”
        </p><p>
            众人受了鼓动，个个把头摇得跟拨浪鼓似的。
        </p><p>
            冒号语重心长地说：“既然范式关乎语感，就需要慢慢的培养和渗透，不可能一蹴而就，因此有些地方不太明白也没关系。现在只是撒下一些种子，慢慢的会生根发芽，直至长成大树。那些设计模式、框架甚至架构等等看似神秘高深的东西，都会自然而然地在这棵大树上结出果实。到那时，你们个顶个的都是内外兼修的武林高手了。怎么样？大家准备好了吗？”
        </p><p>
             “准备好了！”众人齐声道，求知的目光再度点燃。
        </p><p>
             “准备好了就下课吧。”冒号狡笑着，“下节课，下节课我们再谈。”
        </p></div><div class="section" title="，插语"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="note"></a>，插语</h2></div></div></div><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><p><a name="note1"></a>
                    控制反转（Inversion of Control）是一种软件设计原则。与通常的用户代码调用可重用的库（library）代码不同，IoC倒转了控制流方向：由库代码调用用户代码。有人将此比作好莱坞法则：“不要打电话给我们，我们会打给你的”。
                </p></li><li class="listitem"><p><a name="note2"></a>
                    设计模式最经典书籍《Design Patterns: Elements of Reusable Object-Oriented Software》的四位作者常被称为GoF或Gang of Four。
                </p></li></ol></div></div><div class="section" title="。总结"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="summary"></a>。总结</h2></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>
                    库和工具包侧重代码重用，框架侧重设计重用。库和工具包从微观上解决具体问题，是为程序员带来自由的；框架从宏观上控制软件整体的结构和流程，是为程序员带来约束的。框架是通过控制反转（IoC）机制反客为主的。
                </p></li><li class="listitem"><p>
                    设计模式是软件的战术思想，架构是软件的战略决策。与框架、库和工具包不同，它们不是软件产品，而是软件思想。
                </p></li><li class="listitem"><p>
                    设计模式与惯用法都是针对常发问题的解决方案，但前者偏重设计，后者偏重实现。
                </p></li><li class="listitem"><p>
                    架构太高，谈之过早；框架太多，无从谈起；设计模式太少，不必多谈。至于编程范式，对培养编程语言的语感至关重要，需要充分的重视和长期的积累，方能悟其精髓。
                </p></li></ul></div></div><div class="section" title="“”参考"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="reference"></a>“”参考</h2></div></div></div><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><p>
                    Erich Gamma，Richard Helm，Ralph Johnson，John Vlissides．Design Patterns: Elements of Reusable Object-Oriented Software．Boston, MA：Addison-Wesley，1994．26-28
                </p></li></ol></div></div></div>

<!-- below is edited manually -->
<strong><span style="font-family: 宋体">课后思考</span></strong>
<ul style="margin-top: 0cm; list-style-type: none">
    <li>01-01 作为一个软件开发者，你现在处于哪个阶段？你未来的目标是什么？</li>
    <li>01-02 传统的学习方式的弊端在哪里？你是否有切肤之痛？</li>
    <li>01-03 你认为一个优秀的程序员需要具备什么素质和精神？</li>
    <li>01-04 你了解哪些计算机语言？你对一门语言的取舍与喜恶的根据是什么？</li>
    <li>01-05 你认为计算机语言未来的发展方向是什么？</li>
    <li>01-06 你能否在编程中感受到自己的激情和灵性？</li>
    <li>01-07 你了解哪些框架？它们主要解决了哪些问题？应用范围是什么？实现的机理是什么？</li>
    <li>01-08 你了解哪些设计模式？它们为什么能成其为模式？</li>
    <li>01-09 你熟悉的语言中有哪些惯用法？</li>
    <li>01-10 你对编程范式是如何理解的？学习它的意义何在？</li>
</ul><a class="a2a_dd addtoany_share_save" href="http://www.addtoany.com/share_save?linkurl=http%3A%2F%2Fblog.zhenghui.org%2F2009%2F08%2F31%2Fcolon-class-1_5%2F&amp;linkname=%E5%86%92%E5%8F%B7%E8%AF%BE%E5%A0%82%C2%A71.5%EF%BC%9A%E5%BC%80%E5%8F%91%E6%8A%80%E6%9C%AF">分享/保存</a>]]></content:encoded>
			<wfw:commentRss>http://blog.zhenghui.org/2009/08/31/colon-class-1_5/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>
