Java – 範例實作 – 設計類別程式 – 台灣身分證類別

實作一個台灣身分證字號類別, 目的就是提供一個設計一個類別的觀點. 前面的單元, 無論是Bike或是Scooter類別的設計, 怎麼看都是為了解說物件導向的觀念所設計的, 不夠具體也不夠完整. 而此處來進行的類別規劃設計中, 將不只是物件導向觀念的實現而已, 還要來看看每個設計上的細節.

 

開始設計規劃


  • 一個公開使用的類別
  • 有一個字串物件屬性, 表示身分證字號
  • 對於一個該類別物件而言, 身分證字號內容是無法修改的.
  • 任何一個該類別物件實體, 一定會有一組合理的身分證字號
  • 具有回傳性別的方法
  • 物件實體的產生方式
    • 任意產生合理的身分證字號物件實體
      • 完全隨機
      • 指定性別
      • 指定製發地區
      • 指定性別及指定製發地區
    • 字串參數指定產生合理的身分證字號物件實體
      • 如果字串參數不合理, 將無法產生出物件實體.
  • 提供一個不需物件實體, 就可以判斷指定字串內容是否為合理的身分證字號

 

 

開始動工


TWId.java

package tw.brad.java;

public class TWId {

}

 

設計static成員


因為static成員, 是在完全不考慮物件實體下就可以完成的部分, 此時設計最恰當. 當然, 日後已經有物件成員的部分, 那就要盡量以獨立於物件之外的觀點設計. 先來設計一個判斷指定字串內容, 傳回是否合理:

public static boolean isRightTWId(String id)

 

請先仔細了解一下相關的背景常識: wiki

其中一張很重要的表個內容:

  • 在轉換字元欄位中與字母欄位並不是按照字母順序排列
  • 而數字值是從10開始

因此, 先建立一個static屬性的字串物件, 重新將字母順序依照數字值(轉換字元)進行排列, 這樣就可以方便利用該字串物件之charAt() 方法來得知所在的位置, 再將其值加10, 就可以得到轉換字元的值.

 

規劃處理程序


  1. 先以正規表示法來判斷輸入的參數是否符合基本原則
  2. 取第一碼, 一定是[A-Z], 轉換出轉換字元
  3. 分別得到  n1 ~ n11

完成以下程式碼:

package tw.brad.java;

public class TWId {
  private static String letters = "ABCDEFGHJKLMNPQRSTUVXYWZIO";
  
  public static boolean isRightTWId(String id) {
    boolean isRight = false;
    if (id.matches("^[A-Z][12][0-9]{8}$")) {
      // 編碼檢查
      String s12 = id.substring(0, 1);	// "A123456789" => "A"
      int n12 = letters.indexOf(s12) + 10;
      int n1 = n12 / 10;
      int n2 = n12 % 10;
      int n3 = Integer.parseInt(id.substring(1, 2));
      int n4 = Integer.parseInt(id.substring(2, 3));
      int n5 = Integer.parseInt(id.substring(3, 4));
      int n6 = Integer.parseInt(id.substring(4, 5));
      int n7 = Integer.parseInt(id.substring(5, 6));
      int n8 = Integer.parseInt(id.substring(6, 7));
      int n9 = Integer.parseInt(id.substring(7, 8));
      int n10 = Integer.parseInt(id.substring(8, 9));
      int n11 = Integer.parseInt(id.substring(9, 10));
      int sum = n1*1+n2*9+n3*8+n4*7+n5*6+n6*5+n7*4+n8*3+n9*2+n10*1+n11*1;
      
      isRight = sum % 10 == 0;
      
    }
    return isRight;
  }
}

這已經是可以測試使用的類別了, 玩看看吧:

package tw.brad.java;

public class TestTWId {

  public static void main(String[] args) {
    System.out.println(TWId.isRightTWId("A123456789"));
    System.out.println(TWId.isRightTWId("B123456789"));
    System.out.println(TWId.isRightTWId("A323456789"));
  }

}

執行結果如下:

true
false
false

 

處理建構式囉


