treenode

          在路上。

          BlogJava 首頁(yè) 新隨筆 聯(lián)系 聚合 管理
            5 Posts :: 1 Stories :: 53 Comments :: 0 Trackbacks

          2006年6月27日 #


          軟件業(yè)總是充滿了形形色色的隱喻。比如說(shuō),把程序中的問(wèn)題稱為bug;把互聯(lián)網(wǎng)上傳播的病毒叫做蠕蟲;
          把軟件開(kāi)發(fā)的過(guò)程比作造房子......這些都是我們這個(gè)行業(yè)中流行的隱喻,以至于它們已經(jīng)成為軟件開(kāi)
          發(fā)者文化中一個(gè)特有的組成部分。

          在這里,我想要說(shuō)的是一個(gè)特別具有“中國(guó)特色”的隱喻:我稱之為武俠隱喻。

          毋庸我多做解釋,可能很多程序員看到這個(gè)詞就足以勾起豐富的想象。我們中的很多人喜歡把自己所崇敬
          和佩服的、軟件界有影響力的人稱之為大俠,并幻想自己有朝一日能夠達(dá)到他們的境界。(跑題一下,
          這個(gè)稱謂現(xiàn)在似乎有了一個(gè)比較草根的、或者說(shuō)比較Web2.0的版本——叫做牛人)上個(gè)世紀(jì)那個(gè)個(gè)人
          英雄主義的年代,曾經(jīng)涌現(xiàn)出一大批這樣的人物,現(xiàn)在很多人仍然習(xí)慣稱他們?yōu)椤按髠b”——這其
          中包括求伯君、王志東、鮑岳橋、朱崇君...

          除此之外,我們還喜歡將重量級(jí)的出版物稱之為武林秘籍;把軟件開(kāi)發(fā)的組織團(tuán)體比作江湖幫派;
          要形容軟件開(kāi)發(fā)的理想境界,也常會(huì)搬出“飛花摘葉俱可傷人”或“無(wú)劍勝有劍”這樣的句子。所有這些
          都或多或少的表明:武俠深深影響了大量的程序員,他們非常喜歡用武俠中的理念來(lái)比喻軟件開(kāi)發(fā)過(guò)程中
          的現(xiàn)象。或許,也是因?yàn)槌绦騿T0和1的生活太過(guò)枯燥,需要文化來(lái)加一點(diǎn)味道,而他們自覺(jué)不自覺(jué)的
          選擇了武俠。這就是所謂的中國(guó)特色吧。

          我必須老實(shí)的承認(rèn):我自己就曾經(jīng)深受武俠的影響,過(guò)去也一直沒(méi)有感到有什么不妥。但是,
          在前幾天看過(guò)網(wǎng)上的某些回帖中一些充滿武俠隱喻味道的文字,突然覺(jué)得有些不是滋味。
          我開(kāi)始思考:對(duì)于軟件開(kāi)發(fā)來(lái)說(shuō),武俠是不是一個(gè)好的隱喻?結(jié)論:不是不好,而是非常的
          糟。武俠和軟件開(kāi)發(fā)根本沒(méi)有什么共同點(diǎn),甚至可以說(shuō)是水火不容的。

          為什么說(shuō)武俠和軟件開(kāi)發(fā)沒(méi)有共同點(diǎn)?武俠講的是破壞的藝術(shù)。太史公說(shuō)“俠以武犯禁”。
          武俠的意義,在最好的情況下,也僅僅是殺富濟(jì)貧、除暴安良,是對(duì)舊有秩序的破壞。
          問(wèn)題在于:破壞是痛快愜意的,但破而不立就是純粹的破壞,沒(méi)有任何積極意義。
          破壞以后新的秩序如何建立呢?沒(méi)有哪一個(gè)武俠故事為此做出答案,
          也沒(méi)有一個(gè)俠客操心這種事。他們?cè)诤醯氖恰笆綒⒁蝗耍Ю锊涣粜巍钡母呤中蜗螅?br />至于走了以后爛攤子誰(shuí)來(lái)收拾?那本大俠可就管不著了。

          軟件開(kāi)發(fā)是建設(shè),而不是
          破壞。即使舊的系統(tǒng)非常糟糕,我們也沒(méi)有理由將其付之一炬——這就是為什么現(xiàn)代的敏捷
          開(kāi)發(fā)者非常強(qiáng)調(diào)重構(gòu)的原因。構(gòu)造新的代碼固然是極具創(chuàng)造快感的工作,但是軟件開(kāi)發(fā)過(guò)
          程中還有成打的“骯臟”工作:需求分析,設(shè)計(jì),文檔,調(diào)試,維護(hù)......這些工作繁冗
          而瑣碎,但卻是整個(gè)開(kāi)發(fā)過(guò)程中必不可少的組成部分。想瀟灑一下就拍拍屁股走人的
          程序員沒(méi)有什么職業(yè)素質(zhì)可言。

          武俠中的高手是什么形象?天馬行空,獨(dú)往獨(dú)來(lái),神出鬼沒(méi)。這樣的人看起來(lái)很有性格,
          但在現(xiàn)代企業(yè)中恰恰是最忌諱的。而正正經(jīng)經(jīng)提倡Team Work的團(tuán)隊(duì)反倒在武俠中常常成為
          譏刺的對(duì)象——你不妨看看少林或全真這樣的大型團(tuán)隊(duì)在金庸小說(shuō)中被丑化成了什么地步。

          武俠所描繪的是農(nóng)業(yè)社會(huì)的典型情況。一位高手通常只會(huì)把自己的技藝傳授給至親和少數(shù)幾個(gè)
          信得過(guò)的弟子;弟子亦然。這種結(jié)構(gòu)非常脆弱:一旦出現(xiàn)任何問(wèn)題,這門技藝很容易就失傳了。

          武俠中的秘籍是這樣一種東西:你得到它以后,最好藏之名山,偷偷修煉。一旦泄漏,只會(huì)給你
          帶來(lái)殺身之禍。和師徒授受的問(wèn)題相同,這樣只會(huì)讓最好的技藝在歷史長(zhǎng)河中漸漸湮滅。現(xiàn)代社會(huì)和開(kāi)放
          源代碼運(yùn)動(dòng)則顯示了相反的情況:知識(shí)可以由任何人獲取與學(xué)習(xí),而不分門派貴賤。與別人分享
          知識(shí)也不會(huì)給你帶來(lái)任何壞處。

          武俠成為隱喻帶來(lái)的惡果就是,程序員以成為“高手”為榮,以炫耀技巧為樂(lè);無(wú)視風(fēng)險(xiǎn)
          大量采用一些看上去比較炫的新技術(shù);憤世嫉俗以為天下只有自己懷才未遇;不會(huì)與人
          溝通,罔顧客戶需求,把不懂技術(shù)的用戶當(dāng)白癡;不會(huì)開(kāi)誠(chéng)布公,總是自己偷偷留一手;
          凡此種種,不能說(shuō)都是因?yàn)槲鋫b流毒。但是在程序員中造成了不好的風(fēng)氣,武俠的影響
          是不可忽視的。

          武俠是成年人的童話,但軟件開(kāi)發(fā)不是童話。軟件開(kāi)發(fā)要的是腳踏實(shí)地,而不是快意恩仇。
          還在做俠客夢(mèng)的程序員,愿你們?cè)琰c(diǎn)醒來(lái)。

          ?

          posted @ 2006-12-01 15:33 TreeNode 閱讀(1202) | 評(píng)論 (8)編輯 收藏

          可能是為了保持平臺(tái)獨(dú)立性,SWT沒(méi)有開(kāi)放許多控件的自定義接口。例如,Win32中的Button、Label、List和ComboBox都是可以自繪(Owner Draw)的,但是SWT并沒(méi)有把這些繪制方法開(kāi)放出來(lái)。在最新的3.2版本中添加的一個(gè)新特性是Table和Tree現(xiàn)在支持Custom Draw了(但是并未整合到Viewer體系中),不過(guò)對(duì)于上述控件的支持仍付闕如。

          上一次,我實(shí)現(xiàn)了一個(gè)自繪的按鈕。現(xiàn)在,看到有人詢問(wèn)是否可以在Combo的列表中加入圖像。其實(shí)這相當(dāng)容易,只要重載Combo Widget并把自繪接口暴露出來(lái)即可。以下是簡(jiǎn)單的代碼示例:

          package?org.eclipse.swt.widgets;

          import?java.io.*;

          import?org.eclipse.swt.SWT;
          import?org.eclipse.swt.graphics.*;
          import?org.eclipse.swt.internal.win32.*;

          public?class?CustomCombo?extends?Combo
          {
          ????
          public?CustomCombo(?Composite?parent,?int?style?)
          ????{
          ????????
          super(?parent,?style?);

          ????????
          try
          ????????{
          ????????????InputStream?is?
          =?getClass().getResourceAsStream(?"bullet.gif"?);
          ????????????image?
          =?new?Image(?getDisplay(),?is?);
          ????????????is.close();
          ????????}
          ????????
          catch?(?IOException?e?)
          ????????{
          ????????????e.printStackTrace();
          ????????}
          ????????
          final?int?CB_SETITEMHEIGHT?=?0x0153;

          ????????OS.SendMessage(?handle,?CB_SETITEMHEIGHT,?
          0,?24?);
          ????????OS.SendMessage(?handle,?CB_SETITEMHEIGHT,?
          -1,?24?);
          ????}

          ????@Override
          ????
          int?widgetStyle()
          ????{
          ????????
          final?int?CBS_OWNERDRAWFIXED?=?0x0010;
          ????????
          final?int?CBS_HASSTRINGS?=?0x0200;
          ????????
          //?final?int?CBS_OWNERDRAWVARIABLE?=?0x0020;
          ????????return?super.widgetStyle()?|?CBS_OWNERDRAWFIXED?|?CBS_HASSTRINGS;
          ????}

          ????@Override
          ????
          protected?void?checkSubclass()
          ????{
          ????}

          ????@Override
          ????
          public?void?dispose()
          ????{
          ????????image.dispose();
          ????????
          super.dispose();
          ????}

          ????
          /*?@Override
          ????LRESULT?wmMeasureChild(?int?wParam,?int?lParam?)
          ????{
          ????????MEASUREITEMSTRUCT?mis?=?new?MEASUREITEMSTRUCT();
          ????????OS.MoveMemory(?mis,?lParam,?MEASUREITEMSTRUCT.sizeof?);
          ????????mis.itemHeight?=?40;
          ????????OS.MoveMemory(?lParam,?mis,?MEASUREITEMSTRUCT.sizeof?);
          ????????return?null;?//?super.wmMeasureChild(?wParam,?lParam?);
          ????}?
          */

          ????@Override
          ????LRESULT?wmDrawChild(?
          int?wParam,?int?lParam?)
          ????{
          ????????DRAWITEMSTRUCT?dis?
          =?new?DRAWITEMSTRUCT();
          ????????OS.MoveMemory(?dis,?lParam,?DRAWITEMSTRUCT.sizeof?);

          ????????GC?gc?
          =?new?GC(?new?DCWrapper(?dis.hDC?)?);
          ????????Rectangle?rc?
          =?new?Rectangle(?dis.left,?dis.top,?dis.right?-?dis.left,
          ????????????????dis.bottom?
          -?dis.top?);
          ????????Display?display?
          =?getDisplay();
          ????????
          if?(?(dis.itemState?&?OS.ODS_SELECTED)?!=?0?)
          ????????{
          ????????????gc
          ????????????????????.setBackground(?display
          ????????????????????????????.getSystemColor(?SWT.COLOR_LIST_SELECTION?)?);
          ????????????gc.setForeground(?display
          ????????????????????.getSystemColor(?SWT.COLOR_LIST_SELECTION_TEXT?)?);
          ????????????gc.fillRectangle(?rc?);
          ????????}
          ????????
          else
          ????????{
          ????????????gc.setBackground(?display
          ????????????????????.getSystemColor(?SWT.COLOR_LIST_BACKGROUND?)?);
          ????????????gc.setForeground(?display
          ????????????????????.getSystemColor(?SWT.COLOR_LIST_FOREGROUND?)?);
          ????????????gc.fillRectangle(?rc?);
          ????????}
          ????????String?text?
          =?getItem(?dis.itemID?);
          ????????gc.drawImage(?image,?dis.left?
          +?1,?dis.top?+?1?);
          ????????gc.drawText(?text,?dis.left?
          +?20,?dis.top?);

          ????????gc.dispose();

          ????????
          return?null;
          ????}

          ????
          private?static?class?DCWrapper?implements?Drawable
          ????{
          ????????
          private?int????hdc;

          ????????DCWrapper(?
          int?hdc?)
          ????????{
          ????????????
          this.hdc?=?hdc;
          ????????}

          ????????
          public?int?internal_new_GC(?GCData?data?)
          ????????{
          ????????????
          return?hdc;
          ????????}

          ????????
          public?void?internal_dispose_GC(?int?handle,?GCData?data?)
          ????????{
          ????????}
          ????}

          ????
          private?Image????image;
          }


          值得說(shuō)明的是,如果設(shè)置Combo為OwnerDraw Variable風(fēng)格,則必須重載wmMeasureChild方法來(lái)指定每一項(xiàng)的高度。如果使用OwnerDraw Fixed風(fēng)格,則只需要在構(gòu)造的時(shí)候發(fā)送一條CB_SETITEMHEIGHT消息就行了。

          ?另外一種值得考慮的選擇是將Win32的ComboBoxEx控件包裝成SWT Widget。不過(guò),這需要轉(zhuǎn)換若干結(jié)構(gòu)并提供接口,Win32的ImageList管理機(jī)制和SWT的Image包裝方法差別比較大,使得這種方法實(shí)現(xiàn)起來(lái)麻煩的多。

          posted @ 2006-07-06 10:08 TreeNode 閱讀(2355) | 評(píng)論 (4)編輯 收藏

          在上回的blog中,我抱怨過(guò)用Java用內(nèi)部類來(lái)實(shí)現(xiàn)事件回調(diào)的機(jī)制是多么難看和麻煩。在這段時(shí)間里,我一直在考慮是否有什么方法可以不用內(nèi)部類而實(shí)現(xiàn)同樣的效果。因?yàn)镴ava語(yǔ)言本身的限制,所以常規(guī)方法是行不通的。有人建議用反射——的確通過(guò)反射可以調(diào)用任意的方法,但是反射效率不佳,對(duì)頻繁發(fā)生的事件或許不太合適。動(dòng)態(tài)代理也不能解決方法映射的難題。我似乎走進(jìn)了死胡同。

          既然此路不通,那么C#是如何實(shí)現(xiàn)delegate的呢?過(guò)去也曾聽(tīng)聞過(guò)一些內(nèi)幕,不過(guò)這次被逼才真的下決心認(rèn)真去看這方面的東西。原來(lái)M$使用的是代碼生成的技術(shù):對(duì)于每個(gè)delegate,C#都會(huì)為它生成一個(gè)派生于MulticaseDelegate的對(duì)象,其中實(shí)現(xiàn)了一個(gè)和delegate簽名相同的方法。同時(shí),對(duì)delegate的操作符+=和-=也會(huì)被編譯器處理成對(duì)MulticaseDelegate方法的調(diào)用。

          知道了這一點(diǎn),接下來(lái)就需要看看Java中有沒(méi)有類似的代碼生成技術(shù)了。有意思的是,查找的時(shí)候發(fā)現(xiàn)有消息說(shuō),Java 6.0(Mutang)中將會(huì)提供動(dòng)態(tài)代碼生成的功能。這的確很吸引人,不過(guò)Java6還在Beta階段,眼下還指望不上。其他比較出名的方法就是Apache becl和Objectweb ASM了。這兩個(gè)庫(kù)都比較底層,不過(guò)還有一個(gè)開(kāi)源的項(xiàng)目——cglib——它在內(nèi)部使用了asm,不過(guò)提供了較多的實(shí)用功能。據(jù)說(shuō)Hibernate和Spring都用到了這個(gè)東西。研究這個(gè)庫(kù)的時(shí)候,我一眼看到了MethodDelegate類——很明顯這就是我要找的東西了。

          MethodDelegate的設(shè)計(jì)思想很類似于C#的delegate——將接口調(diào)用轉(zhuǎn)發(fā)給類的一個(gè)成員函數(shù)。不過(guò)閱讀文檔的時(shí)候我發(fā)現(xiàn)一個(gè)問(wèn)題。MethodDelegate要求其所實(shí)現(xiàn)的接口必須只有一個(gè)公共方法,但是SWT中的許多事件接口都有不止一個(gè)方法;比如,SelectionListener就有widgetSelected和idgetDefaultSelected兩個(gè)方法。因此要在SWT中使用MethodDelegate,還

          必須再多實(shí)現(xiàn)另外一層轉(zhuǎn)發(fā)。

          了解手段,接下來(lái)的事情就不難了。總結(jié)起來(lái),需要的步驟大致如下:
          1、為每種需要實(shí)現(xiàn)的事件聲明一個(gè)接口。這是MethodDelegate的要求。
          2、用一個(gè)類實(shí)現(xiàn)SWT的事件接口,并將特定的接口調(diào)用轉(zhuǎn)發(fā)到第一步所實(shí)現(xiàn)

          的接口。
          3、用MethodDelegate提供的方法,聲明事件處理對(duì)象(Event Handler

          Target,通常為主窗體或主部件)要實(shí)現(xiàn)上述的事件接口。下面就來(lái)實(shí)現(xiàn)一下。為了簡(jiǎn)單起見(jiàn),將需要實(shí)現(xiàn)的接口聲明為事件轉(zhuǎn)發(fā)類的內(nèi)部接口,以避免維護(hù)太多接口文件(因?yàn)樵摻涌谥恍枰暶饕粋€(gè)方法,所以不會(huì)把外部類搞得太過(guò)復(fù)雜。)例如,處理部件選擇事件(widgetSelected)的類可以如下實(shí)現(xiàn):

          package org.yuhao.swt.events;

          import net.sf.cglib.reflect.MethodDelegate;

          import org.eclipse.swt.events.*;

          public class WidgetSelectedHandler implements SelectionListener
          {
          ?public WidgetSelectedHandler( Object target, String

          methodName )
          ?{
          ??delegate = (IWidgetSelectedDelegate)

          MethodDelegate.create( target,
          ????methodName,

          IWidgetSelectedDelegate.class );
          ?}

          ?public void widgetDefaultSelected( SelectionEvent e )
          ?{
          ?}

          ?public void widgetSelected( SelectionEvent e )
          ?{
          ??delegate.invoke( e );
          ?}

          ?public void invoke( SelectionEvent e )
          ?{
          ?}

          ?public interface IWidgetSelectedDelegate
          ?{
          ??void invoke( SelectionEvent e );
          ?}

          ?private IWidgetSelectedDelegate delegate;
          }

          這個(gè)接口雖然只有外部類用到,但是必須聲明為public的,否則運(yùn)行會(huì)出錯(cuò)(我想大概是因?yàn)榇a生成以后還是外部類,需要公開(kāi)訪問(wèn)權(quán)限)。為了簡(jiǎn)化調(diào)用,再聲明一個(gè)處理事件的輔助類EventHandler,專門管理將各種事件轉(zhuǎn)發(fā)到相應(yīng)的Handler的工作:

          public class EventHandler
          {
          ?public EventHandler( Object target )
          ?{
          ??this.target = target;
          ?}

          ?public void handleSelected( Button btn, String methodName )
          ?{
          ??btn.addSelectionListener( new

          WidgetSelectedHandler( target, methodName ) );
          ?}
          ?private Object target;
          }

          這樣,在窗口中就可以簡(jiǎn)單的如下處理事件:
          public MainShell extends Shell()
          {
          ??public MainShell( Display display )
          ?{
          ??......
          ??handler = new EventHandler( this );
          ??handler.handleSelected( btn, "btn_clicked" );
          ?}

          ?public void btn_clicked( SelectionEvent e )
          ?{
          ??...
          ?}
          }

          這里還需要注意:1、任何事件處理方法必須聲明為public的。這樣似乎有違面向?qū)ο蟮姆庋b原則,不過(guò)實(shí)際上并不會(huì)造成什么大問(wèn)題。2、事件方法的簽名必須和對(duì)應(yīng)的事件方法相同。例如,widgetSelected方法有一個(gè)SelectionEvent參數(shù),那么處理該事件的btn_clicked方法也必須有且只有這一個(gè)參數(shù)。如果寫錯(cuò)了,那么運(yùn)行的時(shí)候會(huì)拋出異常,說(shuō)找不到指定的方法。這還是需要程序員的細(xì)心來(lái)保證。還算幸運(yùn)的是出錯(cuò)的提示非常明顯,不必?fù)?dān)心使用了過(guò)度復(fù)雜的技術(shù)而找不到真正的出錯(cuò)點(diǎn)。

          posted @ 2006-06-27 11:29 TreeNode 閱讀(1226) | 評(píng)論 (0)編輯 收藏

          主站蜘蛛池模板: 恩平市| 漳平市| 泾川县| 高青县| 大冶市| 西贡区| 修水县| 萨迦县| 探索| 四子王旗| 富源县| 银川市| 东海县| 凌源市| 齐齐哈尔市| 筠连县| 奉节县| 平潭县| 萍乡市| 云和县| 搜索| 修文县| 泰兴市| 洛浦县| 会东县| 望城县| 双江| 永平县| 鄱阳县| 西乌珠穆沁旗| 慈溪市| 珠海市| 宣化县| 余干县| 马龙县| 阿瓦提县| 托克逊县| 浮山县| 平阳县| 广安市| 富蕴县|