月薪萬元的面試題-移動用戶資費統計系統業務邏輯筆記 --張孝祥

移動用戶資費統計系統

模擬實現簡易的移動用戶資費統計系統邏輯,具體需求如下:

  • 移動運營商A設置兩種類型的用戶:普通用戶及VIP用戶,現該運營商已有5個VIP用戶和15個普通用戶,共計20個用戶。
  • 普通用戶資費標準如下(不考慮漫遊和長途):

【基準資費】

  無月租費用。

通話費:0.6元/ 分鐘(僅撥打收費,接聽免費)

短信費:0.1元/ 條

數據費:5元/ M

          【優惠套餐】

            話費套餐:月功能費20元,最多可撥打60分鐘電話,超出時間按照0.5元/分鐘計費。

            短信套餐:月功能費10元,最多可發送200條短信,超出條數按照0.1元/條計費。

            數據套餐:月功能費20元,最多可獲50M的流量,超出流量按照3元/M 計費。

注:用戶可以選擇多種套餐,各功能(通話、短信、數據)計費時,如已選擇對應套餐,則按套餐標準計費;如未選擇對應套餐,則按對應的基準資費計費。

  • VIP用戶資費標準如下(不考慮漫遊和長途):

【基準資費】

  月租費用:按天收取,2元/ 天

通話費:0.4元/ 分鐘(僅撥打收費,接聽免費)

短信費:0.1元/ 條

數據費:3元/ M

          【優惠套餐】

          套餐1 :月基本費用100元(無月租費用),提供如下服務:

                   ①最多可撥打750分鐘電話,超出部分按照0.3元/ 分鐘計費。

                   ②最多可發送200條短信,超出條數按照0.1元/ 條計費。

                   ③最多可獲得100M數據流量,超出流量按照1元/ M計費。

            套餐2 :月基本費用200元(無月租費用),提供如下服務:

                   ①最多可撥打2000分鐘電話,超出部分按照0.2元/ 分鐘計費。

                   ②最多可發送500條短信,超出條數按照0.1元/ 條計費。

                   ③最多可獲得300M數據流量,超出流量按照0.5元/ M計費。

注:用戶最多隻能選擇一種套餐,如未選擇任何套餐,則按照基準資費計費。

  • 各類型用戶只能選擇提供給本類型用戶的套餐。
  • 新用戶入網。

            ①對於新入網的普通用戶,入網當月贈送如下服務:免費撥打60分鐘

                電話,免費發送200條短信,免費獲得50M流量。超出贈送的部分

                按照普通用戶基準資費進行計費。

            ②對於新入網的VIP用戶,入網當月贈送如下服務:免費撥打200分

                鍾電話,免費發送200條短信,免費獲得100M數據流量。超出贈送

                的部分按照VIP用戶基準資費進行計費(注意:需按入網天數計算月

                租費用)。

  • 每月爲用戶計算一次賬單,用戶訂製的套餐信息和賬單信息採用文件方式進行存儲(提示:可使用java中的Properties API進行文件操作)。
  • 用戶可自由訂製或退訂所屬用戶類型的套餐,並從下月起生效。
  • 異步隨機生成客戶操作如下:

①撥打電話,每次撥打時長爲1至10分鐘不等(隨機決定,以分鐘爲單位)。

②發送短信,每次發送條數爲1至10條不等(隨機決定)。

③上網獲取數據,每次獲取數據流量可爲50K,100K,200K,500K,1M(隨機決定)。

④訂製或退訂相應套餐。

⑤新用戶入網(隨機決定用戶類型)。

注:隨機生成客戶操作時間間隔自定,可設置。

  • 不要求實現GUI,只考慮系統邏輯實現,可通過Log方式展現程序運行結果。

解題思路

  • 數據分析與統一計算公式:

分析本系統的業務,可以看到普通用戶和VIP用戶在訂購套餐的方式以及月底計算賬單的公式上都有很大的不同:
(1)普通用戶沒有月租費和月基本費、而VIP用戶有月租費或月基本費。
(2)普通用戶是單獨訂購電話、短信和數據套餐,每項套餐單獨收取月功能費;VIP用戶不能單獨訂購電話、短信和數據套餐,VIP用戶訂購的套餐中同時包含了電話、短信和數據等服務功能。

我們可以爲普通用戶和VIP用戶分別設計出一個月底計算賬單的公式,但是,爲了簡化編程,我們也可以爲兩種不同用戶設計出一個統一的月底計算賬單的公式,這就好比“大象有尾巴,而螞蟻沒有尾巴,大象沒有觸角,而螞蟻則有觸角,能否用同一個累加所有器官的公式來計算螞蟻和大象的體重呢?當然可以,這時候只需要假設螞蟻也有尾巴,只是螞蟻的尾巴重量爲0,假設大象也有觸角,只是大象觸角的重量爲0,這樣,就可以用同一種累加所有器官的公式來計算螞蟻和大象的體重了。”我們可以採用如下方式來統一各類用戶在各種情況下的費用計算公式:

     (1) 月基本費或月租費:月基本費方式爲固定值,月租費方式爲當月總天數*每天費用或者(當月總天數-入網日+1)*每天費用,只有vip用戶才存在此項費用,但是爲了統一計算公式,可以認爲普通用戶也有此項費用,值爲0。

     (2) 電話收費時長:等於(電話時長-免費時長),計算後的值小於0則記爲0,免費時長又分爲兩類:新入網的免費和套餐中的免費,新入網的免費在用戶對象中處理,套餐中的免費封裝在套餐策略對象中處理。

     (3) 電話、短信、數據套餐月功能費:只有普通用戶定了套餐纔有此項費用,但是爲了統一計算公式,可以認爲沒定此功能套餐的普通用戶和vip用戶也有此項費用,值爲0。
     (4) 月電話費用=電話套餐月功能費+單位計費價格*電話收費時長
     (5) 按月電話費用的相同規則計算月短信費用和月數據費用
     (6) 月總計費用=月基本費或月租費 + 月電話費用 + 月短信費用 + 月數據費用

我們可以用如下一幅“月賬單費用的組成成分”圖來直觀地理解上面的計算公式:

  • 採用一種便於程序代碼讀取的格式在配置文件中存儲各項數據

剛開始猛然看到這麼多數據項,肯定會感覺紛繁雜亂,理不出頭緒來,但是,不管這些數據項有多麼多,歸結起來,不就是某個用戶要使用自己的某種數據嗎?只是不同的用戶有不同的數據罷了,每個用戶只需要關心和使用自己的數據、而不用關心其他用戶的數據就顯得簡單多了,因此可以寫一個類來專門讀取用戶的數據,在配置文件中存儲的各項數據應想辦法採用一種便於該類讀取的格式。

(1) 要存儲的數據項有:功能單價費用、功能套餐免費數量、功能套餐月費用、新入網免費數量、整體月基本費或月租費。
(2) 一些數據還要隨以下類型進行區分:用戶類型、套餐類型、功能類型。
(3) 在配置文件中通過用點(.)對數據項名稱進行分級的方式來區分各個數據項所屬的類別和功能,如下所示:
         common.normal.phone.price --> 表示普通用戶/非套餐/電話/單價
         common.pack1.phone.price --> 表示普通用戶/套餐/電話/單價
         common.pack1.phone.free --> 表示普通用戶/套餐/電話/免費數量
         common.pack1.phone.rent ?表示普通用戶/套餐/電話/套餐月功能費用

         vip.normal.phone.price --> 表示VIP用戶/非套餐/電話/單價
         vip.pack1.phone.price --> 表示VIP用戶/套餐1/電話/單價
         vip.pack2.phone.price --> 表示VIP用戶/套餐2/電話/單價

         common.new.phone.free --> 表示普通用戶/新開戶/電話/免費數量
         vip.new.phone.free --> 表示VIP用戶/新開戶/電話/免費數量
