<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
  <channel>
    <title>无明</title>
    <description></description>
    <link>http://javaite.javaeye.com</link>
    <language>UTF-8</language>
    <copyright>Copyright 2003-2008, JavaEye.com</copyright>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
    <generator>JavaEye - 做最棒的软件开发交流社区</generator>
      <item>
        <title>install mysql &amp; java on linux</title>
        <author>无明</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://javaite.javaeye.com">无明</a>&nbsp;
          链接：<a href="http://javaite.javaeye.com/blog/210669" style="color:red;">http://javaite.javaeye.com/blog/210669</a>&nbsp;
          发表时间: 2008年07月02日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          mysql<br />(1)下载mysql二进制分发包到工作目录<br /><br />(2)创建mysql 用户<br />shell> groupadd mysql<br />shell> useradd -g mysql mysql <br /><br />(3)解压缩<br />shell> tar zxvf /path/to/mysql-VERSION-OS.tar.gz <br /><br />(4)将工作目录链接到/usr/local/mysql<br />shell> cd /usr/localshell> ln -s full-path-to-mysql-VERSION-OS mysql <br /><br />(5)安装数据库<br />shell> cd mysql<br />shell> scripts/mysql_install_db --user=mysql <br />shell> chown -R root  .<br />shell> chown -R mysql data<br />shell> chgrp -R mysql .<br /><br />*此时可以手动启动数据库：<br />shell> bin/mysqld_safe --user=mysql & <br /><br />(6)设置mysql自动启动<br />将/usr/local/mysql/support-files/mysql.server 复制到/etc/init.d/mysql <br />shell> cp mysql.server /etc/init.d/mysql<br />shell> chmod +x /etc/init.d/mysql <br /><br /><br />在ubuntu server中添加为系统服务: <br />sudo update-rc.d mysql defaults <br />如需删除服务:<br />sudo update-rc.d mysql remove <br /><br />在RHEL4中：<br />shell> chkconfig --add mysql <br /><br />(7)配置mysql<br />添加my.cnf到/etc<br /><br />启动数据库更改root密码：<br />cd /usr/local/mysql/bin./mysql -u root -p<br />mysql>GRANT ALL PRIVILEGES ON *.* TO  root@ localhost IDENTIFIED BY "password";<br /><br />或者启动远程访问：<br />mysql>GRANT ALL PRIVILEGES ON *.* TO  root@ "%" IDENTIFIED BY "password";<br /><br /><br />java<br />(1)下载jdk<br />jdk-1_5_0_09-linux-i586.bin<br /><br />(2)安装jdk<br />shell> ./jdk-1_5_0_09-linux-i586.bin<br />shell> ln -s jdk-1_5_0_09  java<br /><br />(3)设置环境变量shell> vi /etc/profile<br /><br />添加以下内容：<br />JAVA_HOME=/usr/local/java<br />JRE=$JAVA_HOME/jre <br />PATH=$JAVA_HOME/bin:$JRE/bin:$PATH <br />CLASSPATH=.:$JAVA_HOME/bin/tools.jar:$JAVA_HOME/lib/dt.jar <br />export JAVA_HOME JRE CLASSPATH PATH
          <br/><br/>
          <span style="color:red;">
            <a href="http://javaite.javaeye.com/blog/210669#comments" style="color:red;">已有 <strong>0</strong> 人发表留言，猛击-&gt;&gt;<strong>这里</strong>&lt;&lt;-参与讨论</a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 02 Jul 2008 15:54:25 +0800</pubDate>
        <link>http://javaite.javaeye.com/blog/210669</link>
        <guid>http://javaite.javaeye.com/blog/210669</guid>
      </item>
      <item>
        <title>svn 基本命令(zt)</title>
        <author>无明</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://javaite.javaeye.com">无明</a>&nbsp;
          链接：<a href="http://javaite.javaeye.com/blog/156869" style="color:red;">http://javaite.javaeye.com/blog/156869</a>&nbsp;
          发表时间: 2008年01月17日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          把本地项目testsvn整个目录里的内容往备份站点塞, 这是最初的结构, 建立最初的rev 0<br />以后checkout回来同样是按该路径,把目录下的(不包括最后的目录名)文件取回<br />svn import testsvn svn://path/to/repos -m  "最后的这个注释不能少"<br /><br />取回项目, 路径同上. 最后取出来的新目录名不给定就会用最后路径的最后一个目录名, 这里就是repos目录<br />svn checkout svn:///path/to/repos A_NewProjectName<br /><br />更新本地项目. 千万记得,通过第一步import后的那个目录并没有默认建立svn与远程的关系,需要手动从服务器checkout回服务器的版本到本地, 才算正式建立与远程的关系(什么关系? 复杂关系!). checkout回来的项目就可以时不时地执行:<br />svn update<br /><br />搅和取回来的项目一番后,察看动了什么(一下命令都要在项目目录下运行) <br />svn status<br /><br />你对目录结构的修改或添加删除文件等类操作不能自动commit, 需要根据svn status显示的提示将文件/目录 明确 告诉svn是add, delete, copy 还是move<br />svn add new_dir<br />svn delete new_dir<br />svn status仅仅显示最根本的信息,更多的改动变化通过这个获得:<br />svn diff<br /><br /><br />查看项目当前状态。<br />svn status<br />查看你做的更改。<br />svn diff<br />svn commit      -m “message”       提交你的改变到版本库中,后面参数为此次发动的注释. <br />svn  list                   列出版本库中当前目录下的文件<br /> svn delete file          在本地中删除file文件。svn commit  提交更新才会在版本库实际删除file文件。 <br />svn  add      file        增加file文件。同样只有在svn commit 提交后才会在版本库中实际增加。 <br /><br />svn move file file2           将file文件更名为file2 <br /> . svn revert                      去除更改。后面可接操作过的文件。<br />svn log                                    svn 日志。<br /><br />svn各种状态关键字意义：<br />L    abc.c               # svn已经在.svn目录锁定了abc.c<br />M      bar.c               # bar.c的内容已经在本地修改过了<br />M     baz.c               # baz.c属性有修改，但没有内容修改<br />X      3rd_party           # 这个目录是外部定义的一部分<br />?      foo.o               # svn并没有管理foo.o<br />!      some_dir            # svn管理这个，但它可能丢失或者不完整<br />~      qux                 # 作为file/dir/link进行了版本控制，但类型已经改变<br />I      .screenrc           # svn不管理这个，配置确定要忽略它<br />A  +   moved_dir           # 包含历史的添加，历史记录了它的来历<br />M  +   moved_dir/README    # 包含历史的添加，并有了本地修改<br />D      stuff/fish.c        # 这个文件预定要删除<br />A      stuff/loot/bloo.h   # 这个文件预定要添加<br />C      stuff/loot/lump.c   # 这个文件在更新时发生冲突<br />R      xyz.c               # 这个文件预定要被替换<br />S  stuff/squawk        # 这个文件已经跳转到了分支<br /><br /><br />提交你涂污后的大作:<br />svn commit<br /><br /><br />温馨提示: 你可以本地建一个服务器来玩玩, <br />svnadmin create g:/repos<br />记得把g:/repos/conf目录下的passwd和svnserve.conf里的注释去掉, svnserve.conf里要改为<br />[general]<br />password-db = passwd  # 这个表示我要用目录下的passwd文件当作访问密码设置<br />passwd里要加上用户名<br />[users]<br />thisis = userpwd<br />之后,你就可以通过file:///g:/repos来访问.<br />或者, 由svnserve -d -r g:/repos 建立本地svn协议服务器,然后通过svn://localhost/来访问<br />你总是可以通过svn list svn://localhost/来察看服务器该路径下的内容. 然后通过svn checkout 把路径目录下的内容(不包括最后路径目录) 取回到 "./最后路径名/"目录下.
          <br/><br/>
          <span style="color:red;">
            <a href="http://javaite.javaeye.com/blog/156869#comments" style="color:red;">已有 <strong>0</strong> 人发表留言，猛击-&gt;&gt;<strong>这里</strong>&lt;&lt;-参与讨论</a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 17 Jan 2008 12:57:39 +0800</pubDate>
        <link>http://javaite.javaeye.com/blog/156869</link>
        <guid>http://javaite.javaeye.com/blog/156869</guid>
      </item>
      <item>
        <title>在SLES 9 sp3 x64上安装oracle 10gR2的2个问题</title>
        <author>无明</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://javaite.javaeye.com">无明</a>&nbsp;
          链接：<a href="http://javaite.javaeye.com/blog/91094" style="color:red;">http://javaite.javaeye.com/blog/91094</a>&nbsp;
          发表时间: 2007年06月17日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          订的机器快到货了，今天找了个机器，在sles 9 sp3上装了把oracle 10g R2，做做准备。<br /><br />基本环境是这样的：<br />OS : SLES 9 sp3 x64<br />database: oracle 10g R2 x64<br /><br />先到OTN找了个安装文档看，http://www.oracle.com/technology/tech/php/installtxt/10gr2_sles9.txt<br /><br />看下来，发觉在SLES 9上装oracle太简单了，多数工作SLES都为你做好了准备。<br />简单的归纳了一下要点：<br />1.尽可能简单的基本系统，采用默认安装就好。<br />2。通过yast安装需要用到的软件包，有如下几个：<br />cpp,gcc,gcc-c++,libgcc,openmotif.orarun<br />orarun会自动帮你添加oracle用户，组，生成为oracle准备的内核参数修改脚本等等<br />3.设置一下oracle账号，修改一下生成的脚本里的个别参数并执行。<br />这样就完成了安装前的准备工作，比RHEL简单多了。<br /><br />没想到做完这些，安装oracle时就出问题了。先是在安装中过程报"Error in invoking target 'install'  of makefile ....".估计是链接什么库文件时出错了，可我已经是对着安装文档安装所需库的了。<br />google了一通，有人说是oracle 10g的x64版本并不是纯64位版本，但按照其提示的解决方法，也不行。会不会用到了什么32位的库文件？在yast中挑了几个试试：autoconf,automake,bison,bison-32,glibc-devel-32,嘿，还真通过了。但究竟是哪个文件引起的，还不确定，先记下来，下次再安装的时候试试。很可能是glibc-devel-32。<br /><br />安装完oracle后，在创建数据库时有出错了：“ORA-27125，unable to create share memory segment”,一查，原来是SLES 9的x64版本，默认HUGETLBFS为true，直接装10g就会报这个错。改了一下oracle_home/bin/oracle，给它传进去一个参数，把HUGETLBFS禁用了，就正常了。但禁用HUGETLBFS会有啥影响我现在还搞不清楚，先记下来吧，明天查查看。
          <br/><br/>
          <span style="color:red;">
            <a href="http://javaite.javaeye.com/blog/91094#comments" style="color:red;">已有 <strong>5</strong> 人发表留言，猛击-&gt;&gt;<strong>这里</strong>&lt;&lt;-参与讨论</a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Sun, 17 Jun 2007 00:41:24 +0800</pubDate>
        <link>http://javaite.javaeye.com/blog/91094</link>
        <guid>http://javaite.javaeye.com/blog/91094</guid>
      </item>
      <item>
        <title>squid 2.6 反向代理配置</title>
        <author>无明</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://javaite.javaeye.com">无明</a>&nbsp;
          链接：<a href="http://javaite.javaeye.com/blog/90804" style="color:red;">http://javaite.javaeye.com/blog/90804</a>&nbsp;
          发表时间: 2007年06月15日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p><span style="font-family: Arial;">