先從簡單的下手, 規格明確的下手, 就是任意產生出一個合理的身分證字號的物件實體. 何以說簡單呢? 是因為另一個不簡單, 既要以參數字串來建立, 又要求不合理的字串要無法建立出物件實體, 這是不簡單的一件事. 明明在 new 關鍵之後, 就已經產生出物件實體了, 而建構式的目的是要針對該物件進行初始化, 從建構式下手做到無法建立出物件實體這個任務不簡單. 只好等一下再來面對, 先處理建構式做得到的事.

共有四個隨機產生的建構式, 並帶有一個字串屬性, 是被封裝為 private.

package tw.brad.java;

public class TWId {
  private static String letters = "ABCDEFGHJKLMNPQRSTUVXYWZIO";
  private String id;
  
  public TWId() {
  }
  public TWId(boolean isMale) {
  }
  public TWId(int area) {
  }
  public TWId(boolean isMale, int area) {
  }
  
  public static boolean isRightTWId(String id) {
    boolean isRight = false;
    if (id.matches("^[A-Z][12][0-9]{8}$")) {
      // 編碼檢查
      String s12 = id.substring(0, 1);	// "A123456789" => "A"
      int n12 = letters.indexOf(s12) + 10;
      int n1 = n12 / 10;
      int n2 = n12 % 10;
      int n3 = Integer.parseInt(id.substring(1, 2));
      int n4 = Integer.parseInt(id.substring(2, 3));
      int n5 = Integer.parseInt(id.substring(3, 4));
      int n6 = Integer.parseInt(id.substring(4, 5));
      int n7 = Integer.parseInt(id.substring(5, 6));
      int n8 = Integer.parseInt(id.substring(6, 7));
      int n9 = Integer.parseInt(id.substring(7, 8));
      int n10 = Integer.parseInt(id.substring(8, 9));
      int n11 = Integer.parseInt(id.substring(9, 10));
      int sum = n1*1+n2*9+n3*8+n4*7+n5*6+n6*5+n7*4+n8*3+n9*2+n10*1+n11*1;
      
      isRight = sum % 10 == 0;
      
    }
    return isRight;
  }
}

要寫四個建構式, 工程浩大喔! 不是的, 並不需要寫四個, 因為這四個建構式的目的是一樣的, 邏輯是一樣的. 只是對外提供四種方式而已.

以 TWId(){…} 而言, 只需要將性別及地區兩個參數, 以亂數處理, 就可以呼叫 TWId(boolean, int){…}; 而 TWId(boolean), 則只需要將 boolean 值帶入, 並以亂數處理地區的 int 值, 就可以直接呼叫 TWId(boolean, int){…}, 依此類推:

package tw.brad.java;

public class TWId {
  private static String letters = "ABCDEFGHJKLMNPQRSTUVXYWZIO";
  private String id;
  
  public TWId() {
    this((int)(Math.random()*2)==0?true:false,
        (int)(Math.random()*26));
  }
  public TWId(boolean isMale) {
    this(isMale, (int)(Math.random()*26));
  }
  public TWId(int area) {
    this((int)(Math.random()*2)==0?true:false , area);
  }
  public TWId(boolean isMale, int area) {
    
  }
  
  public static boolean isRightTWId(String id) {
    boolean isRight = false;
    if (id.matches("^[A-Z][12][0-9]{8}$")) {
      // 編碼檢查
      String s12 = id.substring(0, 1);	// "A123456789" => "A"
      int n12 = letters.indexOf(s12) + 10;
      int n1 = n12 / 10;
      int n2 = n12 % 10;
      int n3 = Integer.parseInt(id.substring(1, 2));
      int n4 = Integer.parseInt(id.substring(2, 3));
      int n5 = Integer.parseInt(id.substring(3, 4));
      int n6 = Integer.parseInt(id.substring(4, 5));
      int n7 = Integer.parseInt(id.substring(5, 6));
      int n8 = Integer.parseInt(id.substring(6, 7));
      int n9 = Integer.parseInt(id.substring(7, 8));
      int n10 = Integer.parseInt(id.substring(8, 9));
      int n11 = Integer.parseInt(id.substring(9, 10));
      int sum = n1*1+n2*9+n3*8+n4*7+n5*6+n6*5+n7*4+n8*3+n9*2+n10*1+n11*1;
      
      isRight = sum % 10 == 0;
      
    }
    return isRight;
  }
}

 