(4) 對於值爲0的數據項,不用在配置文件中存儲,這樣,當程序代碼從配置文件中沒有讀取到該數據項時,即認爲該值爲0。
(5) 對於vip用戶的整體月基本費或月租費,由於計費單位不一樣,採用配置文件方式存儲將增加程序的複雜度,所以,決定直接在程序代碼中硬編碼。

爲了便於程序編寫,在配置文件中要注意如下兩點:
    (1) 由於程序中要求每次傳輸的數據量都是10k的整數倍,因此可以將數據通信費的單價單位由M轉換成K表示,由於數據通信費的價格5元/M,轉換後則是0.5分/K,這樣程序中就涉及到小數處理了。由於在程序中處理小數是很繁瑣和容易出現誤差的事情,所以,最好還是想辦法先統一轉換成整數形式進行處理,由於數據傳輸量都是10k的整數倍,因此,想到將數據通信費的價格5元/M轉換成5分/10K。因此,在配置文件中將所有的價格和費用的計量單位由元轉換成分表示。
    (2) 後來在配置文件中填寫各項數據時,發現VIP用戶訂購套餐2時的數據費僅爲0.5元/M,這時候轉換的結果是0.5分/10k,又還是出現了小數,故想到把計費單位轉成5釐/10k,所以,在配置文件中最終還是應將所有的價格和費用的計量單位由元轉換成釐進行計費。
按照上面這些思想設計和編寫出來的配置文件conf.properties的完整內容如下:

 
  1. common.normal.phone.price=600 
  2. common.normal.message.price=100 
  3. common.normal.data.price=50 
  4.  
  5. common.pack1.phone.price=500 
  6. common.pack1.message.price=100 
  7. common.pack1.data.price=30 
  8. common.pack1.phone.rent=20000 
  9. common.pack1.message.rent=10000 
  10. common.pack1.data.rent=20000 
  11. common.pack1.phone.free=60 
  12. common.pack1.message.free=200 
  13. common.pack1.data.free=5000 
  14.  
  15. vip.normal.phone.price=400 
  16. vip.normal.message.price=100 
  17. vip.normal.data.price=30 
  18.  
  19. vip.pack1.phone.price=300 
  20. vip.pack1.message.price=100 
  21. vip.pack1.data.price=10 
  22. vip.pack1.phone.free=750 
  23. vip.pack1.message.free=200 
  24. vip.pack1.data.free=10000 
  25.  
  26. vip.pack2.phone.price=200 
  27. vip.pack2.message.price=100 
  28. vip.pack2.data.price=5 
  29. vip.pack2.phone.free=2000 
  30. vip.pack2.message.free=500 
  31. vip.pack2.data.free=30000 
  32.  
  33. common.new.phone.free=60 
  34. common.new.message.free=200 
  35. common.new.data.free=5000 
  36.  
  37. vip.new.phone.free=200 
  38. vip.new.message.free=200 
  39. vip.new.data.free=10000 


接着可編寫一個讀取上面的配置文件中的各項數據的ConfigManager類,該類根據用戶類型、套餐類型、業務功能類別來讀取相應功能套餐的單價、免費數量、功能費,以及新用戶免費數量。源碼如下:






 

  1. public class ConfigManager { 
  2.     private static Properties config = new Properties(); 
  3.     static
  4.         InputStream ips = ConfigManager.class.getResourceAsStream("/conf.properties"); 
  5.         try
  6.             config.load(ips); 
  7.         } catch (IOException e) { 
  8.             throw new ExceptionInInitializerError(e); 
  9.         } 
  10.     } 
  11.      
  12.     private static String makePrefix(int customerType,int packType,int businessType){ 
  13.         String customerTitle = customerType==0?"common":"vip"
  14.         String packTitle = packType==0?"normal":("pack"+packType); 
  15.         String businessTitle = businessType==0?"phone":businessType==1?"message":"data"
  16.         return customerTitle + "." + packTitle + "." + businessTitle; 
  17.     } 
  18.      
  19.     private static int getNumber(String key){ 
  20.         String value = config.getProperty(key); 
  21.         try
  22.             return Integer.parseInt(value); 
  23.         }catch(Exception e){ 
  24.             return 0; 
  25.         }        
  26.     } 
  27.      
  28.     public static int getPrice(int customerType,int packType,int businessType){ 
  29.         return getNumber(makePrefix(customerType,packType,businessType)+".price"); 
  30.     } 
  31.      
  32.     public static int getFree(int customerType,int packType,int businessType){ 
  33.         return getNumber(makePrefix(customerType,packType,businessType)+".free"); 
  34.     } 
  35.      
  36.     public static int getRent(int customerType,int packType,int businessType){ 
  37.         return getNumber(makePrefix(customerType,packType,businessType)+".rent"); 
  38.     } 
  39.      
  40.     public static int getNewCustomerFree(int customerType,int businessType){ 
  41.         String[] businesses = {"phone","message","data"}; 
  42.         return getNumber((customerType==0?"common":"vip")+".new." + businesses[businessType] + ".free"); 
  43.     } 



  • 面向對象的分析和設計:

在進行面向對象設計之前,必須具備和把握了一個重要的經驗:誰擁有數據,誰就對外提供操作這些數據的方法。大家可能會說,剛開始看到需求時,只知道某一個用戶要使用很多各種各樣的數據,而想不到要延伸出哪些對象,其實,只要你把所有數據和使用這些數據的方法歸納起來形成對象,自然就可以發掘出這些對象了。



(一)移動公司裏面有兩類客戶,移動公司裏的客戶可以打電話、發短信、數據通信,還可以訂購和退訂套餐;移動公司每月要爲其中所有客戶生成計費清單,還要模擬各種客戶的行爲。據此,可以分析出如下一些類和方法:



(1)
MobileCorporation類:simulationBusiness方法(模擬一個月的業務,內部隨機做500件事情和結算每個用戶的計費情況,隨機做的事情就是挑選一個用戶做其中任何一件事情:打電話/發短信/數據通信/定套餐/退訂套餐/新用戶入網)
(2)
Customer、CommonCustomer、VipCustomer等類:普通用戶和VIP用戶都可以打電話/發短信/數據通信/定套餐/退訂套餐/結算費用等方法。普通用戶和VIP用戶的區別在於定套餐、退訂套餐、結算費用的策略對象不同。



(二)憑藉積累的面向對象設計的經驗,可以把計算電話、短信、數據費用的功能各封裝成一個策略對象,這些策略對象內部根據當前的用戶類別、當月適用的套餐和計費的功能項目來計算費用。策略對象在計算費用時,要從Properties文件中讀取相應的數據值,爲此可以專門設計一個類來讀取配置文件,策略對象調用該類的方法。據此,可以分析出如下一些類和方法:



(1) ComputeStrategy類:包含computeMoney方法
(2)
ConfigManager類: 包含getPrice、getFree、getRent、getNewCustomerFree等方法。



(三)另外,應該有一個總的策略存儲對象來管理當前用戶的各個功能項目的策略對象以及VIP用戶的月租費或月基本費,所謂訂購某項功能套餐,就是選用哪個策略對象,所以,訂購某個功能套餐和退訂某個功能套餐的方法應分配給這個總的策略存儲對象。這個總的策略存儲對象內部既要存儲各個功能項的當前的套餐對應的策略對象、又要存儲下月訂購的套餐,還要在下個月時將訂購的套餐“設置”爲當前的套餐,這個“設置”不一定是真的變量賦值操作,可以是通過日期比較的方式來達到,這需要設計一個輔助類把某月和從該月開始訂購的功能套餐進行關聯存儲。據此,可以分析出如下一些類和方法:



