PC的blog

          Finding... Thinking... Solving...

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

          2008年7月29日 #

          本文緊接使用重構(gòu)移除丑陋的if else代碼(4)

          上篇文章談到如何能夠徹底把這個(gè)switch也移除掉呢?很簡(jiǎn)單,我們只需要在getSystemStatePerformer()方法被調(diào)用之前先創(chuàng)建所有 performer匿名類的實(shí)例,然后在該方法被調(diào)用時(shí)直接返回對(duì)應(yīng)的實(shí)力。 如何具體實(shí)現(xiàn)呢? 用Map, 請(qǐng)看代碼:

          package de.jingge.refactoring;

           

          import static de.jingge.refactoring.SystemState.*;

          import java.awt.Image;

          import java.awt.image.BufferedImage;

          import java.lang.reflect.Method;

          import java.util.Collections;

          import java.util.HashMap;

          import java.util.Map;

           

          /**

           *

           * 
          @author gejing@gmail.com

           
          */

          public class SystemStatePerformerFactory {

           

          private static SystemStatePerformerFactory INSTANCE = new SystemStatePerformerFactory();

             

              
          private Map<SystemState, SystemStatePerformer> performers;

           

              
          private SystemStatePerformerFactory() {

          }

           

              
          public static SystemStatePerformerFactory getInstance() {

                  
          return INSTANCE;

              }

             

              
          private synchronized Map<SystemState, SystemStatePerformer> getPerformers()

                      
          throws Exception {

                  
          if (performers == null) {

                      performers 
          = new HashMap<SystemState, SystemStatePerformer>();

                      
          // call all @FactoryMethod using reflection

                      
          for (Method m : getClass().getDeclaredMethods()) {

                          
          if (m.getAnnotation(FactoryMethod.class!= null) {

                              SystemStatePerformer p 
          = (SystemStatePerformer) m.invoke(

                                      
          thisnew Object[]{});

                              performers.put(p.getState(), p);

                          }

                      }

                      
          // make it readonly

                      performers 
          = Collections.unmodifiableMap(performers);

                  }

                  
          return performers;

              }

           

              
          public SystemStatePerformer getSystemStatePerformer(SystemState state) throws Exception{

                  
          return getPerformers().get(state);

              }

           

          @FactoryMethod

              
          private SystemStatePerformer createLoggedInPerformer() {

                  
          return new SystemStatePerformer(LOGGEDIN, getImage("loggedin.gif")) {

           

                      @Override

                      
          public void perform() {

                          
          // do something after logging in is successful,

                          
          // for example: show welcome dialog, open the last edit document, etc.

                      }

                  };

              }

           

          @FactoryMethod

              
          private SystemStatePerformer createLoggedOutPerformer() {

                  
          return new SystemStatePerformer(LOGGEDOUT, getImage("loggedout.gif")) {

           

                      @Override

                      
          public void perform() {

                          
          // do something after logging out is successful,

                          
          // for example: free used resource, dispose GUI components, etc.            }

                      }

                  };

              }

           

          @FactoryMethod

              
          private SystemStatePerformer createIdlePerformer() {

                  
          return new SystemStatePerformer(IDLE, getImage("idle.gif")) {

           

                      @Override

                      
          public void perform() {

                          
          // do something after the user is idle,

                          
          // for example: save the application state temporarily, lock the application, etc.

                      }

                  };

              }

           

              
          private Image getImage(String string) {

                  
          return new BufferedImage(1010, BufferedImage.TYPE_4BYTE_ABGR);

              }

          }

          從代碼中可以看出,當(dāng)getPerformers()方法被第一次調(diào)用時(shí),我們會(huì)為每一個(gè)performer匿名類創(chuàng)建一個(gè)實(shí)例,并且將它們納入Map的管 理之中,以后每次調(diào)用的時(shí)候,直接從Map里面提取對(duì)應(yīng)某個(gè)狀態(tài)的performer就可以了, switch可以舍棄了。 @FactoryMethod這個(gè)注釋是我自己寫(xiě)的,使用它主要是為了避免每次新增加一個(gè)create***Performer()方法后,都必須修改 getSystemStatePerformer()。

          @FactoryMethod的代碼如下:

          package de.jingge.refactoring;

           

          import java.lang.annotation.ElementType;

          import java.lang.annotation.Retention;

          import java.lang.annotation.RetentionPolicy;

          import java.lang.annotation.Target;

           


          @Retention(RetentionPolicy.RUNTIME)

          @Target({ElementType.METHOD})

          public @interface FactoryMethod {


          }

          到這里整個(gè)重構(gòu)已經(jīng)結(jié)束了, 我們已經(jīng)將if else, switch完全從代碼里剔除了。

          讀過(guò)Refactoring to Patterns這本書(shū)的朋友可能會(huì)覺(jué)得,這里所作的一些和書(shū)中第七章最后一節(jié)Replace Conditional Dispatcher with Command完全一樣。 Well,第一眼看上去確實(shí)很像,但是看完我寫(xiě)的所有代碼后,再仔細(xì)想一想,兩者還是有區(qū)別的(Refactoring to Patterns這本書(shū)寫(xiě)的非常好,對(duì)此書(shū),我可以說(shuō)是愛(ài)不釋手,還曾經(jīng)寫(xiě)過(guò)一篇書(shū)評(píng)。事實(shí)上,我這篇文章正式基于這本書(shū)的):

          1. Factory + annonymous類而不是每一個(gè)狀態(tài)一個(gè)具體的實(shí)體類。

              這樣處理問(wèn)題, 類的數(shù)量大大減少,類關(guān)聯(lián)的復(fù)雜程度也大大減少,維護(hù)起來(lái)很方便。

          2. performer并不單單是一個(gè)command,它擁有狀態(tài),并且可以處理更多的邏輯。


          全文完。
          posted @ 2008-08-04 03:48 polygoncell 閱讀(4624) | 評(píng)論 (37)編輯 收藏

          本文緊接使用重構(gòu)移除丑陋的if else代碼(3)

          OK, 到目前為止,所有的邏輯代碼已經(jīng)從SystemManager重構(gòu)到了SystemStatePerformer。下一步應(yīng)該繼續(xù)重構(gòu)SystemManager, 將SystemState替換為performer:

          1, 使用IDE的重構(gòu)功能,將變量SystemState改為SystemStatePerformer

          2. 在updateState()方法中調(diào)用SystemStatePerformerFactory

          3. 在測(cè)試代碼里面,調(diào)用manager.statePerformer.getState()

          重構(gòu)后的代碼如下:

          package de.jingge.refactoring;

           

          import static de.jingge.refactoring.SystemState.*;


          public class SystemManager {

           

              SystemStatePerformer statePerformer;

           

              
          public void login() {

                  
          // call service#login()

                  updateState(LOGGEDIN);

              }

           

              
          public void logout() {

                  
          // call service#logout()

                  updateState(LOGGEDOUT);

              }

           

              
          public void idle() {

                  
          // call some other services

                  updateState(IDLE);

              }

           

              
          public void updateState(SystemState state) {

                  
          this.statePerformer = SystemStatePerformerFactory.getInstance()

                          getSystemStatePerformer(state);

                  statePerformer.perform();

              }

          }

          可以看到if else已經(jīng)消失了。


          測(cè)試代碼也要做相應(yīng)修改:
          package de.jingge.refactoring;

           

          import org.junit.AfterClass;

          import org.junit.BeforeClass;

          import org.junit.Test;

          import static org.junit.Assert.*;

          import static de.jingge.refactoring.SystemState.*;


          public class SystemManagerTest {

              
          private static SystemManager manager;

              @BeforeClass
              
          public static void setUpClass() throws Exception {

                  manager 
          = new SystemManager();

                  
          // add some service mock objects

              }

              @AfterClass
              
          public static void tearDownClass() throws Exception {

              }

              @Test
              
          public void login() {

                  manager.login();

                  assertEquals(manager.statePerformer.getState(), LOGGEDIN);

              }

              @Test
              
          public void logout() {

                  manager.logout();

                  assertEquals(manager.statePerformer.getState(), LOGGEDOUT);

              }

              @Test
              
          public void idle() {

                  manager.idle();

                  assertEquals(manager.statePerformer.getState(), IDLE);

              }

          }

          到這里重構(gòu)已經(jīng)差不多完成了,代碼已經(jīng)更加面向?qū)ο罅恕_@里還有一個(gè)小問(wèn)題,在factory里面還有一個(gè)switch,這個(gè)和if else其實(shí)是沒(méi)有本質(zhì)區(qū)別的,也就是說(shuō)if else并沒(méi)有被完全移除掉。


          那么如何能夠徹底把這個(gè)switch也移除掉呢?很簡(jiǎn)單,我們只需要在getSystemStatePerformer()方法被調(diào)用之前先創(chuàng)建所有 performer匿名類的實(shí)例,然后在該方法被調(diào)用時(shí)直接返回對(duì)應(yīng)的實(shí)力。 那么具體如何實(shí)現(xiàn)呢,請(qǐng)看下一篇文章使用重構(gòu)移除丑陋的if else代碼(5)
          posted @ 2008-08-04 03:08 polygoncell 閱讀(1936) | 評(píng)論 (1)編輯 收藏

          本文緊接使用重構(gòu)移除丑陋的if else代碼(2)

          移除if else

          首先仔細(xì)觀察一 下updateState()方法,我們會(huì)發(fā)現(xiàn),導(dǎo)致該方法內(nèi)存在大量if else的原因是它的參數(shù)僅僅是一個(gè)enum。由于enum本身并不含有任何邏輯代碼,因此導(dǎo)致處理enum的方法需要使用if else來(lái)分析enum然后調(diào)用相應(yīng)的邏輯。明白了這個(gè)道理之后,重構(gòu)的方向就明了了。簡(jiǎn)單的說(shuō),我們需要要將方法參數(shù)由enum替換成一個(gè)更加強(qiáng)壯的抽 象類,每一個(gè)繼承該類的子類將具體負(fù)責(zé)處理一個(gè)enum實(shí)例,之后再將updateState()方法中相應(yīng)的邏輯代碼轉(zhuǎn)移到這些子類中。這樣處理之后, 令人討厭的if else就會(huì)消失了。


          我們將這個(gè)替換enum的抽象類命名為SystemStatePerformer,代碼如下:

          package de.jingge.refactoring;

           

          import java.awt.Image;


          public abstract class SystemStatePerformer {

              
          private final SystemState state;

              
          private Image image;

              
          public SystemStatePerformer(SystemState state, Image image) {

                  
          this.state = state;

                  
          this.image = image;

              }

              
          public SystemState getState() {

                  
          return state;

              }

              
          public Image getImage() {

                  
          return image;

              }
              
              
          public abstract void perform();

          }

          從代碼中可以看出,每 一個(gè)performer都含義有一個(gè)SystemState,這個(gè)SystemState屬性,將只能通過(guò)構(gòu)建器映射方式射入一個(gè)performer的對(duì) 象實(shí)例。換句話說(shuō)SystemState只是一個(gè)只讀屬性,而且每一個(gè)performer實(shí)體類都只負(fù)責(zé)處理一個(gè)enum的實(shí)例(下面馬上會(huì)解釋如何實(shí)現(xiàn) 的)。這里使用的Image作為一個(gè)例子,它表示用戶的每一個(gè)狀態(tài)都可以使用一個(gè)圖標(biāo)來(lái)表示。performer()方法將負(fù)責(zé)處理具體的邏輯。這個(gè) SystemStatePerformer的實(shí)體子類可以引用任何類型的對(duì)象,然后在perform()方法里面進(jìn)行調(diào)用。




          下 一步就是編寫(xiě)SystemStatePerformer的實(shí)體子類。我首先想到的是為每一個(gè)enum實(shí)例編寫(xiě)一個(gè)實(shí)際的子類,理論上來(lái)說(shuō)是沒(méi)問(wèn)題的,但是 這樣做必須編寫(xiě)一大堆的子類,不便于管理。所以我決定使用Factory + annonymous classes來(lái)構(gòu)建具體的實(shí)體子類,讓Factory來(lái)管理所有的實(shí)體子類。 代碼如下:

          package de.jingge.refactoring;

           

          import static de.jingge.refactoring.SystemState.*;

          import java.awt.Image;

          import java.awt.image.BufferedImage;


          public class SystemStatePerformerFactory {

           

          private static SystemStatePerformerFactory INSTANCE = new SystemStatePerformerFactory();

             

              
          private SystemStatePerformerFactory() {

          }

           

              
          public static SystemStatePerformer getSystemStatePerformer(SystemState state) {

                  
          switch (state) {

                      
          case LOGGEDIN:

                          
          return createLoggedInPerformer();

                      
          case IDLE:

                          
          return createIdlePerformer();

                      
          case LOGGEDOUT:

                          
          return createLoggedOutPerformer();

                      
          default:

                          
          throw new IllegalAccessError("Unkonw status");

                  }

              }

           

              
          private static SystemStatePerformer createLoggedInPerformer() {

                  
          return new SystemStatePerformer(LOGGEDIN, getImage("loggedin.gif")) {

           

                      @Override

                      
          public void perform() {

                          
          // do something after logging in is successful,

                          
          // for example: show welcome dialog, open the last edit document, etc.

                      }

                  };

              }

           

              
          private static SystemStatePerformer createLoggedOutPerformer() {

                  
          return new SystemStatePerformer(LOGGEDOUT, getImage("loggedout.gif")) {

           

                      @Override

                      
          public void perform() {

                          
          // do something after logging out is successful,

                          
          // for example: free used resource, dispose GUI components, etc.            }

                      }

                  };

              }

           

              
          private static SystemStatePerformer createIdlePerformer() {

                  
          return new SystemStatePerformer(IDLE, getImage("idle.gif")) {

           

                      @Override

                      
          public void perform() {

                          
          // do something after the user is idle,

                          
          // for example: save the application state temporarily, lock the application, etc.

                      }

                  };

              }

           

              
          private static Image getImage(String string) {

                  
          return new BufferedImage(1010, BufferedImage.TYPE_4BYTE_ABGR);

              }

          }

          從 代碼中可以看到,針對(duì)每一個(gè)enum狀態(tài)都有一個(gè)創(chuàng)建performer的方法,該方法返回一個(gè)匿名類。邏輯代碼將會(huì)被轉(zhuǎn)移至個(gè)匿名類的 perform()方法之內(nèi)。整個(gè)Factory只有一個(gè)公開(kāi)的方 法:getSystemStatePerformer(SystemState),SystemManager可以調(diào)用這個(gè)方法來(lái)獲得相應(yīng)的 Performer實(shí)例。


          在 這篇文章中,我希望專屬于if else的問(wèn)題。對(duì)于其他設(shè)計(jì)方面的問(wèn)題,我采取的態(tài)度是能省略就省略。實(shí)際開(kāi)發(fā)中,還有有很多問(wèn)題需要處理,例如,使用static方法會(huì)導(dǎo)致系統(tǒng)的可 測(cè)試性下降,在實(shí)際開(kāi)發(fā)中應(yīng)該盡量避免,解決這類問(wèn)題的方法之一是使用DI框架,例如Google Guice。

          下一篇文章使用重構(gòu)移除丑陋的if else代碼(4)繼續(xù)講解。
          posted @ 2008-08-04 02:54 polygoncell 閱讀(2245) | 評(píng)論 (4)編輯 收藏

          本文緊接使用重構(gòu)移除丑陋的if else代碼(1)

          使用Enum替換int常量

          這一步比較簡(jiǎn)單,先創(chuàng)建一個(gè)enum類:

          package de.jingge.refactoring;

          public enum SystemState {

              LOGGEDIN,

              LOGGEDOUT,

              IDLE;

          }


          然后開(kāi)始重構(gòu)SystemManager, 使用SystemState代替SystemManager里的int狀態(tài):

             1. 添加 import static de.jingge.refactoring.SystemState.*;
             2. 刪除所有的integer常量  
             3. 將變量state的類型改為SystemState.

          代碼如下:



          package de.jingge.refactoring;

          import static de.jingge.refactoring.SystemState.*;

          public class SystemManager {

              SystemState state;

              
          public void login() {
                  
          // call service#login()
                  updateState(LOGGEDIN);
              }
             
              
          public void logout() {
                  
          // call service#logout()
                  updateState(LOGGEDOUT);
              }
             
              
          public void idle() {
                  
          // call some other services
                  updateState(IDLE);
              }
             
              
          public void updateState(SystemState state) {
                  
          if (state == LOGGEDIN) {
                      
          // do something after logging in is successful,
                      
          // for example: show welcome dialog, open the last edit document, etc.
                  } else if (state == LOGGEDOUT) {
                      
          // do something after logging out is successful,
                      
          // for example: free used resource, dispose GUI components, etc.
                  } else if (state == IDLE) {
                      
          // do something after the user is idle,
                      
          // for example: save the application state temporarily, lock the application, etc.
                  } else {
                      
          throw new IllegalArgumentException("unknown state");
                  }
                  
          this.state = state;
              }
          }

          然后重構(gòu)測(cè)試類:

          1.    添加import static de.jingge.refactoring.SystemState.*;
          2.    刪除所有常量前引用的SystemManager.

          package de.jingge.refactoring;

          import org.junit.AfterClass;
          import org.junit.BeforeClass;
          import org.junit.Test;
          import static org.junit.Assert.*;
          import static de.jingge.refactoring.SystemState.*;


          public class SystemManagerTest {

              
          private static SystemManager manager;

              @BeforeClass
              
          public static void setUpClass() throws Exception {
                  manager 
          = new SystemManager();
                  
          // add some service mock objects
              }
             
              @AfterClass
              
          public static void tearDownClass() throws Exception {
              }
             
              @Test
              
          public void login() {
                  manager.login();
                  assertEquals(manager.state, LOGGEDIN);
              }
            
              @Test
              
          public void logout() {
                  manager.logout();
                  assertEquals(manager.state, LOGGEDOUT);
              }

              @Test
              
          public void idle() {
                  manager.idle();
                  assertEquals(manager.state, IDLE);
              }
          }


          運(yùn)行這個(gè)測(cè)試類->通過(guò)

          下一篇文章使用重構(gòu)移除丑陋的if else代碼(3)開(kāi)始處理if else hell
          posted @ 2008-08-04 02:45 polygoncell 閱讀(2092) | 評(píng)論 (0)編輯 收藏

          我們知道因?yàn)榫幊陶Z(yǔ)言的限制,歷史遺留下來(lái)的系統(tǒng)總是有很多的毛病,不夠面向?qū)ο螅绕涫呛芏嘞到y(tǒng)濫用if else。我曾經(jīng)見(jiàn)過(guò)一個(gè)項(xiàng)目,大家基本上就是寫(xiě)一個(gè)方法,然后在里面if else套if esle得嵌套了好幾層,難看就不必說(shuō)了,這種代碼根本就沒(méi)法維護(hù)。

          今天我就使用從實(shí)際項(xiàng)目中提煉出來(lái)的例子來(lái)講解一下如何將這類代碼變得更加面向?qū)ο?- 重構(gòu)成模式并且添加測(cè)試代碼,

          先來(lái)看一個(gè)丑陋的類:

          package de.jingge.refactoring;

           

          public class SystemManager {

           

              
          public static final int LOGGEDIN = 0;

              
          public static final int LOGGEDOUT = 1;

              
          public static final int IDLE = 2;

              
          int state;

           

              
          public void login() {

                  
          // call service#login()

                  updateState(LOGGEDIN);

              }

             

              
          public void logout() {

                  
          // call service#logout()

                  updateState(LOGGEDOUT);

              }

             

              
          public void idle() {

                  
          // call some other services

                  updateState(IDLE);

              }

              
          public void updateState(int state) {

                  
          if (state == LOGGEDIN) {

                      
          // do something after logging in is successful,

                      
          // for example: show welcome dialog, open the last edit document, etc.

                  } 
          else if (state == LOGGEDOUT) {

                      
          // do something after logging out is successful,

                      
          // for example: free used resource, dispose GUI components, etc.

                  } 
          else if (state == IDLE) {

                      
          // do something after the user is idle,

                      
          // for example: save the application state temporarily, lock the application, etc.

                  } 
          else {

                      
          throw new IllegalArgumentException("unknown state");

                  }

                  
          this.state = state;

              }

          }


          這里我們展示了一個(gè) SystemManager,它負(fù)責(zé)處理用戶在系統(tǒng)中的狀態(tài):登入(logged in),登出(logged out),以及空閑(idle)。從代碼中可以看到,這個(gè)類用了int來(lái)定義狀態(tài)并且因此導(dǎo)致了updatteState()方法里面出現(xiàn)大量if else。從目前看來(lái)這些if else是無(wú)法避免的,應(yīng)為這個(gè)類需要針對(duì)不同的狀態(tài)作出反應(yīng)。隨著狀態(tài)的增加,if else的數(shù)量也會(huì)繼續(xù)增加。這個(gè)解決方案顯然很差。

          那么怎么樣才能讓這個(gè)類更加地面向?qū)ο竽兀?br id="i7km89" />
          在處理面向?qū)ο笾埃覀兪紫纫帉?xiě)一個(gè)測(cè)試類,這也是處理這類歷史遺留下來(lái)代碼所必需做的第一步,只有在測(cè)試代碼的保護(hù)下,我們才能放心大膽地進(jìn)行重構(gòu)。

          初步的測(cè)試代碼如下:

          package de.jingge.refactoring;

          import org.junit.AfterClass;
          import org.junit.BeforeClass;
          import org.junit.Test;
          import static org.junit.Assert.*;



          public class SystemManagerTest {
              
          private static SystemManager manager;

              @BeforeClass
              
          public static void setUpClass() throws Exception {
                  manager 
          = new SystemManager();
                  
          // add some service mock objects
              }

              @AfterClass
              
          public static void tearDownClass() throws Exception {

              }

              @Test
              
          public void login() {

                  manager.login();

                  assertEquals(manager.state, SystemManager.LOGGEDIN);

              }

              @Test
              
          public void logout() {

                  manager.logout();

                  assertEquals(manager.state, SystemManager.LOGGEDOUT);

              }

              @Test
              
          public void idle() {

                  manager.idle();

                  assertEquals(manager.state, SystemManager.IDLE);

              }

          }

          運(yùn)行測(cè)試代碼->通過(guò)。

          在下一篇文章我們將正式開(kāi)始重構(gòu)。地址:使用重構(gòu)移除丑陋的if else代碼(2)
          posted @ 2008-08-04 02:36 polygoncell 閱讀(2701) | 評(píng)論 (3)編輯 收藏

          陸陸續(xù)續(xù)又看了一些技術(shù)書(shū),加上最近工作任務(wù)繁重,實(shí)在沒(méi)時(shí)間寫(xiě)技術(shù)類的博文,計(jì)劃斷斷續(xù)續(xù)寫(xiě)一些書(shū)的閱讀評(píng)論。今天先寫(xiě)一本,

          最近閱讀的書(shū)并不都是最新的,因?yàn)樾?shū)的確是需要花時(shí)間反復(fù)研讀,仔細(xì)思考的,例如關(guān)于設(shè)計(jì)模式的書(shū)。

          Refactoring to Patterns就是這樣一本書(shū),一本相對(duì)來(lái)說(shuō)不算太新的書(shū),一本關(guān)于設(shè)計(jì)模式的書(shū),一本讓人從新開(kāi)始思考模式的書(shū)。我本人強(qiáng)烈推舉大家閱讀。

          這本書(shū)剛出來(lái)的時(shí)候,我就一直想好好靜下心來(lái)讀一讀,無(wú)奈工作忙碌,一直沒(méi)有找到機(jī)會(huì)。而且說(shuō)實(shí)話,自己對(duì)設(shè)計(jì)模式也已經(jīng)浸淫數(shù)載,大部分模式都已經(jīng)在實(shí) 際項(xiàng)目中歷練過(guò),不說(shuō)爐火純青,也算得上是熟能生巧。雖然知道這是一本好書(shū),但是心里依然會(huì)泛起陣陣漣漪:just another design pattern book,給我一天時(shí)間,我就能把它拿下。沒(méi)想到從開(kāi)始讀到現(xiàn)在已經(jīng)2個(gè)多月了,現(xiàn)在依然會(huì)抽時(shí)間出來(lái)翻看某個(gè)章節(jié),然后結(jié)合實(shí)際問(wèn)題仔細(xì)思考一番。說(shuō)實(shí) 話,設(shè)計(jì)模式真的是個(gè)好東西,是前輩經(jīng)驗(yàn)的積累,但是當(dāng)我們熟練掌握了各種模式之后,就會(huì)遇到兩個(gè)瓶頸性質(zhì)的問(wèn)題:

          1. 如何將各類模式融匯貫通。感覺(jué)就像是武俠里面的如何將任督二脈打通,功夫再好,任督二脈不通也非高手,不同的模式相互作用會(huì)產(chǎn)生不同的結(jié)果,不同的模式組合會(huì)產(chǎn)生不同的結(jié)構(gòu),或相互補(bǔ)充相互促進(jìn),或互相影響互相抵制。如何選擇,是個(gè)難題。

          2. 如何避免過(guò)度設(shè)計(jì)。模式用熟的朋友可能會(huì)有這種感覺(jué),編程的時(shí)候會(huì)不知不覺(jué)中使用模式進(jìn)行開(kāi)發(fā),模式再好,泛濫使用,過(guò)猶不及。好像武功一般,招式繁多,固然耍起來(lái)好看,但是高手最后的境界往往是無(wú)招勝有招,只要能目的達(dá)到就行了。

          第一個(gè)問(wèn)題由于模式之間的組合千變?nèi)f化,而且很多需要結(jié)合實(shí)際問(wèn)題進(jìn)行考慮,大家只能是在實(shí)際項(xiàng)目開(kāi)發(fā)中慢慢體會(huì),慢慢積累經(jīng)驗(yàn)。或許再過(guò)幾年會(huì)有某個(gè)人或者某些人把這些經(jīng)驗(yàn)收集整理,編排出書(shū)。

          今天主要想說(shuō)說(shuō)第二個(gè)問(wèn)題:過(guò)度設(shè)計(jì)。 Refactoring to Patterns這本書(shū)就是幫助我們盡量避免過(guò)度設(shè)計(jì)的,這也是我推薦大家看這本書(shū)的初衷。

          事實(shí)上,這本書(shū)完全可以看成是極限編程系列里面的一部巨頭著作,其他的重要著作包括TDD,Refactoring,和continuous integration,合起來(lái)應(yīng)該稱為XP四大金剛。

          使用XP開(kāi)發(fā)強(qiáng)調(diào)keep it simple,在編碼的時(shí)候首先用最簡(jiǎn)單的辦法實(shí)現(xiàn)功能(當(dāng)然最好是測(cè)試驅(qū)動(dòng),我會(huì)在評(píng)論下面一本書(shū)是繼續(xù)談測(cè)試驅(qū)動(dòng)),然后在測(cè)試代碼的保護(hù)下對(duì)初級(jí)代 碼進(jìn)行大刀闊斧地重構(gòu),這時(shí)候這些代碼自然最好能夠重構(gòu)成設(shè)計(jì)模式,因?yàn)獒槍?duì)某些特定的問(wèn)題,模式基本上是最佳解決方案。這個(gè)時(shí)候就大家就需要了解如何將 現(xiàn)有的代碼重構(gòu)成為標(biāo)準(zhǔn)的設(shè)計(jì)模式代碼,說(shuō)的嚴(yán)重點(diǎn),這一步關(guān)系到整個(gè)代碼的質(zhì)量,直至引申到真?zhèn)€項(xiàng)目的質(zhì)量。這本書(shū)就針對(duì)這個(gè)關(guān)鍵步驟進(jìn)行了講解,是所 有采用XP方法的開(kāi)發(fā)團(tuán)隊(duì)的必修課之一。


          至于書(shū)的內(nèi)容我就盡量簡(jiǎn)單概括一下:本書(shū)基本上覆蓋了重構(gòu)項(xiàng)目中會(huì)遇到的大部分問(wèn)題,并且針對(duì)這些問(wèn)題提供了實(shí)例講解和具體的解決方案。這些方案不單單適 用于使用TDD開(kāi)發(fā)的項(xiàng)目,它們更適用于那些希望通過(guò)重構(gòu)改善現(xiàn)有代碼質(zhì)量的項(xiàng)目。當(dāng)然,重構(gòu)前,千萬(wàn)別嘗試避開(kāi)編寫(xiě)測(cè)試代碼,要牢記,沒(méi)有測(cè)試代碼保 護(hù),最好不要對(duì)代碼做大手術(shù)。書(shū)中的很多例子都值得大家讀完以后結(jié)合自己的實(shí)際項(xiàng)目仔細(xì)思考一番。我個(gè)人很喜歡第7章,已經(jīng)在項(xiàng)目中采用了書(shū)中提到的很多 解決方案,并且對(duì)于某些實(shí)際例子,開(kāi)始嘗試優(yōu)化書(shū)中的方案,希望以后能夠抽出時(shí)間來(lái)寫(xiě)一篇詳細(xì)的博文。
          posted @ 2008-07-29 19:17 polygoncell 閱讀(1403) | 評(píng)論 (0)編輯 收藏

          這段時(shí)間真是忙得要死,一方面要開(kāi)發(fā)公司項(xiàng)目的系統(tǒng)框架,要將項(xiàng)目分成不同的子項(xiàng)目,編寫(xiě)核心代碼;另一方面要將極限編程(XP)引入團(tuán)隊(duì)開(kāi)發(fā),部署各類 XP需要的服務(wù)例如subversion啦,ant+ivy啦,Hudson啦等等。順便說(shuō)句題外話,ubuntu還真是不是一般的好用,建議有能力的全 部轉(zhuǎn)到ubuntu上去開(kāi)發(fā)。

          我目前開(kāi)發(fā)的這個(gè)框架的客戶端是具肥的客戶端,也就是Swing客戶端了。Swing應(yīng)用相對(duì)于Web應(yīng)用有很多優(yōu)勢(shì),因?yàn)樗省?shù)據(jù)驗(yàn)證就是 其中一個(gè)。當(dāng)然現(xiàn)在的Web應(yīng)用通過(guò)使用Ajax也要比以前強(qiáng)很多了,但是還是避免不了在驗(yàn)證數(shù)據(jù)時(shí)向服務(wù)段發(fā)出請(qǐng)求,至少你無(wú)法避免驗(yàn)證結(jié)果從Web服 務(wù)器傳輸?shù)接脩魹g覽器上這段過(guò)程。而Swing這類肥客戶端可以實(shí)現(xiàn)完全在本地對(duì)數(shù)據(jù)進(jìn)行驗(yàn)證,甚至可以斷網(wǎng)繼續(xù)工作(這也是Web應(yīng)用目前在研發(fā)的一個(gè) 重要課題)。

          前段時(shí)間開(kāi)發(fā)出了一個(gè)可以應(yīng)用于所有Swing應(yīng)用的通用數(shù)據(jù)驗(yàn)證模塊,發(fā)現(xiàn)它在項(xiàng)目中使用后,對(duì)于普通的數(shù)據(jù)驗(yàn)證,程序員幾乎不需要編碼,效率提高了不少,就寫(xiě)了一篇博文拿出來(lái)和大家分享。原文是用英文寫(xiě)的,在這里:http://polygoncell.blogspot.com/2008/07/validation-module-for-swing-application.html。英文好的朋友可以直接去那里看。

          編寫(xiě)這個(gè)模塊使用了很多不同的開(kāi)源框架和類庫(kù),其中很重要的一個(gè)就是JXLayer。文章寫(xiě)完后,我就跑去邀請(qǐng)JXLayer的作者Alexp來(lái)指點(diǎn)一下,然后就在我的文章后面開(kāi)始了一段討論,挺有意思的,他不愧為是Swing team里面的牛人啊!厲害啊!呵呵。

          ok,回到今天這篇文章的正題。今天的主要目的是將我的英文博文翻譯成中文(自己的文章,我就不逐字逐句翻譯了,意思到了就行了,可能還會(huì)隨興展 開(kāi)一番討論)在這里展示給大家,與大家分享開(kāi)發(fā)經(jīng)驗(yàn),希望大家能夠從中獲益,也希望能夠以文會(huì)友,廣交朋友。廢話少說(shuō),切入正題。

          數(shù)據(jù)驗(yàn)證(Validation)一直是軟件開(kāi)發(fā)中非常重要的一環(huán),有了它,你的系統(tǒng)會(huì)讓客戶感到更加友善,同時(shí)你的系統(tǒng)也得到了一定程度的保 護(hù)。一般來(lái)說(shuō),數(shù)據(jù)驗(yàn)證既可以在客戶端也可以在服務(wù)端。默認(rèn)的JSF數(shù)據(jù)驗(yàn)證就是在服務(wù)端,數(shù)據(jù)只能在被提交以后才能夠被驗(yàn)證,然后把錯(cuò)誤信息傳遞回用戶 的瀏覽器。后來(lái)大規(guī)模使用Ajax后,基本可以實(shí)現(xiàn)對(duì)修改的數(shù)據(jù)“即時(shí)”驗(yàn)證,注意這里是個(gè)打了引號(hào)的即時(shí),數(shù)據(jù)事實(shí)上還是要在瀏覽器和服務(wù)端之間進(jìn)行傳 遞的,只不過(guò)Ajax將這種傳遞改為隱式了而已,理論上并沒(méi)有真正實(shí)現(xiàn)(斷網(wǎng))即時(shí)驗(yàn)證。而在Swing應(yīng)用上就能夠達(dá)成這種愿望。

          事實(shí)上,開(kāi)發(fā)Swing應(yīng)用時(shí),數(shù)據(jù)驗(yàn)證一直比較棘手,需要手工編碼的地方太多,效率不高。后來(lái)出了JGoodies Validation 結(jié)合JGoodies binding后,好了一些。這個(gè)JGoodies Validation既可以實(shí)現(xiàn)model層面的驗(yàn)證,也可以實(shí)現(xiàn)Bean層面的驗(yàn)證,但是多年使用下來(lái),發(fā)現(xiàn)其實(shí)它比較適用于中小項(xiàng)目,而且要編寫(xiě)的代 碼其實(shí)一點(diǎn)不比自己手動(dòng)編寫(xiě)的少。

          JGoodies流行了一段時(shí)間后,sun開(kāi)始推出自己的bean綁定方案:beansbinding(JSR 295),我個(gè)人感覺(jué)要比JGoodies binding好用(JGoodies的作者Karsten也在專家組里,這個(gè)人我以前和他一起共事過(guò),我的msn space里面還有跟他的合影,絕對(duì)是Swing界的牛人)。這個(gè)beansbinding也提供數(shù)據(jù)驗(yàn)證,但是它的這個(gè)數(shù)據(jù)驗(yàn)證只是在target被改 動(dòng)后,數(shù)據(jù)被同步回source之前才會(huì)起作用,使用起來(lái)局限性比較大,而且編碼量也不小。

          由于目前絕大部分項(xiàng)目是基于POJO的,Hibernate validator已經(jīng)提供了一個(gè)很好的數(shù)據(jù)驗(yàn)證框架,我們完全沒(méi)必要再重復(fù)發(fā)明輪子,我們應(yīng)該努力站在巨人的肩膀上,這樣我們才能站得更高,看得更遠(yuǎn)。 于是我考慮結(jié)合beansbinding和Hibernate Validator開(kāi)發(fā)數(shù)據(jù)驗(yàn)證。還有一個(gè)重要的問(wèn)題,那就是數(shù)據(jù)錯(cuò)誤的時(shí)候,需要在用戶界面上展示相應(yīng)的信息,例如Error icon和錯(cuò)誤提示,這部分我考慮使用JXLayer。

          你可以在如下鏈接中找到相關(guān)框架的具體信息:

          1. Hibernate Validator: http://www.hibernate.org/hib_docs/validator/reference/en/html_single/
          2. Beansbinding: https://beansbinding.dev.java.net/
          3. JXlayer: http://weblogs.java.net/blog/alexfromsun/

          閱讀這篇文章,不需要你熟悉這些類庫(kù),不過(guò)了解這些類庫(kù)能夠幫助你更好地理解這篇文章。

          我的這個(gè)通用模塊是參考JXLayer里面的一個(gè)demo類TextValidationDemo的,這個(gè)JXlayer是由Alexander Potochkin開(kāi)發(fā)的,我很喜歡,使用起來(lái)很順手,強(qiáng)烈推薦使用。

          下面開(kāi)始介紹代碼。首先是建立一個(gè)java項(xiàng)目,對(duì)于這個(gè)小項(xiàng)目,我使用netbeans。這里說(shuō)句題外話,中型和大型的Swing應(yīng)用,建議最 好還是不要使用netbeans的GUI Builder,一方面它生成的代碼超級(jí)爛,另一方面很難測(cè)試。目前市面上有很多好用的layout的框架,例如 JGoodies form和MigLayout,開(kāi)發(fā)效率絕對(duì)不比netbeans的GUI builder差,你還不需要面對(duì)令人頭疼的機(jī)器成的代碼。

          項(xiàng)目創(chuàng)建好后,加入類庫(kù):



          然后寫(xiě)一個(gè)persistence bean:
          package de.jingge.domain;

          import javax.persistence.Entity;
          import javax.persistence.GeneratedValue;
          import javax.persistence.GenerationType;
          import javax.persistence.Id;
          import org.hibernate.validator.Length;
          import org.hibernate.validator.NotEmpty;


          @Entity
          public class Country extends AbstractBean {

          private static final long serialVersionUID = 5341382564159667599L;
          public static final String PROPERTYNAME_NAME = "name";
          public static final String PROPERTYNAME_CODE = "code";
          private String name;
          private String code;
          private Long id;

          public Country() {
          }

          public Country(String code, String name) {
              
          super();
              setCode(code);
              setName(name);
          }

          @Id
          @GeneratedValue(strategy 
          = GenerationType.AUTO)
          public Long getId() {
              
          return id;
          }

          public void setId(Long id) {
              
          this.id = id;
          }

          @NotEmpty
          public String getName() {
              
          return name;
          }

          public void setName(String name) {
              firePropertyChange(PROPERTYNAME_NAME, 
          this.name, this.name = name);
          }

          @Length(min
          =2, max= 2, message="Code length must be 2")
          @NotEmpty
          public String getCode() {
              
          return code;
          }

          public void setCode(String code) {
              firePropertyChange(PROPERTYNAME_CODE, 
          this.code, this.code = code);
          }
          }


          這里我為了強(qiáng)調(diào)可以在Swing客戶端直接使用和驗(yàn)證persistence bean,故意寫(xiě)了一個(gè)persistence bean,實(shí)際應(yīng)用中,這個(gè)類只需要是一個(gè)pojo就行了。

          這個(gè)Country類代表一個(gè)國(guó)家,它有兩個(gè)屬性,code和name,我給他們分別加上個(gè)各自的驗(yàn)證限制。code不能為空,且必須正好是兩個(gè) 字符,例如CN,DE,US。name不能為空。這些annotaion均出自Hibernate Validator。那個(gè)父類AbstractBean出自SwingX類庫(kù),我們的Country類繼承了它之后就可以支持property change event了。

          ok, 下面可以開(kāi)始編寫(xiě)這個(gè)模塊的核心代碼了。前面說(shuō)過(guò),我會(huì)使用JXlayer。使用它的好處是:所有JXlayer的painting event都會(huì)被轉(zhuǎn)到UI類來(lái),我們只需要編寫(xiě)一個(gè)集成Hibernate Validator的UI類就可以了,我稱這個(gè)類為HibernateValidationUI,代碼如下:

          package de.jingge.view;

          import java.awt.Graphics2D;
          import java.awt.image.BufferedImage;

          import javax.swing.BorderFactory;
          import javax.swing.JComponent;
          import javax.swing.text.JTextComponent;

          import org.hibernate.validator.ClassValidator;
          import org.hibernate.validator.InvalidValue;
          import org.jdesktop.beansbinding.ELProperty;
          import org.jdesktop.beansbinding.PropertyStateEvent;
          import org.jdesktop.beansbinding.PropertyStateListener;
          import org.jdesktop.jxlayer.JXLayer;
          import org.jdesktop.jxlayer.plaf.AbstractLayerUI;

          /**
          * Header:
          * Description: A layerUI which will validate the referenced property value of
          * the object each time when the paint() method is called.

          * The value of the given object property will be observed.
          * Note: This UI works only with {
          @link JXLayer}. Any change of the property
          * will force repainting the UI. The work process looks like: property changed ->
          * jxlayer will be repainted -> the paint() method of this UI will be called.
          * The logic of validation will be handled by the Hibernate validator
          * framework.
          *
          */
          public class HibernateValidationUI extends AbstractLayerUI<jTextComponent> {

          private Object object;
          private String propertyName;
          private ClassValidator validator;
          private ELProperty elProperty;
          private PropertyStateListener propertyChangeHandler;

          public HibernateValidationUI(Object obj, String propertyName) {
              
          this.object = obj;
              
          this.propertyName = propertyName;
              propertyChangeHandler 
          = new PropertyChangeHandler();
              validator 
          = new ClassValidator(obj.getClass());

              elProperty 
          = ELProperty.create("${" + propertyName + "}");
          }

          public void installUI(JComponent c) {
              
          super.installUI(c);
              elProperty.addPropertyStateListener(object, propertyChangeHandler);
          }

          public void uninstallUI(JComponent c) {
              
          super.uninstallUI(c);
              elProperty.removePropertyStateListener(object, propertyChangeHandler);
          }

          protected void paintLayer(Graphics2D g2, JXLayer<jTextComponent> l) {
              
          super.paintLayer(g2, l);
              InvalidValue[] validationMessages 
          = validator.getInvalidValues(object,
                      propertyName);
              
          if (validationMessages.length > 0) {
                  BufferedImage image 
          = Java2DIconFactory.createErrorIcon();
                  g2.drawImage(image, l.getWidth() 
          - image.getWidth() - 1,
                          l.getHeight() 
          - 8null);
                  l.getView().setToolTipText(validationMessages[
          0].getMessage());

                  
          return;
              }
              l.getView().setToolTipText(
          null);
          }

          boolean isValid() {
              
          return validator.getInvalidValues(object, propertyName).length == 0;
          }

          class PropertyChangeHandler implements PropertyStateListener {

              @Override
              
          public void propertyStateChanged(PropertyStateEvent pse) {
                  setDirty(
          true);
              }
          }
          }



          這個(gè)HibernateValidationUI類只有一個(gè)構(gòu)建器,它接收兩個(gè)參數(shù),一個(gè)是source object,也就是我們要修改的那個(gè)Bean類的實(shí)例,另外一個(gè)是這個(gè)bean的一個(gè)屬性,這個(gè)HibernateValidationUI就負(fù)責(zé)驗(yàn)證這個(gè)屬性。

          在installUI()方法中,我們啟動(dòng)對(duì)屬性變化的觀察類,而在uninstallUI()方法里面,我們需要卸載這個(gè)觀察類。

          當(dāng)給定對(duì)象的屬性值發(fā)生變化時(shí),PropertyChangeHandler的propertyStateChanged()方法就會(huì)被調(diào)用,這 個(gè)功能是通過(guò)elProperty和PropertzChangeHandler相結(jié)合來(lái)實(shí)現(xiàn)的。在propertyStateChangeed()方法 里UI類的方法setDirty()會(huì)被調(diào)用,該方法的調(diào)用會(huì)導(dǎo)致UI類的狀態(tài)變化,進(jìn)而引發(fā)(re)painting,之后經(jīng)過(guò)一系列的方法調(diào)用傳 遞,paintLayer(Graphics2D g2, JXLayer<jTextComponent> l)這個(gè)方法將會(huì)被調(diào)用,這個(gè)方法要做的就是我們這個(gè)數(shù)據(jù)驗(yàn)證模塊的核心功能:

          1. 調(diào)用Hibernate Validator驗(yàn)證該屬性。
          2. 如果數(shù)據(jù)不正確,則在GUI上顯示一個(gè)error icon,并且將錯(cuò)誤信息作為tooltip展示給用戶。

          在第二點(diǎn)里面產(chǎn)生了一個(gè)問(wèn)題,謝謝Alexp對(duì)我的指點(diǎn)。Swing team里面有一些規(guī)定,其中之一就是,在paint()方法里面最好不要改變Component的狀態(tài),而setTooltip()方法將會(huì)改變 component的狀態(tài),因此需要在paint()方法之外調(diào)用。我目前使用下來(lái),還沒(méi)有發(fā)現(xiàn)什么嚴(yán)重的錯(cuò)誤,決定暫時(shí)不改了,回頭有時(shí)間在將這個(gè)代碼 翻新一下。

          類中用到的Java2DIconFactory代碼如下:

          package de.jingge.view;

          import java.awt.Color;
          import java.awt.Graphics2D;
          import java.awt.RenderingHints;
          import java.awt.image.BufferedImage;


          public class Java2DIconFactory {

          public static BufferedImage createErrorIcon() {
              
          return createErrorIcon(78);
          }

          public static BufferedImage createErrorIcon(int width, int height) {
              BufferedImage icon 
          = new BufferedImage(width, height,
                      BufferedImage.TYPE_INT_ARGB);
              Graphics2D g2 
          = (Graphics2D) icon.getGraphics();
              g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                      RenderingHints.VALUE_ANTIALIAS_ON);
              g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
                      RenderingHints.VALUE_STROKE_PURE);
              g2.setColor(Color.RED);
              g2.fillRect(
          00, width, height);
              g2.setColor(Color.WHITE);
              g2.drawLine(
          00, width, height);
              g2.drawLine(
          0, height, width, 0);
              g2.dispose();
              
          return icon;
          }
          }


          沒(méi)什么太多好解釋的,就是使用Java 2D畫(huà)一個(gè)Error icon。

          接著,我們需要編寫(xiě)一個(gè)Factory類,構(gòu)建一個(gè)JTextField,盡量把復(fù)雜技術(shù)封裝起來(lái),這樣程序員開(kāi)發(fā)起來(lái)可以提高效率,代碼如下:

          package de.jingge.view;

          import javax.swing.JTextField;
          import javax.swing.text.JTextComponent;
          import org.jdesktop.beansbinding.AutoBinding;
          import org.jdesktop.beansbinding.BeanProperty;
          import org.jdesktop.beansbinding.BindingGroup;
          import org.jdesktop.beansbinding.Bindings;
          import org.jdesktop.beansbinding.ELProperty;
          import org.jdesktop.jxlayer.JXLayer;
          import static org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.*;


          public class GuiComponentFactory {

          public static JXLayer<jTextComponent> createTextField(
                  BindingGroup bindingGroup, Object sourceObject,
                  String sourceProperty) {
              JTextField field 
          = new JTextField();
              AutoBinding binding 
          = Bindings.createAutoBinding(READ_WRITE,
                      sourceObject, ELProperty.create(
          "${" + sourceProperty + "}"),
                      field, BeanProperty.create(
          "text"));
              bindingGroup.addBinding(binding);
              bindingGroup.bind();
              
          return new JXLayer<jTextComponent>(field, new HibernateValidationUI(
                      sourceObject, sourceProperty));
          }
          }


          createTextField()方法主要將給定對(duì)象屬性的值與JTextField的text綁定,然后將JTextField納入到 JXLayer的管理之下。這樣一來(lái),一旦用戶在JTextField里面修改數(shù)據(jù),這個(gè)改變就會(huì)同步到該對(duì)象屬性上,然后就引發(fā)了前面描述的一系列邏 輯,最終改變的數(shù)據(jù)就會(huì)被Hiberante Validator加以驗(yàn)證。

          最后,我們可以編寫(xiě)一個(gè)Demo application來(lái)看看效果如何,代碼如下:

          package de.jingge.main;

          import de.jingge.domain.Country;
          import java.awt.BorderLayout;
          import java.awt.Dimension;
          import java.awt.Toolkit;
          import javax.swing.JFrame;
          import javax.swing.JLabel;
          import javax.swing.JPanel;
          import javax.swing.UIManager;
          import javax.swing.UnsupportedLookAndFeelException;
          import javax.swing.text.JTextComponent;
          import net.miginfocom.swing.MigLayout;
          import org.jdesktop.beansbinding.BindingGroup;
          import org.jdesktop.jxlayer.JXLayer;
          import static de.jingge.view.GuiComponentFactory.*;


          public class ValidationApplicaton {

          private BindingGroup bg;
          private Country country;
          private JXLayer<jTextComponent> codeField;
          private JXLayer<jTextComponent> nameField;

          /**
           * 
          @param args the command line arguments
           
          */
          public static void main(String[] args) {
              
          try {
                  UIManager.setLookAndFeel(
                          
          "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
              } 
          catch (UnsupportedLookAndFeelException ex) {
                  System.err.println(
                          
          "Nimbus L&F does not support. Default L&F will be used.");
              } 
          catch (ClassNotFoundException e) {
                  
          // TODO Auto-generated catch block
                  e.printStackTrace();
              } 
          catch (InstantiationException e) {
                  
          // TODO Auto-generated catch block
                  e.printStackTrace();
              } 
          catch (IllegalAccessException e) {
                  
          // TODO Auto-generated catch block
                  e.printStackTrace();
              }
              ValidationApplicaton app 
          = new ValidationApplicaton();
              JFrame frame 
          = new JFrame("Demo Validation Application");
              frame.setPreferredSize(
          new Dimension(360150));
              frame.getContentPane().add(app.buildPanel(), BorderLayout.CENTER);
              frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
              setCenter(frame);
              frame.setVisible(
          true);
              frame.pack();

          }

          private static void setCenter(JFrame frame) {
              Toolkit toolkit 
          = Toolkit.getDefaultToolkit();
              Dimension screenSize 
          = toolkit.getScreenSize();

              
          // Calculate the frame location
              int x = (screenSize.width - (int) frame.getPreferredSize().getWidth()) / 2;
              
          int y = (screenSize.height - (int) frame.getPreferredSize().getHeight()) / 2;

              
          // Set the new frame location
              frame.setLocation(x, y);
          }

          public ValidationApplicaton() {
              country 
          = new Country();
              bg 
          = new BindingGroup();
          }

          private JPanel buildPanel() {

              codeField 
          = createTextField(bg, country, Country.PROPERTYNAME_CODE);
              nameField 
          = createTextField(bg, country, Country.PROPERTYNAME_NAME);
              JPanel panel 
          = new JPanel(new MigLayout("",
                      
          "[50px, right]10[200px:250px:300px]""[center]"));
              panel.add(
          new JLabel("Code:"), "cell 0 0");
              panel.add(codeField, 
          "cell 1 0, w 200px:250px:300px");
              panel.add(
          new JLabel("Name:"), "cell 0 1");
              panel.add(nameField, 
          "cell 1 1, w 200px:250px:300px");
              
          return panel;
          }
          }


          這個(gè)類比較簡(jiǎn)單了,我簡(jiǎn)單解釋一下:

          在main()方法里面,我們創(chuàng)建了一個(gè)JFrame,然后放入一個(gè)JPanel

          setCenter()方法負(fù)責(zé)將窗口至于屏幕的正中間。

          在構(gòu)建器里面,我們創(chuàng)建了Country和BindingGroup的對(duì)象實(shí)例。

          在buildPanel()方法里面,我們使用MigLayout構(gòu)建了一個(gè)Panel,其中codeField和nameField對(duì)應(yīng)各自的對(duì)象屬性。更多關(guān)于MigLayout的信息看這里:http://www.miglayout.com/。這也是一個(gè)例子,大家可以看到使用MigLayout開(kāi)發(fā)Swing真的是非常方便。

          從這個(gè)Demo里面也可以看出,編寫(xiě)好pojo后,程序員只需要調(diào)用createTextField(bg, country, Country.PROPERTYNAME_CODE); 就可以創(chuàng)建一個(gè)支持?jǐn)?shù)據(jù)驗(yàn)證的JTextField,編碼量已經(jīng)可以說(shuō)是最大限度的降低了。

          運(yùn)行程序,你會(huì)看到:



          這個(gè)code和name的數(shù)據(jù)都不合法,用戶看到了error icon。

          將鼠標(biāo)移到Text field上,你會(huì)看到:



          填好合法數(shù)據(jù)后,Error icon就不見(jiàn)了:



          總結(jié):

          使用這個(gè)通用數(shù)據(jù)驗(yàn)證模塊有很多好處:

          1. 如果項(xiàng)目使用ORM,例如Hibernate,這個(gè)方案應(yīng)該是解決數(shù)據(jù)驗(yàn)證的最好方案之一。
          2. 對(duì)于普通的數(shù)據(jù)驗(yàn)證,例如非空,email,長(zhǎng)度等等,程序員根本不需要編碼,只要在POJO上使用相應(yīng)的Hibernate Validator annotation就可以了。
          3. 對(duì)于復(fù)雜的數(shù)據(jù)驗(yàn)證,Hibernate Validator提供了很好的擴(kuò)展機(jī)制,只要寫(xiě)一個(gè)annotation外加一個(gè)Validator就可以了。Swing應(yīng)用這邊仍然不需要編寫(xiě)任何代碼。

          綜上所述,可以看出通過(guò)使用這個(gè)通用數(shù)據(jù)驗(yàn)證模塊,開(kāi)發(fā)效率會(huì)提高很多。
          posted @ 2008-07-29 18:27 polygoncell 閱讀(2348) | 評(píng)論 (5)編輯 收藏

          主站蜘蛛池模板: 兴宁市| 大丰市| 张掖市| 土默特左旗| 济南市| 同德县| 洛川县| 环江| 新乐市| 玛沁县| 平昌县| 南溪县| 蚌埠市| 灯塔市| 孝义市| 永寿县| 神农架林区| 莲花县| 方山县| 会同县| 石棉县| 阿拉善右旗| 施甸县| 漠河县| 崇州市| 武功县| 桦甸市| 光泽县| 江陵县| 泾源县| 镇坪县| 微博| 五台县| 东丰县| 涿州市| 阳高县| 屏南县| 长治县| 河间市| 乌兰县| 南雄市|