都把責任全部推給 TWId(boolean, int){…} 的建構式來處理, 就是透過 this(boolean, int); 來處理, 這樣一來也增加了程式的維護性.

動手完成吧. 等一下依照參數先處理起來地區碼及性別碼, 就已經隨機產生兩碼了, 接下來的7碼, 全部由亂數決定, 而最後一碼事關驗證結果, 但是分析下來一定是 0 – 9 其中一個數字可以滿足, 只要呼叫前面寫過的 isRightTWId() 的 static 方法就可以輕鬆處理.

package tw.brad.java;

public class TWId {
  private static String letters = "ABCDEFGHJKLMNPQRSTUVXYWZIO";
  private String id;
  
  public TWId() {
    this((int)(Math.random()*2)==0?true:false,
        (int)(Math.random()*26));
  }
  public TWId(boolean isMale) {
    this(isMale, (int)(Math.random()*26));
  }
  public TWId(int area) {
    this((int)(Math.random()*2)==0?true:false , area);
  }
  public TWId(boolean isMale, int area) {
    String temp = letters.substring(area, area+1);
    temp += isMale?"1":"2";
    for (int i=0; i<7; i++) {
      temp += (int)(Math.random()*10);
    }
    
    for (int i=0; i<10; i++) {
      if (isRightTWId(temp + i)) {
        temp += i;
      }
    }
    id = temp;
  }
  
  public static boolean isRightTWId(String id) {
    boolean isRight = false;
    if (id.matches("^[A-Z][12][0-9]{8}$")) {
      // 編碼檢查
      String s12 = id.substring(0, 1);	// "A123456789" => "A"
      int n12 = letters.indexOf(s12) + 10;
      int n1 = n12 / 10;
      int n2 = n12 % 10;
      int n3 = Integer.parseInt(id.substring(1, 2));
      int n4 = Integer.parseInt(id.substring(2, 3));
      int n5 = Integer.parseInt(id.substring(3, 4));
      int n6 = Integer.parseInt(id.substring(4, 5));
      int n7 = Integer.parseInt(id.substring(5, 6));
      int n8 = Integer.parseInt(id.substring(6, 7));
      int n9 = Integer.parseInt(id.substring(7, 8));
      int n10 = Integer.parseInt(id.substring(8, 9));
      int n11 = Integer.parseInt(id.substring(9, 10));
      int sum = n1*1+n2*9+n3*8+n4*7+n5*6+n6*5+n7*4+n8*3+n9*2+n10*1+n11*1;
      
      isRight = sum % 10 == 0;
      
    }
    return isRight;
  }
}

 

再來面對剛剛所謂的不簡單的事情.

 

以 static 方法傳回本類別物件實體

事實上, 觀念只需要轉一下就可以, 透過一個 static 方法來傳回一個合理的 TWId 的物件實體, 如果傳遞的字串參數, 經過驗證之後被判定為不合理, 則可以傳回 null. 這就是方法與建構式之間其中的一個差異處. 方法與建構式都可以接受參數, 但是方法可以進行回傳(return), 而建構式卻只能進行一件事: 物件實體建構之後的第一件事, 初始化.

先寫一個封裝的建構式, 只提供類別內部呼叫, 不對外提供, 因為一旦對外提供, 則將會有機會產生出不合理的身分證字號的物件實體.

package tw.brad.java;

public class TWId {
  private static String letters = "ABCDEFGHJKLMNPQRSTUVXYWZIO";
  private String id;
  
  public TWId() {
    this((int)(Math.random()*2)==0?true:false,
        (int)(Math.random()*26));
  }
  public TWId(boolean isMale) {
    this(isMale, (int)(Math.random()*26));
  }
  public TWId(int area) {
    this((int)(Math.random()*2)==0?true:false , area);
  }
  public TWId(boolean isMale, int area) {
    String temp = letters.substring(area, area+1);
    temp += isMale?"1":"2";
    for (int i=0; i<7; i++) {
      temp += (int)(Math.random()*10);
    }
    
    for (int i=0; i<10; i++) {
      if (isRightTWId(temp + i)) {
        temp += i;
      }
    }
    id = temp;
  }