(1)
PackStrategy類:包含orderRent、cancelRent、getValidRent、orderPack、cancelPack、getValidPack等方法
(2)OrderedStrategyHolder類:包含order、getValidStrategy等方法
(3)Rent類:包含computeRent方法



(四)類圖:



             


(五)計算賬單費用的各個對象的作用與關係




  • 類的編碼實現

(一)MobileCorporation類(代表移動公司)
1.在MobileCorporation類內部定義一個List集合的成員變量,用於存儲移動公司的所有用戶,這裏不需要區分普通用戶和VIP用戶,而是把他們抽象成用戶這個父類,這樣就可以採用統一的方式來調用他們各自的行爲,普通用戶和VIP用戶的行爲差異,在它們各自的方法內部體現,這正是充分利用了面向對象的抽象和多態特性。
2.在構造方法中,向用於存儲所有用戶的List集合中增加15名普通用戶和5名VIP用戶,每名用戶需要有自己的用戶名和入網日期。
3.在MobileCorporation類中定義一個simulationBusiness(Date
month)方法來模擬某個月的業務活動,首先清除用戶上月的記錄信息,然後再模擬發生如下一些事情:某個用戶打電話、某個用戶發短信、某個用戶傳輸數據、某個用戶訂購套餐、某個用戶退訂套餐、新用戶入網,最後再統計出各個用戶在本月的賬單信息。模擬發生的事情隨機發生,總共模擬發生500次,並讓打電話、發短信、傳輸數據等事情發生的概率爲訂購套餐、退訂套餐、新用戶入網的20倍。
4.將模擬隨機發生一件事情的過程封裝成一個私有方法randDoOneThing(Date
month),這個方法內部調用的某個用戶打電話、某個用戶發短信、某個用戶傳輸數據、某個用戶訂購套餐、某個用戶退訂套餐、新用戶入網等功能又各自封裝成一個私有方法。
源碼如下:






  1. package cn.itcast.mobilecounter.strategy; 
  2.  
  3. import java.util.ArrayList; 
  4. import java.util.Calendar; 
  5. import java.util.Date; 
  6. import java.util.Random; 
  7.  
  8. public class MobileCorporation { 
  9.     private ArrayList<Customer> customers = new ArrayList<Customer>(); 
  10.      
  11.     public MobileCorporation(){ 
  12.         for(int i=1;i<=15;i++){ 
  13.             customers.add( 
  14.                     new CommonCustomer(i+"號普通客戶",new Date(108,10,1)) 
  15.             ); 
  16.         } 
  17.          
  18.         for(int i=1;i<=5;i++){ 
  19.             customers.add(           
  20.                     new VIPCustomer(i+"號VIP客戶",new Date(108,10,1)) 
  21.             ); 
  22.         } 
  23.         System.out.println("程序創建了運營商已有的5個VIP用戶和15個普通用戶,並設置他們的入網日期爲2008年10月1日."); 
  24.     } 
  25.      
  26.     //模擬某個月的業務活動
     
  27.     public void simulationBusiness(Date month){ 
  28.         for(Customer customer : customers){ 
  29.             customer.monthBegin(); 
  30.         } 
  31.          
  32.         System.out.println("--------being simulating " + DateUtil.formatDateToMonth(month) + "--------------");  
  33.         for(int i=0;i<500/*30*/;i++){ 
  34.             randDoOneThing(month); 
  35.         } 
  36.          
  37.         System.out.println(DateUtil.formatDateToMonth(month)+"的計費彙總清單:"); 
  38.         //彙總所有人的賬單
     
  39.         for(int i=0;i<customers.size();i++){ 
  40.             customers.get(i).countMonthMoney(month); 
  41.         } 
  42.  
  43.     } 
  44.     /**
  45.      * 隨機調用下面的某一個方法
  46.      * */ 
  47.     private void randDoOneThing(Date month){ 
  48.         Calendar calendar = Calendar.getInstance(); 
  49.         calendar.setTime(month); 
  50.         calendar.add(Calendar.MONTH, 1); 
  51.         Date monthOfOrderPack = calendar.getTime();          
  52.         /*讓orderPack、cancelPack、joinNewCustomer的出現概率是其他操作的1/20。*/            
  53.         int rand = new Random().nextInt(63); 
  54.         if(rand>=0 && rand<20){ 
  55.             callPhone(); 
  56.         } 
  57.         else if(rand>=20 && rand<40){          
  58.             sendMessage(); 
  59.         } 
  60.         else if(rand>=40 && rand<60){          
  61.             transferData(); 
  62.         }else
  63.             switch(rand){ 
  64.                 case 60:         
  65.                     orderPack(monthOfOrderPack); 
  66.                     break
  67.                 case 61:                     
  68.                     cancelPack(monthOfOrderPack); 
  69.                     break
  70.                 case 62: 
  71.                     joinNewCustomer(month); 
  72.                     break;   
  73.             } 
  74.         } 
  75.     } 
  76.     /**
  77.      * 隨機選中一個用戶,讓其隨機撥打的電話時長爲1至10分鐘不等
  78.      */ 
  79.     private void callPhone(){ 
  80.         int rand = new Random().nextInt(customers.size()); 
  81.         Customer customer = customers.get(rand); 
  82.         int phoneTimes = new Random().nextInt(10) + 1; 
  83.         customer.callPhone(phoneTimes); 
  84.         System.out.println(customer + "打了" + phoneTimes + "分鐘電話"); 
  85.     } 
  86.      
  87.     /**
  88.      * 隨機選中一個用戶,讓其隨機發送的短信數目爲1至10條不等
  89.      */      
  90.     private void sendMessage(){ 
  91.         int rand = new Random().nextInt(customers.size()); 
  92.         Customer customer = customers.get(rand); 
  93.         int messageNumbers = new Random().nextInt(10) + 1; 
  94.         customer.sendMessage(messageNumbers); 
  95.         System.out.println(customer + "發了" + messageNumbers + "條短信"); 
  96.     } 
  97.     /**
  98.      * 隨機選中一個用戶,讓其隨機獲取的數據流量爲50K,100K,200K,500K,1M
  99.      */  
  100.     private void transferData(){ 
  101.         int rand = new Random().nextInt(customers.size()); 
  102.         Customer customer = customers.get(rand); 
  103.          
  104.         int [] dataSize = new int[]{50,100,200,500,1000}; 
  105.         int randSizeKey = new Random().nextInt(5); 
  106.         customer.transferData(dataSize[randSizeKey]); 
  107.         System.out.println(customer + "傳送了" + dataSize[randSizeKey] + "k數據");        
  108.     } 
  109.     /**
  110.      * 隨機選中一個用戶,爲其隨機訂購一款套餐
  111.      */  
  112.     private void orderPack(Date month){ 
  113.         int rand = new Random().nextInt(customers.size()); 
  114.         customers.get(rand).randomOrderPack(month); 
  115.     } 
  116.     /**
  117.      * 隨機選中一個用戶,並將其已經有的套餐取消
  118.      */ 
  119.     private void cancelPack(Date month){ 
  120.         int rand = new Random().nextInt(customers.size()); 
  121.         customers.get(rand).randomCancelPack(month); 
  122.     } 
  123.      
  124.     private void joinNewCustomer(Date month){ 
  125.         Calendar calendar = Calendar.getInstance(); 
  126.         calendar.setTime(month); 
  127.         int maxDay = calendar.getMaximum(Calendar.DAY_OF_MONTH); 
  128.         int randDay = new Random().nextInt(maxDay) + 1; 
  129.         /*下面的複製過程很重要,不能直接修改date,當然最好是對Calendar直接操作
  130.          * 這裏是爲了演示要注意clone而保留的。
  131.          */  
  132.         Date joinTime = (Date)month.clone(); 
  133.         joinTime.setDate(randDay); 
  134.          
  135.         int randType = (new Random().nextInt(10))%2; 
  136.         Customer customer = null
  137.         if(randType == 0){ 
  138.             int commonId = IdGenerator.getInstance().nextCommonId(); 
  139.             customer = new CommonCustomer(commonId+"號普通客戶",joinTime); 
  140.             customers.add(customer); 
  141.         }else
  142.             int vipId = IdGenerator.getInstance().nextVipId();   
  143.             customer = new VIPCustomer(vipId+"號VIP客戶",joinTime); 
  144.             customers.add(customer); 
  145.         } 
  146.         System.out.println(DateUtil.formatDateToDay(joinTime) + "新註冊了" + customer); 
  147.     } 
  148.      


