<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    莊周夢蝶

    生活、程序、未來
       :: 首頁 ::  ::  :: 聚合  :: 管理

         摘要: 簡單的web server性能測試,一般多線程方式與采用jdk5線程池的比較。  閱讀全文

    posted @ 2007-08-29 18:10 dennis 閱讀(4729) | 評論 (6)編輯 收藏

        linux/unix系統的I/O也就是一般所說的低級I/O——操作系統提供的基本IO服務,與os綁定,特定于*nix平臺。而標準I/O是ANSI C建立的一個標準I/O模型,是一個標準函數包和stdio.h頭文件中的定義,具有一定的可移植性。兩者一個顯著的不同點在于,標準I/O默認采用了緩沖機制,比如調用fopen函數,不僅打開一個文件,而且建立了一個緩沖區(讀寫模式下將建立兩個緩沖區),還創建了一個包含文件和緩沖區相關數據的數據結構。低級I/O一般沒有采用緩沖,需要自己創建緩沖區,不過其實在*nix系統中,都是有使用稱為內核緩沖的技術用于提高效率,讀寫調用是在內核緩沖區和進程緩沖區之間進行的數據復制。

    1.fopen與open
    標準I/O使用fopen函數打開一個文件
    FILE* fp=fopen(const char* path,const char *mod)

    其中path是文件名,mod用于指定文件打開的模式的字符串,比如"r","w","w+","a"等等,可以加上字母b用以指定以二進制模式打開(對于*nix系統,只有一種文件類型,因此沒有區別),如果成功打開,返回一個FILE文件指針,如果失敗返回NULL,這里的文件指針并不是指向實際的文件,而是一個關于文件信息的數據包,其中包括文件使用的緩沖區信息。

    *nix系統使用open函數用于打開一個文件
    int fd=open(char *name,int how);
    與fopen類似,name表示文件名字符串,而how指定打開的模式:O_RDONLY(只讀),O_WRONLY(只寫),O_RDWR (可讀可寫),還有其他模式請man 2 open。成功返回一個正整數稱為文件描述符,這與標準I/O顯著不同,失敗的話返回-1,與標準I/O返回NULL也是不同的。

    2.fclose與close
    與打開文件相對的,標準I/O使用fclose關閉文件,將文件指針傳入即可,如果成功關閉,返回0,否則返回EOF
    比如:
    if(fclose(fp)!=0)  
      printf(
    "Error in closing file");

    而*nix使用close用于關閉open打開的文件,與fclose類似,只不過當錯誤發生時返回的是-1,而不是EOF,成功關閉同樣是返回0。C語言用error code來進行錯誤處理的傳統做法。

    3.讀文件,getc,fscanf,fgets和read
    標準I/O中進行文件讀取可以使用getc,一個字符一個字符的讀取,也可以使用gets(讀取標準io讀入的)、fgets以字符串單位進行讀取(讀到遇到的第一個換行字符的后面),gets(接受一個參數,文件指針)不判斷目標數組是否能夠容納讀入的字符,可能導致存儲溢出(不建議使用),而fgets使用三個參數:
     char * fgets(char *s, int size, FILE *stream);
    第一個參數和gets一樣,用于存儲輸入的地址,第二個參數為整數,表示輸入字符串的最大長度,最后一個參數就是文件指針,指向要讀取的文件。最后是fscanf,與scanf類似,只不過增加了一個參數用于指定操作的文件,比如fscanf(fp,"%s",words)

    *nix系統中使用read函數用于讀取open函數打開的文件,函數原型如下:
    ssize_t numread=read(int fd,void *buf,size_t qty);

    其中fd就是open返回的文件描述符,buf用于存儲數據的目的緩沖區,而qty指定要讀取的字節數。如果成功讀取,就返回讀取的字節數目(小于等于qty)。

    4.判斷文件結尾,如果嘗試讀取達到文件結尾,標準IO的getc會返回特殊值EOF,而fgets碰到EOF會返回NULL,而對于*nix的read函數,情況有所不同。read讀取qty指定的字節數,最終讀取的數據可能沒有你所要求的那么多(qty),而當讀到結尾再要讀的話,read函數將返回0.

    5.寫文件:putc,fputs,fprintf和write

    與讀文件相對應的,標準C語言I/O使用putc寫入字符,比如:
    putc(ch,fp);
    第一個參數是字符,第二個是文件指針。而fputs與此類似:
    fputs(buf,fp);
    僅僅是第一個參數換成了字符串地址。而fprintf與printf類似,增加了一個參數用于指定寫入的文件,比如:
    fprintf(stdout,"Hello %s.\n","dennis");
    切記fscanf和fprintf將FILE指針作為第一個參數,而putc,fputs則是作為第二個參數。

    在*nix系統中提供write函數用于寫入文件,原型與read類似:
    ssize_t result=write(int fd,void *buf ,size_t amt);

    fd是文件描述符,buf是將要寫入的內存數據,amt是要寫的字節數。如果寫入成功返回寫入的字節數,通過result與amt的比較可以判斷是否寫入正常,如果寫入失敗返回-1。write函數僅僅是將數據寫入了緩沖區,何時寫入磁盤由內核決定,如果要強制寫入硬盤,那么在open的時候選擇O_SYNC選項,或者調用fsync函數

    6.隨機存取:fseek()、ftell()和lseek()
    標準I/O使用fseek和ftell用于文件的隨機存取,先看看fseek函數原型
    int fseek(FILE *stream, long offset, int whence);
    第一個參數是文件指針,第二個參數是一個long類型的偏移量(offset),表示從起始點開始移動的距離。第三個參數就是用于指定起始點的模式,stdio.h指定了下列模式常量:
    SEEK_SET            文件開始處
    SEEK_CUR            當前位置
    SEEK_END            文件結尾處
    看幾個調用例子:
    fseek(fp,0L,SEEK_SET);  //找到文件的開始處
    fseek(fp,0L,SEEK_END);  //定位到文件結尾處
    fseek(fp,2L,SEEK_CUR);  //文件當前位置向前移動2個字節數

    而ftell函數用于返回文件的當前位置,返回類型是一個long類型,比如下面的調用:
    fseek(fp,0L,SEEK_END);//定位到結尾
    long last=ftell(fp);  //返回當前位置
    那么此時的last就是文件指針fp指向的文件的字節數。

    與標準I/O類似,*nix系統提供了lseek來完成fseek的功能,原型如下:
    off_t lseek(int fildes, off_t offset, int whence);

    fildes是文件描述符,而offset也是偏移量,whence同樣是指定起始點模式,唯一的不同是lseek有返回值,如果成功就返回指針變化前的位置,否則返回-1。因此可以通過下列方法模擬ftell函數來返回當前偏移量:
    off_t    currpos;
    currpos = lseek(fd, 0, SEEK_CUR);
    whence的取值與fseek相同:SEEK_SET,SEEK_CUR,SEEK_END,但也可以用整數0,1,2相應代替。

    最后,以一個例子結尾,通過c語言編寫linux系統的cp指令,先看看使用標準I/O版本的:
    #include<stdio.h>
    #include
    <stdlib.h>
    void oops(char *,char *);
    int main(int ac,char *av[])
    {
      FILE 
    *in,*out;
      
    int ch;

      
    if(ac!=3){
       fprintf(stderr,
    "Useage:%s source-file target-file.\n",av[0]);
       exit(
    1);
      }
      
    if((in=fopen(av[1],"r"))==NULL)
       oops(
    "can not open ",av[1]);
      
    if((out=fopen(av[2],"w"))==NULL)
       oops(
    "can not open ",av[2]);
      
    while((ch=getc(in))!=EOF)
        putc(ch,out);
      
    if(fclose(in)!=0||fclose(out)!=0)
        oops(
    "can not close files.\n"," ");
      
    return 0;
    }
    void oops(char *s1,char* s2)
    {
      fprintf(stderr,
    "Error:%s %s\n",s1,s2);
      exit(
    1);
    }

    再看一個使用unix io的版本:
    #include<unistd.h>
    #include
    <stdio.h>
    #include
    <fcntl.h>

    #define BUFFERSIZE 
    4096
    #define COPYMODE 
    0644
    void oops(char *,char *);

    int main(int ac,char *av[])
    {
      
    int in_fd,out_fd,n_chars;
      
    char buf[BUFFERSIZE];
      
    if(ac!=3){
        fprintf(stderr,
    "useage:%s source-file target-file.\n",av[0]);
        exit(
    1);
      }

      
    if((in_fd=open(av[1],O_RDONLY))==-1)
         oops(
    "Can't open ",av[1]);
      
    if((out_fd=creat(av[2],COPYMODE))==-1)
        oops(
    "Can't open ",av[2]);
      
    while((n_chars=read(in_fd,buf,BUFFERSIZE))>0)
          
    if(write(out_fd,buf,n_chars)!=n_chars)
               oops(
    "Write error to ",av[2]);
      
    if(n_chars==-1)
          oops(
    "Read error from ",av[1]);
      
    if(close(in_fd)==-1||close(out_fd)==-1)
        oops(
    "Error closing files","");
      
    return 0;
    }
    void oops(char *s1,char *s2)
    {
      fprintf(stderr,
    "Error:%s",s1);
      perror(s2);
      exit(
    1);
    }

    顯然,在使用unix i/o的時候,你要更多地關注緩沖問題以提高效率,而stdio則不需要考慮。






    posted @ 2007-08-22 16:47 dennis 閱讀(1328) | 評論 (0)編輯 收藏

        這一周一口氣開發了4個新功能,修正了十幾個客戶提出的新要求,設計了新的績效評價算法,將《Programming Erlang》讀到第11章,《C primer plus》讀到14章,開始讀《unix/linux編程實踐》,寫了數十道C習題,累,而又充實。今天下午,前幾天聯系我的一家美國公司聘請ROR兼職的在MSN面試了一下,一開始沒有意識到是面試,對自己的表現很不滿意,先問下了我對自己優勢和弱點的看法,然后就針對這些開始提問,問了sql語句,一般的db設計應該注意的問題,數據結構中queue和stack區別,最后發了封email,一個比較長的需求分析,考察我的E文理解能力和需求分析、設計能力。對自己的回答并不滿意吧,我覺的我沒有完全展現出自己的能力,也是太累了,回答完剛好下班,躺在沙發上竟然睡著了。能不能成也要看運氣了,做ROR兼職真是項有趣的挑戰,我很希望試試。

    posted @ 2007-08-10 18:40 dennis 閱讀(385) | 評論 (1)編輯 收藏

         摘要:     這是最近在項目中的一個需求,已知a=3,求字符串"a<=2"的值,也就是應該返回false。這個問題可大可小,就我們的應用場景也就是用來讓用戶自定義變量區間,比如類似下面這樣的規則:a<=2    返回積分系數1.02<a<=5  返回積分系數1.1a>5   ...  閱讀全文

    posted @ 2007-08-06 12:37 dennis 閱讀(7973) | 評論 (7)編輯 收藏

        這是Programming Erlang第8章節的一個練習,創建N個process,連接成一個圈,然后在這個圈子里發送消息M次,看看時間是多少,然后用另一門語言寫同樣的程序,看看時間是多少。我自己寫的版本在處理3000個進程,1000次消息循環(也就是300萬次消息傳遞)時花了5秒多,后來去google別人寫的版本,竟然讓我找到一個98年做的benchmark:Erlang vs. java,也是同樣的的問題。測試的結果是Erlang性能遠遠大于java,這也是顯然的結果,Erlang的process是輕量級、無共享的,而java的線程是os級別的,兩者創建的cost不可同日而語。詳細的比較請看這里
        不過我分析了這個測試里的Erlang代碼,存在問題,并沒有完成所有的循環,進程就結束了,這對比較結果有較大的影響。原始代碼如下:

    -module(zog).

    %% This is a test program that first creates N processes (that are
    %% "connected" in a ring) and then sends M messages in that ring.
    %%
    %% - September 1998
    %% - roland


    -export([start/0, start/1, start/2]).

    -export([run/2, process/1]). % Local exports - ouch

    start() -> start(16000).

    start(N) -> start(N, 1000000).

    start(N, M) -> spawn(?MODULE, run, [N, M]).


    run(N, M) when N < 1 ->
    io:format("Must be at least 1 process~n", []),
    0.0;
    run(N, M) ->
    statistics(wall_clock),

    Pid = setup(N-1, self()),

    {_,T1} = statistics(wall_clock),
    io:format("Setup : ~w s", [T1/1000]),
    case N of
    1 -> io:format(" (0 spawns)~n", []);
    _ -> io:format(" (~w us per spawn) (~w spawns)~n",
    [1000*T1/(N-1), N-1])
    end,
    statistics(wall_clock),

    Pid ! M,
    K = process(Pid),

    {_,T2} = statistics(wall_clock),
    Time = 1000*T2/(M+K),
    io:format("Run : ~w s (~w us per msg) (~w msgs)~n",
    [T2/1000, Time, (M+K)]),

    Time.

    setup(0, OldPid) ->
    OldPid;
    setup(N, OldPid) ->
    NewPid = spawn(?MODULE, process, [OldPid]),
    setup(N-1, NewPid).


    process(Pid) ->
    receive
    M ->
    Pid ! M-1,
    if
    M < 0 -> -M;
    true -> process(Pid)
    end
    end.
      我將process修改一下
    process(Pid) ->
    receive
    M ->
    Pid ! M-1,
    io:format("form ~w to ~w~n",[self(),Pid]),
    if
    M < 0 -> -M;
    true -> process(Pid)
    end
    end.
    然后執行下zog:run(3,3),你將發現消息繞了兩圈就結束了,第三圈根本沒有進行,不知道測試者是什么用意。依照現在的執行300萬次消息傳送竟然只需要3毫秒!我修改了下了下代碼如下:
    -module(zog).

    %% This is a test program that first creates N processes (that are
    %% "connected" in a ring) and then sends M messages in that ring.
    %%
    %% - September 1998
    %% - roland
    -export([start/0, start/1, start/2]).

    -export([run/2, process/2]).                    % Local exports - ouch

    start() -> start(16000).

    start(N) -> start(N, 1000000).

    start(N, M) -> spawn(?MODULE, run, [N, M]).


    run(N, M) when N < 1 ->
        io:format("Must be at least 1 process~n", []),
        0.0;
    run(N, M) ->
        statistics(wall_clock),
        Limit=N-N*M+1+M,
        Pid = setup(N-1,Limit,self()),

        {_,T1} = statistics(wall_clock),
        io:format("Setup : ~w s", [T1/1000]),
        case N of
            1 -> io:format(" (0 spawns)~n", []);
            _ -> io:format(" (~w us per spawn) (~w spawns)~n",
                           [1000*T1/(N-1), N-1])
        end,
        statistics(wall_clock),
      %  io:format("run's Pid=~w~n",[Pid]),
        Pid ! M,
        K = process(Pid,Limit),
      %  io:format("run's K=~w~n",[K]),

        {_,T2} = statistics(wall_clock),
        Time = 1000*T2/(M+K),
        io:format("Run   : ~w s (~w us per msg) (~w msgs)~n",
                  [T2/1000, Time, (M+K)]),
     T2/1000.

    setup(0,Limit, OldPid) ->
        OldPid;
    setup(N,Limit, OldPid) ->
        NewPid = spawn(?MODULE, process, [OldPid,Limit]),
        setup(N-1, Limit,NewPid).


    process(Pid,Limit) ->
        receive
            M ->
                Pid ! M-1,
             %   io:format("from ~w to ~w and M=~w~n",[self(),Pid,M]),
                if
                    M <Limit  -> -M;
                    true   -> process(Pid,Limit)
                end
        end.
    修改之后,執行zog:run(3000,1000),也就是3000個進程,1000次消息循環,總共300萬次消息傳遞,結果在2.5秒左右,這也是相當驚人的結果。有人用haskell和scheme各實現了一個版本,有興趣的看看這里這里


    posted @ 2007-08-04 17:46 dennis 閱讀(1526) | 評論 (0)編輯 收藏

        《C Primer Plus》讀到12章,我的C語言復習進展的挺不錯。這一章介紹存儲類、連接和內存管理,可以說是重中之重。
    C的5種存儲類:
    自動——在一個代碼塊內(或在一個函數頭部作為參量)聲明的變量,無論有沒有存儲類修飾符auton,都屬于自動存儲類。該類具有自動存儲時期、代碼塊的作用域和空鏈接(no linkage),如未初始化,它的值是不確定的(java要求局部變量必須初始化)

    寄存器——在一個代碼塊內(或在一個函數頭部作為參量)使用修飾符register聲明的變量屬于寄存器存儲類。該類與自動存儲類相似,具有自動存儲時期、代碼塊作用域和空連接,聲明為register僅僅是一個請求,而非命令,因此變量仍然可能是普通的自動變量,但是仍然無法獲取地址。。如果沒有被初始化,它的值也是未定的。

    靜態、空鏈接——在一個代碼塊內使用存儲類修飾符static聲明的局部變量屬于靜態空連接存儲類。該類具有靜態存儲時期、代碼塊作用域和空鏈接,僅在編譯時初始化一次。如未明確初始化,它的字節將被設定為0.

    靜態、外部鏈接——在所有函數外部定義、未使用static修飾的變量屬于靜態、外部鏈接存儲類。改類具有靜態存儲時期、文件作用域和外部鏈接,僅在編譯時初始化一次。如未明確初始化,它的字節也被設定為0.

    靜態、內部鏈接——與靜態、外部鏈接存儲類不同的是,它使用static聲明,也定義在所有函數外部,但是具有內部鏈接(僅能被與它在同一個文件的函數使用),僅在編譯時初始化一次。如未明確初始化,它的字節也被設定為0.

    兩個關鍵字:volatile和restrict,兩者都是為了方便編譯器的優化。

    volatile告訴編譯器該被變量除了可被程序修改意外還可能被其他代理修改,因此,當要求使用volatile 聲明的變量的值的時候,系統總是重新從它所在的內存讀取數據,而不是使用寄存器中的緩存。比如
    val1=x;
    val2=x;
    如果沒有聲明volatile,系統在給val2賦值的時候可能直接從寄存器讀取x(假定聰明的編譯器優化了),而不是從內存的初始位置,那么在兩次賦值之間,x完全有可能被被某些編譯器未知的因素更改(比如:操作系統、硬件或者其它線程等)。如果聲明為volatile,編譯器將不使用緩存,而是每次都從內存重新讀取x。

    而restrict是c99引入的,它只可以用于限定指針,并表明指針是訪問一個數據對象的唯一且初始的方式,考慮下面的例子:
    int ar[10];
    int * restrict restar=(int *)malloc(10*sizeof(int));
    int *par=ar;

    這里說明restar是訪問由malloc()分配的內存的唯一且初始的方式。par就不是了。
    那么:
    for(n=0;n<10;n++)
    {
       par[n]+=5;
       restar[n]+=5;
       ar[n]*=2;
       par[n]+=3;
       restar[n]+=3;
    }
    因為restar是訪問分配的內存的唯一且初始的方式,那么編譯器可以將上述對restar的操作進行優化:
       restar[n]+=8;

    而par并不是訪問數組ar的唯一方式,因此并不能進行下面的優化:
       par[n]+=8;
    因為在par[n]+=3前,ar[n]*=2進行了改變。使用了關鍵字restric,編譯器就可以放心地進行優化了。這個關鍵字據說來源于古老的FORTRAN。有興趣的看看這個。

    posted @ 2007-08-04 15:34 dennis 閱讀(9474) | 評論 (1)編輯 收藏

        昨天在讀到《Programming Erlang》第8章,開篇點出Erlang是一門純粹的消息傳遞風格語言(message passing),我才算是領悟了消息傳遞。為了這個問題,我還冒昧地去問javaeye上的T1,對這個問題的興趣是因為SICP第二章以及《失蹤的鏈環》上的介紹。T1給我解答如下:智能能對象只是消息傳遞的一種具體應用.消息傳遞說的更為清晰一些就是一種映射關系或者說映射規則.f:a->b;這個規則可以是任意的。我一直將procedural representations of data,也就是sicp中聲稱intelligent data objects (智能對象)等價于消息傳遞, 而其實智能對象僅僅是消息傳遞的一種具體應用罷了。
    消息傳遞機制通俗地來講就是類似于馬路上到處投遞小廣告的投遞者,它采取的是Send and Pray策略,既不關心消息是否能精確的傳送到真正需要消息的接收者,而是以廣播的方式把消息發送給所有人,然后通過回饋來確定消息接收者的類型(引自《失蹤的鏈環》)。因此,動態語言的duct typing是消息傳遞風格,智能對象是消息傳遞風格,顯然,Erlang的process間的通信機制同樣是消息傳遞風格(Process之間完全通過send message來進行控制和指示,不確定接收方是否具有處理消息的能力 ,異步的,接收的確認也要等待reply)。
        再來說說lambda算子理論,推薦下g9老大的lambda算子系列文章,這是開篇《lambda算子簡介1.a》,以及另外一篇《康托爾、哥德爾、圖靈——永恒的金色對角線(rev#2)》。lambda算子理論是函數式編程的理論基礎,通過9條公理就可以推到出一個圖靈完備的形式系統,其中的Y combinator的推導簡直是魔法(為了表示遞歸),再次領略了計算理論的魅力。另外,最近讀sicp第三章《模塊化、對象和狀態》,也理解了最初的面向對象思想來自何處,在引入了內部狀態模擬時間變化之后,對象的最初思想也產生了,同時也帶來了賦值導致的Side-Effect,而其實這正是動態OO語言中的對象的理念,通過消息來決定對象的type(ducktyping)??涩F代的靜態OO語言,在type和clas
    之間畫上了等號,java里面說一切都是object,其實他想表達的卻是一切都是class,通過type以及函數簽名等來決定消息的分派(message dispatch),導致更多的代碼集中在消息分派,而不是真正的計算任務上,可以說靜態OO已經偏離原始的對象模型很遠。
        一點胡思亂想吧,我沒有科班經歷,所有的東西都是自己在學,在摸索,如有理論和常識上的謬誤,請不吝賜教。

    posted @ 2007-08-03 09:23 dennis 閱讀(1262) | 評論 (0)編輯 收藏

    習題3.6,我的實現如下:
    (define rand
      (let ((x 3))
         (lambda(arg)
          (cond((eq? arg 'generate)
                ((lambda()(set! x (rand-update x)) x)))
               ((eq? arg 'reset)
                (lambda(init) (set! x init) (set! x (rand-update x)) x))
               (else
                 error "Unkonown OP")))))
    簡單解釋下,當參數是generate時,直接調用匿名lambda函數(lambda()(set! x (rand-update x)) x) ,最后返回隨機值。而當第一個參數是reset時,返回匿名函數(lambda(init) (set! x init) (set! x (rand-update x)) x),這個匿名函數接受一個新的初始值,并賦值給x,然后調用rand-update

    習題3.7,引入賦值的代價就是引入了副作用以及相應的復雜性,3.3小節提出了命令式語言與函數式語言的基本差別。這一題,首先修改習題3.3(參見《sicp 3.1小結習題嘗試解答》),增加一個檢查密碼是否正確的功能,用以檢查輸入的原始帳戶密碼是否正確,make-account修改一下
    ;習題3.3
    (define (make-account2 balance passwd)
      (define (checkpwd pwd)
        (eq? pwd passwd))
      (define (withdraw amount)
        (if (>= balance amount)
            (begin (set! balance (- balance amount)) balance)
            "余額不足"))
      (define (deposit amount)
        (set! balance (+ balance amount))
        balance)
      (define (dispatch pwd m)
        (if (eq? pwd passwd)
            (cond ((eq? m 'withdraw) withdraw)
                  ((eq? m 'deposit) deposit)
                  ((eq? m 'checkpwd) checkpwd)
                (else
                   (error "Unknow request--MAKE-ACCOUNT" m)))
            (lambda(x) "Incorrect password")))
            
      dispatch)

    那么,make-joint可以寫為:
    (define (make-joint account-name account-pass new-pass)
      (if (account-name 'checkpwd account-pass)
          (lambda (passwd m)
          (if (eq? new-pass passwd)
              (account-name account-pass m)
               (error "Incorrect password")))
          (error
             "Incorrect password to the original account")))

    首先是檢查原始帳戶的密碼是否正確,正確返回匿名函數(兩個參數passwd m),此匿名函數檢查密碼以及調用原始帳戶操作;如果不正確就提示消息。
    測試一下:
    > (define dennis-acc (make-account2 100 '123))
    > (define zane-acc (make-joint dennis-acc '123 'abc))
    > ((dennis-acc '123 'withdraw) 10)
    90
    >  ((zane-acc 'abc 'withdraw) 10)
    80


    習題3.8,這一題比較簡單了,在內部維持一個狀態變量即可
    (define f
      (let ((y 1))
        (lambda(x) (set! y (* x y)) y)))
    測試可知,在(f 1) (f 0)執行的順序不同時,返回的值不同。


    posted @ 2007-08-01 15:45 dennis 閱讀(403) | 評論 (0)編輯 收藏

        知道這個模式還是通過《重構》,這個模式的出現還是了為了解決代碼重復的壞味道。在項目中很經常見到類似下面這樣的代碼:
    if(prj.getProjectId==null)
        plan.setCost(
    0.0);
    else
        plan.setCost(prj.getCost());

       我們在很多地方有類似的檢查對象是否為null,如果為null,需要一個默認值等等這樣的場景。顯然,代碼重復是壞味道,怎么消除這個壞味道呢?答案就是使用NullObject替代之,Null Object繼承原對象。
    class NullProject extends Project{
       
    public boolean isNull(){
          
    return true;
       }
    }
    class Project{
       
    private double cost;
       
    private String projectId;
       .
       
    public boolean isNull(){
            
    return false;
       }
    }

    那么,原來的代碼可以改寫為:
    if(prj.isNull())
        plan.setCost(
    0.0);
    else
        plan.setCost(prj.getCost());

        如果Null Object的引入僅僅是帶來這個好處,似乎沒有理由讓我們多敲這么多鍵盤。問題的關鍵是類似上面這樣的判斷也許出現在很多處,那么有價值的技巧出現了,我們在NullObject覆寫getCost,提供缺省值:
    class NullProject extends Project{
       
    public boolean isNull(){
          
    return true;
       }
       
    public double getCost(){
          
    return 0.0;      
       }
    }
        因此,檢查對象是否為null的代碼可以去掉if...else了:
    plan.setCost(prj.getCost());

        請注意,只有那些大多數客戶端代碼都要求null object做出相同響應時,這樣的行為才有意義。比如我們這里當工程id為null,很多地方要求費用就默認為0.0。 特殊的行為我們仍然使用isNull進行判斷。
        當然,另外在需要返回NullObject的地方,你應該創建一個null object以替代一般的對象,我們可以建立一個工廠方法:

    class Project{
       
    private double cost;
       
    private String projectId;
       .
       
    public boolean isNull(){
            
    return false;
       }
       
    public Project createNullProject(){
            
    return new NullProject();
       }
    }

       Null Object模式帶來的好處:減少了檢查對象是否為null的代碼重復,提高了代碼的可讀性,通常這些Null Object也可以為單元測試帶來簡便。

    posted @ 2007-07-31 17:48 dennis 閱讀(5177) | 評論 (7)編輯 收藏

        亞洲杯我沒怎么關注,昨天本來是想找找看能不能看到酋長杯的轉播,可這地方收不到廣東體育臺,于是我就看亞洲杯決賽了,沒想到還真值了,非常精彩,好久沒有這樣忘情地看一場比賽了。誰也沒有想到國內沒有職業聯賽,自己的國家還在戰火中煎熬,隊員的親人近幾日在恐怖襲擊中喪生,教練是剛接手球隊兩個月,一開始就沒有人看好他們的伊拉克隊闖進了亞洲杯的決賽,他們已經創造歷史,他們已經親手締造了奇跡,不管決賽的結果如何。相比于他們,中國男子足球隊應該找塊豆腐撞死,不,應該解散他們。
        還是回到比賽,比賽中我一個細節讓我印象深刻,伊拉克的一個后防隊員奮不顧身地用頭去解圍一個低空球,沙特的隊員的腳堪堪從他的耳朵旁大力射門劃過,解說員說伊拉克人真是拼了,連受傷都不怕了,那時我想,對于伊拉克隊員們來說,死亡都不可怕,更何況是區區的受傷。他們是在用靈魂在戰斗,他們希望用勝利來告慰苦難中的祖國和逝去的生命,這已經不是一場普通的球賽,這也不是一場戰爭,讓我想到的詞語竟然是悲劇,一幕讓人激昂而又悲哀的悲劇。

    posted @ 2007-07-30 13:10 dennis 閱讀(225) | 評論 (0)編輯 收藏

    僅列出標題
    共56頁: First 上一頁 35 36 37 38 39 40 41 42 43 下一頁 Last 
    主站蜘蛛池模板: 午夜免费啪视频在线观看| 国产免费MV大全视频网站| 97青青草原国产免费观看| 亚洲中文字幕久久精品无码喷水| 免费的黄色的网站| 亚洲精品国产自在久久| 久久久精品视频免费观看| 亚洲乱码中文字幕手机在线| 黄页网址大全免费观看12网站| 免费人成在线观看网站品爱网日本| 亚洲av无码偷拍在线观看| 国产一区在线观看免费| 免费夜色污私人影院网站电影| 亚洲区日韩区无码区| 国产啪精品视频网站免费尤物| 久久精品国产亚洲AV麻豆不卡 | 野花高清在线电影观看免费视频 | 亚洲av无码天堂一区二区三区 | 亚洲国产成人手机在线电影bd | 亚洲狠狠久久综合一区77777| 久久久久久久岛国免费播放| 亚洲伊人久久大香线蕉结合| 日本免费观看网站| yellow视频免费在线观看| 亚洲AV人无码激艳猛片| 99re热免费精品视频观看| 精品亚洲成A人在线观看青青| 国内精品99亚洲免费高清| 日本免费一区二区三区| 国产亚洲精品影视在线| 亚洲人妻av伦理| 99久久国产免费-99久久国产免费| 国产AV旡码专区亚洲AV苍井空| 国产一区二区视频免费| 人人玩人人添人人澡免费| 亚洲一区二区三区国产精华液| 亚洲一区精品伊人久久伊人| 亚洲精品视频免费在线观看| 免费国产va视频永久在线观看| 亚洲AV无码成人精品区天堂| 在线观看免费精品国产|