<p><span style="font-family: Arial;">http_port 192.168.1.233:80 defaultsite=192.168.1.150 vhost vport=80<br /> cache_peer 192.168.1.150 parent 80 0 no-query originserver<br /> cache_peer 192.168.1.233 parent 100 0 no-query originserver<br /> cache_peer_domain 192.168.1.150 www.xx.cn<br /> cache_peer_domain 192.168.1.233 www.aa.cn</span></p>
<p><span style="font-family: Arial;">acl all src 0.0.0.0/0.0.0.0<br /> acl Safe_ports port 80 <br /> acl Safe_ports port 443<br /> http_access deny !Safe_ports<br /> http_access allow all </span></p>
<p><span style="font-family: Arial;"><br /><span style="font-family: Arial;">refresh_pattern -i .&nbsp; 0 100% 0 reload-into-ims</span></span></p>
<p><span style="font-family: Arial;">只有一个IP，但是有几台机器要发布到internet上，便想起用squid来做个反向代理，只是起转发请求的作用，不缓存东西。</span></p>
</span></p>
          <br/><br/>
          <span style="color:red;">
            <a href="http://javaite.javaeye.com/blog/90804#comments" style="color:red;">已有 <strong>4</strong> 人发表留言，猛击-&gt;&gt;<strong>这里</strong>&lt;&lt;-参与讨论</a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 15 Jun 2007 17:27:48 +0800</pubDate>
        <link>http://javaite.javaeye.com/blog/90804</link>
        <guid>http://javaite.javaeye.com/blog/90804</guid>
      </item>
      <item>
        <title>ExtJS tree in rails</title>
        <author>无明</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://javaite.javaeye.com">无明</a>&nbsp;
          链接：<a href="http://javaite.javaeye.com/blog/76860" style="color:red;">http://javaite.javaeye.com/blog/76860</a>&nbsp;
          发表时间: 2007年05月03日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          今天发现ExtJS支持Prototype了(不喜欢在rails中混用prototype和yui)，赶紧去下载一个,并试验了一下tree组件，感觉还不错，考虑以后就用ExtJS了。<br />闲话不说了，贴代码<br />schema.rb<br /><br /><pre name="code" class="ruby">
ActiveRecord::Schema.define() do

  create_table "categories", :force => true do |t|
    t.column "id", :int
    t.column "parent_id", :int
    t.column "tree_id",   :int
    t.column "lft",       :int
    t.column "rgt",       :int
    
    t.column "name",      :string 
    t.column "description", :string
    
    t.column "created_at",:int
       
  end
   
   add_index :categories, :name, :unique  => true
    

end

</pre><br /><br />接着是model:<br /><pre name="code" class="ruby">
class Category &lt; ActiveRecord::Base
  acts_as_nested_set :scope => :tree_id
  
  def leaf?
    if self.rgt - self.lft == 1
      return true
    else
      return false
    end 
  end

end

</pre><br />这里用的是Nested_set存储树，所以只需计算节点的左右值是否连续来判断叶子节点。Nested_set查询比较好用，就是插入效率差，用于目录的存储比较合适，一般目录不会频繁更新。<br /><br />然后是rhtml：<br /><pre name="code" class="ruby">
&lt;html>
    &lt;head>
        &lt;title>r&lt;/title>

        &lt;%= stylesheet_link_tag "../javascripts/ext/resources/css/ext-all.css" %>
        &lt;%= javascript_include_tag "ext/adapter/prototype/prototype.js" %>
        &lt;%= javascript_include_tag "ext/adapter/prototype/scriptaculous.js" %>
        &lt;%= javascript_include_tag "ext/adapter/prototype/effects.js" %>
        &lt;%= javascript_include_tag "ext/adapter/prototype/ext-prototype-adapter.js" %>
        &lt;%= javascript_include_tag "ext/ext-all-debug.js" %>

    &lt;/head>	
    &lt;body>	
       &lt;%= javascript_include_tag "category_tree.js" %>
       &lt;div id="tree-div" style="border:5px solid #99bbe8; overflow:hidden; width:130px;">&lt;/div>
   
    &lt;/body>
&lt;/html>
</pre><br />让页面加载时同时加载树组件，也就是那个category_tree.js：<br /><br /><pre name="code" class="java">
Ext.onReady(function(){
    // shorthand
    var Tree = Ext.tree;
    
    var tree = new Tree.TreePanel('tree-div', {
        animate:true, 
        loader: new Tree.TreeLoader({dataUrl:'http://localhost:3000/admin/category/category_tree'}), //修改这里
        enableDD:true,
        containerScroll: true
    });

    // set the root node
    var root = new Tree.AsyncTreeNode({
        text: 'Ext JS',
        draggable:false,
        id:'source'
    });
    tree.setRootNode(root);


    // render the tree
    tree.render();
    root.expand();
});
</pre><br />这个是抄ExtJS的Demo，只是改了数据源。ExtJS需要接收JSON格式的数据，我们可以在actioncontroller中提供：<br /><pre name="code" class="ruby">
class Admin::CategoryController &lt; ApplicationController

  def category_tree
    
    categories = Category.find_by_sql("select * from categories where parent_id is null") 
    
    data = get_tree(categories,nil)  
   
    render :text=>data.to_json, :layout=>false
    
  end 
  
  def get_tree(categories, parent)
     data = Array.new
     
     categories.each { |category|
       if !category.leaf?
         
         if data.empty?
           data =   [{"text"  =>  category.name,"id"  => category.id,"cls" => "folder" ,"leaf"  => false,
             "children" => get_tree(category.children,category) }] 
         else
           data.concat ([{"text"  =>  category.name,"id"  => category.id,"cls" => "folder" ,"leaf"  => false,
              "children" => get_tree(category.children,category)}])
        end
        
       else
         data.concat([{"text" => category.name,"id" => category.id,"cls" => "file","leaf" => true}]) 
       end
         
     }
     return data
     
  end
 
end

</pre><br />这段代码使用递归构造了一棵树，并转换成ExtJS需要的json数据返回页面。<br />好啦，这时用浏览器访问就能看到效果了.只是写了一点点代码，加上ExtJS，我们就有一个树型目录了，想起以前用java+xloadtree做树型目录的那个费劲啊，哎
          <br/><br/>
          <span style="color:red;">
            <a href="http://javaite.javaeye.com/blog/76860#comments" style="color:red;">已有 <strong>13</strong> 人发表留言，猛击-&gt;&gt;<strong>这里</strong>&lt;&lt;-参与讨论</a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 03 May 2007 22:19:29 +0800</pubDate>
        <link>http://javaite.javaeye.com/blog/76860</link>
        <guid>http://javaite.javaeye.com/blog/76860</guid>
      </item>
      <item>
        <title>mysql的双重许可</title>
        <author>无明</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://javaite.javaeye.com">无明</a>&nbsp;
          链接：<a href="http://javaite.javaeye.com/blog/57269" style="color:red;">http://javaite.javaeye.com/blog/57269</a>&nbsp;
          发表时间: 2007年03月05日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>今天在cu逛了逛，看到一个有意思的帖子：</p>
<p><span style="font-family: Arial;"><a href="http://www.chinaunix.net/jh/17/814671.html">http://www.chinaunix.net/jh/17/814671.html</a></span></p>
<p>也就是mysql的双重许可问题。以前我一直以为mysql是基于GPL发布的，只是象Redhat那样卖服务，不过从这个帖子看似乎不是那么回事：关键是，如果你的程序是商业应用，那么要么选择把你的程序也遵循GPL发布，要么就要向mysql购买商业许可。</p>
<p>上mysql的网站看了看，<span style="font-family: Arial;"><a href="http://www.mysql.com/company/legal/licensing/commercial-license.html">http://www.mysql.com/company/legal/licensing/commercial-license.html</a></span></p>
<p>其中有这么一段：</p>
<p>Specifically: <br /><br /> If&nbsp;you&nbsp;include&nbsp;the&nbsp;MySQL&nbsp;server&nbsp;with&nbsp;an&nbsp;application&nbsp;that&nbsp;is&nbsp;not&nbsp;licensed&nbsp;under&nbsp;the&nbsp;GPL&nbsp;or&nbsp;GPL-compatible&nbsp;license,&nbsp;you&nbsp;need&nbsp;a&nbsp;commercial&nbsp;license&nbsp;for&nbsp;the&nbsp;MySQL&nbsp;server. <br /><br /> If&nbsp;you&nbsp;develop&nbsp;and&nbsp;distribute&nbsp;a&nbsp;commercial&nbsp;application&nbsp;and&nbsp;as&nbsp;part&nbsp;of&nbsp;utilizing&nbsp;your&nbsp;application,&nbsp;the&nbsp;end-user&nbsp;must&nbsp;download&nbsp;a&nbsp;copy&nbsp;of&nbsp;MySQL;&nbsp;for&nbsp;each&nbsp;derivative&nbsp;work,&nbsp;you&nbsp;(or,&nbsp;in&nbsp;some&nbsp;cases,&nbsp;your&nbsp;end-user)&nbsp;need&nbsp;a&nbsp;commercial&nbsp;license&nbsp;for&nbsp;the&nbsp;MySQL&nbsp;server&nbsp;and/or&nbsp;MySQL&nbsp;client&nbsp;libraries. </p>
<p>要是这么算的话，mysql用来做商业应用可不是好选择&mdash;&mdash;除非愿意购买它的商业许可。</p>
<p>另外，还有一件事就是mysql似乎不打算再释出二进制安装版本（传闻挺多，官方网站上也言辞闪烁，但有一点可以肯定，更新速度会大大慢于商业版本），虽然自己可以编译安装，但总觉得怪怪的，让人觉得mysql靠扛着开源大旗发家后，开始打背叛革命的主意，一心搂钱了。</p>
          <br/><br/>
          <span style="color:red;">
            <a href="http://javaite.javaeye.com/blog/57269#comments" style="color:red;">已有 <strong>15</strong> 人发表留言，猛击-&gt;&gt;<strong>这里</strong>&lt;&lt;-参与讨论</a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 05 Mar 2007 01:01:12 +0800</pubDate>
        <link>http://javaite.javaeye.com/blog/57269</link>
        <guid>http://javaite.javaeye.com/blog/57269</guid>
      </item>
      <item>
        <title>面向对象语言导论(节选) 2(ZT)</title>
        <author>无明</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://javaite.javaeye.com">无明</a>&nbsp;
          链接：<a href="http://javaite.javaeye.com/blog/22556" style="color:red;">http://javaite.javaeye.com/blog/22556</a>&nbsp;
          发表时间: 2004年08月02日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          第三章基于类的高级特性 (Advanced Class-Based Features) <br /><br />传统的基于类的面向对象语言的一个主要特点就是inheritance, subclassing和subtyping之间的密不可分的联系。很多的面向对象语言的语法，概念，就是从这三者而来的。比如说，通过subclassing, 你可以继承父类的一些方法，而同时你又可以在子类中改写父类的方法。这个改写过的方法，通过subtyping, subsumption, 又可以从一个类型是父类的对象去调用。 <br /><br />但是，inheritance, subclassing, subtyping这三者并不是永远和睦相处的。在一些场合，这三者之间的纠缠不清会妨碍到通过继承或泛型得到的代码重用。因此，人们开始注意到把这三者分离开来的可能性。区分subclassing和subtyping已经很常见了。而其它的一些方法还处于研究的阶段。这一章我们将介绍这样一些方法。 <br /><br />一，对象类型 <br />在早期的面向对象语言中（如Simula）, 类型的定义是和方法的实现是混合在一起的。这种方式违反了我们今天已经被广泛认识到的把实现和规范(Specification) 分离的原则。这种分离得原则在开发是团队进行的时候尤其显得重要。 <br /><br />更近期一些的语言，通过引入不依赖于实现的对象类型来区分实现和规范。Modula-3以及其它如Java等的支持class和interface的语言都是采用的这种技术。 <br /><br />在本书中，我们开始引入InstanceTypeOf(cell)时，它代表的概念相当有限。看上去，它似乎只表示用new cell生成的对象的类型，于是，我们并不能用它来表示从其它类new出来的对象。但后来，当我们引入了subclassing, method overriding, subsumption和dynamic dispatch之后，事情变得不那么简单了。我们的InstanceTypeOf(cell)已经可以用来表示从cell的子类new出来的对象，这些对象可以包括不是cell类定义的属性和方法。 <br />如此看来，让InstanceTypeOf(cell)依赖于一个具体的类似乎是不合理的。实际上，一个InstanceTypeOf(cell)类型的对象不一定会跟class cell扯上任何关系。 <br />它和cell类的唯一共同之处只是它具有了所有cell类定义的方法的签名（signature）. <br /><br />基于这种考虑，我们可以引入对象类型的语法： <br />针对cell类和reCell类的定义： <br />代码: <br /><br />class cell is <br />    var contents: Integer :=0; <br />    method get(): Integer is <br />        return self.contents; <br />    end; <br />    method set(n:Integer) is <br />        self.contents := n; <br />    end; <br />end; <br /><br />subclass reCell of cell is <br />    var backup: Integer := 0; <br />    override set(n: Integer) is <br />        self.backup := self.contents; <br />        super.set(n); <br />    end; <br />    method restore() is <br />        self.contents := self.backup; <br />    end; <br />end; <br /> <br /><br /><br />我们可以给出这样的对象类型定义： <br />代码: <br /><br />ObjectType Cell is <br />    var contents: Integer; <br />    method get(): Integer; <br />    method set(n:Integer); <br />end; <br /><br />ObjectType ReCell is <br />    var contents: Integer; <br />    var backup: Integer; <br />    method get(): Integer <br />    method set(n: Integer); <br />    method restore(); <br />end; <br /> <br /><br /><br />这两个类型的定义包括了所有cell类和reCell类定义的属性和方法的类型，但却并不包括实现。这样，它们就可以被当作与实现细节无关的的接口以实现规范和实现的分离。两个完全无关的类c和c’, 可以具有相同的类型Cell, 而Cell类型的使用者不必关心它使用的是c类还是c’类。 <br /><br />注意，我们还可以加入额外的类似继承的语法来避免在ReCell里重写Cell里的方法签名。但那只是小节罢了。 <br /><br />二，分离Subclassing和Subtyping. <br />在我们上一章的讨论中，subtype的关系是建立在subclass关系的基础上的。但如果我们想要让type独立于class, 那么我们也需要定义独立于subclass的subtype. <br />在定义subtype时，我们又面临着几种选择：subtype是由类型的组成结构决定的呢？还是由名字决定呢？ <br />由类型的组成结构决定的subtype是这样的：如果类型一具有了类型二的所有需要具备的属性和方法，我们就说类型一是类型二的subtype. <br />由类型名字决定的subtype是这样的：只有当类型一具有了类型二的所有需要具备的属性和方法， 并且类型一被明确声明为类型二的subtype时，我们才认可这种关系。 <br /><br />而如果我们的选择是一，那么那些属性和方法是subtype所必需具备的呢？哪些是可有可无的呢？ <br /><br />由组成结构决定的subtype能够在分布式环境和object persistence系统下进行类型匹配（译者注：对这点，我也不甚明了。看来，纸造得还是不够）。缺点是，如果两个类型碰巧具有了相同的结构，但实际上却风马牛不相及，那就会造成错误。不过，这种错误是可以用一些技术来避免的。 <br /><br />相比之下，基于名字的subtype不容易精确定义，而且也不支持基于结构的subtype. <br />(译者按，这里，我无论如何和作者找不到同感。基于结构的subtype的缺点是一目了然，不过完美的避免的方法我却看不出来。而基于名字的subtype为什么就不能精确定义呢？C++/Java/C#, 所有流行的OO语言都只支持基于名字的subtype, 也没有发现有什么不够灵活的地方。需要在不同名字但类似结构的类型之间架桥的话，adapter完全可以胜任嘛！) <br /><br />目前，我们可以先定义一个简单的基于结构的subtype关系： <br />对两个类型O和O’, <br />O’ &lt;: O 当 O’ 具有所有O类型的成员。O’可以有多于O的成员。 <br />例如：ReCell &lt;: Cell. <br /><br />为了简明，这个定义没有考虑到方法的特化。 <br /><br />另外，当类型定义有递归存在的时候（类似于链表的定义），对subtype的定义需要额外地加小心。我们会在第九章及之后章节讲到递归的时候再详细说明。（译者按：第九章啊？饶了我吧！想累死我啊？） <br /><br />因为我们不关心成员的顺序，这种subtype的定义自动地就支持多重的subtype. <br /><br />比如说： <br />代码: <br /><br />ObjectType ReInteger is <br />    var contents: Integer; <br />    var backup: Integer; <br />    method restore(); <br />end; <br /> <br /><br />那么，我们就有如下的subtype的关系： <br />代码: <br /><br />ReCell &lt;: Cell <br />ReCell &lt;: ReInteger <br /> <br /><br /><br />(译者按，作者的例子中没有考虑到象interface不能包含数据域这样的细节。实际上，如果我们支持对数据域的override, 而不支持shadowing -- 作者的基于结构的subtype语义确实隐含着这样的逻辑— 那么，interface里包含不包含数据域就无关紧要了，因为令人头疼的名字冲突问题已经不存在了) <br /><br />从这个定义，我们可以得出： <br />如果c’是c的子类， 那么ObjectTypeOf(c’) &lt;: ObjectTypeOf(c) <br /><br />注意，这个定义的逆命题并不成立，也就是说： <br />即使c’和c之间没有subclass的关系，只要它们所定义的成员符合了我们subtype的定义，ObjectTypeOf(c’) &lt;: ObjectTypeOf(c)仍然成立。 <br /><br />回过头再看看我们在前一章的subclass-is-subtyping： <br />InstanceTypeOf(c’) &lt;: InstanceTypeOf(c) 当且仅当 c’是c的子类 <br />在那个定义中，只有当c’是c的子类时，ObjectTypeOf(c’) &lt;: ObjectTypeOf(c)才能成立。 <br /><br />相比之下，我们已经部分地把subclassing和subtyping分离开了。Subclassing仍然是subtyping, 但subtyping不再一定要求是subclassing了。我们把这种性质叫做“subclassing-implies-subtyping”而不是“subclass-is-subtyping”了。 <br /><br />三，泛型 (Type Parameters) <br />一般意义上来说，泛型是一种把相同的代码重用在不同的类型上的技术。它作为一个相对独立于其它面向对象特性的技术，在面向对象语言里已经变得越来越普遍了。我们这里之所以讨论泛型，一是因为泛型这种技术本身就很让人感兴趣，另外，也是因为泛型是一个被用来对付二元方法问题 (binary method problem) 的主要工具。 <br /><br />和subtyping共同使用，泛型可以用来解决一些在方法特化等场合由反协变带来的类型系统的困难。考虑这样一个例子： <br />我们有Person和Vegitarian两种类型，同时，我们有Vegitable和Food两种类型。而且，Vegitable &lt;: Food. <br /><br />代码: <br /><br />ObjectType Person is <br />    … <br />    method eat(food: Food); <br />end; <br />ObjectType Vegetarian is <br />    … <br />    method eat(food: Vegitable); <br />end; <br /> <br /><br />这里，从常识，我们知道一个Vegitarian是一个人。所以，我们希望可以有Vegetarian &lt;: Person. <br />不幸的是，因为参数是反协变的，如果我们错误地认为Vegetarian &lt;: Person, 根据subtype的subsumption原则，一个Vegetarian的对象就可以被当作Person来用。于是一个Vegetarian就可以错误地吃起肉来。 <br /><br />使用泛型技术，我们引入Type Operator (也就是，从一个类型导出另一个类型，概念上类似于对类型的函数)。 <br />代码: <br /><br />ObjectOperator PersonEating[F&lt;:Food] is <br />    … <br />    method eat(food: F); <br />end; <br />ObjectOperator VegetarianEating[F&lt;: Vegetable] is <br />    … <br />    method eat(food: F); <br />end; <br /> <br /><br />这里使用的技术被称作Bounded Type Parameterization. (Trelli/Owl, Sather, Eiffel, PolyTOIL, Raptide以及Generic Java都支持Bounded Type Parameterization. 其它的语言，如C++, 只支持简单的没有类型约束的泛型) <br /><br />F是一个类型参数，它可以被实例化成一个具体的类型。 类似于变量的类型定义，一个bound如F&lt;:Vegitable限制了F只能被Vegitable及其子类型所实例化。所以，VegitarianEating[Vegitable], VegitarianEating[Carrot]都是合法的类型。而VegitarianEating[Beef]就不是一个合法的类型。类型VegitarianEating[Vegitable]是VegitarianEating的一个实例，同时它等价于类型Vegitarian. （我们用的是基于结构的subtype） <br /><br />于是，我们有： <br />对任意F&lt;:Vegitable, VegitarianEating[F] &lt;: PersonEating[F] <br /><br />对于原来的Vegitarian类型，我们有： <br />Vegetarian = VegetarianEating[Vegetable] &lt;: PersonEating[Vegitable] <br /><br />这种关系，正确地表达了“一个素食者是一个吃蔬菜的人”的概念。 <br /><br /><br />除了Bounded Type Parameterization之外，还有一种类似的方法也可以解决这个素食者的问题。这种方法被叫做：Bounded Abstract Type <br />请看这个定义： <br />代码: <br /><br />ObjectType Person is <br />    Type F&lt;: Food; <br />    … <br />    var lunch: F; <br />    method eat(food: F); <br />end; <br />ObjectType Vegetarian is <br />    Type F&lt;: Vegitable; <br />    … <br />    var lunch: F; <br />    method eat(food: F); <br />end; <br /> <br /><br /><br />这里，F&lt;:Food的意思是，给定一个Person, 我们知道他能吃某种Food, 但我们不知道具体是哪一种。这个lunch的属性提供这个Person所吃的Food. <br /><br />在创建Person对象时，我们可以先选定一个Food的subtype, 比如说，F=Dessert. 然后，用一个Dessert类型的变量赋给属性lunch. 最后再实现一个eat(food:Dessert)的方法。 <br /><br />这样，Vegetarian &lt;: Person是安全的了。当你把一个Vegetarian当作一个Person处理时，这个Vegitarian可以安全地吃他自带的午餐，即使你不知道他吃的是肉还是菜。 <br />这种方法的局限在于，Person, Vegitarian只能吃他们自带的午餐。你不能让他们吃买来的午餐。 <br /><br /><br /><br />四，彻底划清界限（继续分离Subclassing和Subtyping） <br />在第二节我们讨论了部分分离Subclassing和subtyping的方法，即subclassing-implies-subtyping. 现今的许多面向对象语言，如Java, C#都是采用了这种技术。除此之外，还有一种进一步分离Subclassing和subtyping的方法。这种被称作inheritance-is-not-subtyping的方法通过完全割裂subclassing和subtyping之间的联系而在更大程度上方便了代码的重用。 <br />它的产生很大程度上是由于人们想要使用在反协变位置上的Self类型 （如Self类型的参数）。当然，增大继承的能力的代价是subsumption的灵活性降低了。当Self类型出现在反协变的位置上时，subclass不再意味着subtype, 因此，subsumption也就不存在了。 <br /><br />下面请考虑这样两个类型： <br />代码: <br /><br />ObjectType Max is <br />    var n: Integer; <br />    method max(other:Max): Max; <br />end; <br />ObjectType MinMax is <br />    var n: Integer; <br />    method max(other:MinMax): MinMax; <br />    method min(other:MinMax): MinMax; <br />end; <br /> <br /><br />再考虑两个类： <br />代码: <br /><br />class MaxClass is <br />    var n:Integer :=0; <br />    method max(other: Self): Self is <br />        if self.n &gt; other.n then return self else return other end; <br />    end; <br />end; <br />subclass MinMaxClass of MaxClass is <br />    method min(other: Self): Self is <br />        if self.n &lt; other.n then return self else return other end; <br />    end; <br />end; <br /> <br /><br /><br />方法min和max是二元的，因为它操作两个对象：self和other. other的类型是一个出现在反协变位置上的Self类型。 <br />注意，方法max有一个反协变的参数类型Self, 并且它被从类MaxClass继承到了MinMaxClass. <br /><br />很直观地，类MaxClass对应着类型Max；类MinMaxClass对应着类型MinMax. 为了精确地表示这种对应关系，我们必须针对包含使用Self类型的成员的类重新定义ObjectTypeOf，以便得到ObjectTypeOf(MaxClass) = Max, ObjectTypeOf(MinMaxClass) = MinMax。 <br /><br />为了使以上的等式成立，我们把类中的Self类型映射到ObjectType中的类型名称本身。我们同时让Self类型在继承的时候特化。 <br />在本例中，当我们映射MinMaxClass的类型时，我们把继承来的max方法中的Self类型映射到MinMax类型。而对MaxClass中max方法的Self类型，我们使用Max类型。 <br />如此，我们可以得到，任何MaxClass生成的对象，都具备Max类型。而任何MinMaxClass生成的对象都具备MinMax类型。 <br /><br />虽然MinMaxClass是MaxClass的子类，但这里MinMax却不是Max的子类型(subtype). <br />举个例子，如果我们假设subtype在这种情况下成立，那么，对以下的这个类： <br /><br />代码: <br /><br />subclass MinMaxClass’ of MinMaxClass is <br />    override max(other: Self): Self is <br />        if other.min(self) = other then return self else return other end; <br />    end; <br />end; <br /> <br /><br /><br />根据我们对Self类型的映射规则和基于结构的subtype规则，我们知道，ObjectTypeOf(MinMaxClass’) = MinMax, 所以，对任何MinMaxClass’生成的对象mm’ ，我们可以知道mm’ : MinMax. <br />而如果MinMax &lt;: Max成立，根据subsumption, 我们就能推出mm’ : Max. <br />于是当我们调用mm’.max(m)的时候，m可以是任何Max类型的对象。但是，当max的方法体调用other.min(self)的时候，如果这个other不具有min方法，这个方法就会失败。 <br />由此可见，MinMax &lt;: Max并不成立。 <br />子类(subclass) 在使用反协变的Self类型时就不再具有subtype的性质了。 <br /><br /><br />五，对象协议 （Object Protocol） <br /><br /><br />从上一节的讨论，我们看到对使用反协变Self类型的类，subclass不再是subtype了。这是一个令人失望的结果，毕竟很多激动人心的面向对象的优点是通过subtype, subsumption来实现的。 <br />不过，幸运的是，虽然失去了subtype, 我们还是可以从中挖掘出来一些可以作为补偿的有用的东西的。只不过，不象subtype, 我们不能享受subsumption了。 <br />下面就让我们来研究这种新的关系。 <br /><br />在第四节的MinMax的例子中，subtype不再成立；简单地使用泛型，引入 <br />ObjectOperator P[M &lt;: Max] is … end; 也似乎没有什么用。P[Max]虽然成立，但P[MinMax]却是不合法的，因为MinMax &lt;: Max不成立。 <br /><br />但是，直观上看，任何支持MinMax这种协议的对象，也支持Max协议的 （虽然我们还不知道这个“协议”到底是个什么东西）。于是，似乎隐隐约约地又一个叫做“子协议”(subprotocol)的家伙在向我们招手了。 <br />为了发现这个子协议的关系，让我们先定义两个type operator (还记得吗？就是作用在类型上的函数)： <br />代码: <br /><br />ObjectOperator MaxProtocol[X] is <br />    var n: Integer; <br />    method max(other: X) :X; <br />end; <br />ObjectOperator MinMaxProtocol[X] is <br />    var n:Integer; <br />    method max(other: X):X; <br />    method min(other: X):X; <br />end; <br /> <br /><br /><br />这样，Max = MaxProtocol[Max], MinMax = MinMaxProtocol[MinMax] <br />更一般地说，我们可以定义： <br />代码: <br /><br />什么 = 什么-Protocol[什么] <br /> <br /><br /><br />还记得lamda-calculus里的fixpoint吗？给定一个函数F, F(fixpoint(F)) = fixpoint(F) <br />而在我们这个子协议的type operator里，如果我们认为type operator是作用于类型的函数的话， 那么这个“什么”，就是“什么-Protocol”函数的fixpoint啊！ <br />也就是说： <br />代码: <br /><br />什么= fixpoint (什么-Protocol). <br /> <br /><br /><br />除了以上的fixpoint的性质，我们还发现了存在于Max和MinMax之间的关系。 <br />首先，MinMax是MaxProtocol的一个post-fixpoint，即： <br />代码: <br /><br />MinMax &lt;: MaxProtocol[MinMax] <br /> <br /><br />其次，我们可以看出： <br />代码: <br /><br />MinMaxProtocol[Max] &lt;: MaxProtocol[Max] <br />MinMaxProtocol[MinMax] &lt;: MaxProtocol[MinMax] <br /> <br /><br /><br />最后，如果我们用&lt;::来表示一个更高阶的子类型关系： <br />代码: <br /><br />P &lt;:: P’ 当且仅当 P[T] &lt;: P’[T]　 <br /> <br /><br />那么，MinMaxProtocol &lt;:: MaxProtocol. <br /><br /><br /><br />对于子协议的定义，我们可以采取上面的&lt;::的定义，即： <br />如果S-Protocol&lt;::T-Protocol, 那么我们称类型S和类型T之间是子协议关系。　(1) <br /><br />我们也可以不用这个高阶的关系，仍然使用&lt;:这个subtype的关系： <br />如果S&lt;:T-Protocol[S], 那么我们称类型S和类型T之间是子协议关系。　(2) <br /><br />其实，第一个定义似乎更直观一点，它更明确地显示出子协议关系是作用于类型上的函数(type operator)之间的关系，而不是类型之间的关系。 <br /><br />使用泛型技术，如果我们的某一个类型需要一个实现MaxProtocol的类型来实例化的话，我们可以采用下面两种方法中的一种： <br />代码: <br /><br />ObjectOperator P1[X &lt;: MaxProtocol[X]] is … end; (1) <br />ObjectOperator P2[P &lt;:: MaxProtocol] is … end; (2) <br /> <br /><br /><br />这两种方法在表达能力上是相同的。第一种方法叫做F-bounded parameterization. (译者按，Generic Java据说就采用了这个方法)；第二种方法叫做 higher-order bounded parameterization. <br /><br />对于具体语言的实现，为了方便，我们可以隐藏这个type operator. 语法上可以直接对类型支持subprotocol的关系（用&lt;#来表示）。 <br />对我们的MinMax的例子来说，我们就有： <br />MinMax &lt;# Max. <br />(译者按，绕了一大圈，什么fixpoint啊，post-fixpoint啊，什么高阶关系啦，希望没把你绕晕。其实，你只需要记住MinMax &lt;# Max, 就八九不离十了，呵呵) <br /><br />&lt;#这个关系并不具有subsumption的特性，所以，你不能指望从它身上得到传统OO里面的多态。但是，与泛型相结合，它却是非常有用的。我们可以对我们的generic class给出这样的约束：所有你用来当作类型参数传给我的模板（译者按，我这里使用模板这个不大精确的名词，但也许对大量的C++程序员会更易理解）的类型，必须符合如下规范：·％￥＃……％＃（（（ <br /><br />思考题： <br />１．　Java是支持Covariant的Array的。你可以把一个String[] 类型的对象当作一个Object[]类型来使用。 <br />那么，Java的covariant array是类型安全的吗？为什么？ <br />２．　大家都知道经典的矩形和正方形之间的关系吧？在支持get, set的正方形和矩形之间，并不存在subtype的关系，这是因为subsumption对get或set操作是不安全的。但是，是否正方形和矩形之间就不可能有subtype的关系呢？如果矩形的类型只支持get, 结果会是什么？如果正方形只支持set, 结果又是什么呢？
          <br/><br/>
          <span style="color:red;">
            <a href="http://javaite.javaeye.com/blog/22556#comments" style="color:red;">已有 <strong>0</strong> 人发表留言，猛击-&gt;&gt;<strong>这里</strong>&lt;&lt;-参与讨论</a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 02 Aug 2004 11:35:36 +0800</pubDate>
        <link>http://javaite.javaeye.com/blog/22556</link>
        <guid>http://javaite.javaeye.com/blog/22556</guid>
      </item>
      <item>
        <title>面向对象语言导论(节选) 1(ZT)</title>
        <author>无明</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://javaite.javaeye.com">无明</a>&nbsp;
          链接：<a href="http://javaite.javaeye.com/blog/22555" style="color:red;">http://javaite.javaeye.com/blog/22555</a>&nbsp;
          发表时间: 2004年08月02日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          （译自Martin Abadi, Luca Cardelli的对象理论一书的第一部分） <br />译者前言 <br />这本书是我们上面向对象类型理论的教材。当时上这门课时，心里满不以为然，觉得自己的C++和OO已经颇有造纸，C++和Java的类型系统不说倒背如流，也是轻车熟路，上这么一门课不是白拿学分？哈哈! <br />但一上起来，才发现自己竟如井底之蛙一样。老天，原来就这么简单的面向对象竟有这么多说道！原来除了C++, Java, 面向对象还有这么多没见过甚至没想过的东西！ <br />前几章概论，勉强还都搞定了。但后面上到类型系统的建模，subject reduction的证明，就发现自己就象回到了本科时代，这，这，这怎么都是数学啊！ <br />这两天心血来潮。就想把它翻译一下。后面艰深的地方自觉功力太浅，就不不自量力了。不过，倒可以把前面几章的概论翻译一下，如果能起到帮助大家开阔眼界的作用，也就没白费劲。 <br /><br />第二章，基于类的面向对象语言 <br />基于类的面向对象语言是面向对象世界里的主流。它包括： <br />Simula, 第一个面向对象语言 <br />Smalltalk, 第一个支持动态类型的语言 <br />C++, 它的大部分基于类的特性继承自Simula. <br />等等等等。 <br />与基于类的语言相对应的是基于对象的面向对象语言。这里“基于对象”的概念和把Visual Basic叫做基于对象的概念是不同的。这里的“基于对象”是指一个只以对象为中心，没有类的概念的语言，类似Python之类的语言。 <br /><br />现在，我们来介绍一下基于类的面向对象语言的一些共同特征。 <br /><br />1．类和对象 <br />让我们先看一个类的定义： <br />代码: <br /><br />class cell is <br />var contents: Integer :=0; <br />method get(): Integer is <br />return self.contents; <br />end; <br />method set(n:Integer) is <br />self.contents := n; <br />end; <br />end; <br /><br /><br /><br />一个类是用来描述所有属于这个类的对象的共同结构的。这个cell类表示的对象拥有一个叫做contents的整数属性(attribute)，这个属性被初始化成0。它还描述了两个操作contents的方法。Get和set. 这两个方法的内容都是很直观的。Self变量表示这个对象自己。 <br /><br />对象的动态语义可以这样理解： <br />一个对象在内部被表示为一个指向一组属性的指针。任何对这个对象的操作都会经过这个指针操作对象的属性和方法。而当对象被赋值或被当作参数传递的时候，所传递的只是指针，这样一来，同一组属性就可以被共享。 <br />(注， 有些语言如C++, 明确区分指向属性组的指针和属性组本身，而一些其它的语言则隐藏了这种区别) <br /><br />对象可以用new从一个类中实例化。准确地说，new C分配了一组属性， <br />并返回指向这组属性的指针。这组属性被赋予了初始值，并包括了类C所定义的方法的代码。 <br /><br />下面我们来考虑类型。对一个new C所生成的对象，我们把它的类型记为InstanceTypeOf(c). 一个例子是： <br /><br />代码: <br />Code:var myCell: <br />InstanceTypeOf(cell) := new cell; <br /><br /><br /><br /><br />这里，通过引入InstanceTypeOf(cell), 我们开始把class和type区分开来了。我们也可以把cell本身当作是类型，但接下来，你就会发现，那样做会导致混淆的。 <br /><br /><br /><br />2．方法解析。(Method Lookup) <br />给出一个方法的调用o.m(……), 一个由各个语言自己实现的叫做方法解析的过程负责找到正确的方法的代码。（译者按：是不是想起了vtable了？）。 <br />直观地看，方法的代码可以被嵌入各个单个对象中，而且，对于许多面向对象语言，对属性和方法的相似的语法，也确实给人这种印象。 <br />不过，考虑到节省空间，很少有语言这样实现。比较普遍的方法是，语言会生成许多method suite, 而这些method suite可以被同一个类的对象们所共享。方法解析过程会延着对象内指向method suite的指针找到方法。 <br />在考虑到继承的情况，方法解析会更加复杂化。Method suite也许会被组成一个树，而对一个方法的解析也许要查找一系列method suite. 而如果有多继承的话，method suite甚至可能组成有向图，或者是环。 <br /><br />方法解析可能发生在编译时，也可能发生在运行时。 <br /><br />在一些语言中，方法到底是嵌入对象中的，还是存在于method suite中这种细节，对程序员是无关紧要的。因为，所有能区分这两种模式的语言特性一般在基于类的面向对象语言中都不被支持。 <br />比如说，方法并不能象属性一样从对象中取出来当作函数使用。方法也不能象属性一样在对象中被更新。(也就是说，你更新了一个对象的方法，而同一个类的其它对象的该方法保持不变。) <br /><br />3. 子类和继承 （Subclassing and Inheritance） <br />子类和一般的类一样，也是用来描述对象的结构的。但是，它是通过继承其它类的结构来渐进式地实现这个目的。 <br />父类的属性会被隐式地复制到子类，子类也可以添加新的属性。在一些语言中，子类甚至可以override父类的属性（通过更改属性的类型来实现） <br />父类中的方法可以被复制到子类，也可以被子类override. <br />一个子类的代码的示例如下： <br /><br />Code:subclass reCell of cell is <br />var backup: Integer := 0; <br />override set(n: Integer) is <br />self.backup := self.contents; <br />super.set(n); <br />end; <br />method restore() is <br />self.contents := self.backup; <br />end; <br />end; <br /><br /><br />对有subclass的方法解析，根据语言是静态类型还是动态类型而有所不同。 <br />在静态类型的语言（如C++, Java）里，父类，子类的method suite的拓扑结构在编译时就已经确定，所以可以把父类的method suite里的方法合并到子类的method suite中去，方法解析时就不用再搜索这个method suite的树或图了。（译者按：C++的vtable就是这种方法） <br />而对于动态类型的语言，（也就是说，父子类的关系是在运行时决定的），method suite就无法合并了。所以，方法解析时，就要沿着这个动态生成的树或有向图搜索直到找到合适的方法。而如果语言支持多继承，这个搜索就更复杂了。 <br /><br />4. Subsumption和Dynamic Dispatch (译者按：呵呵，黔驴技穷，找不到合适的翻译了) <br /><br />从上述的几个例子来看，似乎子类只是用来从父类借用一些定义，以避免重复。但是，当我们考虑到subsumption, 事情就有些不同了。什么是Subsumption呢？请看下面这个例子： <br /><br />代码: <br /><br />Code:var myCell: InstanceTypeOf(cell) := new cell; <br />var myReCell: InstanceTypeOf(reCell) := new reCell; <br />procedure f(x: InstanceTypeOf(cell)) is … end; <br /><br /><br /><br />再看下面这段代码： <br /><br />代码: <br /><br />Code:myCell := myReCell; <br />f(myReCell); <br /><br /><br /><br />在这两行代码中，头一行把一个InstanceTypeOf(reCell)类型的变量赋值给一个InstanceTypeOf(cell)的变量。而第二行则用InstanceTypeOf(reCell)类型的变量作为参数传递给一个参数类型为InstanceTypeOf(cell)的函数。 <br />这种用法在类似Pascal的语言中是不合法的。而在面向对象的语言中，依据以下的规则，它则是完全正确的用法。该规则通常被叫做subtype polimorphism, 即子类型多态（译者按：其实subtyping应该是OO语言最区别于其它语言的地方了） <br />如果c’是c的子类，并且o’是c’的一个实例，那么o’也是c的一个实例。 <br /><br />更严格地说： <br />如果c’是c的子类，并且o’: InstanceTypeOf(c’)，那么o’: <br />代码: <br /><br />InstanceTypeOf( c ). <br /><br /><br /><br />仔细分析上面这条规则，我们可以在InstanceTypeOf的类型之间引入一个满足自反和传递性的子类型关系， 我们用&lt;:符号来表示。（译者按：自反就是说， 对任何a, a 关系 a都成立，比如说，数学里的相等关系就是自反的。而传递性是说，如果a 关系 b, b 关系c, 就能推出a 关系c。 大于，小于等关系都是具备传递性的） <br /><br />那么上面这条规则可以被拆成两条规则： <br />1． 对任何a: A, 如果 A &lt;: B, 那么 a: B. <br />2． InstanceTypeOf(c’) &lt;: InstanceTypeOf(c) 当且仅当 c’是c的子类 <br /><br />第一条规则被叫做Subsumption. 它是判断子类型（注意，是subtype, 不是subclass）的唯一标准。 <br />第二条规则可以叫做subclassing-is-subtyping (子类就是子类型，绕嘴吧？) <br />一般来说，继承都是和subclassing相关的，所以这条规则也可以叫做：inheritance-is-subtyping (继承就是子类型) <br /><br />所有的面向对象语言都支持subsumption (可以说，没有subsumption, 就不成为面向对象)。 <br />大部分的基于类的面向对象语言也并不区分subclassing和subtyping. 但是，一些最新的面向对象语言则采取了把subtyping和subclassing分开的方法。也就是说，A是B的子类，但A类的对象却不可以当作B类的对象来使用。（译者按：有点象C++里的私有继承，但内容比它丰富） <br />好吧，关于区分subclassing和subtyping, 我们后面会讲到。 <br /><br />下面，让我们重新回头来看看这个procedure f. 在subsumption的情况下，下面这个代码的动态语义是什么呢？ <br />代码: <br /><br />Code:Procedure f(x: InstanceTypeOf(cell)) is <br />x.set(3); <br />end; <br />f(myReCell); <br /><br /><br /><br />当myReCell被当作InstanceTypeOf(cell)的对象传入f的时候，x.set(3)究竟是调用哪一个版本的set方法呢？是定义在cell中的那个set还是定义在reCell中的那个呢？ <br />这时，我们有两种选择， <br />1． Static dispatch (按照编译时的类型来决定) <br />2． Dynamic dispatch (按照对象运行时真正类型来决定) <br />（译者按，熟悉C++的朋友们一定微笑了，这再简单不过了。） <br />static dispatch没什么可说的。 <br />dynamic dispatch却有一个有趣的属性。那就是，subsumption一定不能影响对象的状态。如果你在subsumption的时候，改变了这个对象的状态，比如象C++中的对象切片，那么动态解析的方法就可能会失败。 <br />好在，这个属性无论对语义，还是对效率，都是很有好处的。 <br />（译者按， <br />C++中的object slicing会把新的对象的vptr初始化成它自己类型的vtable指针, 所以不存在动态解析的问题。但实际上，对象切片根本不能叫做subsumption。 <br />具体语言实现中，如C++, 虽然subsumption不会改变对象内部的状态，但指针的值却是可能会变化的。这也是一个让人讨厌的东西，但 C++ vtable的方案却只能这样。有一种变种的vtable方法，可以避免指针的变化，也更高效。我们会在另外的文章中阐述这种方法。） <br /><br /><br />5． 赛翁失马 （关于类型信息） <br />虽然subsumption并不改变对象的状态，在一些语言里（如Java）, 它甚至没有任何运行时开销。但是，它却使我们丢掉了一些静态的类型信息。 <br /><br />比如说，我们有一个类型InstanceTypeOf(Object), 而Object类里没有定义任何属性和方法。我们又有一个类MyObject, 它继承自Object。那么当我们把MyObject的对象当作InstanceTypeOf(Object)类型来处理的时候，我们就得到了一个什么东西也没有的没用的空对象。 <br /><br />当然，如果我们考虑一个不那么极端的情况，比如说，Object类里面定义了一个方法f, 而MyObject对方法f做了重载，那么, 通过dynamic dispatch, 我们还是可以间接地操作MyObject中的属性和方法的。这也是面向对象设计和编程的典型方法。 <br /><br />从一个purist的角度看（译者按，很不幸，我就是一个purist）, dynamic dispatch是唯一你应该用来操作已经被subsumption忘掉的属性和方法的东西。它优雅，安全，所有的荣耀都归于dynamic dispatch！！！ (译者按，这句话是我说的) <br /><br />不过，让purist们失望的是，大部分语言还是提供了一些在运行时检查对象类型，并从而操作被subsumption遗忘的属性和方法。这种方法一般被叫做RTTI（Run Time Type Identification）。如C++中的dynamic_cast, 或Java中的instanceof. <br /><br />实事求是地说，RTTI是有用的。（译者按，典型的存在就是合理的强盗逻辑，气死我了！）。但因为一些理论上以及方法论上的原因，它被认为是破坏了面向对象的纯洁性。 <br />首先，它破坏了抽象，使一些本来不应该被使用的方法和属性被不正确地使用。 <br />其次，因为运行时类型的不确定性，它有效地把程序变得更脆弱。 <br />第三点，也许是最重要的一点，它使你的程序缺乏扩展性。当你加入了一个新的类型时，你也许需要仔细阅读你的dynamic_cast或instanceof的代码，必要时改动它们，以保证这个新的类型的加入不会导致问题。而在这个过程中，编译器将不会给你任何帮助。 <br />很多人一提到RTTI, 总是侧重于它的运行时的开销。但是，相比于方法论上的缺点，这点运行时的开销真是无足轻重的。 <br /><br />而在purist的框架中（译者按，吸一口气，目视远方，做深沉状），新的子类的加入并不需要改动已有的代码。 <br />这是一个非常好的优点，尤其是当你并不拥有全部源代码时。 <br /><br />总的来说，虽然RTTI （也叫type case）似乎是不可避免的一种特性，但因为它的方法论上的一些缺点，它必须被非常谨慎的使用。今天面向对象语言的类型系统中的很多东西就是产生于避免RTTI的各种努力。 <br />比如有些复杂的类型系统中可以在参数和返回值上使用Self类型来避免RTTI. 这点我们后面会介绍到。 <br /><br /><br /><br />6．协变，反协变和压根儿不变 （Covarance, Contravariance and Invariance） <br /><br />在下面的几个小节里，我们来介绍一种避免RTTI的类型技术。在此之前，我们先来介绍“协变”，“反协变”和“压根儿不变”的概念。 <br /><br />协变 <br />首先，让我们来看一个Pair类型： A*B <br />这个类型支持一个getA()的操作以返回这个Pair中的A元素。 <br /><br />给定一个A’ &lt;: A, 那么，我们可以说A’*B &lt;: A*B。 <br /><br /><br />为什么呢？我们可以用Subsumption的属性加以证明: <br /><br />假设我们有一个A’*B类型的对象a’*b, 这里，a’:A’, b:B, a’*b &lt;: A’*B <br />那么，因为，A’ &lt;: A， 从subsumption, 我们可以知道a’:A, getA():A 所以， a’*b&lt;: A*B <br /><br />这样，我们就定义A*B这个类型对于A是协变的。 <br />同理，我们也可以证明A*B对于B也是协变的。 <br /><br /><br />正规一点说，Covariance是这样定义的： <br /><br />给定L(T), 这里，类型L是通过类型T组合成的。那么， <br />如果 T1 &lt;: T2 能够推出 L(T1) &lt;: L(T2), 那么我们就说L是对T协变的。 <br /><br /><br />反协变 <br /><br />请看一个函数： A f(B b); (用functional language 的定义也许更简洁， 即f: B-&gt;A) <br /><br />那么，给定一个B’ &lt;: B, 在B-&gt;A 和 B’-&gt;A之间有什么样的subtype关系呢？ <br /><br />可以证明，B-&gt;A &lt;: B’-&gt;A 。 <br />基于篇幅，我们不再做推导。 <br /><br />所以，函数的参数类型是反协变的。 <br />Contravariance的正规点的定义是这样的： <br />给定L(T), 这里，类型L是通过类型T组合成的。那么， <br />如果 T1 &lt;: T2 能够推出 L(T2) &lt;: L(T1), 那么我们就说L是对T反协变的。 <br /><br />同样，可以证明，函数的返回类型是协变的。 <br /><br />压根儿不变 <br /><br />那么我们再考虑函数g: A-&gt;A <br />这里，A既出现在参数的位置，又出现在返回的位置，可以证明，它既不是协变的，也不是反协变的。 <br /><br />对于这种既不是协变的，也不是反协变的情况，我们称之为Invariance (译者按：“压根儿不变”是我编的，这么老土的翻译，各位不必当真) <br /><br />值得注意的是，对于第一个例子中的Pair类型，如果我们支持setA(A), 那么，Pair就变成Invariance了。 <br /><br /><br />7．方法特化 （Method Specialization） <br />在我们前面对subclass的讨论中，我们采取了一种最简单的override的规则，那就是，overriding的方法必须和overriden的方法有相同的signature. <br />但是，从类型安全的角度来说，这并不是必须的。应用我们前面讨论的协变和反协变的知识，我们完全可以让方法的返回类型协变，让方法的参数类型反协变。 <br />这样，只要A &lt;: A’, B’ &lt;: B, 下面的代码就是合法的： <br />代码: <br /><br />Code:class c is <br />method m(x:A):B is … end; <br />method m1(x1:A1):B1 is … end; <br />end; <br />subclass c’ of c is <br />override m(x: A’):B’ is … end; <br />end; <br /><br /><br /><br />我们暂时不允许属性的协变。因为只有immutable的属性才是协变的。允许对属性的修改使得属性都是invariant的。 <br /><br />特殊变量self这里有一个有趣的属性，它是一个参数，但它确是协变的。这种特殊特性是由于self变量只能隐式地由编译器传入，所以避免了协变参数的不安全性。 <br /><br />还有一点有趣的地方是，上面的协变发生在override的时候，也就是，子类要改写父类的方法的时候。但是，在继承时，参数和返回类型的变化规律就又是另一回事了。 <br />比如说，下面这个例子： <br />代码: <br /><br />Code:class c is <br />method m(x:A):B is … end; <br />method m1(x1:A1):B1 is … end; <br />end; <br />subclass c’ of c is <br />inherit m(x: A’):B’; <br />//这里，方法m的代码被继承，子类只是重定义方法m的接口signature <br />end; <br /><br /><br /><br />那么，这里，参数就是协变的，而返回类型却是反协变的了。 <br /><br /><br />这里，从另一个侧面，我们看到了subtyping (通过override), 和subclassing (通过inheritance) 的本质上的区别。 <br /><br />8. Self类型的特化 （Self Type Specialization） <br /><br />方法特化允许你灵活地在子类中继承或改写父类的方法时改变类型。 <br />除此之外，我们也许还需要另一种灵活性。考虑下面的代码： <br />代码: <br /><br />Code:class c is <br />method getSelf(): InstanceTypeOf(c) is <br />return self; <br />end; <br />end; <br />subclass c’ of c is <br />var y: Integer := 0; <br />end; <br /><br />InstanceTypeOf(c’) x := (new c’).getSelf(); <br /><br /><br /><br />这里，最后一句代码是非法的，因为getSelf()的返回类型是InstanceTypeOf(c), 而x的类型是InstanceTypeOf(c’). <br /><br />当然，子类c’可以重载getSelf()方法， 返回类型InstanceTypeOf(c’)： <br />代码: <br /><br />Code:subclass c’ of c is <br />var y: Integer := 0; <br />override getSelf(): InstanceTypeOf(c’) is <br />return self; <br />end; <br />end; <br /><br /><br />这样，因为方法的返回类型是协变的，并且特殊变量self (译者按，就是C++/Java中的this)不论是在继承还是重载中都是协变的, 所以以上的子类定义是可行的。 <br />但是，这要求每个子类都要重载这个方法。而且，如果某些返回self的方法想要隐藏一些逻辑的话，这种重载也是不可能的。比如： <br />代码: <br /><br />Code:class c is <br />method changeAndReturn(): InstanceTypeOf(c) is <br />…//做一些更新操作。并且对子类隐藏逻辑。 <br />return self; <br />end; <br />end; <br /><br />subclass c’ of c is <br />var y: Integer := 0; <br />override changeAndReturn (): InstanceTypeOf(c’) is <br />return super.changeAndReturn(); <br />end; <br />end; <br /><br /><br />这里这个子类c’的定义就是不成立的， 因为super.changeAndReturn()返回的是InstanceTypeOf(c). <br /><br />那么，有没有方法可以使子类c’继承的方法自然地返回InstanceTypeOf(c’)呢？这样一来，因为我们没有被迫丢失一些类型信息，RTTI就能够被避免。 <br /><br />这种考虑自然而然地引出了一种新的类型：Self类型。与self变量相似，Self类型代表的是这个对象的运行时真正类型。只有self变量是Self类型的。因为self变量的特殊协变特性，这样的类型是安全的。使用Self类型， 以上的例子可以被改写成： <br />代码: <br /><br />Code:class c is <br />method getSelf(): Self is <br />return self; <br />end; <br />end; <br />subclass c’ of c is <br />var y: Integer := 0; <br />end; <br /><br />InstanceTypeOf(c’) x := (new c’).getSelf(); <br /><br /><br /><br />这时，最后一句就合法了，因为现在(new c’).getSelf()的返回类型是InstanceTypeOf(c’)了。 <br /><br />除了方法的返回类型，我们也可以把属性定义为Self类型，为了保证类型的安全，这样的属性只能用self或其它Self类型的值进行初始化或更新。也就是说，如果在class c中定义了一个Self类型的属性，你不能用一个InstanceTypeOf(c)的变量来初始化或更新它。 <br /><br />对典型的基于类的面向对象语言做如上所述的扩展是可行的，而且是没有任何副作用的（除了会使类型检查系统稍微复杂一些）。它使强类型面向对象语言的表达能力大大增强。并且有效地防止了类型信息的不必要丢失。 <br /><br />很自然地，读者也许会想到把Self类型用于方法的参数。Eiffel就是这样做的。但不幸的是，参数对于重载来说却是contravariant的，就象我们前面所说明的那样。重载时，类型泛化是可以的，但特化却是不安全的。 <br /><br />不过，在基于类的面向对象领域里，对参数使用Self类型还是被广泛研究并使用的。我们会在后面在介绍相关的技术。 <br /><br />下章预告： <br />对象类型，泛型技术，分离subclassing和subtyping, 对象协议(object protocol).
          <br/><br/>
          <span style="color:red;">
            <a href="http://javaite.javaeye.com/blog/22555#comments" style="color:red;">已有 <strong>0</strong> 人发表留言，猛击-&gt;&gt;<strong>这里</strong>&lt;&lt;-参与讨论</a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 02 Aug 2004 11:32:46 +0800</pubDate>
        <link>http://javaite.javaeye.com/blog/22555</link>
        <guid>http://javaite.javaeye.com/blog/22555</guid>
      </item>
  </channel>
</rss>