庄周梦蝶

生活、程序、未来
   :: 首页 ::  ::  :: 聚合  :: 管理


XRuby是什么?它是一个编译器。与其它编译器一样,它完成的工作是将一种格式的语言转换成另一种。与大多数编译器不同的是,它是将Ruby的代码(.rb)转换成Java的bytecode(.class)。

Xruby是一群中国开发者维护的项目,它的目的如上所述。它的主页是http://code.google.com/p/xruby/。与JRuby不同,JRuby一开始是想使用java写ruby解析器,性能上是个大问题,当然现在也走上了编译这条路。而XRuby是第一个实现这种想法的人。

我翻译下了《XRuby Hacking Guide》,这篇文章是XRuby的入门指南。

介绍

这篇文章是为了帮助用户/开发者理解xruby的内部结构而写的。

如何编译ruby?

怎么将ruby编译成java字节码呢?首先,你不必成为一名字节码方面的专家来考虑这个问题,Java的字节码是对原生机器语言的较高层次的抽象,非常类似于java源代码。你可以简化这个问题为:如何用java表示一段ruby程序?

这两门语言有很多的相同之处:ruby是一门OO语言,它有类、方法、变量等,java也是如此。这是否意味着我们可以将一个ruby类类比为java类,ruby方法作为java方法?可是,除了这些相同之处外,它们之间有足够的不同点让你打消这个主意:首先,ruby是一门动态类型语言,因此一个方法可以接受不同类型的参数,而在java中,参数类型是方法签名(signature)的一部分。其次,在ruby中,方法可以从一个类中动态地添加和移除;但是目前的JVM并不支持这样的行为。值的注意的上述的这些问题也许会在将来的JVM版本中解决,请参考Gilad Bracha's work at JSR 292.

第一个办法是我们自己维护一个类型系统,这正是xruby目前采用的办法(Ruby.net好像也是如此)。从JVM的角度看,一个ruby类只是一个Object,这个Object中包含着代表方法等的其他object。我们将在后面更多讨论这点。

另一个办法是动态地编译(ruby)源代码,在运行时获得类型信息,将源代码编译成高效的代码(字节码?)是可能的。(一些方法由于duct typeing的特性将被编译成好几个版本)

我们将比较这两个办法,

实例

通过一个例子来了解xruby:
def say_hello_three_times
    
3.times {puts 'hello'}
end

say_hello_three_times

将上面的代码存为test.rb,使用xruby编译(下载的xruby解压后运行build.bat或者build.sh生成xruby-0.1.3.jar):
java -jar xruby-0.1.3.jar -c test.rb

可以看到生成了一个test.jar文件,执行下面的命令来运行这个程序:
java -jar test.jar

当然,你将看到下面的输出:

hello
hello
hello
如果你查看test.jar文件,你将看到以下3个class文件:
  • test/BLOCK$1.class
  • test/say_hello_three_times$0.class
  • test/main.class
    这些class文件等价于下面这段java程序:
    //test/main.class
    public class main
        
    implements RubyProgram
    {

        
    public main()
        {
        }

        
    public static void main(String args[])
        {
            RubyRuntime.init(args);
            (
    new main()).run();
            RubyRuntime.fini();
        }

        
    public RubyValue run()
        {
            RubyRuntime.ObjectClass.defineMethod(
    "say_hello_three_times"new say_hello_three_times._cls0());
            
    return RubyRuntime.callMethod(ObjectFactory.topLevelSelfValue, nullnull"say_hello_three_times");
        }
    }


    //say_hello_three_times$0.class
    class say_hello_three_times$0 extends RubyMethod
    {

        
    protected RubyValue run(RubyValue rubyvalue, RubyArray arrayvalue, RubyBlock rubyblock)
        {
            
    return RubyRuntime.callPublicMethod(ObjectFactory.createFixnum(3), nullnew BLOCK._cls1(), "times");
        }

        
    public say_hello_three_times$0()
        {
            
    super(0false);
        }
    }


    //test/BLOCK$1.class
    class BLOCK$1 extends RubyBlock
    {

        
    protected RubyValue run(RubyValue rubyvalue, RubyArray arrayvalue)
        {
            RubyArray arrayvalue1 
    = new RubyArray(1);
            arrayvalue1.add(ObjectFactory.createString(
    "hello"));
            
    return RubyRuntime.callMethod(rubyvalue, arrayvalue1, null"puts");
        }

        
    public BLOCK$1()
        {
            
    super(0false);
        }
    }

    在main类中:首先在"Object"类中定义了一个私有的方法"say_hello_three_times",然后通过no parameter, no block和一个top level "self"作为接收者的方式调用这个方法。

    "say_hello_three_times$0"类表示say_hello_three_times方法的实现(参考command模式)。在代码中,我们可以看到Fixnum"3"(接收者)调用了"timer"这个方法,仍然没有parameter,但是有一个block被传进去(方法)。

    BLOCK$1类则表示传进"3.times"方法中的block,代码中是"puts 'hello'"的实现。

    源码结构

    • com.xruby.compiler.parser 提供了一个compiler前端(parser and tree parser)。 Parser转换ruby脚本成AST (Abstract Syntax Tree),然后 tree parser将AST转换为内部结构(internal structure)。
      编译器前端使用 Antlr 作为语法分析器的生成器.实践证明,将这个前端分为两部分可以带来好处:parser 和 tree parser;其中 parser 解析脚本,而tree parser生成内部结构(internal structure)。
    • com.xruby.compiler.codedom 定义了描述ruby脚本结构的内部结构(internal structure)。内部结构作为前端和后端的接口,对于xruby是非常重要的。
    • com.xruby.compiler.codegen 实现了编译器的后端(代码生成)。后端将前端生成的内部结构转换为java字节码。代码生成是通过ASM实现的,ASM简化了对字节码的操作。
    • com.xruby.runtime 实现了xruby运行时(runtime),它维护着运行ruby脚本必需的类型系统, com.xruby.runtime.lang 描述了ruby类型的运行时结构,一些ruby内建标准库实现在 com.xruby.runtime.builtin.

    内建库

    通往xuby hacking之路最简便的办法就是学习 'com.xruby.runtime.builtin'包的源代码。

    下面是来自Fixnum::+方法实现的代码片段:

    class Fixnum_operator_plus extends RubyMethod {
        
    public Fixnum_operator_plus() {
            
    super(1);
        }

        
    protected RubyValue run(RubyValue receiver, RubyArray args, RubyBlock block) {
            RubyFixnum value1 
    = (RubyFixnum)receiver.getValue();
            RubyFixnum value2 
    = (RubyFixnum)args.get(0).getValue();
            
    return ObjectFactory.createFixnum(value1.intValue() + value2.intValue());
        }
    }


    RubyClass c 
    = RubyRuntime.GlobalScope.defineNewClass("Fixnum", RubyRuntime.IntegerClass);
    c.defineMethod(
    "+"new Fixnum_operator_plus());

    XRuby的语法解析器

    Xruby的解析器使用 Antlr 作为解析器的生成器。 这是目前相比于c ruby唯一另类的ruby语法。

    对于大部分编程语言来说,词法分析(lexing)和语法解析是两个不同的步骤:首先词法分析器将输入的字符组织成单词(token),然后解析器将单词组织成句法单元。但是在ruby(和perl语言)中,词法分析器和语法解析器是紧紧地耦合在一起的:有时候词法分析器需要从语法分析器中获取上下文信息。

    疑难解决

    作为Xruby的开发者,我们的任何改变都可能导致编译器出错并且生成有问题的字节码。当这种情况发生时,我们可以依赖3样工具:javap,ASM和你所喜欢的java反编译器(比如jad

    如果生成的class文件格式正确但是运行结果不是预期的,我们可以简单地使用反编译工具将字节码转换成可读的java源代码,以便查找错误。

    如果你遇到是一个verifier error,大部分的反编译器都不能正常工作(jad在这种情况也许会crash掉)。我们不得不使用javap来研读字节码。多数情况下,JVM class验证器(verifier)给出的信息没什么用处,但是我们可以通过ASM更快地找到错误发生点。(see ASM FAQ: Why do I get the [xxx] verifier error?).


  • posted @ 2007-03-29 11:53 dennis 阅读(2125) | 评论 (0)编辑 收藏

        今天看到一段介绍C#实现代理模式的代码,使用到了MarshalByRefObject。那么MarshalByRefObject到底是什么东西呢?简单来讲,继承此类的对象可以跨越应用程序域边界被引用,甚至被远程引用。远程调用时,将产生一个远程对象在本地的透明代理,通过此代理来进行远程调用。一篇很好的解释文章,来自http://dudu.cnblogs.com/archive/2004/03/04/2182.html


    问:

    打扰一下,请问MarshalByRefObject中的"Marshal"应该怎样理解?

     

    回复

    按照package的意思理解——当一个对象需要长途跋涉到另一个环境中时,需要将其marshal成一个可以传输的形态(比如在.NET Remoting中对象将被打包成一个serializableObjRef实例——这个ByRef就是指ObjRef这种形态);同理,当打包以后传输到目标地点,还要执行unmarshal的操作将其还原为内存中的对象。:)

     

    问:

    谢谢!

    MarshalByRefObject是不是可以这样理解:对被引用的对象进行Marshal。如果按照package的意思理解,那package的过程是怎样的?

     

    MSDN上这样讲:

    MarshalByRefObject是通过使用代理交换消息来跨应用程序域边界进行通讯的对象的基类.

    MarshalByRefObject对象在本地应用程序域的边界内可直接访问。远程应用程序域中的应用程序首次访问MarshalByRefObject时,会向该远程应用程序传递代理。对该代理后面的调用将封送回驻留在本地应用程序域中的对象。

     

    Marshal中,上面所说的代理是什么?有什么用?

     

    MSDN上还讲到:

    当跨应用程序域边界使用类型时,类型必须是从MarshalByRefObject继承的,而且由于对象的成员在创建它们的应用程序域之外无法使用,所以不得复制对象的状态

     

    既然对象的状态不能传递过去,那传递这个对象又有何意义?

    第一次去理解MarshalByRefObject,有的问题可能提的比较肤浅,请您指点。

     

    回复:

    MarshalByRefObject是所有可以在AppDomain边界外部访问的对象的基类,重心不是marshal,而是object,即object that could be marshaled by reference,也就是可以通过Ref(实际上是ObjRef对象)的机制进行封送MSDN中文版对marshal一词的翻译)的对象。封送的行为是由代理来做的,这里说的代理就是我文章中讲过的.NET Remoting的真实代理(即RemotingProxy)。真实代理不是有一个Invoke()方法吗?当你透过对一个MBRO的透明代理访问该对象的方法时,透明代理将把基于堆栈的方法调用转换为方法调用消息(IMethodCallMessage)并转发给真实代理(在Remoting的场合中也即RemotingProxy),而RemotingProxy的任务就是把对象封送并连同方法调用消息一起转发给远程应用程序域;到达目的地以后的操作类似:远程应用程序域中的监听方当收到发来的方法调用消息时,先取出封送好的ObjRef(这个对象里面保存着发来调用的那个对象!),将其结封(unmarshal)为本地的对象,并获得其透明代理,然后就可以把方法调用消息在转换回基于堆栈的调用发送给这个对象。

     

    对象是在本地维护的,但是方法可以在远程调用。你比如说一个web应用程序,你是通过本地的浏览器远程访问这个应用程序,但是应用程序的状态不会由你的浏览器负责(所以你只是在访问这个应用程序提供给你的功能而已,你并没于拥有应用程序本身,包括其所有数据),你只是发送一个个的请求,服务器告诉你处理的结果。在Remoting中 也是一样,当你获得一个远程对象的时候,你实际上只拥有对这个对象的一个远程引用,虽然你可以调用它的方法,但实际上这些操作都是发生在远程的(就是前面 讲过的过程),你只是传入了一些参数,得到了一个结果,但对象的状态还是在远程维护的(换句话说,对象本身也就是对象的所有状态并没有被往返传递,传递的 只是传入传出的参数——当然,如果参数是一个MBRO的话,还是传递对象被封送的引用)。 

    也许应该给你准备一个好理解的例子……你就会豁然开朗了。:)

     

    问:

    我这样的理解对不对?

    一般的对象与从MarshalByRefObject继承的对象区别是:

    一般的对象只能在本地应用程序域之内被引用,而MarshalByRefObject对象可以跨越应用程序域边界被引用,甚至被远程引用。

     

    回复

    Exactly! 当对象跨出AppDomain边界的时候,实际上只是它的一个引用(ObjRef)。你比如说吧:

     

    public class LocalObject
    {

      public void CallRemoteObject(MarshalByRefObject mbro)
      {
        Console.WriteLine(mbro.ToString());

      }
    }

     

    当传入一个在本地创建的mbro对象时,ToString()方法是直接发送给对象的;而当mbro是通过Remoting创建的远程对象的话,实际上它只是一个包含有已经marshal好的ObjRef的透明代理,ObjRef里面有什么?对象实例的URI!所以当你调用这个远程对象时,相当于向这个远程端口(tcp://remoteServer/xxxx.rem)发送方法调用消息而已。只不过透明代理隐藏了对象位置的概念,而RemotingProxy真实代理则是实际上处理远程方法调用和对象封送的中枢对象。

    posted @ 2007-03-28 17:55 dennis 阅读(11539) | 评论 (1)编辑 收藏

        最近Grails的开发者做了一个rails与Grails性能上的对比测试,得出结论:Grails的性能远远超过rails。就这一点,引出了不少的争议和讨论。比如javayeye上的这个帖子《Grails Vs rails bookmark》。blogjava上其实一早就有这个报道,我也大概看了下,对这个测试的价值一开始就没太在意,robbin说的没错,对一个典型的web应用,单纯测试JSP/ASP/PHP/Ruby的纯解析器性能,没有任何意义。我也相信Grails是个很不错的项目,但是与已经支持REST从而领先所有web框架的rails相比,差距仍然不小。坚定的java fan很难爱上rails,可我想,他们都没有真正地深入体验过ruby和rails,就像有的用过scaffold的人说武断地下结论:rails的魔法来自代码生成:) 用过这门语言的,我还没见过不说舒服的。请注意,不是性能,不是魔法,是舒服。ruby确实做到了“最小惊奇原则”,你会发现,原来程序语言可是如此的自然和谐。争论语言的优劣实在是很无聊的事情,就我个人而言,我把工作之外的学习当成了乐趣,我只是好奇,我只是想了解更多不同的东西,哪怕也许永远用不到工作中,但是,编程的乐趣不正在这个探索的过程中吗?

    posted @ 2007-03-26 17:57 dennis 阅读(2008) | 评论 (0)编辑 收藏

        这本书也算是创了个记录:打广告打了9个月,用一个星期“压书”,译者和出版商一次一次地出尔反尔——无论如何,总算是十月怀胎,终成正果了。第一时间下了订单,据说有千人预定呢,预防要等重印啊。网上银行还有一百块,再搭一本《测试驱动开发》影印版,刚好96块大洋,钱啊钱,哭!-_-,一个月一两百块的买书钱是省不了了,这就是程序员的命。
        希望能翻译得好点吧,英文版我也只是初略地看过一遍,这次可以好好再读一遍。我在用rails1.2写一个有趣的站点,琢磨着建个站,可惜国内的rails主机都还没升级到1.2,看看情况吧,也许下个月情况就改观了。

    posted @ 2007-03-22 21:15 dennis 阅读(860) | 评论 (0)编辑 收藏

    1.关于REST的URL的详细讨论,参见《RESTful Rails development》文档,这里有中文翻译。Path-Methods对照表:
    REST7.gif

    2.在Controller和View中新增加了一些helper方法,URL路由的设置来自config目录下的routes.rb中的一行代码:
     map.resources :projects

    这行代码将自动产生用于Controller的系列url方法和用于View的系列Path方法,对照表
    路由             产生的Helper
    -----------------------------------------------------------
    projects       projects_url, projects_path
    project        project_url(id), project_path(id)
    new_project    new_project_url, new_project_path
    edit_project   edit_project_url(id), edit_project_path(id)

    3.路由的定制,通过下面的选项来定制符合个人需要的路由规则:
    :controller.   设置使用的Controller.
    :path_prefix.  URL前缀.
    :name_prefix.  用于设置产生的helper方法的前缀 
    :singular. To name the singular name to be used for the member route.

    4.Nested Resource(嵌套资源乎?),当rails采用REST架构时如何处理过去的Model之间的关联关系,比如1:N?比如以文档中的例子来说明,一个project可能有多个iteration,典型的一对多关系,我们在产生Model后,与传统rails一样,设置关联关系:
    class Project < ActiveRecord::Base
      has_many 
    :iterations
    end

    class Iteration 
    < ActiveRecord::Base
      belongs_to 
    :project
    end

    1)既然是REST架构,那么路由改如何设置呢?或者说我们该怎么访问某个project的所有itration呢?直观的感觉应该是http://localhost:3000/project/:project_id/iterations,那么可以这样修改routes.rb,把生成的
    map.resources :iterations
    修改为:
    map.resources :projects do |projects|
       projects
    .resources :iterations
    end
    将自动产生如下格式的路由:
    /project/:project_id/iterations
    /project/:project_id/iterations/:id

    2)接下来,应当修改IterationsController的增、改、查action了,因为要关联project对象,在Controller中可以通过params[:project_id]获取该iteration所在的project。比如修改index action:
    def index
      project 
    = Project.find(params[:project_id])
      
    @iterations = project.iterations.find(:all)
      respond_to 
    do |format|
         
    format.html # index.rhtml
         format.xml { render :xml => @iterations.to_xml }
      end
    end

    3)相应的iteration的Controller和View的Url和Path等helper都增加了一个参数,他们的第一个参数都将是project_id,比如
    <%= link_to "Show",iteration_path(iteration.project,
    iteration) 
    %>

    <%= link_to "Edit", edit_iteration_path(iteration.project,
    iteration) 
    %>
    同样,所有form_for指向的url的helper也都增加了这个参数。总结一句话,被嵌套类(这里的iteration)的所有helper都增加一个参数并且是第一个参数——外包类(这里的project)的id

    5.自定义action,对于不能归结为crud操作的action,我们需要自己定义action,已经说过,REST把所有的远程调用抽象为对远程资源的CRUD操作,非CRUD操作应当转化或者说抽象成CRUD操作,比如对于project可以有一个关闭操作close,我们可以把它理解成一个http POST请求去修改project的closed字段为true,这样一来这个操作也可以当作CRUD操作了。需要做的是在routes.rb增加一行:
    map.resources :projects, :member => { :close => :post }

    定义close action是POST方法,在Controller增加close方法:
    def close
       respond_to 
    do |format|
          if Project.find(params[:id]).update_attribute(:closed, true)
           flash[
    :notice] = "Project was successfully closed."
           format.html { redirect_to projects_path }
           format.xml { head :ok }
         else
           flash[
    :notice] = "Error while closing project."
           format.html { redirect_to projects_path }
           format.xml { head 500 }
         end
       end
    end

    你可以通过http://localhost:3000/project/:project_id;close来调用此方法,请注意,POST的方法需要通过Form来实现,因此我们使用button_to:
    <td><%= button_to "Close", close_project_path(project) %></td>

    自定义action不仅仅可以使用REST风格,传统的controller/action/id的方式仍然可以使用,注意下routes.rb的最后两行即可。

    6.自定义格式,rails支持的格式有:
    respond_to do |wants|
      wants
    .text
      wants
    .html
      wants
    .js
      wants
    .ics
      wants
    .xml
      wants
    .rss
      wants
    .atom
      wants
    .yaml
    end

    自定义格式需要在config/environment.rb中增加一行进行注册,比如pdf格式?
    Mime::Type.register "application/pdf", :pdf
    当然,你需要实现自己的to_pdf方法了

    7.在rails1.2中使用AJAX与过去没有什么不同,仅仅是页面调用的URL全部改成新增加的那些Path helper

    8.激动人心的ActiveResource,目前还未正式加入rails1.2,值的期待,简单来说就是就是通过这个库你将可以使用所有按照REST实现的web APIS,操作远程的资源将和操作本地的ActiveRecord一样。

    《RESTful Rails Development》下载


    posted @ 2007-03-21 15:28 dennis 阅读(3517) | 评论 (1)编辑 收藏

        REST这个名词已经听过许久,在javaeye的ruby版上也看到不少的讨论,一开始是搞不明白的,似乎跟webservice有关。今天读了《RESTfull Rails Development》和几篇介绍REST的文章开始有点明白。REST 是英文 Representational State Transfer 的缩写,有中文翻译为“具象状态传输”。读这篇文章《学习REST》对于初次接触REST的人来说更好理解。

        我们在 Web 应用中处理来自客户端的请求时,通常只考虑 GET 和 POST 这两种 HTTP 请求方法。实际上,HTTP 还有 HEAD、PUT、DELETE 等请求方法。而在 REST 架构中,用不同的 HTTP 请求方法来处理对资源的 CRUD(创建、读取、更新和删除)操作:

    • POST: 创建
    • GET: 读取
    • PUT: 更新
    • DELETE: 删除

    经过这样的一番扩展,我们对一个资源的 CRUD 操作就可以通过同一个 URI 完成了。需要注意的是REST的核心就是资源(resources)这个概念。我们所说的webservice是一种建立在http协议上的远程调用,而REST就是把远程调用抽象成对远程资源的CRUD的操作,正好可以用HTTP的PUT GET POST DELETE来对应,而不是重新发明一个协议(比如soap,简单对象访问协议)。REST与AJAX的流行,甚至远至设计模式的兴起,都充分说明一个现象,在成熟的应用的基础上创新而非扩展出复杂所谓“创新性”架构在软件行业是更为可靠。

        实战体验REST可以从IBM Developer的这篇文章开始《跨越边界:Rest On Rails》。这篇文章是在Rails1.2发布之前出来的,有些地方已经可以修改的更简练,我把我的练习过程记录下,并添加了C#调用REST风格web service的例子。

        首先,你的机器上需要安装rails1.2,并且假设你对rails有基本的了解,建立一个应用叫service,命令行执行:

      
     rails service

    rails自动帮你生成应用的基本结构和基础代码,然后编辑config下面的database.yml设置数据库,并建立数据service_development,我用的是mysql数据库。

    create.bmp

    利用rails1.2新的scaffold命令:

    ruby script/generate scaffold_resource person

    这个命令将自动生成ActiveRecord,Controller以及View,在\app\models下可以发现自动生成的Model——person.rb。打开service\db\migrate下面的001_create_people.rb,编辑如下:

    class CreatePeople < ActiveRecord::Migration
      def self
    .up
        create_table 
    :people do |t|
         t
    .column :first_name, :string, :limit => 40
         t
    .column :last_name, :string, :limit => 40
         t
    .column :email, :string, :limit => 40
         t
    .column :phone, :string, :limit => 15
        end
      end

      def self
    .down
        drop_table 
    :people
      end
    end

    利用rake命令自动建表,执行
    rake db:migrate
    rails默认表明是Model的复数形式,也就是这里将自动建立一张名叫people的表。

    OK,一切就绪,启动WEBric,访问http://localhost:3000/people,显示:
    rest1.bmp

    scaffold已经帮我们自动生成了一个对person资源的crud操作,增删改查似乎跟传统的rails没有什么不同嘛。如果你认真观察在操作过程中URL的变化情况就会发现在操作过程中URL的变化很小,而且与传统rails的URL路由相比,省去了action名称。出现的变化在/people、/people/1、/people/1;edit和/people/new这几个之中。在/people的URL中隐藏这可能是http的POST或者GET的方法,前者用于create操作,而GET用于show操作,具体你可以查看app/controllers/目录下的PeopleController类,每个action的前面都注释了它们将对应哪个HTTP方法。而/people/1中的1指的是资源的标志符,比如这里person的id,通过这个ID来进行资源的操作,也许是PUT方法(更新),也许是DELETE方法(删除)。rails实现PUT和Delete是通过隐藏字段来实现的,查看编辑页面生成的html源代码,你将发现一个_method的隐藏字段,值为PUT。而另外两个URL:/people/1;edit和/people/new,这两个并非严格意义上的RESTful URL,它们只是为了显示用,显示form表单用于新建和编辑。关于RESTful风格的URL的详细讨论请见《RESTfull Rails Development》文档。

        如果rails只是这样的威力,那就有点小提大做了,看看PeopleController的show action,它对应于http的GET请求,返回people列表:
    # GET /people/1
      # GET /people/1.xml

      def show
        
    @person = Person.find(params[:id])

        respond_to 
    do |format|
          
    format.html # show.rhtml
          format.xml  { render :xml => @person.to_xml }
        end
      end

    神奇的地方在respond_to方法中,根据请求文件类型(http Header的ContentType),显示html格式,或者xml格式(还有其他支持,比如json、RSS、Atom等等)。比如你添加了一个person,通过http://localhost:3000/people/1访问,可以看到这个人员的具体信息:
    rest2.bmp
    我们再通过http://localhost:3000/people/3.xml访问看到的却是一个xml文件:

    rest3.bmp

    不仅如此,我们也可以通过其他语言编写客户端来调用http://localhost:3000/people/1这个url,慢着,这不正是web service远程调用吗?没错,REST风格的web service相比于wsdl、soap定义的web service简单了太多太多,也更加实用。我们来编写一个java类调用http://localhost:3000/people获得所有的人员列表:
    package example;

    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.io.OutputStreamWriter;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.net.URLConnection;

    public class RESTDemo {

        
    /**
         * 
    @param args
         
    */
        
    public static void main(String[] args) {
            RESTDemo restDemo 
    = new RESTDemo();
                restDemo.get();
            
        }

        
    void get() {

            
    try {
                URL url 
    = new URL("http://localhost:3000/people");
                URLConnection urlConnection 
    = url.openConnection();
                urlConnection.setRequestProperty(
    "accept""text/xml");
                BufferedReader in 
    = new BufferedReader(new InputStreamReader(
                        urlConnection.getInputStream()));
                String str;

                
    while ((str = in.readLine()) != null) {
                    System.out.println(str);
                }

                in.close();
            } 
    catch (Exception e) {
                System.out.println(e);
            }
        }
    }

    我们没有什么服务端接口class,我们也不用生成什么stub,我们调用的最常见最常见的http协议,发送的是默认的GET请求,rails自动将该请求转发给show action。注意,我们这里把accept设置为text/xml,show方法根据此格式返回一个xml文档,下面是输出:
    <?xml version="1.0" encoding="UTF-8"?>
    <people>
      
    <person>
        
    <email>killme2008@gmail.com</email>
        
    <first-name>dennis</first-name>
        
    <id type="integer">1</id>
        
    <last-name>zane</last-name>
        
    <phone>1355XXXXXXX</phone>
      
    </person>
    </people>

    如果仅仅是GET请求是不够的,我们说过,把远程调用抽象成对远程资源的CRUD操作,那么如何create、delete和update远程资源呢?同样很简单,比如我们通过C#远程调用,创建一个新person,还记的我说过吗?/people可以是POST请求,他将调用PeopleController的create方法:
    using System;
    using System.Net;
    using System.IO;
    using System.Text;
    namespace demo
    {
        
    class RESTDemo
        {
            
    static void Main(string[] args)
            {
                
    string xmlText = "<person> " + "<first-name>jordan</first-name>"
                        
    + "<last-name>jordan</last-name>"
                        
    + "<email>maggie@tate.com</email>"
                        
    + "<phone>010-XXXXXXXX</phone>" + "</person>";
                Uri address 
    = new Uri("http://localhost:3000/people");  
       
                
    // 创建web请求
                HttpWebRequest request = WebRequest.Create(address) as HttpWebRequest;  
       
                
    // 设置请求类型为POST,调用create action
                request.Method = "POST";  
                request.ContentType 
    = "application/xml";

                
    byte[] xmlBytes = Encoding.ASCII.GetBytes(xmlText);

                
    using (Stream reqStream = request.GetRequestStream())
                {
                    reqStream.Write(xmlBytes, 
    0, xmlBytes.Length);
                }
                
    using (WebResponse wr = request.GetResponse())
                {
                    wr.
                    
    //打印返回的http头
                    Console.WriteLine(wr.Headers.ToString());
                   
                }              
               

            }
        }
    }

    执行此程序,刷新http://localhost:3000/people,可以看到新建了一个人员如下

    rest4.bmp

    好极了,GET和POST都有了,那么PUT对应的更新和DELETE对应的删除又该怎么做呢,唯一的区别就是设置请求类型不同而已,java调用如下:
        void put() {
            
    try {
                String xmlText 
    = "<person> " + "<first-name>test</first-name>"
                        
    + "<last-name>test</last-name>"
                        
    + "<email>maggie@tate.com</email>"
                        
    + "<phone>010-XXXXXXXX</phone>" + "</person>";

                URL url 
    = new URL("http://localhost:3000/people/1");
                HttpURLConnection conn 
    = (HttpURLConnection) url.openConnection();
                conn.setDoOutput(
    true);
                //设置请求为PUT
                conn.setRequestMethod(
    "PUT");
                conn.setRequestProperty(
    "Content-Type""text/xml");
                OutputStreamWriter wr 
    = new OutputStreamWriter(conn
                        .getOutputStream());
                wr.write(xmlText);
                wr.flush();
                wr.close();
            } 
    catch (Exception e) {
                System.out.println(
    "Error" + e);
            }
        }

        
    void delete() {
            
    try {
                URL url 
    = new URL("http://localhost:3000/people/2");
                HttpURLConnection conn 
    = (HttpURLConnection) url.openConnection();
                conn.setDoOutput(
    true);
                //设置请求为DELETE
                conn.setRequestMethod(
    "DELETE");
                conn.setRequestProperty(
    "Content-Type""text/xml");
                
    if(conn.getResponseCode()==200)
                    System.out.println(
    "删除成功!");
            }
    catch (Exception e) {
                System.out.println(
    "Error" + e);
            }
        }

    这里的put方法将第一个人员的名字改了,而delete方法干脆将刚才C#添加的人员删除掉。异构系统的远程调用变的如此简单很轻松,把什么EJB、CORBA、SOAP统统忘掉吧。想象这样的场景,所有的网站都提供REST风格的API,这个世界将是什么模样?

        REST带来的不仅仅是web service的改变,对MVC架构同样具有很重要的意义,过去我们的复用通常在MODEL层,我们一直希望复用业务逻辑层,却没有想过是否能复用Controller甚至View呢?REST为我们提供了可能,比如以一个很经常被提到的例子来说,用户加入某个圈子这个操作跟圈子的管理员将用户加入圈子的操作是一样,但是操作成功后的跳转显示的页面也许不同,过去也许我们是通过写两个不同的Action来实现,而现在,同一个Action(加入圈子这个操作)只负责发送数据(XML格式的文档),而页面的展示将留给客户端去选择,从而复用了Controller,减少了Action和View层的代码量。进一步,请你想象,REST与AJAX的技术结合产生多么有趣的画面。REST仅用于提供数据,展现更多的交给了客户端。

        本文仅仅是我接触REST这两天的学习总结,对于REST的应用才刚刚起步,需要更多的探讨和实践。其实java实现REST也是相当简单的,servlet本身就是很好的模型,恐怕没有多人注意到HttpServlet类中的doPut和doDelete方法,我们过去太强调GET和POST,反而忽视了PUT和DELETE可能带来的改变。java开源世界中已经有了REST风格的框架,比如cetia4,这是一个servlet-base的REST框架,值的关注。

    posted @ 2007-03-20 20:04 dennis 阅读(2907) | 评论 (0)编辑 收藏

    HttpWebRequest 是 .net 基类库中的一个类,在命名空间 System.Net 下面,用来使用户通过 HTTP 协议和服务器交互。 

    HttpWebRequest 对 HTTP 协议进行了完整的封装,对 HTTP 协议中的 Header, Content, Cookie 都做了属性和方法的支持,很容易就能编写出一个模拟浏览器自动登录的程序。 

    程序使用 HTTP 协议和服务器交互主要是进行数据的提交,通常数据的提交是通过 GET 和 POST 两种方式来完成,下面对这两种方式进行一下说明: 

    1. GET  方式。 GET 方式通过在网络地址附加参数来完成数据的提交,比如在地址 http://www.google.com/webhp?hl=zh- CN 中,前面部分 http://www.google.com/webhp 表示数据提交的网址,后面部分 hl=zh-CN 表示附加的参数,其中  hl 表示一个键(key), zh-CN 表示这个键对应的值(value)。程序代码如下: 

    HttpWebRequest req = (HttpWebRequest) HttpWebRequest.Create( "http://www.google.com/webhp?hl=zh-CN" );
    req.Method 
    = "GET";
    using (WebResponse wr = req.GetResponse())
    {
       
    //在这里对接收到的页面内容进行处理


    2. POST 方式。 POST 方式通过在页面内容中填写参数的方法来完成数据的提交,参数的格式和 GET 方式一样,是类似于 hl=zh-CN&newwindow=1 这样的结构。程序代码如下: 

    string param = "hl=zh-CN&newwindow=1";
    byte[] bs = Encoding.ASCII.GetBytes(param);

    HttpWebRequest req 
    = (HttpWebRequest) HttpWebRequest.Create( "http://www.google.com/intl/zh-CN/" );
    req.Method 
    = "POST";
    req.ContentType 
    = "application/x-www-form-urlencoded";
    req.ContentLength 
    = bs.Length;

    using (Stream reqStream = req.GetRequestStream())
    {
       reqStream.Write(bs, 
    0, bs.Length);
    }
    using (WebResponse wr = req.GetResponse())
    {
       
    //在这里对接收到的页面内容进行处理


    在上面的代码中,我们访问了 www.google.com 的网址,分别以 GET 和 POST 方式提交了数据,并接收了返回的页面内容。然而,如果提交的参数中含有中文,那么这样的处理是不够的,需要对其进行编码,让对方网站能够识别。 

    3. 使用 GET 方式提交中文数据。 GET 方式通过在网络地址中附加参数来完成数据提交,对于中文的编码,常用的有 gb2312 和 utf8 两种,用 gb2312 方式编码访问的程序代码如下: 

    Encoding myEncoding = Encoding.GetEncoding("gb2312");
    string address  = "http://www.baidu.com/s?" + HttpUtility.UrlEncode("参数一", myEncoding) +  "=" + HttpUtility.UrlEncode("值一", myEncoding);
    HttpWebRequest req 
    = (HttpWebRequest)HttpWebRequest.Create(address);
    req.Method 
    = "GET";
    using (WebResponse wr = req.GetResponse())
    {
       
    //在这里对接收到的页面内容进行处理


    在 上面的程序代码中,我们以 GET 方式访问了网址 http://www.baidu.com/s ,传递了参数“参数一=值一”,由于无法告知对方提 交数据的编码类型,所以编码方式要以对方的网站为标准。常见的网站中, www.baidu.com (百度)的编码方式是 gb2312,  www.google.com (谷歌)的编码方式是 utf8。 

    4. 使用 POST 方式提交中文数据。 POST 方式通过在页面内容中填写参数的方法来完成数据的提交,由于提交的参数中可以说明使用的编码方式,所以理论上能获得更大的兼容性。用 gb2312 方式编码访问的程序代码如下: 

    Encoding myEncoding = Encoding.GetEncoding("gb2312");
    string param  = HttpUtility.UrlEncode("参数一", myEncoding) + "=" + HttpUtility.UrlEncode ("值一", myEncoding) + "&" + HttpUtility.UrlEncode("参数二", myEncoding)  + "=" + HttpUtility.UrlEncode("值二", myEncoding);

    byte[] postBytes = Encoding.ASCII.GetBytes(param);

    HttpWebRequest req 
    = (HttpWebRequest) HttpWebRequest.Create( "http://www.baidu.com/s" );
    req.Method 
    = "POST";
    req.ContentType 
    = "application/x-www-form-urlencoded;charset=gb2312";
    req.ContentLength 
    = postBytes.Length;

    using (Stream reqStream = req.GetRequestStream())
    {
       reqStream.Write(
    postBytes 0, postBytes .Length);
    }
    using (WebResponse wr = req.GetResponse())
    {
       
    //在这里对接收到的页面内容进行处理


    从上面的代码可以看出, POST 中文数据的时候,先使用 UrlEncode 方法将中文字符转换为编码后的 ASCII 码,然后提交到服务器,提交的时候可以说明编码的方式,用来使对方服务器能够正确的解析。 

    以 上列出了客户端程序使用 HTTP 协议与服务器交互的情况,常用的是 GET 和 POST 方式。现在流行的 WebService 也是通过  HTTP 协议来交互的,使用的是 POST 方法。与以上稍有所不同的是, WebService 提交的数据内容和接收到的数据内容都是使用了  XML 方式编码。所以, HttpWebRequest 也可以使用在调用 WebService 的情况下。

    posted @ 2007-03-20 16:32 dennis 阅读(7769) | 评论 (4)编辑 收藏

    在db2和oracle中的对树的递归查询语句。

    表结构:

    create table  MAIN_NODE (

    MLA_ID               INTEGER     not null        ,   //节点ID

    MLA_ROOTID          INTEGER,                      //根节点ID

    MLA_PARENTID        INTEGER,                      //父节点ID

    MLA_NAME            VARCHAR2(50),                //节点名称

    constraint P_mlaid primary key (MLA_ID) );

    MLA_ID   MLA_PARENTID  MLA_ROOTID   MLA_NAME

    1           0                   0             父节点1     

    2           1                   0            (父节点1/)子节点1   

    3            2                   0            (父节点1/子节点1/)孙子节点1

    4           0                   0             父节点2     

    5           4                   0            (父节点2/)子节点1   

    树结构直观图:

    根节点(0
         父节点11
               (父节点1/)子节点12 
                         (父节点1/子节点1/)孙子节点13
         父节点24
               (父节点2/)子节点15

    语句要求的功能:实现给出一个父节点id然后得到该父节点下的所有子节点的信息Db2 的查询语句:

    WITH  RPL (mla_parentid, mla_id, mla_name) AS

    (

    SELECT ROOT.mla_parentid, ROOT.mla_id, ROOT.mla_name FROM main_node ROOT WHERE ROOT.mla_id = ?

    UNION  ALL

    SELECT CHILD.mla_parentid, CHILD.mla_id, CHILD.mla_name FROM RPL PARENT, main_node CHILD WHERE PARENT.mla_parentid = CHILD.mla_id

     )

    SELECT DISTINCT mla_parentid, mla_id, mla_name FROM RPL ORDER BY mla_parentid, mla_id, mla_name

    让我们研究这个查询语句:
    • RPL 作为一个具有以下三列的虚拟表:mla_parentid, mla_idmla_name
    • WITH 子句内的第一个 SELECT 语句是初始化表。它只执行一次。它的结果形成虚拟表的初始内容以作为递归的种子。在上面的示例中,种子是 mla_id 为 任意传进去的参数 的一行或多行。
    • 第 二个 SELECT 语句执行多次。将种子作为输入(JOIN 中的辅助表)传递给第二个 SELECT 语句以产生下一个行集合。将 JOIN 的结果添加(UNION ALL)到虚拟表的当前内容中,并放回到其中以形成用于下一次传递的输入。只要有行产生,这个过程就会继续。
    • 如果期望,虚拟表上最后的 SELECT 允许我们选择递归查询所产生的所有行或仅部分行。

    Oracle的查询语句:

       select mla_parentid, mla_id, mla_name from main_node

    start with mla_id=? connect by prior mla_id=mla_parentid

    让我们研究这个查询语句:
    •   本语句实际上是 start with ...connect by 的用法, start with 后面所跟的就是就是递归的种子。在上面的示例中,种子是 mla_id 为 任意传进去的参数
    • connect by 后面的"prior"如果缺省:则只能查询到符合条件的起始行,并不进行递归查询;
    • connect by prior 后面所放的字段是有关系的,它指明了查询的方向。如果后面放的是 mla_id=mla_parentid 则表明从本节点查向叶子节点;如果后面放的是 mla_parentid = mla_id则表明从根节点查向本节点;

    posted @ 2007-03-20 10:40 dennis 阅读(7042) | 评论 (0)编辑 收藏

        TDD的理念早已知道,JUnit用过,也仅仅是浅尝则止,毕竟公司的现实氛围不可能让我去实践TDD,而其实最主要的原因还是自身,为逃避单元测试寻找种种接口,写测试浪费时间啊、项目时间紧压力大、别人都没写我写的话会不会太多此一举了?可正如gigix在blog写到,linux这样的系统没有写单元测试,因为写它们的人是天才,而这世界上大部分的程序员们都只是普通人,单元测试就是给我们这些普通人准备的一把利器。
        昨天晚上又翻了翻那本《JUnit in action》,结对编程、单元测试、XP......悠然神往的开发流程,为什么不从自己做起呢?为什么不真正地尝试一把呢?就算别人都还停留在debug代码的阶段,自己为什么不试着改变呢?我问自己,很惭愧。是的,改变从一点一滴做起。既然想做,那就做吧,或多或少,水到渠成。

    posted @ 2007-03-19 16:30 dennis 阅读(543) | 评论 (0)编辑 收藏

        系统的演化应当依赖于组合,而不是继承;这就提出了将类的实例化委托给一个对象的要求,因此创建型模式将变的越来越重要。
    创建型模式属于对象创建模型。所谓对象创建模型就是说将实例化的工作委托给另一个对象来做。与之相对应的是类创建模型,这是一种通过继承改变被实例化的类。
           创建型模式有两个重要的特点:
    1) 客户不知道创建的具体类是什么(除非看源代码)
    2) 隐藏了类的实例是如何被创建和放在一起的

    一。抽象工厂模式
    1.意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体的类。
    2.适用场景:
    1)一个系统要独立于它的产品的创建、组合和表示时
    2)一个系统要由多个产品系列中的一个来配置时
    3)当你要强调一系列相关的产品对象的设计以便进行联合使用时
    4)当你提供一个产品类库,而只想显示它们的接口而不是实现时

    3.UML图——结构

    4.效果:
    1)分离了具体的类,通过抽象接口将客户与具体的类分离
    2)易于交换产品系列
    3)有利于产品的一致性
    4)难以支持新种类的产品,比如我们现在有一个ProductC产品,我们需要增加类AbstractProductC,增加AbstractFactory:: CreanteProductC方法,并且两个产品系列的实际创建者ConCreateFactory1、ConCreateFactor2都要实现该方 法。
    可以通过给方法加参数的方式来指明创建的是什么产品,这样客户代码就无需改变,只要传递不同的参数。AbstractFactory类只需要提供一个CreateProduct(const string& name)方法即可。

    5.代码实现,以《深入浅出设计模式(java C#)》的动物工厂为例:
    using System;
    namespace AnimalWorld
    {
        
    // 抽象大陆工厂
        abstract class ContinentFactory
        {
            
    abstract public Herbivore CreateHerbivore();
            
    abstract public Carnivore CreateCarnivore();
        }
        
    //非洲大陆,有角马,狮子
        class AfricaFactory : ContinentFactory
        {
            
    override public Herbivore CreateHerbivore()
            {
                
    return new Wildebeest();
            }
            
    override public Carnivore CreateCarnivore()
            {
                
    return new Lion();
            }
        }
        
    // 美洲大陆,有狼,野牛
        class AmericaFactory : ContinentFactory
        {
            
    override public Herbivore CreateHerbivore()
            {
                
    return new Bison();
            }
            
    override public Carnivore CreateCarnivore()
            {
                
    return new Wolf();
            }
        }
        
    //食草动物"
        abstract class Herbivore
        {
        }
        
    //肉食动物"
        abstract class Carnivore
        {
            
    //猎食食草动物的方法
            abstract public void Eat( Herbivore h );
        }
        
    //角马
        class Wildebeest : Herbivore
        {
        }
        
    //狮子"
        class Lion : Carnivore
        {
            
    //重载猎食食草动物的方法
            override public void Eat( Herbivore h )
            {
                Console.WriteLine( 
    this + " eats " + h );
            }
        }
        
    //野牛
        class Bison : Herbivore
        {
        }
        
    //
        class Wolf : Carnivore
        {
            
    //重载猎食食草动物的方法
            override public void Eat( Herbivore h )
            {
                Console.WriteLine( 
    this + " eats " + h );
            }
        }
        
    //动物世界类
        class AnimalWorld
        {
            
    private Herbivore herbivore;
            
    private Carnivore carnivore;
            
    // 创建两种动物分类
            public AnimalWorld( ContinentFactory factory )
            {
                carnivore 
    = factory.CreateCarnivore();
                herbivore 
    = factory.CreateHerbivore();
            }
            
    //运行食物链
            public void RunFoodChain()
            {
                
    //肉食动物猎食食草动物
                carnivore.Eat( herbivore );
            }
        }
        
    /// <summary>
        
    /// 抽象工厂模式客户应用测试
        
    /// </summary>
        class GameApp
        {
            [STAThread]
            
    static void Main(string[] args)
            {
                
    //创造并运行非洲动物世界
                ContinentFactory africa = new AfricaFactory();
                AnimalWorld world 
    = new AnimalWorld( africa );
                world.RunFoodChain();
                
    //创造并运行美洲动物世界
                ContinentFactory america = new AmericaFactory();
                world 
    = new AnimalWorld( america );
                world.RunFoodChain();
                Console.Read();
            }

        }
    }

    二。Builder模式
    1.意图:将一个复杂对象的构建与它的表示相分离,使得同样的构建过程可以创建不同的表示(或者说产品)

    2.适用场景:
    1)当创建复杂对象的算法应该独立于改对象的组成部分以及它们的装配方式时
    2)当构造过程必须允许被构造的对象有不同的表示时

    3.UML图——结构


    Director接受一个Builder子类对象,完成创建过程,并通知Builder对象返回以及构造好的产品。

    4.效果:
    1)可以使你改变一个对象的内部表示
    2)构造代码和表示代码分开
    3)可以对构造过程进行更精细的控制

    5。实现:以一个车辆建造过程为例(C#)
    using System;

    namespace CarShop
    {
        
    using System;
        
    using System.Collections;
        
    //指挥者,Director
        class Shop{
            
    public void Construct( VehicleBuilder vehicleBuilder ){
                vehicleBuilder.BuildFrame();
                vehicleBuilder.BuildEngine();
                vehicleBuilder.BuildWheels();
                vehicleBuilder.BuildDoors();
            }
        }
        
    /* "Builder 建造者",Builder
        抽象建造者具有四种方法
        装配框架
        装配发动机
        装配轮子
        装配车门
        
    */
        
    abstract class VehicleBuilder
        {
            
    protected Vehicle vehicle;
            
    //返回建造完成的车辆
            public Vehicle Vehicle{
                
    getreturn vehicle; }
            }
            
    abstract public void BuildFrame();
            
    abstract public void BuildEngine();
            
    abstract public void BuildWheels();
            
    abstract public void BuildDoors();
        }
        
    //具体建造者-摩托车车间
        class MotorCycleBuilder : VehicleBuilder
        {
            
    override public void BuildFrame(){
                vehicle 
    = new Vehicle( "摩托车" );
                vehicle[ 
    "frame" ] = "MotorCycle Frame";
            }

            
    override public void BuildEngine(){
                vehicle[ 
    "engine" ] = "500 cc";
            }

            
    override public void BuildWheels(){
                vehicle[ 
    "wheels" ] = "2";
            }

            
    override public void BuildDoors(){
                vehicle[ 
    "doors" ] = "0";
            }
        }
        
    //具体建造者-轿车车间
        class CarBuilder : VehicleBuilder
        {
            
    override public void BuildFrame(){
                vehicle 
    = new Vehicle( "轿车" );
                vehicle[ 
    "frame" ] = "Car Frame";
            }
            
    override public void BuildEngine(){
                vehicle[ 
    "engine" ] = "2500 cc";
            }
            
    override public void BuildWheels(){
                vehicle[ 
    "wheels" ] = "4";
            }
            
    override public void BuildDoors(){
                vehicle[ 
    "doors" ] = "4";
            }
        }
        
    // 具体建造者-单脚滑行车车间
        class ScooterBuilder : VehicleBuilder
        {
            
    override public void BuildFrame(){
                vehicle 
    = new Vehicle( "单脚滑行车" );
                vehicle[ 
    "frame" ] = "Scooter Frame";
            }

            
    override public void BuildEngine(){
                vehicle[ 
    "engine" ] = "none";
            }

            
    override public void BuildWheels(){
                vehicle[ 
    "wheels" ] = "2";
            }

            
    override public void BuildDoors(){
                vehicle[ 
    "doors" ] = "0";
            }
        }
        
    //车辆产品类
        class Vehicle
        {
            
    private string type;
            
    private Hashtable parts = new Hashtable();
            
    //筑构函数,决定类型
            public Vehicle( string type ){
                
    this.type = type;
            }
            
    //索引
            public object thisstring key ]{
                
    getreturn parts[ key ]; }
                
    set{ parts[ key ] = value; }
            }
            
    //显示方法
            public void Show()
            {
                Console.WriteLine( 
    "\n---------------------------");
                Console.WriteLine( 
    "车辆类类型: "+ type );
                Console.WriteLine( 
    " 框架 : " + parts[ "frame" ] );
                Console.WriteLine( 
    " 发动机 : "+ parts[ "engine"] );
                Console.WriteLine( 
    " #轮子数: "+ parts[ "wheels"] );
                Console.WriteLine( 
    " #车门数 : "+ parts[ "doors" ] );
            }
        }
        
    /// <summary>
        
    /// 建造者模式应用测试
        
    /// </summary>
         class CarShop
        {
            [STAThread]
            
    static void Main(string[] args)
            {
                
    // 创造车间及车辆建造者
                Shop shop = new Shop();
                VehicleBuilder b1 
    = new ScooterBuilder();
                VehicleBuilder b2 
    = new CarBuilder();
                VehicleBuilder b3 
    = new MotorCycleBuilder();
                
    // 筑构并显示车辆
                shop.Construct( b1 );
                b1.Vehicle.Show();
                shop.Construct( b2 );
                b2.Vehicle.Show();
                shop.Construct( b3 );
                b3.Vehicle.Show();
                Console.Read();

            }
        }
    }


    三。Factory Method模式
    1.意图:定义一个用于创建对象的接口,让子类决定实例化具体的哪一个类。
    2.适用场景:
    1)当一个类不知道它所必须创建的对象的类的时候,让子类来决定
    2)当一个类希望由它的子类来决定它所创建的对象的时候

    3。UML图——结构:

    4.效果:
    1)为子类提供回调函数
    2)连接平行的类层次
    3) 创建函数可以接收参数来决定创建什么产品
    4)Factory Method容易导致创建过多的Creator的子类以对应不同的产品,这个方法可以通过模板技术来解决

    6.实现,手机工厂,产品可能是Nokia,也可能是Motorola
    using System;
    using System.Collections.Generic;
    using System.Text;

    namespace HandPhone
    {
        
    //手机接口
        interface Mobile
        {
             
    void call();
        }

        
    //手机工厂接口
        interface MobileFactory
        {
              Mobile createMobile();
        }

        
    //Nokia
        class Nokia:Mobile
        {
            
    public void call()
            {
                Console.WriteLine(
    "This is a {0} phone"this.GetType().Name);
            }
        }

        
    //Motorola
        class Motorola : Mobile
        {
            
    public void call()
            {
                Console.WriteLine(
    "This is a {0} phone"this.GetType().Name);
            }
        }

        
    //Motorola工厂
        class MotorolaFactory : MobileFactory
        {
             
    public Mobile createMobile()
            {
                
    return new Motorola();
            }
        }

        
    //Nokia工厂
        class NokiaFactroy : MobileFactory
        {
            
    public Mobile createMobile()
            {
                
    return new Nokia();
            }
        }

        
    public class Client
        {
            
    static void Main(String []args)
            {
                MobileFactory factory
    =null;
                Mobile mobile
    =null;

                factory
    =new NokiaFactroy();
                mobile
    =factory.createMobile();
                mobile.call();

                factory
    =new MotorolaFactory();
                mobile
    =factory.createMobile();
                mobile.call();
                
            }
        }

    }

    四。Prototype模式

    1.意图:通过原型实例指定创建对象的种类,并通过拷贝这些原型来创建新的对象

    2.适用场景:
    1)要实例化的类是在运行时刻指定的,比如动态装载
    2)为了避免创建一个与产品层次平行的工厂类层次
    3)当一个类的实例只能有几个不同的状态组合中的一种时,建立相应数目的原型并克隆它们可能比每次用合适的状态手工化该类更方便一些。

    3.UML图——结构:



    4.效果:
    1)运行时动态增加或者删除产品
    2)减少子类的构造数目
    3)用类动态配置应用
    4)动态指定新的对象,通过改变结构或者值
    5)缺陷在于每一个Prototype的子类都需要实现clone操作

    5。实现,无论java还是C#都从语言层次内置了对prototype模式的支持。具体不再详述。

    五。singleton模式
    1。意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点

    2.适用场景:
    1)当需要类只有一个实例,并且客户只能通过一个全局点去访问它
    2)当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用扩展的实例

    3.UML图:略

    4.效果:
    1)对唯一实例的受控访问
    2)缩小命名空间
    3)允许对操作和表示的细化
    4)允许可变数目的实例

    5实现,关于singleton在java多线程环境下的实现,请见讨论《当Singleton遇到multi-threading》,C#对singleton多线程环境下的能够正确实现Double-checked模式:
    public sealed class Singleton
    {
        
    private static volatile Singleton instance;
        
    private static object syncRoot = new Object();

        
    private Singleton() {}

        
    public static Singleton Instance
        {
            
    get 
            {
                
    if (instance == null
                {
                    
    lock (syncRoot) 
                    {
                        
    if (instance == null
                            instance 
    = new Singleton();
                    }
                }

                
    return instance;
            }
        }
    }


    本文仅作速查记忆用,摘记于《设计模式——可复用面向对象软件基础》和《深入浅出设计模式(java C#)》两书

    posted @ 2007-03-17 17:01 dennis 阅读(962) | 评论 (1)编辑 收藏

    仅列出标题
    共56页: First 上一页 44 45 46 47 48 49 50 51 52 下一页 Last