(二)IdGenerator類(新用戶生成新Id編號)
1.由於系統中原來已經有了15名普通用戶和5名VIP用戶,所以,新產生的普通用戶的編號應從16開始,新產生的VIP用戶的編號應該從6開始。
2.把該類寫成單例,邏輯上更爲嚴謹,在前面的博文中有人批評我寫程序不必那麼嚴謹、湊合即可,那就請思考爲什麼我們一直做不出奔馳這樣精緻的汽車的原因,因爲湊合的想法在很多國人心中已經根深蒂固了,馬馬虎虎已經是我等中國人的優良傳統了,但我並不想繼承這一優良傳統。
源碼如下:






  1. public class IdGenerator { 
  2.     private IdGenerator(){ 
  3.          
  4.     } 
  5.     private static IdGenerator instance = new IdGenerator(); 
  6.     public static IdGenerator getInstance(){ 
  7.         return instance; 
  8.     } 
  9.      
  10.     private int lastCommonId = 15; 
  11.     private int lastVipId = 5; 
  12.      
  13.     public synchronized int nextCommonId(){ 
  14.         return ++lastCommonId; 
  15.     } 
  16.      
  17.     public synchronized int nextVipId(){ 
  18.         return ++lastVipId; 
  19.     } 


(三)MainClass類(整個程序的主運行類)
1.調用MobileCorporation類的simulationBusiness(Date
date)方法,總共模擬15個連續的月份。
源碼如下:






  1. public class MainClass { 
  2.  
  3.     public static void main(String[] args) { 
  4.         MobileCorporation corp = new MobileCorporation(); 
  5.         //設置要模擬的起始月份
     
  6.         Date month = new Date(109,0,1);  
  7.         System.out.println("程序開始模擬從2009年1月1日開始,連續15個月的運行情況.");       
  8.         //總共模擬15個連續的月份
     
  9.         for(int i=0;i<15/*3*/;i++){ 
  10.             corp.simulationBusiness(month); 
  11.  
  12.             Calendar calendar = Calendar.getInstance(); 
  13.             calendar.setTime(month); 
  14.             calendar.add(Calendar.MONTH, 1); 
  15.             month = calendar.getTime(); 
  16.         } 
  17.     } 


(四)Customer類(普通用戶與VIP用戶的抽象父類)
1.在Customer類中定義了用戶名稱和入網時間;還定義了4個List集合的成員變量,分別用於存儲用戶每次撥打的電話時間、每次發送的短信數量和每次傳輸的數據量,以及用戶的操作列表;此外,
Customer類中還定義了一個用於計算當月賬單費用的策略存儲對象,它用於存儲用戶選擇的各項功能套餐的策略對象。
2.在Customer類中定義打電話、發短信、傳輸數據的方法,這些方法分別爲對應的List集合增加記錄,並且都需要向操作列表集合中增加記錄。此外,由於普通用戶和VIP用戶定套餐和退訂套餐的方式有很大的區別,所以,這兩個功能在Customer類中對應的方法被定義成了抽象的,這兩個方法由普通用戶和VIP用戶根據自己的實際情況去具體實現。
3.在Customer類中定義了一個countMonthMoney方法來計算用戶當前月的賬單費用,首先從電話、短信、數據等集合中累加出各項功能的數量,對於新用戶,則減去新用戶要優惠的數量,然後再將這些數量分別傳遞給用戶訂購的相應的策略對象去計算費用,最後彙總和打印出總費用。
源碼如下:






  1. public abstract class Customer { 
  2.     protected String name; 
  3.     //用戶入網的時間
     
  4.     private Date joinTime; 
  5.     private int customerType = 0; 
  6.     protected List<ActionRecord> actionRecords = new ArrayList<ActionRecord>(); 
  7.      
  8.     //積累的結果只表示當月的所有通話記錄,不代表所有歷史記錄
     
  9.     private ArrayList phoneRecords = new ArrayList(); 
  10.     //積累的結果只表示當月的所有短信記錄,不代表所有歷史記錄
     
  11.     private ArrayList messageRecords = new ArrayList(); 
  12.     //積累的結果只表示當月的所有數據傳送記錄,不代表所有歷史記錄
     
  13.     private ArrayList dataRecords = new ArrayList(); 
  14.     protected PackStrategy packStrategy; 
  15.  
  16.     public void monthBegin(){ 
  17.         phoneRecords.clear(); 
  18.         messageRecords.clear(); 
  19.         dataRecords.clear(); 
  20.         actionRecords.clear(); 
  21.     } 
  22.      
  23.     public Customer(String name,Date joinTime,int customerType){ 
  24.         this.name = name; 
  25.         this.joinTime = joinTime; 
  26.         this.customerType = customerType; 
  27.     } 
  28.  
  29.     public String toString(){ 
  30.         return name; 
  31.     } 
  32.      
  33.     public void callPhone(int times){ 
  34.         phoneRecords.add(times); 
  35.         actionRecords.add(new ActionRecord("打電話",times + "分鐘")); 
  36.     } 
  37.     public void sendMessage(int numbers){ 
  38.         messageRecords.add(numbers); 
  39.         actionRecords.add(new ActionRecord("發短信",numbers + "條"));        
  40.     } 
  41.     public  void transferData(int size){ 
  42.         dataRecords.add(size); 
  43.         actionRecords.add(new ActionRecord("傳數據",size + "k"));           
  44.     } 
  45.  
  46.     /**
  47.      *
  48.      * @param currentMonth 正在被計費處理的當月的日期,
  49.      * 注意:日字段設置爲1,以便於方法內部計算是否是新用戶
  50.      */  
  51.     public int countMonthMoney(Date currentMonth){ 
  52.         boolean newcome = !joinTime.before(currentMonth);//joinTime.after(currentMonth);  
  53.          
  54.         int totalPhone = gatherRecords(phoneRecords); 
  55.         int totalMessage = gatherRecords(messageRecords); 
  56.         int totalData = gatherRecords(dataRecords); 
  57.          
  58.         int freePhone = 0; 
  59.         int freeMessage = 0; 
  60.         int freeData = 0; 
  61.         if(newcome){ 
  62.             freePhone = ConfigManager.getNewCustomerFree(customerType,0); 
  63.             freeMessage = ConfigManager.getNewCustomerFree(customerType,1); 
  64.             freeData = ConfigManager.getNewCustomerFree(customerType,2);     
  65.         } 
  66.          
  67.         int chargePhone = totalPhone>freePhone?totalPhone-freePhone:0; 
  68.         int chargeMessage = totalMessage>freeMessage?totalMessage-freeMessage:0; 
  69.         int chargeData = totalData>freeData?totalData-freeData:0;                 
  70.          
  71.         //彙總打印:包括姓名,入網日期,統計月份,通話清單,費用清單,總費用。
     
  72.         System.out.println(name + "," + DateUtil.formatDateToDay(joinTime) + "入網."); 
  73.         System.out.println(" 操作清單如下-----");  
  74.             for(int i=0;i<actionRecords.size();i++){ 
  75.                 System.out.println("  " + actionRecords.get(i)); 
  76.             } 
  77.         System.out.println(" 統計清單如下-----"); 
  78.         System.out.println("  通話:" + phoneRecords + "分鐘:短信" + messageRecords + "條:數據" + dataRecords + "k"); 
  79.         System.out.println("  通話累計:" + totalPhone + "分鐘,減除新開戶" + freePhone + "分鐘,實際收費" + chargePhone + "分鐘"); 
  80.         System.out.println("  短信累計:" + totalMessage + "條,減除新開戶" + freeMessage + "條,實際收費" + chargeMessage + "條"); 
  81.         System.out.println("  數據累計:" + totalData + "k,減除新開戶" + freeData + "k,實際收費" + chargeData + "k"); 
  82.  
  83.          
  84.          
  85.         ComputeStrategy phoneStrategy = packStrategy.getValidPhonePack(currentMonth); 
  86.         ComputeStrategy messageStrategy = packStrategy.getValidMessagePack(currentMonth); 
  87.         ComputeStrategy dataStrategy = packStrategy.getValidDataPack(currentMonth); 
  88.          
  89.         int sum = 0; 
  90.         //VIP用戶纔有月租金或基本費
     
  91.         Rent rent = packStrategy.getValidRent(currentMonth); 
  92.         if(rent != null){ 
  93.             int rentMoney = rent.coputeRent(joinTime,currentMonth); 
  94.             sum +=  rentMoney; 
  95.             System.out.println("  月租費或基本費:" + rentMoney + "釐錢");             
  96.         } 
  97.          
  98.         sum += phoneStrategy.computeMoney(chargePhone); 
  99.         sum += messageStrategy.computeMoney(chargeMessage); 
  100.         sum += dataStrategy.computeMoney(chargeData/10); 
  101.         System.out.println("  總計:" + sum/1000f + "元錢"); 
  102.         return sum; 
  103.     } 
  104.      
  105.     private int gatherRecords(ArrayList records){ 
  106.         int sum = 0; 
  107.         for(int i=0;i<records.size();i++){ 
  108.             sum +=  (Integer)(records.get(i)); 
  109.         } 
  110.         return sum; 
  111.     } 
  112.      
  113.     public abstract void randomCancelPack(Date month);   
  114.     public abstract void randomOrderPack(Date month);    
  115.  


(五)ActionRecord類(代表用戶的一項操作)
1.代表用戶的一項操作信息,包含操作名稱和數量。
源碼如下:






  1. public class ActionRecord { 
  2.     private String name; 
  3.     private String value; 
  4.     public ActionRecord(String name, String value) { 
  5.         this.name = name; 
  6.         this.value = value; 
  7.     } 
  8.      
  9.     public String toString(){ 
  10.         return name + ":" + value; 
  11.     } 


(六)CommonCustomer類(代表普通用戶)
1.在構造方法中爲策略存儲對象賦初始值,即用戶類型爲0、各項功能的套餐類別爲0,月租金或月基本費對象爲null。
2.實現訂購套餐和退訂套餐的功能,通過產生隨機數來決定是訂購和退訂哪一項功能的套餐,訂購某個功能套餐就是將相應的策略對象的類型設置爲1,退訂某個功能套餐就是將相應的策略對象的類型設置爲0。其中的代碼省略了一些瑣碎的細節,包括阻止用戶再次訂購已經訂購了的功能套餐、阻止用戶退訂根本沒有訂購過的套餐。


源碼如下:






  1. public class CommonCustomer extends Customer { 
  2.  
  3.     public CommonCustomer(String name,Date joinTime){ 
  4.         super(name,joinTime,0); 
  5.         packStrategy = new PackStrategy(0,0,null); 
  6.         actionRecords.add(new ActionRecord(name, DateUtil.formatDateToDay(joinTime)+"入網"));      
  7.     } 
  8.      
  9.     public void randomCancelPack(Date orderedMonth){ 
  10.         int rand = new Random().nextInt(3); 
  11.         switch(rand){ 
  12.         case 0: 
  13.             /*if(packStrategy.getOrderedPhonePack() == null || packStrategy.getOrderedPhonePack().getPackType() == 0){
  14.                 System.out.println(name + "試圖退訂根本就沒有訂過的電話套餐");         
  15.                 return;
  16.             }*/          
  17.             packStrategy.cancelPhonePack(orderedMonth); 
  18.             System.out.println(name + "退訂了" + "電話套餐" + "(從" + DateUtil.formatDateToMonth(orderedMonth) + "開始)");     
  19.             actionRecords.add(new ActionRecord("退訂電話套餐",""));    
  20.             break
  21.         case 1: 
  22.             /*if(packStrategy.getOrderedMessagePack() ==null || packStrategy.getOrderedMessagePack().getPackType() == 0){
  23.                 System.out.println(name + "試圖退訂根本就沒有訂過的短信套餐");         
  24.                 return;
  25.             }*/          
  26.             packStrategy.cancelMessagePack(orderedMonth);    
  27.             System.out.println(name + "退訂了" + "短信套餐" + "(從" + DateUtil.formatDateToMonth(orderedMonth) + "開始)");     
  28.             actionRecords.add(new ActionRecord("退訂短信套餐","")); 
  29.             break
  30.         case 2: 
  31. /*          if(packStrategy.getOrderedDataPack()==null || packStrategy.getOrderedDataPack().getPackType() == 0){
  32.                 System.out.println(name + "試圖退訂根本就沒有訂過的數據套餐");         
  33.                 return;
  34.             }   */       
  35.             packStrategy.cancelDataPack(orderedMonth);   
  36.             System.out.println(name + "退訂了" + "數據套餐" + "(從" + DateUtil.formatDateToMonth(orderedMonth) + "開始)");     
  37.             actionRecords.add(new ActionRecord("退訂數據套餐","")); 
  38.             break
  39.         } 
  40.     } 
  41.      
  42.     public void randomOrderPack(Date month){ 
  43.         int rand = new Random().nextInt(3); 
  44.         switch(rand){ 
  45.         case 0: 
  46.             //if(packStrategy.getOrderedPhonePack()==null || packStrategy.getOrderedPhonePack().getPackType() == 0){
     
  47.                 packStrategy.orderPhonePack(month,1); 
  48.                 System.out.println(name + "訂購了" + "電話套餐" + "(從" + DateUtil.formatDateToMonth(month) + "開始)");        
  49.                 actionRecords.add(new ActionRecord("定電話套餐",""));     
  50.             //}
     
  51.             break
  52.         case 1: 
  53.             //if(packStrategy.getOrderedMessagePack() == null || packStrategy.getOrderedMessagePack().getPackType() == 0){          
     
  54.                 packStrategy.orderMessagePack(month,1);  
  55.                 System.out.println(name + "訂購了" + "短信套餐" + "(從" + DateUtil.formatDateToMonth(month) + "開始)");        
  56.                 actionRecords.add(new ActionRecord("定短信套餐",""));         
  57.             //}
     
  58.             break
  59.         case 2: 
  60.             //if(packStrategy.getOrderedDataPack() == null || packStrategy.getOrderedDataPack().getPackType() == 0){            
     
  61.                 packStrategy.orderDataPack(month,1);     
  62.                 System.out.println(name + "訂購了" + "數據套餐" + "(從" + DateUtil.formatDateToMonth(month) + "開始)");    
  63.                 actionRecords.add(new ActionRecord("定數據套餐",""));     
  64.             //}
     
  65.             break
  66.         } 
  67.     }        


(七)VIPCustomer類(代表普通用戶)
1.在構造方法中爲策略存儲對象賦初始值,即用戶類型爲1、各項功能的套餐類別爲0,月租金或月基本費對象爲按天計算月租金。
2.實現訂購套餐和退訂套餐的功能,通過產生隨機數來決定是訂購和退訂套餐1或套餐2。訂購套餐1或套餐2就是將各個功能套餐相應的策略對象的類型都設置爲1或2,並設置相應的Rent對象;退訂套餐就是將各個功能套餐相應的策略對象的類型都設置爲0,並設置相應的Rent對象爲按天計算月租金。其中的代碼省略了一些瑣碎的細節,包括阻止用戶再次訂購已經訂購了的功能套餐、用戶退訂根本沒有訂購過的套餐。
源碼如下:






  1. public class VIPCustomer extends Customer { 
  2.  
  3.     public VIPCustomer(String name,Date joinTime){ 
  4.         super(name,joinTime,1); 
  5.         packStrategy = new PackStrategy(1,0,new Rent(200,RentUnit.DAY)); 
  6.         actionRecords.add(new ActionRecord(name, DateUtil.formatDateToDay(joinTime)+"入網")); 
  7.     } 
  8.  
  9.     public void orderPack1(Date month){ 
  10. /*      if(packStrategy.getOrderedPhonePack()==null || packStrategy.getOrderedPhonePack().getPackType() != 1)
  11.         {       */ 
  12.             packStrategy.orderRent(month,new Rent(10000,RentUnit.MONTH)); 
  13.             packStrategy.orderPhonePack(month,1); 
  14.             packStrategy.orderMessagePack(month,1); 
  15.             packStrategy.orderDataPack(month,1); 
  16.             System.out.println(name+ "訂購了套餐1" + "(從" + DateUtil.formatDateToMonth(month) + "開始)" );                  
  17.             actionRecords.add(new ActionRecord("訂購套餐1",""));     
  18.         //}
     
  19.     } 
  20.      
  21.     public void orderPack2(Date month){ 
  22. /*      if(packStrategy.getOrderedPhonePack() ==null || packStrategy.getOrderedPhonePack().getPackType() != 2)
  23.         {*/ 
  24.             packStrategy.orderRent(month,new Rent(20000,RentUnit.MONTH)); 
  25.             packStrategy.orderPhonePack(month,2); 
  26.             packStrategy.orderMessagePack(month,2); 
  27.             packStrategy.orderDataPack(month,2); 
  28.             System.out.println(name+ "訂購了套餐2" + "(從" + DateUtil.formatDateToMonth(month) + "開始)" );                  
  29.             actionRecords.add(new ActionRecord("訂購套餐2",""));     
  30.         //}
     
  31.     } 
  32.      
  33.     public void randomCancelPack(Date orderedMonth){ 
  34.         /*
  35.         if(packStrategy.getOrderedPhonePack() ==null ||
  36.             packStrategy.getOrderedPhonePack().getPackType() == 0){
  37.         System.out.println(name + "試圖退訂根本就沒有訂過的套餐"); return; }
  38.          */ 
  39.         packStrategy.orderRent(orderedMonth, new Rent(200, RentUnit.DAY)); 
  40.         packStrategy.orderPhonePack(orderedMonth, 0); 
  41.         packStrategy.orderMessagePack(orderedMonth, 0); 
  42.         packStrategy.orderDataPack(orderedMonth, 0); 
  43.         System.out.println(name + "退訂了" + "套餐" + "(從" 
  44.                 + DateUtil.formatDateToMonth(orderedMonth) + "開始)"); 
  45.         actionRecords.add(new ActionRecord("退定套餐", ""));                     
  46.     } 
  47.      
  48.     public void randomOrderPack(Date month){ 
  49.         //如果以前訂購過某套餐,現在仍然可以重新訂購該套餐  
  50.         int randType = (new Random().nextInt(10))%2; 
  51.         if(randType == 0){ 
  52.             orderPack1(month);       
  53.         }else if(randType == 1){ 
  54.             orderPack2(month);       
  55.         } 
  56.     }    


(八)DateUtil類(將日期格式化成字符串的輔助類)
源碼如下:






  1. public class DateUtil { 
  2.     public static String formatDateToMonth(Date date){ 
  3.         SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月"); 
  4.         String result = sdf.format(date); 
  5.         return result; 
  6.     } 
  7.      
  8.     public static String formatDateToDay(Date date){ 
  9.         SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日"); 
  10.         String result = sdf.format(date); 
  11.         return result; 
  12.     }    



(九)ComputeStrategy類(計算某項功能費用的策略類)
1.該類用於根據用戶類型、套餐類型、業務功能類別來計算當月的某項業務功能的費用,其內部採用同一種算法公式來處理各種情況,僅僅參數值不同,所以,該中定義了三個屬性來分別記住是哪種客戶的哪種套餐下的哪種服務項,這樣,要計算某種用戶類型的某種套餐的某種業務功能的當月費用,只需要將這三個屬性設置爲相應的值即可。
2.如果VIP客戶和common客戶的不同套餐的每種服務費的算法公式區別很大,那就需要爲每種客戶的每種套餐的每種服務方式各設計一個子類,並將它們的共同之處抽象成爲父類,譬如,定義PhoneComputeStrategy、MessageComputeStrategy、DataComputeStrategy等子類繼承ComputeStragtegy。由於本系統可以採用同一種算法公式來處理各種情況,僅僅參數值不同,所以不需要採用多個子類的方式來做。


源碼如下:






  1. public class ComputeStrategy { 
  2.     private int customerType; 
  3.     private int packType; 
  4.     private int businessType; 
  5.     private String businessName = ""
  6.      
  7.     public ComputeStrategy(int customerType, int packType, int businessType) { 
  8.         this.customerType = customerType; 
  9.         this.packType = packType; 
  10.         this.businessType = businessType; 
  11.         switch(businessType){ 
  12.             case 0:businessName = "電話";break
  13.             case 1:businessName = "短信";break
  14.             case 2:businessName = "數據";break;    
  15.         }        
  16.     } 
  17.      
  18.     public int computeMoney(int quantity){ 
  19.         int price = ConfigManager.getPrice(customerType, packType,businessType); 
  20.  
  21.         int freeQuantity = ConfigManager.getFree(customerType, packType,businessType); 
  22.         int chargeQuantity = quantity - freeQuantity; 
  23.         if(chargeQuantity < 0){ 
  24.             chargeQuantity = 0; 
  25.         } 
  26.         int phoneBaseMoney = ConfigManager.getRent(customerType, packType,businessType);     
  27.         System.out.print(businessName + "功能費:" + phoneBaseMoney + "釐錢,");                
  28.         int fee = price * chargeQuantity; 
  29.         System.out.println(businessName + "計價費:" + quantity + "-" + freeQuantity + "=" + chargeQuantity + ","  
  30.                     + chargeQuantity + "*" + price + "=" + fee +"釐錢"); 
  31.         return phoneBaseMoney + fee; 
  32.     } 



(十)Rent類(用於計算VIP用戶月租費或月基本費)
1.VIP用戶需要支付月租費或月基本費,如果是月租費,則還需要按照天來計算,這個計算相對來說也有一點複雜,爲此,可以將計算月租費或月基本費的功能封裝到一個Rent類中,這樣對整個計算賬單費用的程序模塊來說,就不需要關心具體的計算細節了,只需要調用Rent類的實例對象即可得到用戶要繳納的月租費或月基本費。


源碼如下:






  1. public class Rent { 
  2.     private int price; 
  3.     private RentUnit unit = RentUnit.MONTH; 
  4.      
  5.     public Rent(int price,RentUnit unit){ 
  6.         this.price = price; 
  7.         this.unit = unit; 
  8.     } 
  9.      
  10.     public int coputeRent(Date startTime,Date currentMonth){ 
  11.         //首先應該想到去找開源的日期運算類      
     
  12.         if(unit == RentUnit.DAY){ 
  13.             Calendar start = Calendar.getInstance(); 
  14.             start.setTime(startTime); 
  15.  
  16.             Calendar end = Calendar.getInstance(); 
  17.             end.setTime(currentMonth); 
  18.             //將日期設置爲當月的最後一天
     
  19.             end.set(Calendar.DAY_OF_MONTH, 1); 
  20.             end.add(Calendar.MONTH, 1); 
  21.             end.add(Calendar.DAY_OF_MONTH, -1); 
  22.              
  23.             int days =  end.get(Calendar.DAY_OF_MONTH) ; 
  24.             if(end.get(Calendar.MONTH) == start.get(Calendar.MONTH)){ 
  25.                 days -= start.get(Calendar.DAY_OF_MONTH) + 1;                
  26.             } 
  27.  
  28.             return  price*days; 
  29.         } 
  30.         else
  31.             return price; 
  32.         } 
  33.     }    


Rent類中所使用的枚舉類RentUnit非常簡單,其中僅僅是定義了MONTH和DAY兩個成員, 其源碼如下:






  1. public enum RentUnit { 
  2.     DAY,MONTH; 



(十一)PackStrategy類(用於存儲用戶的各項業務套餐策略)
這個類中的程序代碼的邏輯最爲複雜,建議讀者在閱讀此類的代碼之前,先思考清楚如下問題:
1.假如用戶在2月時訂購從3月以後的套餐,那麼在結算2月份的費用時,不能使用訂購的套餐計費方式,而應該使用原先的套餐計費方式;結算3月以後的費用時,就應採用訂購的套餐計費方式了。顯然,要處理某項業務功能的月賬單費用,在PackStrategy中類中要爲之存儲兩份ComputeStrategy對象,一份是原先或當前的套餐計費策略,另一份是訂購的從某個月開始的套餐計費策略。
2.
問:
假設2月份的時候訂3月以後的套餐, 2月底計算2月份的費用時用哪個套餐?3月底計算3月份的費用時用哪個套餐?
答:
2月底計算2月份的費用時用PackStrategy中的原先或當前的套餐計費策略,
3月底計算3月份的費用時用訂購的套餐計費策略。
總結:當計算某月的賬單費用時,是採用PackStrategy中的原先或當前的套餐計費策略,還是採用訂購的從某個月開始的套餐計費策略呢?這就要看是在計算哪個月的賬單費用了,如果“計算賬單費用的當前年月份”大於或等於“訂購的從某個月開始的套餐計費策略的起效年月份”,那麼就要用訂購的從某個月開始的套餐計費策略,否則,就採用PackStrategy中的原先或當前的套餐計費策略。
3.
問:
2月的初始計費策略爲套餐0, 2月份的時候訂3月以後的套餐1,3月份的時候又訂4月以後的套餐2,請問,3月底計算3月份的費用時用哪個套餐?
答:
3月底計算3月份的費用時應該用套餐1。在程序代碼中,在3月訂4月的套餐2時,如果直接對訂購變量進行賦值,它將沖掉
2月份訂購的3月份以後的套餐1信息,而PackStrategy中的原先或當前的計費策略爲套餐0,這樣在3月底計算3月份的費用時,將得到錯誤的結果。所以,應該先將2月份訂購的3月份以後的套餐1賦值給PackStrategy中的用於記錄原先或當前的套餐計費策略的變量,再用3月訂4月的套餐2對訂購變量賦值,這樣在3月底計算3月份的費用時,採用PackStrategy中的原先或當前的套餐計費策略,正好就是2月份訂購的3月份以後的套餐1。
4.
問:
2月的初始計費策略爲套餐0,
2月份的時候訂了3月以後的套餐1,隨後,還是在2月份的時候又重新訂購3月以後的套餐,改訂爲套餐2,請問,2月底計算2月份的費用時用哪個套餐?
答:還應該用2月的初始計費策略套餐0。也就是說,在2月份的時候又重新訂購3月以後的套餐2時,應該直接對訂購變量進行賦值,讓它沖掉
2月份訂購的3月份以後的套餐1信息,而不要將2月份訂購的3月份以後的套餐1賦值給PackStrategy中的用於記錄原先或當前的套餐計費策略的變量,這一點與上面的情況正好不同。
總結:當重新訂購新的套餐時,只有原來訂購的套餐對當前的年月份起作用時(也就是
“訂購變量中存儲的日期”小於或等於當前的年月份,即“新訂購的套餐的年月份”大於“訂購變量中存儲的日期”,因爲訂購的年月份總是當前月份基礎上加1),原來訂購的套餐信息纔要賦值給PackStrategy中的用於記錄原先或當前的套餐計費策略的變量,以便計算當前月的費用時使用,否則,如果原來訂購的套餐對當前的年月份是不起作用,那麼,則不能將原來訂購的套餐信息纔要賦值給PackStrategy中的用於記錄原先或當前的套餐計費策略的變量,
讓它直接被新訂購的策略沖掉即可。
5.根據面向對象的封裝特性,當我們訂購新的計費策略時,原來訂購的計費策略是否對當前月有效,即是否將原來訂購的套餐信息賦值給PackStrategy中的用於記錄原先或當前的套餐計費策略的變量,
這個功能不是在PackStrategy類中完成,而是在訂購記錄對象中完成,因爲原來訂購的套餐策略的訂購日期是存儲在訂購記錄類中的。同樣的道理,在計算某月的賬單費用時,是否採用訂購的從某個月開始的套餐計費策略,這一功能也是由訂購記錄類來提供。


源碼如下:






  1. public class PackStrategy { 
  2.     private int customerType; 
  3.     private int packType = 0; 
  4.     private ComputeStrategy currentStrategies[] = new ComputeStrategy[3]; 
  5.     private Rent rent;                                                               
  6.     private  OrderedStrategyHolder<ComputeStrategy> orderedStrategies[] = new OrderedStrategyHolder[]{ 
  7.               new OrderedStrategyHolder<ComputeStrategy>(),   
  8.               new OrderedStrategyHolder<ComputeStrategy>(), 
  9.               new OrderedStrategyHolder<ComputeStrategy>(), 
  10.     }; 
  11.     private OrderedStrategyHolder<Rent> orderedRent = new OrderedStrategyHolder<Rent>(); 
  12.      
  13.     public PackStrategy(int customerType,int packType,Rent rent){ 
  14.         this.customerType = customerType; 
  15.         this.packType = packType; 
  16.         this.rent = rent; 
  17.         for(int i=0;i<3;i++){ 
  18.             currentStrategies[i] = new ComputeStrategy(customerType, packType,i); 
  19.         } 
  20.          
  21.     } 
  22.      
  23.     public Rent getValidRent(Date month){ 
  24.         Rent validRent = orderedRent.getValidComputeStrategy(month);  
  25.         return validRent==null?rent:validRent; 
  26.      
  27.     }    
  28.      
  29.     public void orderRent(Date orderedMonth,Rent rent){ 
  30.         Rent oldRent = orderedRent.order(orderedMonth, rent); 
  31.         if(oldRent != null){ 
  32.             this.rent = oldRent; 
  33.         }    
  34.     } 
  35.  
  36.     public void cancelRent(Date orderedMonth,Rent rent){ 
  37.         orderRent(orderedMonth,null); 
  38.     }    
  39.      
  40.     public ComputeStrategy getValidPhonePack(Date month){ 
  41.         ComputeStrategy computeStrategy = (ComputeStrategy)orderedStrategies[0].getValidComputeStrategy(month);  
  42.         return computeStrategy==null?currentStrategies[0]:computeStrategy; 
  43.     } 
  44.  
  45.     public ComputeStrategy getValidMessagePack(Date month){ 
  46.         ComputeStrategy computeStrategy = (ComputeStrategy)orderedStrategies[1].getValidComputeStrategy(month);  
  47.         return computeStrategy==null?currentStrategies[1]:computeStrategy; 
  48.     } 
  49.  
  50.     public ComputeStrategy getValidDataPack(Date month){ 
  51.         ComputeStrategy computeStrategy = (ComputeStrategy)orderedStrategies[2].getValidComputeStrategy(month);  
  52.         return computeStrategy==null?currentStrategies[2]:computeStrategy; 
  53.     }    
  54.      
  55.     public void orderPhonePack(Date orderedMonth,int packType){ 
  56.         ComputeStrategy oldComputeStrategy = orderedStrategies[0].order(orderedMonth, new ComputeStrategy(customerType, packType, 0)); 
  57.         if(oldComputeStrategy != null){ 
  58.             this.currentStrategies[0] = oldComputeStrategy; 
  59.         } 
  60.  
  61.     } 
  62.  
  63.     public void orderMessagePack(Date orderedMonth, int packType){   
  64.         ComputeStrategy oldComputeStrategy = orderedStrategies[1].order(orderedMonth, new ComputeStrategy(customerType, packType, 1)); 
  65.         if(oldComputeStrategy != null){ 
  66.             this.currentStrategies[1] = oldComputeStrategy; 
  67.         } 
  68.     } 
  69.  
  70.     public void orderDataPack(Date orderedMonth, int packType){ 
  71.         ComputeStrategy oldComputeStrategy = orderedStrategies[2].order(orderedMonth, new ComputeStrategy(customerType, packType, 2)); 
  72.         if(oldComputeStrategy != null){ 
  73.             this.currentStrategies[2] = oldComputeStrategy; 
  74.         } 
  75.     } 
  76.  
  77.     public void cancelPhonePack(Date orderedMonth){ 
  78.         orderPhonePack(orderedMonth, 0); 
  79.     } 
  80.      
  81.     public void cancelMessagePack(Date orderedMonth){ 
  82.         orderMessagePack(orderedMonth, 0);   
  83.     } 
  84.      
  85.     public void cancelDataPack(Date orderedMonth){ 
  86.         orderDataPack(orderedMonth, 0); 
  87.     } 
  88.      
  89. /*  public PhoneComputeStrategy getOrderedPhonePack(){
  90.         PhoneComputeStrategy phoneHolderStrategy = (PhoneComputeStrategy)phoneOrderedStrategyHolder.getOrderedStrategy();
  91.         return phoneHolderStrategy;
  92.     }

  93.     public MessageComputeStrategy getOrderedMessagePack(){
  94.         MessageComputeStrategy messageHolderStrategy = (MessageComputeStrategy)messageOrderedStrategyHolder.getOrderedStrategy();
  95.         return messageHolderStrategy;
  96.     }
  97.    
  98.     public DataComputeStrategy getOrderedDataPack(){
  99.         DataComputeStrategy dataHolderStrategy = (DataComputeStrategy)dataOrderedStrategyHolder.getOrderedStrategy();
  100.         return dataHolderStrategy;
  101.     }   */ 



(十二)OrderedStrategyHolder類(用於存儲一項業務套餐訂購信息)
1.用戶訂購一項業務功能套餐時需要記錄生效的年月份和相應的業務功能套餐策略等兩個信息,爲此,需要設計一個訂購記錄類來管理這些信息,該類包含兩個字段:套餐生效的年月份和訂購的業務功能套餐策略。由於VIP用戶訂購套餐時,除了要記錄各項業務功能的計費策略外,還要記錄月租金或月基本費,月租金或月基本費的記錄方式與記錄一項業務功能的套餐方式完全一樣,只是記錄的參數類型不同,一個是ComputeStrategy策略對象,一個是Rent對象,所以,可以採用泛型技術來設計訂購記錄類,讓它可以記錄業務功能套餐策略的訂購,也可以記錄月租金或月基本費的訂購。
2.本訂購記錄類中提供了一個order()方法來更新其中記錄的套餐訂購信息。前面講解PackStrategy類的設計原理時已經提到過:因爲原來訂購的套餐策略的訂購日期是存儲在訂購記錄類中的,根據面向對象的封裝特性,當我們訂購新的計費策略時,原來訂購的計費策略是否對當前月有效,即是否將原來訂購的套餐信息賦值給PackStrategy中的用於記錄原先或當前的套餐計費策略的變量,
這個功能不是在PackStrategy類中完成,而是在訂購記錄對象中完成。要防止設置下個月的計費策略時把當前月的計費策略沖掉,這個邏輯是在order方法中完成的,order方法中要根據原來記錄的計費策略的生效年月份來判斷是否要把原來記錄的計費策略返回出去,如果原來記錄計費策略的生效年月份已經是下個月了,則不能將此計費策略返回出去,這時候返回null,表示訂購記錄中原來存儲的計費策略對當前月無效,PackStrategy類應使用其中的原先或當前的套餐計費策略的變量來計算當前月的費用,否則,
PackStrategy類應使用order方法返回的計費策略對象來計算當前月的費用。
3.
在計算某月的賬單費用時,是否採用訂購的從某個月開始的套餐計費策略,同樣還是根據面向對象的封裝特性,這一功能也應由訂購記錄類來提供。所以,本訂購記錄類中提供了一個getValidStrategy()方法來判斷其中記錄的套餐是否對某月有效,如果有效則返回該套餐策略對象,否則返回null。


源碼如下:






  1. public class OrderedStrategyHolder<T> { 
  2.     private Date orderedMonth; 
  3.     private T computeStrategy; 
  4.      
  5.     public T order(Date orderedMonth,T computeStrategy){ 
  6.         T oldComputeStrategy = null
  7.         if(this.orderedMonth!=null && this.orderedMonth.before(orderedMonth)){ 
  8.             oldComputeStrategy = this.computeStrategy; 
  9.         } 
  10.         this.orderedMonth = orderedMonth; 
  11.         this.computeStrategy = computeStrategy; 
  12.  
  13.         return oldComputeStrategy; 
  14.     } 
  15.      
  16.     public T getValidComputeStrategy(Date month){ 
  17.         if(this.orderedMonth!=null && !month.before(orderedMonth)){ 
  18.             return computeStrategy; 
  19.         } 
  20.         return null
  21.     } 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章