  private TWId(String id) {
    this.id = id;
  }

  public static boolean isRightTWId(String id) {
    boolean isRight = false;
    if (id.matches("^[A-Z][12][0-9]{8}$")) {
      // 編碼檢查
      String s12 = id.substring(0, 1);	// "A123456789" => "A"
      int n12 = letters.indexOf(s12) + 10;
      int n1 = n12 / 10;
      int n2 = n12 % 10;
      int n3 = Integer.parseInt(id.substring(1, 2));
      int n4 = Integer.parseInt(id.substring(2, 3));
      int n5 = Integer.parseInt(id.substring(3, 4));
      int n6 = Integer.parseInt(id.substring(4, 5));
      int n7 = Integer.parseInt(id.substring(5, 6));
      int n8 = Integer.parseInt(id.substring(6, 7));
      int n9 = Integer.parseInt(id.substring(7, 8));
      int n10 = Integer.parseInt(id.substring(8, 9));
      int n11 = Integer.parseInt(id.substring(9, 10));
      int sum = n1*1+n2*9+n3*8+n4*7+n5*6+n6*5+n7*4+n8*3+n9*2+n10*1+n11*1;
      
      isRight = sum % 10 == 0;
      
    }
    return isRight;
  }
}

再來處理 static 方法:

package tw.brad.java;

public class TWId {
  private static String letters = "ABCDEFGHJKLMNPQRSTUVXYWZIO";
  private String id;
  
  public TWId() {
    this((int)(Math.random()*2)==0?true:false,
        (int)(Math.random()*26));
  }
  public TWId(boolean isMale) {
    this(isMale, (int)(Math.random()*26));
  }
  public TWId(int area) {
    this((int)(Math.random()*2)==0?true:false , area);
  }
  public TWId(boolean isMale, int area) {
    String temp = letters.substring(area, area+1);
    temp += isMale?"1":"2";
    for (int i=0; i<7; i++) {
      temp += (int)(Math.random()*10);
    }
    
    for (int i=0; i<10; i++) {
      if (isRightTWId(temp + i)) {
        temp += i;
      }
    }
    id = temp;
  }
  private TWId(String id) {
    this.id = id;
  }
  public static TWId createTWId(String id) {
    if (isRightTWId(id)) {
      return new TWId(id); 
    }else {
      return null;
    }
  }
  public static boolean isRightTWId(String id) {
    boolean isRight = false;
    if (id.matches("^[A-Z][12][0-9]{8}$")) {
      // 編碼檢查
      String s12 = id.substring(0, 1);	// "A123456789" => "A"
      int n12 = letters.indexOf(s12) + 10;
      int n1 = n12 / 10;
      int n2 = n12 % 10;
      int n3 = Integer.parseInt(id.substring(1, 2));
      int n4 = Integer.parseInt(id.substring(2, 3));
      int n5 = Integer.parseInt(id.substring(3, 4));
      int n6 = Integer.parseInt(id.substring(4, 5));
      int n7 = Integer.parseInt(id.substring(5, 6));
      int n8 = Integer.parseInt(id.substring(6, 7));
      int n9 = Integer.parseInt(id.substring(7, 8));
      int n10 = Integer.parseInt(id.substring(8, 9));
      int n11 = Integer.parseInt(id.substring(9, 10));
      int sum = n1*1+n2*9+n3*8+n4*7+n5*6+n6*5+n7*4+n8*3+n9*2+n10*1+n11*1;
      
      isRight = sum % 10 == 0;
      
    }
    return isRight;
  }
}

 

隨便聊聊


寫這樣的範例一點也不偉大, 只是將前面學過的物件導向觀念實現一下.

學習程式語言, 如果只是學習語法, 那是不夠的. 開發觀念才是重點, 要會運用觀念來實作才是王道.

  • 對於物件實體而言, 建構式只會被執行一次, 在物件形成之後馬上執行.
  • 而物件屬性如果要被封裝, 要考慮到何時該進行初始化.
  • static 成員是提供了屬於該類別的成員, 是分類的的工具.

 

 

 

 

%d bloggers like this: