身份证号码解析、验证工具3

前两天的进度似乎有些慢,今天加快了一点, 不把每一步说的那么详细了.

==》地区信息的提取

继性别和生日之后,最后一个信息块,只是列出测试如下.


(本文版权属于© 2012 - 2013 予沁安

==》有效性

这是一个比较大的问题. 前面,我临时性的把不同地方的验证去掉了. 代码原作者也过来, 畅叙了他关于验证的看法. 他是对的, 这种完全验证的方式,根本上说是 DDD的设计思想。不过,想我所说,我知识临时性的去掉,保证测试的单元性。验证的功能,由验证的测试来驱动。而第二点考虑,我的验证打算放在构造器中,也就是说,如果,有任何错误的输入,连第一道门都进不来。

这里,测试和实现都很简单,看起来很多,只是一些罗列,不同的错误场景而已。

  1. [Subject("身份证,有效性")]  
  2.     public class when_create_social_id_with_valid_format {  
  3.         private Because of = () => subject = new SocialID("430103123456780020");  
  4.   
  5.         private It should_create_social_properly =  
  6.             () => subject.getCardNumber().ShouldEqual("430103123456780020");  
  7.         private static SocialID subject;  
  8.     }  
  9.     [Subject("身份证,有效性")]  
  10.     public class when_create_social_id_with_null_string {  
  11.         private Because of = () =>exception= Catch.Exception(()=>new SocialID(null));  
  12.   
  13.         private It should_not_allow_to_create =  
  14.             () =>exception.ShouldNotBeNull();  
  15.         private static SocialID subject;  
  16.         private static Exception exception;  
  17.     }  
  18.   
  19.     [Subject("身份证,有效性")]  
  20.     public class when_create_social_id_with_empty_string {  
  21.         private Because of = () => exception = Catch.Exception(() => new SocialID(string.Empty));  
  22.   
  23.         private It should_not_allow_to_create =  
  24.             () => exception.ShouldNotBeNull();  
  25.         private static SocialID subject;  
  26.         private static Exception exception;  
  27.     }  
  28.   
  29.     [Subject("身份证,有效性")]  
  30.     public class when_create_social_id_with_2_length_string {  
  31.         private Because of = () => exception = Catch.Exception(() => new SocialID("12"));  
  32.   
  33.         private It should_not_allow_to_create =  
  34.             () => exception.ShouldNotBeNull();  
  35.         private static SocialID subject;  
  36.         private static Exception exception;  
  37.     }  
  38.     [Subject("身份证,有效性")]  
  39.     public class when_create_social_id_with_20_length_string {  
  40.         private Because of = () => exception = Catch.Exception(() => new SocialID("12345678901234567890"));  
  41.   
  42.         private It should_not_allow_to_create =  
  43.             () => exception.ShouldNotBeNull();  
  44.         private static SocialID subject;  
  45.         private static Exception exception;  
  46.     }  
  47.     [Subject("身份证,有效性")]  
  48.     public class when_create_social_id_alphet_length_string {  
  49.         private Because of = () => exception = Catch.Exception(() => new SocialID("A23456789012345678"));  
  50.   
  51.         private It should_not_allow_to_create =  
  52.             () => exception.ShouldNotBeNull();  
  53.         private static SocialID subject;  
  54.         private static Exception exception;  
  55.     }  

实现

  1. public SocialID(String cardNumber)  
  2.         {  
  3.             if (string.IsNullOrEmpty(cardNumber))  
  4.                 throw new ApplicationException("Card Number is empty");  
  5.             if (cardNumber.Length != CARD_NUMBER_LENGTH)  
  6.                 throw new ApplicationException("Card Number Length is wrong.");  
  7.             if (!SOCIAL_NUMBER_PATTERN.IsMatch(cardNumber))  
  8.                 throw new ApplicationException("Card Number has wrong charactor(s).");            
  9.         }  

==》验证码

验证码是个特殊的有效性检查,较为复杂,我这里,把这部分逻辑代码提炼出来成为一个验证器。

测试极其简单,和实现几乎原封不动。

测试:

  1. public class when_verify_soical_number:Specification<Verifier>  
  2.     {  
  3.         Because of = () => { code = subject.verify("43010319791211453"); };  
  4.   
  5.         private It verify_code_should_match =  
  6.             () => code.ShouldEqual('4');  
  7.         private static char code;  
  8.     }  

实现

  1. namespace Skight.eLiteWeb.Domain.Specs.Properties  
  2. {  
  3.     public class Verifier  
  4.     {  
  5.         private static char[] VERIFY_CODE =  
  6.             {  
  7.                 '1''0''X''9''8''7',  
  8.                 '6''5''4''3''2'  
  9.             };  
  10.   
  11.         /** 
  12.          * 18位身份证中,各个数字的生成校验码时的权值 
  13.          */  
  14.   
  15.         private static int[] VERIFY_CODE_WEIGHT =  
  16.             {  
  17.                 7, 9, 10, 5, 8, 4, 2, 1,  
  18.                 6, 3, 7, 9, 10, 5, 8, 4, 2  
  19.             };  
  20.         private static int CARD_NUMBER_LENGTH = 18;  
  21.   
  22.         public char verify(string source)  
  23.         {  
  24.             /** 
  25.              * <li>校验码(第十八位数):<br/> 
  26.              * <ul> 
  27.              * <li>十七位数字本体码加权求和公式 S = Sum(Ai * Wi), i = 0...16 ,先对前17位数字的权求和; 
  28.              * Ai:表示第i位置上的身份证号码数字值 Wi:表示第i位置上的加权因子 Wi: 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 
  29.              * 2;</li> 
  30.              * <li>计算模 Y = mod(S, 11)</li> 
  31.              * <li>通过模得到对应的校验码 Y: 0 1 2 3 4 5 6 7 8 9 10 校验码: 1 0 X 9 8 7 6 5 4 3 2</li> 
  32.              * </ul> 
  33.              *  
  34.              * @param cardNumber 
  35.              * @return 
  36.              */  
  37.   
  38.             int sum = 0;  
  39.             for (int i = 0; i < CARD_NUMBER_LENGTH - 1; i++)  
  40.             {  
  41.                 char ch = source[i];  
  42.                 sum += ((int) (ch - '0'))*VERIFY_CODE_WEIGHT[i];  
  43.             }  
  44.             return VERIFY_CODE[sum%11];  
  45.         }  
  46.   
  47.     }  
  48. }  

这时候,身份证构造器的完整实现就变成了

  1. public SocialID(String cardNumber)  
  2.         {  
  3.             if (string.IsNullOrEmpty(cardNumber))  
  4.                 throw new ApplicationException("Card Number is empty");  
  5.             if (cardNumber.Length != CARD_NUMBER_LENGTH)  
  6.                 throw new ApplicationException("Card Number Length is wrong.");  
  7.             if (!SOCIAL_NUMBER_PATTERN.IsMatch(cardNumber))  
  8.                 throw new ApplicationException("Card Number has wrong charactor(s).");  
  9.   
  10.             if (cardNumber[CARD_NUMBER_LENGTH - 1] != verifier.verify(cardNumber))  
  11.                 throw new ApplicationException("Card Number verified code is not match.");  
  12.             this.cardNumber = cardNumber;  
  13.         }  

至此,代码已经很干净了。 是的,还有进一步的改进,如,3个元素(地区,生日,性别)的提炼应该移到构造器中,各个提取的功能就变成了,简单的数据读取。Social 的类型,不是class而是struct,因为这是典型的Value Object。 另外,我把15转18位的部分也去掉了,这可以看作一个Utilit,可以在外部做,不是核心功能。

你,是否能继续了?

最后,欣赏一下测试结果:


完整代码:

posted @ 2013-04-21 12:06  季相相  阅读(195)  评论(0编辑  收藏  举报