<?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; trait</title>
	<atom:link href="http://blog.zhenghui.org/tag/trait/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>冒号课堂§10.2：抽象类型</title>
		<link>http://blog.zhenghui.org/2009/10/24/colon-class-10_2/</link>
		<comments>http://blog.zhenghui.org/2009/10/24/colon-class-10_2/#comments</comments>
		<pubDate>Sat, 24 Oct 2009 01:37:53 +0000</pubDate>
		<dc:creator>hui</dc:creator>
				<category><![CDATA[冒号课堂]]></category>
		<category><![CDATA[mixin]]></category>
		<category><![CDATA[trait]]></category>
		<category><![CDATA[抽象数据类型]]></category>
		<category><![CDATA[抽象类]]></category>
		<category><![CDATA[抽象类型]]></category>
		<category><![CDATA[接口]]></category>
		<category><![CDATA[继承]]></category>

		<guid isPermaLink="false">http://blog.zhenghui.org/?p=518</guid>
		<description><![CDATA[<b>抽象类型</b>——实中之虚（<em>介绍抽象类型的种类、意义及其用法</em>）<br/>
•	浅显的比方只是门槛前的台阶，借之或可拾级入门，却无法登堂入室<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: 宋体">第十课 多态机制（2）</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="id603878"></a>抽象类型——实中之虚</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><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>接口是为了克服（Java或C#中）抽象类不能多重继承的缺点吗？</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>
            冒号调整了焦点：“鉴于目前专注的范式是OOP，参数多态最好放在以后的GP专题再作探讨。除非特别说明，下面提到的多态专指子类型多态。谈到这类多态，就不得不提及抽象类型。谁来说说，究竟什么是抽象类型？”
        </p><p>
            冒号抬手内扬，摆出了对练的姿势。
        </p><p>
            叹号率先抢攻：“抽象类型指的是至少含有一个抽象方法的类型。”
        </p><p>
            冒号轻松化解：“在C++中这句话尚可勉强成立，但在Java和C#中则大不尽然：一个类即使没有一个抽象方法也可以被申明为抽象的；一个没有任何成员的空接口或称<span class="term">标记接口</span>同样属于抽象类型。”
        </p><p>
            “抽象类型是指无法实例化的类型。”逗号发起二次进攻。
        </p><p>
            冒号见招拆招：“Java中的Math类也不能实例化，原因是它只有private构造器，并且没有一个能返回实例的静态方法。C#中的Math类是静态类，同样不能实例化。”
        </p><p>
            问号纵身而上：“抽象类型指能且只能通过继承来实例化的类型。Math类是final类，无法被继承。最主要的是，它的价值体现在它的静态方法上，压根儿就没有实例化的必要。”
        </p><p>
            冒号借力反打：“为什么要强调无法实例化呢？”
        </p><p>
            引号一旁助攻：“一个抽象类型代表着一个抽象概念，而抽象概念自然是无法具化的。比如你无法实例化抽象的形状，但可以实例化长方形、三角形等具体的形状；无法实例化抽象的水果，但可以实例化苹果、桔子等具体的水果。”
        </p><p>
            “很官方的说法。这就好比将继承关系说成‘is-a’关系一样，理论上虽通俗易懂，实践上却不足为训。”冒号收起架势，“要说抽象，Java和C#中的Object类可谓包罗万象，该够抽象了吧？不照样实例化？<span class="term">列表</span>（list）与<span class="term">映射</span>（map）是抽象的还是具体的？在C++中它们是具体类型，而在Java和C#中它们却是抽象类型<a class="link" href="#note1"><sup>[1]</sup></a>。这又是为什么？”
        </p><p>
            一连串的反问让大家陷入沉思。
        </p><p>
            “相比其他编程范式，OOP更贴合客观世界，人们经常用打比方的形式来描述和理解OOP的一些概念和思想。这本身并无不妥，但一定要保持清醒的头脑：浅显的比方只是门槛前的台阶，借之或可拾级入门，却无法登堂入室。”冒号谆戒道，“天下之理皆同，天下之人皆同，故凡学问殿堂之前皆一般景象：入门者众，入室者寡。本班的目的便是，引导诸位从徘徊于编程之门左右的人群中越众而出，早达内室。”
        </p><p>
            “那就成了传说中的内室弟子吧？大伙在门边转悠很久了，头都发晕了，师父还是快些领我等入室吧。” 逗号近乎戏谑地恳求。
        </p><p>
            冒号一笑：“我可算不得你们的师父，只不过是个闻道在先的师兄而已。”
        </p><p>
            一直没有出手的句号忽然开腔：“抽象是个相对概念，一个类型是否是抽象的完全取决于设计者对它的角色定位。如果想用它来创建对象，它就是可实例化的具体类型；如果想用它来作为其他类型的基类，它就是不可实例化的抽象类型。”
        </p><p>
            “这才击中了要害！”冒号不禁喝彩道，“整理一下你的观点：<span class="strong"><strong>具体类型是创建对象的模板，抽象类型是创建类型的模块</strong></span>。一个是为对象服务的，一个是为类型服务的。显然，后者的抽象性正是源自其服务对象的抽象性。就拿刚才的实例来说，模板方法模式中的Authenticator类是抽象的，是为创建子类型SimpleAuthenticator、Sha1Authenticator等服务的；策略模式中的Authenticator类是具体的，是为创建对象服务的，但它合成的两个接口KeyValueKeeper和Encrypter又是为创建算法类型服务的。值得注意的是，不要把抽象类型与抽象数据类型（ADT）混为一谈，后者的抽象指的是类型的接口不依赖其实现。或者说，<span class="strong"><strong>抽象数据类型的核心是数据抽象，而抽象类型<a class="link" href="#note2"><sup>[2]</sup></a>的核心是多态抽象</strong></span>。”
        </p><p>
            问号想让概念更明确些：“抽象类型就只有<span class="term">接口</span>（interface）和<span class="term">抽象类</span>（abstract class）两种吗？”
        </p><p>
            “在Java和C#中基本上是这样，但在C++中这两种类型没有显式的区别<a class="link" href="#note3"><sup>[3]</sup></a>。”冒号，“此外，动态OOP语言如Ruby、Python、Perl、Scala、Smalltalk等还至少支持mixin和trait中的一种类型。mixin直译为‘混入’，trait直译为‘特质’，为避免翻译上的问题，今后我们还是采用英文术语。这两种类型大同小异，为简便起见，下面以mixin类型为代表<a class="link" href="#note4"><sup>[4]</sup></a>。它们的出现是为了弥补接口与抽象类的一些不足，更好地实现代码重用。我们知道，接口的主要目的是创建多态类型，本身不含任何实现。子类型通过接口继承只能让代码被重用，却无法重用超类型的实现代码。抽象类可以重用代码，可又有多重继承的问题。Java和C#不支持这种机制，C++虽支持但有不少弊端。”
        </p><p>
            引号奇道：“这个问题上节课不是已经解决了吗？用合成来代替继承啊。”
        </p><p>
            冒号解释：“合成是一种解决办法，但也不是没有缺陷。首先，合成的用法不如继承那么简便优雅，这也是许多人喜欢用继承的主要原因；其次，合成不能产生子类型，而有时这正是设计者所需要的；再次，合成无法覆盖基础类的方法，也无法访问它的protected成员；最后，却可能是最大的缺点是：合成的基础类只能是具体类型，不能是抽象类型<a class="link" href="#note5"><sup>[5]</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="emphasis"><em>纯粹性</em></span>，倾向它包含尽可能少的功能。”冒号擘肌分理，“妥协的结果是，一个新类型往往只用到基础类型的部分功能，却可能受到其他功能变动的影响。虽然这种影响在良好的封装之下会大大削弱，但也难以完全消弭。”
        </p><p>
            句号思索片刻，已明其意：“换句话说，以具体类型为代码重用的基本单位，难免颗粒度过大？”
        </p><p>
            “然也！”冒号的手在空中挽了个花，“其实作为抽象类型的接口也有类似的尴尬：对它的客户类来说，它承诺的服务是多多益善；对它的实现类来说，承诺越多负担却越重。如果能有这样一种可重用的模块，既不像具体类型那样面面俱到，又不像接口那样有名无实，也没有抽象类的多重继承之弊，岂不妙哉？”
        </p><p>
            “想必就是mixin了！”叹号眼中闪过一道光芒，旋即又暗淡下来，“只可惜Java并不支持啊。”。
        </p><p>
            “Java不支持就没兴趣了？” 冒号听出他的话里有话，“要成为优秀的程序员，千万不能画地为牢、自我禁锢。始终要保持一颗开放的心，不要拘于某些语言或范式，也不要囿于某些概念或技术。”
        </p><p>
            叹号的耳根有点发热。
        </p><p>
            “陌生的理论和技术开始总是拒人千里，不过一旦你了解其问题来源，它们会慢慢变得和蔼可亲起来。”冒号循循善诱，“既然具体类型和现存的两种抽象类型均有不足之处，mixin的产生便合情合理了。它是具体类型与接口类型的一种折衷，既可有抽象方法，也可有具体方法。这一点类似抽象类，但又没有抽象类的多重继承问题。举例来说，Ruby中的Comparable就是一个简单却很典型的mixin。”
        </p><p>
            问号插话：“Java中也有Comparable接口啊。”
        </p><p>
            冒号道出其中差异：“Java中的Comparable和C#中的IComparable只有一个抽象的比较方法，而Ruby中的除了有类似的抽象方法——比较（&lt;=&gt;）之外，还提供了小于（&lt;）、小于等于（&lt;=）、等于（==）、大于（&gt;）、大于等于（&gt;=）和介于（between?）等六种具体方法。显而易见，多出的方法均可通过唯一抽象的比较方法来实现。”
        </p><p>
            引号一点即通：“如此一来，重用Comparable的类只需实现一个抽象方法，便可自动拥有另外六个有用的功能。这既满足了客户类的需求，又不增加实现类的负担。”
        </p><p>
            “买一送六，这买卖划算！”逗号来劲了。
        </p><p>
            冒号双眼微眯：“更划算的买卖是Ruby中Enumerable。任何包含该mixin的类只要实现一个遍历方法each，便可免费得到二十多个有关遍历和搜寻的方法。如果再实现比较方法&lt;=&gt;，还可获赠排序和最值方法。相比Java中Enumeration和Iterator接口，优势历然。”
        </p><p>
            问号很好奇：“为什么称为mixin呢？”
        </p><p>
            冒号述说由来：“冰淇淋中经常会掺混一些薄荷、香草、巧克力之类的调味料和花生、坚果之类的小零碎，人们管它们叫mix-in。后来被借用来表示一种抽象类型，主要有如下特点：一、抽象性和依赖性：本身没有独立存在的意义，必须融入主体类型才能发挥作用；二、实用性和可重用性：不仅提供接口，还提供部分实现；三、专一性和细粒度性：提供的接口职责明确而单一；四、可选性和边缘性：为主体类型提供非核心的辅助功能。”
        </p><p>
            “这些特点与风味添加料还真的颇为神似。”叹号想着想着，嘴里不自觉地咂摸了一下。
        </p><p>
            “虽然C++、Java和C#在语法上尚不支持mixin，但C++可通过多重继承、Java和C#可通过合成和接口来分别模拟mixin。不仅如此，借助<span class="term">切面式编程</span>（AOP），Java和C#甚至可完全实现mixin；借助<span class="term">泛型式编程</span>（GP），C++也能通过<span class="term">模板</span>更好地实现mixin<a class="link" href="#note6"><sup>[6]</sup></a>。”冒号点到为止，“就此我们重温前面提到的两个观点。一是编程范式之间的合作性：mixin属于OOP的范畴，但其他编程范式如切面式、泛型式以及二者背后的元编程都能与之相通；二是设计与语言的相关性：C++、Java和C#以及其他诸如Ruby、Python等动态语言对mixin有着不同的支持方式，这在一定程度上会影响系统的OOP设计。”
        </p><p>
            引号憧憬道：“语言是在发展的，说不定哪天Java也会支持mixin的。”
        </p><p>
            冒号以实相应：“Java的动态小兄弟Groovy在1.6版已经开始支持mixin ，而C#3.0也新引入了对mixin更友好的语法特性<a class="link" href="#note7"><sup>[7]</sup></a>。”
        </p><p>
            逗号提了一个长期困惑大家的问题：“每当一个新技术出现，我就觉得很矛盾：不追怕落伍，追吧又怕落空。如何判断一个它是昙花一现，还是大势所趋呢？”
        </p><p>
            “任何技术都是在赞美与批判中成长起来的，预测它们是流星还是恒星绝非易事。就拿OOP来说，上个世纪六十年代就出现了支持OOP的语言<a class="link" href="#note8"><sup>[8]</sup></a>，但直到九十年代中后期它才真正成为主流的编程范式。这段时间恐怕比大多数人的程序员生涯还长吧。再说mixin，其实并非今日的重点，介绍它的目的不是盲目追新，而是希望透过其背后的需求驱动点，重新审视现有技术。至于它今后会不会为主流语言所接纳，反倒不是那么重要了。如果一定要我给个建议，那就是八个字：‘<span class="strong"><strong>不执一法，不舍一法</strong></span>’。”冒号以禅语作答，“软件技术这棵大树经过多年的快速成长，早已枝蔓丛生。欲臻不执不舍之境，当如开班导言中所说：究其根本以知过去，握其主干以知现在，察其生长点以知未来。我之所以倾向于用抽象的方式来谈论技术，正是因为抽象的东西更接近根、更接近干、更接近生长点，从而更普泛深刻，也更稳定持久。”
        </p><p>
            句号借机问道：“您认为抽象比具体更重要？”
        </p><p>
            “抽象与具体无所谓孰高孰低，它们只是功用不同而已。”冒号轻轻晃了晃脑袋，“正所谓：<span class="strong"><strong>必先以术养道，而后以道御术</strong></span>。也就是说，在学习时应注重从具体知识中领悟抽象思想，在应用时应注重用抽象理论来指导具体实践。类似地，软件开发也是如此：从具体需求中构建出抽象模型，再根据抽象模型来完成具体实现。因此，在设计阶段抽象类型尤为关键，而在实现阶段则是具体类型更为重要。”
        </p><p>
            问号表示理解：“假如从具体需求直接跨到具体实现，省去中间的抽象建模过程，那还用得着架构师和分析师吗？”
        </p><p>
            “话虽不错，但疑似倒果为因。”冒号洞若观火，“是否有必要抽象建模，关键看项目需求。如果需求简单而稳定，一步到位又何尝不可？甚至软件的开发效率和运行效率还更高——为劈几根细柴而磨刀，值吗？如果需求复杂而多变，引入抽象方有‘磨刀不误砍柴工’之效。毕竟抽象不是目的而是手段，对它片面的追求反会导致过度的设计。”
        </p><p>
            众人这才发现，给老冒戴顶“抽象派”的帽子是有些冤枉他了，应该是“抽象现实派”的。
        </p><p>
            冒号续道：“为进一步认识抽象类型，我举个非常实用的例子。它只适用于C++，而不适用于Java和C#。如果你对这一点感到遗憾的话，不要忘记我们的原则：具体实例永远是为抽象思想服务的。”
        </p><p>
            幻灯一闪，现出一段C++代码——
        </p><div class="informalexample"><pre class="programlisting">
/** 一个不可复制的类 */
class NonCopyable
{
protected:
    // 非公有构造函数防止创建对象
    NonCopyable() {}  
    // 非公有非虚析构函数建议子类非公有继承
    ~NonCopyable() {}
private: 
    // 私有复制构造函数防止直接的显式复制和通过参数传递的隐式复制
    NonCopyable(const NonCopyable&amp;);
    // 私有赋值运算符防止通过赋值来复制
    const NonCopyable&amp; operator=(const NonCopyable&amp;); // copy assignment
};

/** NonCopyable的一个私有继承类 */
class SingleCopy : private NonCopyable {};

/** 测试代码 */
int main()
{
    SingleCopy singleCopy1;
    SingleCopy copy(singleCopy1); // 编译器报错：企图复制singleCopy1

    SingleCopy singleCopy2;
    singleCopy2 = singleCopy1; // 编译器报错：企图复制singleCopy1
    return 0;
}</pre></div><p>
            冒号讲解道：“有些对象是不希望被复制的。比如一些代表网络连接、数据库连接的资源对象，它们的复制要么意义不大，要么实现困难。由于C++的编译器为每个类提供了默认的<span class="term">复制构造函数</span>（copy constructor）和<span class="term">赋值运算符</span>（assignment operator），要想阻止对象的复制，通常做法是将这两个函数私有化。引入NonCopyable后，它的任何子类将自动拥有不可复制的特性。这样为开发者节省了代码编写量，还免掉了相应的文档说明，使用者也一望而知其意，可说是一石三鸟。虽然NonCopyable从语法上说不是抽象类，但从本质上看是一种类似mixin功能的抽象类型。”
        </p><p>
            引号考量一番后说道：“单就它的功效而言，的确非常符合mixin的四大特点，只是它的子类用的是私有继承，而不是类继承或接口继承。”
        </p><p>
            “你说得很对。可问题是，我们并没有要求mixin或者trait一定要通过继承的方式来重用啊？事实上，有些mixin甚至可在运行期间产生，还能克服继承的静态缺陷。即使采用继承，一般也不满足‘is-a’关系。你总不能说草莓冰淇淋是一种草莓吧？”冒号淡淡地说，“先前你们总结出抽象类型有两个特征：需要继承和无法实例化，但它们并非本质，关键还是它的目的——为类型服务。提供可被继承的超类型只是一种服务方式，却非唯一的方式；无法实例化只因它不是为对象服务的，禁止实例化不过是语法上的加强，目的是让用户在编译期间就能发现用法错误。其实，即便NonCopyable类的构造函数是公有的，也不会有人去实例化。原因很简单，它的价值只有通过子类才能体现，这是由其抽象的本性所决定的。”
        </p><p>
            逗号有些奇怪：“为什么在Java中就没有类似的对象复制问题呢？”
        </p><p>
            “这是一个非常基础的问题，请容我下次再回答你。”冒号破天荒地没有立即解疑，“以下重点还是放在接口和抽象类上面，我们称之为基本抽象类型，以别于mixin、trait等其他抽象类型。我们先从语法上简单地对比一下这两种类型。”
        </p><p>
            屏幕上显示出一张表格（如表10-1所示）——
        </p><div class="table"><a name="id604285"></a><p class="title"><b>表 10-1. Java/C#的抽象类与接口在语法上的区别</b></p><div class="table-contents"><table summary="Java/C#的抽象类与接口在语法上的区别" border="1"><colgroup><col><col><col></colgroup><thead><tr><th> </th><th>抽象类</th><th>接口</th></tr></thead><tbody><tr><td>提供实现代码</td><td>能</td><td>否</td></tr><tr><td>多重继承</td><td>否</td><td>能</td></tr><tr><td>拥有非public成员</td><td>能</td><td>否</td></tr><tr><td>拥有域成员</td><td>能</td><td>否（Java中的static final域成员除外）</td></tr><tr><td>拥有static成员</td><td>能</td><td>否（Java中的static final域成员除外）</td></tr><tr><td>拥有非abstract方法成员</td><td>能</td><td>否</td></tr><tr><td>方法成员的默认修饰符</td><td>无</td><td>public abstract（Java：可选；C#：不能含有任何修饰符）</td></tr><tr><td>域成员的默认修饰符</td><td>无</td><td>Java：public static final；C#：不允许域成员</td></tr></tbody></table></div></div><br class="table-break"><p>
            冒号简明扼要地总结：“C#的语法与Java的稍有不同，但二者在接口与抽象类的关键区别上还是一致的：接口不能提供实现但能多重继承，抽象类则正相反；接口只能包含<span class="emphasis"><em>公有</em></span>的、<span class="emphasis"><em>非静态</em></span>的、<span class="emphasis"><em>抽象</em></span>的方法成员<a class="link" href="#note9"><sup>[9]</sup></a>，抽象类则无此限制。”
        </p><p>
            问号言明难处：“从语法上区分它们并不难，难的是从设计上区分它们。”
        </p><p>
            逗号实话实说：“按照上节课‘提倡接口继承，慎用实现继承’的方针，应该倾向用接口而非抽象类。但总觉得接口太虚了，没有抽象类实在。”
        </p><p>
            引号反驳：“要说实在，具体类型更实在啊。”
        </p><p>
            叹号坦言：“在编程中经常需要用到标准的或第三方的类库，可查起API来经常是左一个接口右一个接口的，迟迟不见具体类型现身，心里哪个急啊！”
        </p><p>
            冒号打了个比方：“如果到包子铺买包子，作为客户你也许会认为包子是具体类型，但对提供包子的人来说它却是抽象类型。他一定会问你：是要肉包、菜包还是豆沙包？是要蒸包、煎包还是小笼包？他的铺子开得越专业，给你出的选择题越多，众口难调嘛。同样道理， 要建一个高度可重用的类库，一些接口是必不可少的。”
        </p><p>
            句号悟道：“接口的意义就在于：提供者不是擅作主张，而是推迟决定，让客户选择实现方式。”
        </p><p>
            “言之有理！类似地，抽象类的意义就在于：父类推迟决定，让子类选择实现方式。‘推迟’二字道出了抽象类型除创建类型之外的另一功用：<span class="strong"><strong>提供动态节点</strong></span>。如果是具体类型，节点已经固定，没有太多变化的余地<a class="link" href="#note10"><sup>[10]</sup></a>。反过来，要使节点动态化，一般通过多态来实现。由此，抽象类型常常与多态机制形影不离。”冒号稍加引申，“就说前面的验证类吧，用模板方法模式实现的Authenticator类将关键的方法交给子类SimpleAuthenticator或Sha1Authenticator处理，用策略模式实现的Authenticator类将关键的方法交给内嵌接口KeyValueKeeper和Encrypter的实现类处理。后者的两次接口继承比前者的一次实现继承多了一个动态节点，因而更加灵活。这也是为什么一个需要（M×N）个实现类，一个只要（M+N）个的原因。当然，这也不是完全没有代价的。比如要创建一个用SHA-1算法加密的验证类实例，两种方法对比如下——”
        </p><div class="informalexample"><pre class="programlisting">
模板方法模式：new Sha1Authenticator()
策略模式：    new Authenticator(new MemoryKeeper(), new Sha1Encrypter())
</pre></div><p>
            冒号指点着黑板：“显然，后者无论是使用上还是性能上都比前者稍有不如。但权衡利弊，多数时候它仍是更好的选择。” 
        </p><p>
            “包子铺的包子用料种类越多、做法越多，买一个包子越费事。但只要不到饿得发昏的地步，大家还是更喜欢花样更多的包子铺。看来我也不该再抱怨类库的接口过多了。”叹号心下释然。
        </p><p>
            “大家再看看这个电脑主板，开过机箱攒过机的人应该对它并不陌生。”冒号终于亮出了蓄藏已久的道具， “上面密密麻麻地布满了各种元件，那是它的实部，而我们关注的是它的虚部——各种插槽和接口，包括CPU插槽、内存插槽、PCI插槽、AGP插槽、ATA接口、PS/2接口、USB接口以及其他林林总总的扩展插槽等等。这些接口的存在，使得主板与CPU、内存条、外围设备以及扩展卡等不必硬性焊接在一起，大大增强了电脑主机的可定制性。”
        </p><p>
            引号受到启发：“主板与其他硬件就好比一个个的<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>
            “比喻非常到位！” 冒号很满意，“不过准确地说，与接口类型对应的不是物理接口，而是<span class="strong"><strong>接口规范</strong></span>。如果仅仅是物理接口，只能保证该接口适用于某种特定型号的硬件产品，却不能保证同时适用于其他型号或者其他类型的硬件。以大家熟悉的USB（Universal Serial Bus）接口为例，它能接入各种外部设备，包括鼠标、键盘、打印机、外置硬盘、闪存和形形色色的数码产品。这当然不是偶然的，因为所有厂家在生产这些硬件时均遵循了相同的业界标准——USB协议规范。换言之，任何一个与USB接口兼容的设备，都可看作是实现了此接口的具体类型，而主机对该设备的自动识别能力则可看作一种多态机制。”
        </p><p>
            “这下我更深刻地理解那句话了：接口继承不是为了重用，而是为了被重用。”句号品味道，“比如一个鼠标，可以有串行接口、PS/2接口、USB接口或者无线接口，还可以同时拥有多个不同类型的接口。无论怎样，它本身都是完整的产品，根本不需要重用主机上的其他硬件，它实现某些接口的目的完全是为了能被主机所用。”
        </p><p>
            逗号意识到：“看样子，硬件设计也需要OOP思想呢。”
        </p><p>
            “相比软件设计师，硬件设计师往往能更好地贯彻OOP的理念。”冒号加强了语气，“他们的对象化概念更清晰更自然，因为硬件模块比软件模块更实在更具体；他们更注重设计，因为硬件比软件的修改成本大得多；他们更注重设计重用，因为硬件重新发明轮子的成本普遍很高；他们更注重实现重用，因为无法在举手之间完成‘复制-粘贴’工作；他们更注重接口明确、封装完好，因为把内部的接口或结构暴露在外不仅难看，还容易带来缠绕、磨损、短路等问题；他们采用合成和接口来组装模块，因为硬件没有类似实现继承的机制。”
        </p><p>
            “看起来我们真得向硬件设计师取经了。”叹号有些信服了。
        </p><p>
            冒号旧话重提：“我们曾对OOP有过这样的描述：如果把OOP系统看作民主制社会，每个对象是独立而平等的公民，那么封装使得公民拥有个体身份，继承使得公民拥有家庭身份，多态使得公民拥有社会身份。补充一下，其中的继承主要指类继承，多态主要指接口继承带来的多态。经过这段时间的学习，大家对此有何见解？”
        </p><p>
            问号发表看法：“广义封装让每个类成为独立的模块，从而让每个对象具备了个体身份。狭义封装又进一步地把类的接口与实现分离，从而让每个对象具有显著的外在行为和隐藏的内在特性。继承机制可使一个类成为其他类的子类或父类，从而确立了对象在类型家族中的身份。至于多态嘛，嗯。。。”
        </p><p>
            问号努力想抓住若隐若现的头绪。
        </p><p>
            句号接过话头：“一个公民的社会身份是指他在社会中所处的地位和扮演的角色。比如，一个人在学校里是学生，在公司里是职员，在商店里是顾客，他真正的个体身份往往是被掩盖的。同样地，一个对象在与外界联系时，通常不以其实际类型的身份出现，而是在不同的场合下以不同的抽象类型的身份出现。我想，这大概就是多态带来的社会身份吧。”
        </p><p>
            “这种社会身份的意义何在？”冒号不动声色地问。
        </p><p>
            句号接着回答：“社会身份既是一种资格也是一种义务。比如在列车上有人得了急病，可以通过广播找医生。人们不用事先知道来者的具体个人身份，只要他是医生，就会放心地让他第一时间去救人。”
        </p><p>
            “这个比喻很恰当。”冒号赞道，“不用<span class="emphasis"><em>事先</em></span>知道个人身份，不正说明广播呼叫的对象是一个多态的抽象类型吗？同理，当一个具体类型显式继承了一个接口，它的对象便拥有了个体身份之外社会身份：有资格以该接口的形式与外界打交道，也有义务履行该接口的职责。”
        </p><p>
            “咦，那为什么把社会身份归功于多态而不是继承呢？”问号发出疑问。
        </p><p>
            冒号释疑：“继承自然有功劳，毕竟子类型多态要建立在它的基础上。但如果没有多态机制，要确保一个对象的实际方法而不是其超类型的方法被调用，必须将其还原为具体类型，从而使社会身份变得几乎有名无实。”
        </p><p>
            问号憬然醒悟。
        </p><p>
            冒号继续深入：“对象每多一种社会身份，便多一条与外界交流的渠道。为什么遮遮掩掩地不肯以本来面目示人呢？非是羞于见人，盖因一般的具体类型在公共场合是不为人知的，只有少数核心库里的核心类是例外。即使侥幸被认识，也难被认可，因为那会以代码的复杂度和耦合度为代价。社会身份则不然，它远比一般的个体身份更容易被接受。”
        </p><p>
            逗号举出例证：“这就好比上课得有学生证，上班得有工作证，上火车得有火车票，上飞机得有登机牌。只要不是炙手可热的公众人物，很多场合都是认牌认证不认人的。”
        </p><p>
            “道理人人都懂，可总有不少人以为自己编写的类都是明星大腕，大有‘天下谁人不识我’的豪迈，无牌无证就敢到处乱窜。更有甚者，不用多态就算了，连封装也不要，简直是在裸奔嘛。”冒号揶揄道。
        </p><p>
            全班笑不可仰。
        </p><p>
            冒号恢复肃容：“谈到这里，我们不能不再次提到‘针对接口编程’的基本原则。它有一种建立于数据抽象之上的形式，能让用户只关心抽象数据类型的API接口而无视其具体实现。不过，它至少有两大局限。其一，虽然在接口不变的情况下，实现代码的改变不会影响客户代码，但仍需要重新编译，对于需要头文件的C++来说则需要更多的编译链接时间。其二，虽然相同的接口可以有多种实现方法，但它们不能同时并存，更无法动态切换。于是，另一种建立于多态抽象之上的形式应运而生。它把抽象数据类型隐藏在抽象类型的背后，从而提升了抽象接口。同一个抽象接口允许有多种实现并存，且能动态切换，新增、删除或修改某种实现也不会导致其他代码的修改或重新编译。方才我们从主体类的角度来看，它的对象尽量以社会身份参与社会活动；现在再从客户类的角度看，它会尽量召集有社会身份的对象。两相结合，以社会身份而非个人身份作为公民之间联系的纽带，正是针对接口而非实现来编程的<span class="strong"><strong>社会现实版</strong></span>。”
        </p><p>
            问号有所顾虑：“可是，有不少具体类型并没有实现任何接口，也就没有社会身份。”
        </p><p>
            “排除设计不良的因素，没有抽象超类型的具体类型最常见的有两种可能。一种是与世隔绝，一辈子几乎足不出户，至多在小圈子里活动。典型的有非公有类、内部类、局部类等等。一种是名满天下，他的脸就是一张天然名片，他的个人身份也就是社会身份。典型的有基本数据类型、字符串类型、日期类型等通用数据类型以及特定领域的通用数据类型。可见，个人身份与社会身份并无绝对的界限。同样，家庭身份与社会身份也有交合之处，正如名门望族也可成为社会身份一样。典型的有Java IO库中的InputStream和OutputStream、Reader和Writer，以及UI库中的Component和JComponent等等。”冒号信手拈来，“因此我们谈到的社会身份，不必拘泥于接口，甚至不必限于抽象类型，关键是该类型是否具备了足够的<span class="strong"><strong>通用性和规范性、稳定性和独立性、灵活性和专业性</strong></span>。还是应了那句话：抽象不是目的而是手段。再拿现实社会说事，每种社会身份都代表了个体与社会缔结的一种契约，它有如下的特点：<span class="emphasis"><em>独立而稳定</em></span>——先于个体而存在，且不随个体的变化而变化；<span class="emphasis"><em>公开而权威</em></span>——为人所知、为人所信；<span class="emphasis"><em>规范而开放</em></span>——制定的协议标准明确，且允许个体在遵守协议的前提下百花齐放。毫无疑问，推行契约制将使社会大受其惠。首先，相同身份的个体可相互替换、新型个体可随时加入，而且不会影响整体框架和流程，保证了系统的灵活性和扩展性。其次，整体不因某一个体的变故而受冲击，保证了系统的稳定性和可靠性；最后，个体角色清晰、分工明确，保证了系统的规范性和可读性。”
        </p><p>
            引号非常注重概念：“社会身份所代表的契约对应的正是<span class="term">规范抽象</span>吧。”
        </p><p>
            “每种身份都是规范抽象的结果。” 冒号推而广之，“具体地说，个体身份对应的规范抽象借助封装，以<span class="term">数据抽象</span>（data abstraction）的形式出现；家庭身份对应的规范抽象借助继承，以<span class="term">类型层级</span>（type hierarchy）的形式出现；社会身份对应的规范抽象借助多态，以<span class="term">多态抽象</span>（polymorphic abstraction）的形式出现。至此，我们分别从行为和规范两个角度分别诠释了OOP的三大特征与公民的三大身份之间的关系。这也非常合乎情理：一个合理设计和实现的类，其对象的行为与规范本应保持一致。”
        </p><p>
            句号欲印证自己的想法：“我的理解是，接口是一个携带契约的角色标签，接口继承的作用就是<span class="emphasis"><em>静态地</em></span>为对象贴上该标签，而多态机制的作用就是<span class="emphasis"><em>动态地</em></span>让对象发挥该角色。因此，要赋予对象某个角色，就应该让相应的类去继承相应的接口。”
        </p><p>
            “你的前半部分表述得非常精当，后半部分则稍有瑕疵。”冒号评论道，“接口可用来代表角色，但角色却不一定要通过接口。正如你提到的，接口继承是静态的，而角色却可能是动态的。比如学生毕业后变成职员，职员升迁后变成经理等等。对于静态类型语言来说，这类问题的解决单靠接口继承是不够的，还需要利用合成等手段，或者利用前面提到的其他抽象类型如mixin或trait。”
        </p><p>
            叹号仍有疑惑：“接口的意义已经很清楚了，那抽象类呢？它们的区别真的很大吗？”
        </p><p>
            “我们已经从语法上比较了它们的区别，那些只是表象的东西。如果对语言规则的理解仅仅停留于语法层面，那么它更多体现为<span class="strong"><strong>对实现的束缚</strong></span>。只有提升到语义层面，它才更多体现为<span class="strong"><strong>对设计的保障</strong></span>。”冒号保持一贯高举高打的风格，“从语义上看，抽象类与接口的区别，并不比它与具体类的区别小多少。”
        </p><p>
            叹号错愕不已：“怎么可能？抽象类与接口好歹都是抽象类型啊。”
        </p><p>
            冒号反诘：“为什么不说抽象类与具体类好歹都是类呢？”
        </p><p>
            叹号一时无语。
        </p><p>
            “先看段历史吧。”冒号幽幽地说，“开始C++是没有抽象类型的，直到1989年C++ Release 2.0发布前的最后一刻，Bjarne Stroustrup才力排众议引入抽象类。从C++的前身C with Classes 开始算起，其间已经整整十年了。即便如此，它的意义在当时仍不为大多数人所认识。推出一个看似小小的语法特征竟会如此艰难，恐怕远远超出诸位的想象吧！有人幻想只通过看语法书就能完全领会语言的精髓，又与痴人说梦何异？”
        </p><p>
            冒号的声音渐渐激昂起来。
        </p><p>
            逗号为自己找到了安慰：“难怪当初学到抽象类时，总感到只知其意而不知其用。”
        </p><p>
            冒号紧接着说：“抽象类的出现，让两种不同角色的类在语法上有了明确的界定：具体类<span class="strong"><strong>描述对象</strong></span>，重在实现；抽象类<span class="strong"><strong>描述规范</strong></span>，重在接口。这种分工降低了用户与实现者之间的耦合度，大大减少了代码的维护成本以及编译时间<a class="link" href="#note11"><sup>[11]</sup></a>。由于抽象类不是为了创建对象，它的实例化自然是没有意义的。又由于它是接口规范，在子类没有实现其所有规范之前，是不能实例化的，否则规范岂不成了一纸空文？在没有抽象类的语法之前，要实现类似的功能，唯一的办法是：在本该抽象的方法被调用时强行中止程序。烦琐丑陋不说，还只能在运行期间捕捉错误。在<span class="term">纯虚函数</span>（pure virtual function）——相当于Java和C#中的抽象方法——被引入之后，任何含有抽象方法的类都是抽象类，编译器将保证它不会被实例化。”
        </p><p>
            问号连连点头：“从这个角度来理解抽象类的语法，一切都顺理成章了。不过，抽象类与接口的区别好像还是没有看到。”
        </p><p>
            谈到兴头，冒号出言更如下阪走丸：“从具体类中分离出抽象类是一次质的飞跃，从抽象类中进一步地分离出接口则是另一次飞跃。Java推出接口类型之时同样饱受质疑，最终还是经受了实践的考验，后又为C#所采纳。其实最初C++的抽象类是为了定义一组协议并强令各子类遵守，实质上正是Java和C#中的接口所起的作用。但在协议规范的实现过程中，可能会产生一些不完全实现类。允许这种类的存在固然是一种灵活的举措，但必须认识到它们与纯规范的抽象类已判若云泥。打个比方，如果把对象看作产品，把具体类看作一个制作产品的模具，那么接口就是模具的规格标准，而抽象类是在模具加工过程中产生的半成品。接口与抽象类无法实例化，模具规格与模具半成品也不能直接制作产品；一个具体类可以有多个接口，一个模具也可有多个不同方面的规格；一个具体类至多只能继承一个抽象类，一个模具也至多只能在一种模具半成品的基础上直接加工。”
        </p><p>
            引号细加回味：“如果具体类、抽象类和接口分别对应于模具、模具半成品和模具规格，那后两者的区别的确比前两者的区别还大。可是假如一个抽象类完全没有任何实现呢？抛开多重继承的限制，它与接口又有何区别呢？”
        </p><p>
            冒号辨析其别：“一个抽象类可以没有任何实现，但也随时可以加入实现。接口则不同，永远都不能有实现代码。这正是引入关键字interface的目的，明明白白地表明：此乃规范集合所在，杜绝任何自以为是、画蛇添足的实现。初看似乎不合常理：这不是自缚手脚、自废武功吗？殊不知<span class="strong"><strong>自由源于自制</strong></span>。许多人为了贪恋一点点代码重用，总忍不住把一些实现放在本该只是规范的地方。一来，这模糊了规范与实现的界限，背离了接口与实现相分离的设计初衷。要知道，再完美的实现都有改动的余地，将其捆绑到规范中只会增加不稳定因素；再完美的实现也不应该影响其他的实现，先入为主只会降低灵活性。二来，带有实现的抽象类无法用于合成，必须通过类继承才能起作用，而实现继承的弊端我们已经见识过了。在有些情况下，规范的实现比较复杂，需要渐进实现，保留一些中间状态的抽象类也是合理的，但最初的接口最好保留。总不能因为有了模具半成品，就抛弃模具规格吧？以Java Collections Framework为例，既规范了Collection、Set、List、Map等接口，又为这些接口提供了抽象类和具体类，从而给了用户三种选择：直接利用具体类、扩展抽象类、直接实现接口，方便程度递减而灵活程度递增。”
        </p><p>
            句号进行反思：“我在想，为什么以前对接口总有本能的排斥心理？原因在于：满脑子更多想的是怎么让程序工作，而不是想怎么让程序工作得更好。因此更重视代码实现，比较忽视规范设计。”
        </p><p>
            众人皆有同感。
        </p><p>
            “确实，在缺乏设计观念的人看来，使用接口和脱裤放屁差不多。”冒号轻笑道，“特别需要注意一种常见的说法：接口是为了克服Java或C#中抽象类不能多重继承的缺点。这句话具有相当大的误导性，因为该处的多重继承是指多重实现继承，而接口甚至连单重实现继承都做不到！许多人对接口与抽象类的认识之所以模糊不清，原因是他们习惯于从<span class="emphasis"><em>定义和语法</em></span>中寻找<span class="emphasis"><em>表象的答案</em></span>，不习惯从<span class="emphasis"><em>本源和语义</em></span>上进行<span class="emphasis"><em>本质的分析</em></span>。然而不可否认，毕竟接口与抽象类提供了相似的抽象机制，在实践中往往确难抉择。因此光从语法上对比二者的差别是远远不够的，需要进一步在语义上进行对比（如表10-2所示）——”
        </p><div class="table"><a name="id604782"></a><p class="title"><b>表 10-2. Java/C#的抽象类与接口在语义上的区别</b></p><div class="table-contents"><table summary="Java/C#的抽象类与接口在语义上的区别" border="1"><colgroup><col><col><col><col><col><col><col><col><col></colgroup><thead><tr><th> </th><th>关系</th><th>共性</th><th>特征</th><th>联系</th><th>重用</th><th>实现</th><th>重点</th><th>演变</th></tr></thead><tbody><tr><td><span class="strong"><strong>接口</strong></span></td><td>can-do</td><td>相同功能</td><td>边缘特征</td><td>横向联系</td><td>规范重用</td><td>多种实现</td><td>可置换性</td><td>新增类型</td></tr><tr><td><span class="strong"><strong>抽象类</strong></span></td><td>is-a</td><td>相同种类</td><td>核心特征</td><td>纵向联系</td><td>代码重用</td><td>多级实现</td><td>可扩展性</td><td>新增成员</td></tr></tbody></table></div></div><br class="table-break"><p>
            冒号展开叙述：“先从本性上看：接口是一套功能规范集合，因此相同的接口代表相同的功能，多表示‘can-do’关系，常用后缀为‘-able’的形容词命名，如Comparable、Runnable、Cloneable等等。接口一般表述的是对象的边缘特征<a class="link" href="#note12"><sup>[12]</sup></a>，或者说一个对象在某一方面的特征，因此能在本质不同的类之间建立起横向联系。由于一个对象可拥有多方面的角色特征，故而可有多种接口。与之相对地，抽象类是一类对象的本质属性的抽象，因此相同的抽象基类代表相同的种类，多表示‘is-a’关系，常用名词命名。抽象类一般表述的是对象的核心特征，只能在本质相同的类之间沿着继承树建立起纵向联系。由于一个对象通常只有一个核心，故而只能有一种基类。再从目的上看：接口是为了规范重用，让一个规范有多种实现，看重的是<span class="emphasis"><em>可置换性</em></span>；抽象类主要是为了代码重用<a class="link" href="#note13"><sup>[13]</sup></a>，能逐级分步实现基类的抽象方法，看重的是<span class="emphasis"><em>可扩展性</em></span>。”
        </p><p>
            叹号追问：“演变指的又是什么呢？”
        </p><p>
            冒号答道：“严格说来，演变不属语义范畴，属于语法规则的一个推论。在系统演变过程中，接口与抽象类的表现差异很大。接口由于是被广泛采用的规范，相当于行业标准，一经确立不能轻易改动。一旦被广泛采用，它的任何改动——包括增减接口、修改接口的签名或规范——将波及整个系统，必须慎之又慎。抽象类的演变则没有那么困难，一则它在系统中用得没有接口那么广泛，更多地是家庭身份而非社会身份；二则它可随时新增域成员或有默认实现的方法成员<a class="link" href="#note14"><sup>[14]</sup></a>，所有子类将自动得以扩充。这是抽象类的最大优点之一。不过接口也有抽象类所不具备的优点，虽然自身难以演化，但很容易让其他类型演化为该接口的子类型。例如，JDK5.0之前的StringBuffer、CharBuffer、Writer和PrintStream本是互不相关的，在引进了接口Appendable并让以上类实现该接口后，它们便有了横向联系，均可作为格式化输出类Formatter的输出目标。”
        </p><p>
            问号还留有一个疑点：“现在接口与抽象类之间的差异是越来越清晰了，我只是有一点一直没想通：标记接口究竟有什么用？它一个方法都没有，也就谈不上规范，也无法利用多态机制，继承这类接口又有何意义呢？”
        </p><p>
            逗号随口说：“这就好比有些社会身份是光挂名头不干事的虚衔，不足为奇。”
        </p><p>
            冒号回应道：“先需澄清一点，一个类型的规范不限于单个的方法，类型整体上也有规范，比如主要目的、适用场合、限定条件、类不变量等等。另外，接口的目的是为了产生<span class="emphasis"><em>多态类型</em></span>，不能只看到‘多态’而忽略‘类型’。一个接口哪怕没有一个方法，也是有意义的。首先，接口是一种类型，有严格的语法保障和明确的语义提示，这也是静态类型的优势所在。让一个具体类继承特定接口，既凸显了设计者的用意，也授予用户针对性地处理该类型的权力。比如java.util.EventListener接口为所有的事件监听器提供了统一的根类型。其次，有时需要对某些类型提出特殊要求、提供特殊服务或进行特殊处理，而这些并不能通过公有方法来办到，也没有其他有效的语言支持。标记接口可担此任，成为类型<span class="term">元数据</span>（metadata）的载体。比如给一个类贴上一个java.io.Serializable的标签，它的对象便能被序列化<a class="link" href="#note15"><sup>[15]</sup></a>，具体工作由JVM来完成。用户也可以通过自定义私有的writeObject 、readObject等方法来定制序列化方式。值得指出的是，当标记接口仅仅用于元数据时，更好的办法是采用<span class="term">属性导向式编程</span>（@OP），Java中的annotation、C#中的attribute即作此用。”
        </p><p>
            逗号摸了摸后脑勺：“原来标记接口并非虚有其名，还是在偷偷地干实事呢。”
        </p><p>
            冒号见时候已到，准备落下帷幕：“至此，我们探讨了OOP中最基本的机制——封装、最独特的机制——继承、最重要的机制——多态。在今天的课结束之前，请大家每人用一个关键词来形容自己眼中的OOP，并作简要说明。”
        </p><p>
            引号说：“责任——在契约化的公民社会中，最重要的是对自己、对家庭、对社会的责任感。”
        </p><p>
            问号说：“变化——采用封装以防个人之变，慎用继承以防家庭之变，采用多态以防社会之变。”
        </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>
                    C#中表示列表和映射的抽象类型（具体类型）分别是IList（List）和IDictionary（Dictionary）。
                </p></li><li class="listitem"><p><a name="note2"></a>
                    此处主要指以继承为基础的抽象类型，如接口与抽象类。
                </p></li><li class="listitem"><p><a name="note3"></a>
                    在C++中，如果一个类不含数据只含抽象的成员函数（即pure virtual function），则该类有时被称为纯抽象类（pure abstract class），与Java和C#中的interface的功用大致相当。
                </p></li><li class="listitem"><p><a name="note4"></a>
                    参考文献【4】和【5】对trait有深入的介绍，并与mixin作了详细的比较。
                </p></li><li class="listitem"><p><a name="note5"></a>
                    该问题的一个解决方式是依赖注射，即把创建被合成对象的职责交给外界。但严格说来这不是合成，而是聚合或关联。它们之间的详细区别请参见§11.2。
                </p></li><li class="listitem"><p><a name="note6"></a>
                    C++可利用CRTP（<span class="strong"><strong>C</strong></span>uriously <span class="strong"><strong>R</strong></span>ecurring <span class="strong"><strong>T</strong></span>emplate <span class="strong"><strong>P</strong></span>attern）的惯用法来实现mixin。
                </p></li><li class="listitem"><p><a name="note7"></a>
                    指C#3.0的扩展方法（extension method）。
                </p></li><li class="listitem"><p><a name="note8"></a>
                    第一个支持OOP的语言是Simula 67（1967年）。
                </p></li><li class="listitem"><p><a name="note9"></a>
                    例外情形：Java的interface可含static final域成员，C#的interface还可含property、event或indexer成员。
                </p></li><li class="listitem"><p><a name="note10"></a>
                    虽然具体类型有可能被继承，但通常并不提倡。
                </p></li><li class="listitem"><p><a name="note11"></a>
                    据参考文献【1】中介绍，一些大型系统在引入抽象类后，编译时间少了一个数量级。
                </p></li><li class="listitem"><p><a name="note12"></a>
                    接口也可能描述对象的核心特征，但一个类至多一个这样的接口。
                </p></li><li class="listitem"><p><a name="note13"></a>
                    由于类继承同时也继承了接口，抽象类也能规范重用，但更侧重代码重用。
                </p></li><li class="listitem"><p><a name="note14"></a>
                    前提是新增的方法成员不与子类型的方法发生冲突。
                </p></li><li class="listitem"><p><a name="note15"></a>
                    严格说来，还要求该类所有非static非transient的域都是可序列化的。
                </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>
                    抽象数据类型的核心是数据抽象，而抽象类型的核心是多态抽象。
                </p></li><li class="listitem"><p>
                    抽象类型除了接口和抽象类外，还有mixin、trait等，它们用来克服以下问题——
                </p><p class="simpara"><span class="emphasis"><em>合成的缺陷：</em></span></p><p class="simpara">用法不如继承那么简便优雅；</p><p class="simpara">不能产生子类型；</p><p class="simpara">无法覆盖基础类的方法，也无法访问它的protected成员；</p><p class="simpara">不能以抽象类型为基础类。</p><p class="simpara"><span class="emphasis"><em>具体类型的矛盾与缺陷：</em></span></p><p class="simpara">作为创造对象的单位，功能越多越好；</p><p class="simpara">作为可重用的单位，功能越少越好。</p><p class="simpara">不宜被继承。</p><p class="simpara"><span class="emphasis"><em>接口的矛盾与缺陷：</em></span></p><p class="simpara">客户类希望它提供尽可能多的服务；</p><p class="simpara">实现类希望尽可能少的实现代码。</p><p class="simpara">无法代码重用。</p><p class="simpara"><span class="emphasis"><em>抽象类的缺陷：</em></span></p><p class="simpara">多重类继承或复杂晦涩或未获支持。</p></li><li class="listitem"><p>
                    mixin的特点：抽象性和依赖性；实用性和可重用性；专一性和细粒度性；可选性和边缘性。
                </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>
                    从具体类中分离出抽象类是一次质的飞跃，从抽象类中分离出接口则是另一次飞跃。
                </p></li><li class="listitem"><p>
                    接口与抽象类的语法区别：接口不能提供实现但能多重继承，抽象类则正相反；接口只能包含公有的、非静态的、抽象的方法成员，抽象类则无此限制。
                </p></li><li class="listitem"><p>
                    接口与抽象类的语义区别：接口是一套功能规范集合，相同的接口代表相同的功能，多表示“can-do”关系，一般是对象的边缘特征，在本质不同的类型之间建立横向联系；抽象类是一类对象的本质属性的抽象，相同的抽象基类代表相同的种类，多表示“is-a”关系，一般是对象的核心特征，在本质相同的类型之间建立纵向联系。接口看重规范重用和可置换性；抽象类看重代码重用和可扩展性。
                </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>
                    Bjarne Stroustrup．The Design and Evolution of C++．Reading, MA：Addison-Wesley，1994．277-281
                </p></li><li class="listitem"><p>
                    Joshua Bloch．Effective Java: Programming Language Guide．Boston, MA：Addison-Wesley，2001．84-88
                </p></li><li class="listitem"><p>
                    Nathanael Schärli，Stéphane Ducasse，Oscar Nierstrasz，Andrew P. Black．Traits: Composable Units of Behaviour．ECOOP，2003，LNCS 2743：248–274
                </p></li><li class="listitem"><p>
                    Stéphane Ducasse，Oscar Nierstrasz，Nathanael Schärli，Roel Wuyts，Andrew P. Black．Traits: A Mechanism for Fine-grained Reuse．ACM Transactions，2006，28(2)：331-388
                </p></li><li class="listitem"><p>
                    Wikipedia．Mixin．<a class="link" href="http://en.wikipedia.org/wiki/Mixin" target="_top">http://en.wikipedia.org/wiki/Mixin</a>
                </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>10-01 多态类型在何种程度上解放了静态类型的束缚？</li>
    <li>10-02 请总结参数多态与子类型多态的特点和适用场合。</li>
    <li>10-03 为什么抽象类型如此重要？</li>
    <li>10-04 你认为有必要引入mixin或trait类型吗？</li>
    <li>10-05 区分接口与抽象类的意义何在？</li>
    <li>10-06 你常有往接口中放置代码的冲动吗？</li>
    <li>10-07 如何理解文中“多态使得公民拥有社会身份”这句话？</li>
    <li>10-08 “针对接口编程”与“公民之间以社会身份互相交流”有何相似之处？</li>
    <li>10-09 你是如何理解OOP中抽象、封装、继承和多态的？</li>
    <li>10-10 每当一项新技术出现时，你通常抱什么态度？</li>
    <li>10-11 你会在编程中对某些语法上的限制感到恼火吗？</li>
    <li>10-12  在理解或比较一些编程概念时，你是更习惯从定义和语法的角度，还是更习惯从本源和语义的角度？</li>
    <li>10-13  本课与前课均提到了编程与武术相通之处：攻守兼备，动静得宜，刚柔并济，虚实结合。对此你有何心得体会？</li>
</ul><a class="a2a_dd addtoany_share_save" href="http://www.addtoany.com/share_save?linkurl=http%3A%2F%2Fblog.zhenghui.org%2F2009%2F10%2F24%2Fcolon-class-10_2%2F&amp;linkname=%E5%86%92%E5%8F%B7%E8%AF%BE%E5%A0%82%C2%A710.2%EF%BC%9A%E6%8A%BD%E8%B1%A1%E7%B1%BB%E5%9E%8B">分享/保存</a>]]></content:encoded>
			<wfw:commentRss>http://blog.zhenghui.org/2009/10/24/colon-class-10_2/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
	</channel>
</rss>
