月薪万元的面试题-移动用户资费统计系统业务逻辑笔记 --张孝祥
2011-12-21 17:03
471 查看
移动用户资费统计系统
模拟实现简易的移动用户资费统计系统逻辑,具体需求如下:
移动运营商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) 月总计费用=月基本费或月租费 + 月电话费用 + 月短信费用 + 月数据费用
我们可以用如下一幅“月账单费用的组成成分”图来直观地理解上面的计算公式:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202003/14/e5cbff3c23e50c86d5284f9db136d332.gif)
采用一种便于程序代码读取的格式在配置文件中存储各项数据
刚开始猛然看到这么多数据项,肯定会感觉纷繁杂乱,理不出头绪来,但是,不管这些数据项有多么多,归结起来,不就是某个用户要使用自己的某种数据吗?只是不同的用户有不同的数据罢了,每个用户只需要关心和使用自己的数据、而不用关心其他用户的数据就显得简单多了,因此可以写一个类来专门读取用户的数据,在配置文件中存储的各项数据应想办法采用一种便于该类读取的格式。
(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的完整内容如下:
common.normal.phone.price=600
common.normal.message.price=100
common.normal.data.price=50
common.pack1.phone.price=500
common.pack1.message.price=100
common.pack1.data.price=30
common.pack1.phone.rent=20000
common.pack1.message.rent=10000
common.pack1.data.rent=20000
common.pack1.phone.free=60
common.pack1.message.free=200
common.pack1.data.free=5000
vip.normal.phone.price=400
vip.normal.message.price=100
vip.normal.data.price=30
vip.pack1.phone.price=300
vip.pack1.message.price=100
vip.pack1.data.price=10
vip.pack1.phone.free=750
vip.pack1.message.free=200
vip.pack1.data.free=10000
vip.pack2.phone.price=200
vip.pack2.message.price=100
vip.pack2.data.price=5
vip.pack2.phone.free=2000
vip.pack2.message.free=500
vip.pack2.data.free=30000
common.new.phone.free=60
common.new.message.free=200
common.new.data.free=5000
vip.new.phone.free=200
vip.new.message.free=200
vip.new.data.free=10000
面向对象的分析和设计:
在进行面向对象设计之前,必须具备和把握了一个重要的经验:谁拥有数据,谁就对外提供操作这些数据的方法。大家可能会说,刚开始看到需求时,只知道某一个用户要使用很多各种各样的数据,而想不到要延伸出哪些对象,其实,只要你把所有数据和使用这些数据的方法归纳起来形成对象,自然就可以发掘出这些对象了。
(一)移动公司里面有两类客户,移动公司里的客户可以打电话、发短信、数据通信,还可以订购和退订套餐;移动公司每月要为其中所有客户生成计费清单,还要模拟各种客户的行为。据此,可以分析出如下一些类和方法:
(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方法
(四)类图:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202003/14/12dff7679f0b5ed0721687f1ef65c1ff.gif)
(五)计算账单费用的各个对象的作用与关系
![](https://oscdn.geek-share.com/Uploads/Images/Content/202003/14/1cc7a3b70d5d165e5f764f745e9a5b58.gif)
类的编码实现
(一)MobileCorporation类(代表移动公司)
1.在MobileCorporation类内部定义一个List集合的成员变量,用于存储移动公司的所有用户,这里不需要区分普通用户和VIP用户,而是把他们抽象成用户这个父类,这样就可以采用统一的方式来调用他们各自的行为,普通用户和VIP用户的行为差异,在它们各自的方法内部体现,这正是充分利用了面向对象的抽象和多态特性。
2.在构造方法中,向用于存储所有用户的List集合中增加15名普通用户和5名VIP用户,每名用户需要有自己的用户名和入网日期。
3.在MobileCorporation类中定义一个simulationBusiness(Date
month)方法来模拟某个月的业务活动,首先清除用户上月的记录信息,然后再模拟发生如下一些事情:某个用户打电话、某个用户发短信、某个用户传输数据、某个用户订购套餐、某个用户退订套餐、新用户入网,最后再统计出各个用户在本月的账单信息。模拟发生的事情随机发生,总共模拟发生500次,并让打电话、发短信、传输数据等事情发生的概率为订购套餐、退订套餐、新用户入网的20倍。
4.将模拟随机发生一件事情的过程封装成一个私有方法randDoOneThing(Date
month),这个方法内部调用的某个用户打电话、某个用户发短信、某个用户传输数据、某个用户订购套餐、某个用户退订套餐、新用户入网等功能又各自封装成一个私有方法。
源码如下:
view
plaincopy to
clipboardprint?
package cn.itcast.mobilecounter.strategy;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;
public class MobileCorporation {
private ArrayList<Customer> customers =
new ArrayList<Customer>();
public MobileCorporation(){
for(int i=1;i<=15;i++){
customers.add(
new CommonCustomer(i+"号普通客户",new Date(108,10,1))
);
}
for(int i=1;i<=5;i++){
customers.add(
new VIPCustomer(i+"号VIP客户",new Date(108,10,1))
);
}
System.out.println("程序创建了运营商已有的5个VIP用户和15个普通用户,并设置他们的入网日期为2008年10月1日.");
}
//模拟某个月的业务活动
public
void simulationBusiness(Date month){
for(Customer customer : customers){
customer.monthBegin();
}
System.out.println("--------being simulating " + DateUtil.formatDateToMonth(month) +
"--------------");
for(int i=0;i<500/*30*/;i++){
randDoOneThing(month);
}
System.out.println(DateUtil.formatDateToMonth(month)+"的计费汇总清单:");
//汇总所有人的账单
for(int i=0;i<customers.size();i++){
customers.get(i).countMonthMoney(month);
}
}
/**
* 随机调用下面的某一个方法
* */
private
void randDoOneThing(Date month){
Calendar calendar = Calendar.getInstance();
calendar.setTime(month);
calendar.add(Calendar.MONTH, 1);
Date monthOfOrderPack = calendar.getTime();
/*让orderPack、cancelPack、joinNewCustomer的出现概率是其他操作的1/20。*/
int rand =
new Random().nextInt(63);
if(rand>=0 && rand<20){
callPhone();
}
else
if(rand>=20 && rand<40){
sendMessage();
}
else if(rand>=40 && rand<60){
transferData();
}else{
switch(rand){
case 60:
orderPack(monthOfOrderPack);
break;
case 61:
cancelPack(monthOfOrderPack);
break;
case 62:
joinNewCustomer(month);
break;
}
}
}
/**
* 随机选中一个用户,让其随机拨打的电话时长为1至10分钟不等
*/
private
void callPhone(){
int rand = new Random().nextInt(customers.size());
Customer customer = customers.get(rand);
int phoneTimes =
new Random().nextInt(10) + 1;
customer.callPhone(phoneTimes);
System.out.println(customer +
"打了" + phoneTimes + "分钟电话");
}
/**
* 随机选中一个用户,让其随机发送的短信数目为1至10条不等
*/
private void sendMessage(){
int rand =
new Random().nextInt(customers.size());
Customer customer = customers.get(rand);
int messageNumbers =
new Random().nextInt(10) + 1;
customer.sendMessage(messageNumbers);
System.out.println(customer +
"发了" + messageNumbers + "条短信");
}
/**
* 随机选中一个用户,让其随机获取的数据流量为50K,100K,200K,500K,1M
*/
private void transferData(){
int rand =
new Random().nextInt(customers.size());
Customer customer = customers.get(rand);
int [] dataSize =
new int[]{50,100,200,500,1000};
int randSizeKey =
new Random().nextInt(5);
customer.transferData(dataSize[randSizeKey]);
System.out.println(customer +
"传送了" + dataSize[randSizeKey] + "k数据");
}
/**
* 随机选中一个用户,为其随机订购一款套餐
*/
private void orderPack(Date month){
int rand =
new Random().nextInt(customers.size());
customers.get(rand).randomOrderPack(month);
}
/**
* 随机选中一个用户,并将其已经有的套餐取消
*/
private
void cancelPack(Date month){
int rand = new Random().nextInt(customers.size());
customers.get(rand).randomCancelPack(month);
}
private void joinNewCustomer(Date month){
Calendar calendar = Calendar.getInstance();
calendar.setTime(month);
int maxDay = calendar.getMaximum(Calendar.DAY_OF_MONTH);
int randDay =
new Random().nextInt(maxDay) + 1;
/*下面的复制过程很重要,不能直接修改date,当然最好是对Calendar直接操作
* 这里是为了演示要注意clone而保留的。
*/
Date joinTime = (Date)month.clone();
joinTime.setDate(randDay);
int randType = (new Random().nextInt(10))%2;
Customer customer = null;
if(randType == 0){
int commonId = IdGenerator.getInstance().nextCommonId();
customer = new CommonCustomer(commonId+"号普通客户",joinTime);
customers.add(customer);
}else{
int vipId = IdGenerator.getInstance().nextVipId();
customer = new VIPCustomer(vipId+"号VIP客户",joinTime);
customers.add(customer);
}
System.out.println(DateUtil.formatDateToDay(joinTime) +
"新注册了" + customer);
}
}
(三)MainClass类(整个程序的主运行类)
1.调用MobileCorporation类的simulationBusiness(Date
date)方法,总共模拟15个连续的月份。
源码如下:
view
plaincopy to
clipboardprint?
public class MainClass {
public
static void main(String[] args) {
MobileCorporation corp = new MobileCorporation();
//设置要模拟的起始月份
Date month = new Date(109,0,1);
System.out.println("程序开始模拟从2009年1月1日开始,连续15个月的运行情况.");
//总共模拟15个连续的月份
for(int i=0;i<15/*3*/;i++){
corp.simulationBusiness(month);
Calendar calendar = Calendar.getInstance();
calendar.setTime(month);
calendar.add(Calendar.MONTH, 1);
month = calendar.getTime();
}
}
}
(五)ActionRecord类(代表用户的一项操作)
1.代表用户的一项操作信息,包含操作名称和数量。
源码如下:
view
plaincopy to
clipboardprint?
public class ActionRecord {
private String name;
private String value;
public ActionRecord(String name, String value) {
this.name = name;
this.value = value;
}
public String toString(){
return name +
":" + value;
}
}
(七)VIPCustomer类(代表普通用户)
1.在构造方法中为策略存储对象赋初始值,即用户类型为1、各项功能的套餐类别为0,月租金或月基本费对象为按天计算月租金。
2.实现订购套餐和退订套餐的功能,通过产生随机数来决定是订购和退订套餐1或套餐2。订购套餐1或套餐2就是将各个功能套餐相应的策略对象的类型都设置为1或2,并设置相应的Rent对象;退订套餐就是将各个功能套餐相应的策略对象的类型都设置为0,并设置相应的Rent对象为按天计算月租金。其中的代码省略了一些琐碎的细节,包括阻止用户再次订购已经订购了的功能套餐、用户退订根本没有订购过的套餐。
源码如下:
view
plaincopy to
clipboardprint?
public class VIPCustomer extends Customer {
public VIPCustomer(String name,Date joinTime){
super(name,joinTime,1);
packStrategy = new PackStrategy(1,0,new Rent(200,RentUnit.DAY));
actionRecords.add(new ActionRecord(name, DateUtil.formatDateToDay(joinTime)+"入网"));
}
public
void orderPack1(Date month){
/* if(packStrategy.getOrderedPhonePack()==null || packStrategy.getOrderedPhonePack().getPackType() != 1)
{ */
packStrategy.orderRent(month,new Rent(10000,RentUnit.MONTH));
packStrategy.orderPhonePack(month,1);
packStrategy.orderMessagePack(month,1);
packStrategy.orderDataPack(month,1);
System.out.println(name+
"订购了套餐1" + "(从" + DateUtil.formatDateToMonth(month) +
"开始)" );
actionRecords.add(new ActionRecord("订购套餐1",""));
//}
}
public
void orderPack2(Date month){
/* if(packStrategy.getOrderedPhonePack() ==null || packStrategy.getOrderedPhonePack().getPackType() != 2)
{*/
packStrategy.orderRent(month,new Rent(20000,RentUnit.MONTH));
packStrategy.orderPhonePack(month,2);
packStrategy.orderMessagePack(month,2);
packStrategy.orderDataPack(month,2);
System.out.println(name+
"订购了套餐2" + "(从" + DateUtil.formatDateToMonth(month) +
"开始)" );
actionRecords.add(new ActionRecord("订购套餐2",""));
//}
}
public
void randomCancelPack(Date orderedMonth){
/*
if(packStrategy.getOrderedPhonePack() ==null ||
packStrategy.getOrderedPhonePack().getPackType() == 0){
System.out.println(name + "试图退订根本就没有订过的套餐"); return; }
*/
packStrategy.orderRent(orderedMonth, new Rent(200, RentUnit.DAY));
packStrategy.orderPhonePack(orderedMonth, 0);
packStrategy.orderMessagePack(orderedMonth, 0);
packStrategy.orderDataPack(orderedMonth, 0);
System.out.println(name +
"退订了" + "套餐" +
"(从"
+ DateUtil.formatDateToMonth(orderedMonth) + "开始)");
actionRecords.add(new ActionRecord("退定套餐",
""));
}
public void randomOrderPack(Date month){
//如果以前订购过某套餐,现在仍然可以重新订购该套餐
int randType = (new Random().nextInt(10))%2;
if(randType == 0){
orderPack1(month);
}else
if(randType == 1){
orderPack2(month);
}
}
}
(九)ComputeStrategy类(计算某项功能费用的策略类)
1.该类用于根据用户类型、套餐类型、业务功能类别来计算当月的某项业务功能的费用,其内部采用同一种算法公式来处理各种情况,仅仅参数值不同,所以,该中定义了三个属性来分别记住是哪种客户的哪种套餐下的哪种服务项,这样,要计算某种用户类型的某种套餐的某种业务功能的当月费用,只需要将这三个属性设置为相应的值即可。
2.如果VIP客户和common客户的不同套餐的每种服务费的算法公式区别很大,那就需要为每种客户的每种套餐的每种服务方式各设计一个子类,并将它们的共同之处抽象成为父类,譬如,定义PhoneComputeStrategy、MessageComputeStrategy、DataComputeStrategy等子类继承ComputeStragtegy。由于本系统可以采用同一种算法公式来处理各种情况,仅仅参数值不同,所以不需要采用多个子类的方式来做。
源码如下:
view
plaincopy to
clipboardprint?
public class ComputeStrategy {
private int customerType;
private
int packType;
private int businessType;
private String businessName =
"";
public ComputeStrategy(int customerType,
int packType, int businessType) {
this.customerType = customerType;
this.packType = packType;
this.businessType = businessType;
switch(businessType){
case 0:businessName =
"电话";break;
case 1:businessName =
"短信";break;
case 2:businessName =
"数据";break;
}
}
public int computeMoney(int quantity){
int price = ConfigManager.getPrice(customerType, packType,businessType);
int freeQuantity = ConfigManager.getFree(customerType, packType,businessType);
int chargeQuantity = quantity - freeQuantity;
if(chargeQuantity < 0){
chargeQuantity = 0;
}
int phoneBaseMoney = ConfigManager.getRent(customerType, packType,businessType);
System.out.print(businessName +
"功能费:" + phoneBaseMoney + "厘钱,");
int fee = price * chargeQuantity;
System.out.println(businessName +
"计价费:" + quantity + "-" + freeQuantity +
"=" + chargeQuantity + ","
+ chargeQuantity + "*" + price +
"=" + fee +"厘钱");
return phoneBaseMoney + fee;
}
}
Rent类中所使用的枚举类RentUnit非常简单,其中仅仅是定义了MONTH和DAY两个成员, 其源码如下:
view
plaincopy to
clipboardprint?
public enum RentUnit {
DAY,MONTH;
}
(十二)OrderedStrategyHolder类(用于存储一项业务套餐订购信息)
1.用户订购一项业务功能套餐时需要记录生效的年月份和相应的业务功能套餐策略等两个信息,为此,需要设计一个订购记录类来管理这些信息,该类包含两个字段:套餐生效的年月份和订购的业务功能套餐策略。由于VIP用户订购套餐时,除了要记录各项业务功能的计费策略外,还要记录月租金或月基本费,月租金或月基本费的记录方式与记录一项业务功能的套餐方式完全一样,只是记录的参数类型不同,一个是ComputeStrategy策略对象,一个是Rent对象,所以,可以采用泛型技术来设计订购记录类,让它可以记录业务功能套餐策略的订购,也可以记录月租金或月基本费的订购。
2.本订购记录类中提供了一个order()方法来更新其中记录的套餐订购信息。前面讲解PackStrategy类的设计原理时已经提到过:因为原来订购的套餐策略的订购日期是存储在订购记录类中的,根据面向对象的封装特性,当我们订购新的计费策略时,原来订购的计费策略是否对当前月有效,即是否将原来订购的套餐信息赋值给PackStrategy中的用于记录原先或当前的套餐计费策略的变量,
这个功能不是在PackStrategy类中完成,而是在订购记录对象中完成。要防止设置下个月的计费策略时把当前月的计费策略冲掉,这个逻辑是在order方法中完成的,order方法中要根据原来记录的计费策略的生效年月份来判断是否要把原来记录的计费策略返回出去,如果原来记录计费策略的生效年月份已经是下个月了,则不能将此计费策略返回出去,这时候返回null,表示订购记录中原来存储的计费策略对当前月无效,PackStrategy类应使用其中的原先或当前的套餐计费策略的变量来计算当前月的费用,否则,
PackStrategy类应使用order方法返回的计费策略对象来计算当前月的费用。
3.
在计算某月的账单费用时,是否采用订购的从某个月开始的套餐计费策略,同样还是根据面向对象的封装特性,这一功能也应由订购记录类来提供。所以,本订购记录类中提供了一个getValidStrategy()方法来判断其中记录的套餐是否对某月有效,如果有效则返回该套餐策略对象,否则返回null。
源码如下:
view
plaincopy to
clipboardprint?
public class OrderedStrategyHolder<T> {
private Date orderedMonth;
private T computeStrategy;
public T order(Date orderedMonth,T computeStrategy){
T oldComputeStrategy = null;
if(this.orderedMonth!=null &&
this.orderedMonth.before(orderedMonth)){
oldComputeStrategy = this.computeStrategy;
}
this.orderedMonth = orderedMonth;
this.computeStrategy = computeStrategy;
return oldComputeStrategy;
}
public T getValidComputeStrategy(Date month){
if(this.orderedMonth!=null && !month.before(orderedMonth)){
return computeStrategy;
}
return null;
}
}
public class OrderedStrategyHolder<T> {
private Date orderedMonth;
private T computeStrategy;
public T order(Date orderedMonth,T computeStrategy){
T oldComputeStrategy = null;
if(this.orderedMonth!=null && this.orderedMonth.before(orderedMonth)){
oldComputeStrategy = this.computeStrategy;
}
this.orderedMonth = orderedMonth;
this.computeStrategy = computeStrategy;
return oldComputeStrategy;
}
public T getValidComputeStrategy(Date month){
if(this.orderedMonth!=null && !month.before(orderedMonth)){
return computeStrategy;
}
return null;
}
}
模拟实现简易的移动用户资费统计系统逻辑,具体需求如下:
移动运营商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) 月总计费用=月基本费或月租费 + 月电话费用 + 月短信费用 + 月数据费用
我们可以用如下一幅“月账单费用的组成成分”图来直观地理解上面的计算公式:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202003/14/e5cbff3c23e50c86d5284f9db136d332.gif)
采用一种便于程序代码读取的格式在配置文件中存储各项数据
刚开始猛然看到这么多数据项,肯定会感觉纷繁杂乱,理不出头绪来,但是,不管这些数据项有多么多,归结起来,不就是某个用户要使用自己的某种数据吗?只是不同的用户有不同的数据罢了,每个用户只需要关心和使用自己的数据、而不用关心其他用户的数据就显得简单多了,因此可以写一个类来专门读取用户的数据,在配置文件中存储的各项数据应想办法采用一种便于该类读取的格式。
(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的完整内容如下:
common.normal.phone.price=600
common.normal.message.price=100
common.normal.data.price=50
common.pack1.phone.price=500
common.pack1.message.price=100
common.pack1.data.price=30
common.pack1.phone.rent=20000
common.pack1.message.rent=10000
common.pack1.data.rent=20000
common.pack1.phone.free=60
common.pack1.message.free=200
common.pack1.data.free=5000
vip.normal.phone.price=400
vip.normal.message.price=100
vip.normal.data.price=30
vip.pack1.phone.price=300
vip.pack1.message.price=100
vip.pack1.data.price=10
vip.pack1.phone.free=750
vip.pack1.message.free=200
vip.pack1.data.free=10000
vip.pack2.phone.price=200
vip.pack2.message.price=100
vip.pack2.data.price=5
vip.pack2.phone.free=2000
vip.pack2.message.free=500
vip.pack2.data.free=30000
common.new.phone.free=60
common.new.message.free=200
common.new.data.free=5000
vip.new.phone.free=200
vip.new.message.free=200
vip.new.data.free=10000
public class ConfigManager { private static Properties config = new Properties(); static{ InputStream ips = ConfigManager.class.getResourceAsStream("/conf.properties"); try { config.load(ips); } catch (IOException e) { throw new ExceptionInInitializerError(e); } } private static String makePrefix(int customerType,int packType,int businessType){ String customerTitle = customerType==0?"common":"vip"; String packTitle = packType==0?"normal":("pack"+packType); String businessTitle = businessType==0?"phone":businessType==1?"message":"data"; return customerTitle + "." + packTitle + "." + businessTitle; } private static int getNumber(String key){ String value = config.getProperty(key); try{ return Integer.parseInt(value); }catch(Exception e){ return 0; } } public static int getPrice(int customerType,int packType,int businessType){ return getNumber(makePrefix(customerType,packType,businessType)+".price"); } public static int getFree(int customerType,int packType,int businessType){ return getNumber(makePrefix(customerType,packType,businessType)+".free"); } public static int getRent(int customerType,int packType,int businessType){ return getNumber(makePrefix(customerType,packType,businessType)+".rent"); } public static int getNewCustomerFree(int customerType,int businessType){ String[] businesses = {"phone","message","data"}; return getNumber((customerType==0?"common":"vip")+".new." + businesses[businessType] + ".free"); } } public class ConfigManager { private static Properties config = new Properties(); static{ InputStream ips = ConfigManager.class.getResourceAsStream("/conf.properties"); try { config.load(ips); } catch (IOException e) { throw new ExceptionInInitializerError(e); } } private static String makePrefix(int customerType,int packType,int businessType){ String customerTitle = customerType==0?"common":"vip"; String packTitle = packType==0?"normal":("pack"+packType); String businessTitle = businessType==0?"phone":businessType==1?"message":"data"; return customerTitle + "." + packTitle + "." + businessTitle; } private static int getNumber(String key){ String value = config.getProperty(key); try{ return Integer.parseInt(value); }catch(Exception e){ return 0; } } public static int getPrice(int customerType,int packType,int businessType){ return getNumber(makePrefix(customerType,packType,businessType)+".price"); } public static int getFree(int customerType,int packType,int businessType){ return getNumber(makePrefix(customerType,packType,businessType)+".free"); } public static int getRent(int customerType,int packType,int businessType){ return getNumber(makePrefix(customerType,packType,businessType)+".rent"); } public static int getNewCustomerFree(int customerType,int businessType){ String[] businesses = {"phone","message","data"}; return getNumber((customerType==0?"common":"vip")+".new." + businesses[businessType] + ".free"); } }
面向对象的分析和设计:
在进行面向对象设计之前,必须具备和把握了一个重要的经验:谁拥有数据,谁就对外提供操作这些数据的方法。大家可能会说,刚开始看到需求时,只知道某一个用户要使用很多各种各样的数据,而想不到要延伸出哪些对象,其实,只要你把所有数据和使用这些数据的方法归纳起来形成对象,自然就可以发掘出这些对象了。
(一)移动公司里面有两类客户,移动公司里的客户可以打电话、发短信、数据通信,还可以订购和退订套餐;移动公司每月要为其中所有客户生成计费清单,还要模拟各种客户的行为。据此,可以分析出如下一些类和方法:
(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方法
(四)类图:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202003/14/12dff7679f0b5ed0721687f1ef65c1ff.gif)
(五)计算账单费用的各个对象的作用与关系
![](https://oscdn.geek-share.com/Uploads/Images/Content/202003/14/1cc7a3b70d5d165e5f764f745e9a5b58.gif)
类的编码实现
(一)MobileCorporation类(代表移动公司)
1.在MobileCorporation类内部定义一个List集合的成员变量,用于存储移动公司的所有用户,这里不需要区分普通用户和VIP用户,而是把他们抽象成用户这个父类,这样就可以采用统一的方式来调用他们各自的行为,普通用户和VIP用户的行为差异,在它们各自的方法内部体现,这正是充分利用了面向对象的抽象和多态特性。
2.在构造方法中,向用于存储所有用户的List集合中增加15名普通用户和5名VIP用户,每名用户需要有自己的用户名和入网日期。
3.在MobileCorporation类中定义一个simulationBusiness(Date
month)方法来模拟某个月的业务活动,首先清除用户上月的记录信息,然后再模拟发生如下一些事情:某个用户打电话、某个用户发短信、某个用户传输数据、某个用户订购套餐、某个用户退订套餐、新用户入网,最后再统计出各个用户在本月的账单信息。模拟发生的事情随机发生,总共模拟发生500次,并让打电话、发短信、传输数据等事情发生的概率为订购套餐、退订套餐、新用户入网的20倍。
4.将模拟随机发生一件事情的过程封装成一个私有方法randDoOneThing(Date
month),这个方法内部调用的某个用户打电话、某个用户发短信、某个用户传输数据、某个用户订购套餐、某个用户退订套餐、新用户入网等功能又各自封装成一个私有方法。
源码如下:
view
plaincopy to
clipboardprint?
package cn.itcast.mobilecounter.strategy;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;
public class MobileCorporation {
private ArrayList<Customer> customers =
new ArrayList<Customer>();
public MobileCorporation(){
for(int i=1;i<=15;i++){
customers.add(
new CommonCustomer(i+"号普通客户",new Date(108,10,1))
);
}
for(int i=1;i<=5;i++){
customers.add(
new VIPCustomer(i+"号VIP客户",new Date(108,10,1))
);
}
System.out.println("程序创建了运营商已有的5个VIP用户和15个普通用户,并设置他们的入网日期为2008年10月1日.");
}
//模拟某个月的业务活动
public
void simulationBusiness(Date month){
for(Customer customer : customers){
customer.monthBegin();
}
System.out.println("--------being simulating " + DateUtil.formatDateToMonth(month) +
"--------------");
for(int i=0;i<500/*30*/;i++){
randDoOneThing(month);
}
System.out.println(DateUtil.formatDateToMonth(month)+"的计费汇总清单:");
//汇总所有人的账单
for(int i=0;i<customers.size();i++){
customers.get(i).countMonthMoney(month);
}
}
/**
* 随机调用下面的某一个方法
* */
private
void randDoOneThing(Date month){
Calendar calendar = Calendar.getInstance();
calendar.setTime(month);
calendar.add(Calendar.MONTH, 1);
Date monthOfOrderPack = calendar.getTime();
/*让orderPack、cancelPack、joinNewCustomer的出现概率是其他操作的1/20。*/
int rand =
new Random().nextInt(63);
if(rand>=0 && rand<20){
callPhone();
}
else
if(rand>=20 && rand<40){
sendMessage();
}
else if(rand>=40 && rand<60){
transferData();
}else{
switch(rand){
case 60:
orderPack(monthOfOrderPack);
break;
case 61:
cancelPack(monthOfOrderPack);
break;
case 62:
joinNewCustomer(month);
break;
}
}
}
/**
* 随机选中一个用户,让其随机拨打的电话时长为1至10分钟不等
*/
private
void callPhone(){
int rand = new Random().nextInt(customers.size());
Customer customer = customers.get(rand);
int phoneTimes =
new Random().nextInt(10) + 1;
customer.callPhone(phoneTimes);
System.out.println(customer +
"打了" + phoneTimes + "分钟电话");
}
/**
* 随机选中一个用户,让其随机发送的短信数目为1至10条不等
*/
private void sendMessage(){
int rand =
new Random().nextInt(customers.size());
Customer customer = customers.get(rand);
int messageNumbers =
new Random().nextInt(10) + 1;
customer.sendMessage(messageNumbers);
System.out.println(customer +
"发了" + messageNumbers + "条短信");
}
/**
* 随机选中一个用户,让其随机获取的数据流量为50K,100K,200K,500K,1M
*/
private void transferData(){
int rand =
new Random().nextInt(customers.size());
Customer customer = customers.get(rand);
int [] dataSize =
new int[]{50,100,200,500,1000};
int randSizeKey =
new Random().nextInt(5);
customer.transferData(dataSize[randSizeKey]);
System.out.println(customer +
"传送了" + dataSize[randSizeKey] + "k数据");
}
/**
* 随机选中一个用户,为其随机订购一款套餐
*/
private void orderPack(Date month){
int rand =
new Random().nextInt(customers.size());
customers.get(rand).randomOrderPack(month);
}
/**
* 随机选中一个用户,并将其已经有的套餐取消
*/
private
void cancelPack(Date month){
int rand = new Random().nextInt(customers.size());
customers.get(rand).randomCancelPack(month);
}
private void joinNewCustomer(Date month){
Calendar calendar = Calendar.getInstance();
calendar.setTime(month);
int maxDay = calendar.getMaximum(Calendar.DAY_OF_MONTH);
int randDay =
new Random().nextInt(maxDay) + 1;
/*下面的复制过程很重要,不能直接修改date,当然最好是对Calendar直接操作
* 这里是为了演示要注意clone而保留的。
*/
Date joinTime = (Date)month.clone();
joinTime.setDate(randDay);
int randType = (new Random().nextInt(10))%2;
Customer customer = null;
if(randType == 0){
int commonId = IdGenerator.getInstance().nextCommonId();
customer = new CommonCustomer(commonId+"号普通客户",joinTime);
customers.add(customer);
}else{
int vipId = IdGenerator.getInstance().nextVipId();
customer = new VIPCustomer(vipId+"号VIP客户",joinTime);
customers.add(customer);
}
System.out.println(DateUtil.formatDateToDay(joinTime) +
"新注册了" + customer);
}
}
view plaincopy to clipboardprint? public class IdGenerator { private IdGenerator(){ } private static IdGenerator instance = new IdGenerator(); public static IdGenerator getInstance(){ return instance; } private int lastCommonId = 15; private int lastVipId = 5; public synchronized int nextCommonId(){ return ++lastCommonId; } public synchronized int nextVipId(){ return ++lastVipId; } } public class IdGenerator { private IdGenerator(){ } private static IdGenerator instance = new IdGenerator(); public static IdGenerator getInstance(){ return instance; } private int lastCommonId = 15; private int lastVipId = 5; public synchronized int nextCommonId(){ return ++lastCommonId; } public synchronized int nextVipId(){ return ++lastVipId; } }
(三)MainClass类(整个程序的主运行类)
1.调用MobileCorporation类的simulationBusiness(Date
date)方法,总共模拟15个连续的月份。
源码如下:
view
plaincopy to
clipboardprint?
public class MainClass {
public
static void main(String[] args) {
MobileCorporation corp = new MobileCorporation();
//设置要模拟的起始月份
Date month = new Date(109,0,1);
System.out.println("程序开始模拟从2009年1月1日开始,连续15个月的运行情况.");
//总共模拟15个连续的月份
for(int i=0;i<15/*3*/;i++){
corp.simulationBusiness(month);
Calendar calendar = Calendar.getInstance();
calendar.setTime(month);
calendar.add(Calendar.MONTH, 1);
month = calendar.getTime();
}
}
}
view plaincopy to clipboardprint? public abstract class Customer { protected String name; //用户入网的时间 private Date joinTime; private int customerType = 0; protected List<ActionRecord> actionRecords = new ArrayList<ActionRecord>(); //积累的结果只表示当月的所有通话记录,不代表所有历史记录 private ArrayList phoneRecords = new ArrayList(); //积累的结果只表示当月的所有短信记录,不代表所有历史记录 private ArrayList messageRecords = new ArrayList(); //积累的结果只表示当月的所有数据传送记录,不代表所有历史记录 private ArrayList dataRecords = new ArrayList(); protected PackStrategy packStrategy; public void monthBegin(){ phoneRecords.clear(); messageRecords.clear(); dataRecords.clear(); actionRecords.clear(); } public Customer(String name,Date joinTime,int customerType){ this.name = name; this.joinTime = joinTime; this.customerType = customerType; } public String toString(){ return name; } public void callPhone(int times){ phoneRecords.add(times); actionRecords.add(new ActionRecord("打电话",times + "分钟")); } public void sendMessage(int numbers){ messageRecords.add(numbers); actionRecords.add(new ActionRecord("发短信",numbers + "条")); } public void transferData(int size){ dataRecords.add(size); actionRecords.add(new ActionRecord("传数据",size + "k")); } /** * * @param currentMonth 正在被计费处理的当月的日期, * 注意:日字段设置为1,以便于方法内部计算是否是新用户 */ public int countMonthMoney(Date currentMonth){ boolean newcome = !joinTime.before(currentMonth);//joinTime.after(currentMonth); int totalPhone = gatherRecords(phoneRecords); int totalMessage = gatherRecords(messageRecords); int totalData = gatherRecords(dataRecords); int freePhone = 0; int freeMessage = 0; int freeData = 0; if(newcome){ freePhone = ConfigManager.getNewCustomerFree(customerType,0); freeMessage = ConfigManager.getNewCustomerFree(customerType,1); freeData = ConfigManager.getNewCustomerFree(customerType,2); } int chargePhone = totalPhone>freePhone?totalPhone-freePhone:0; int chargeMessage = totalMessage>freeMessage?totalMessage-freeMessage:0; int chargeData = totalData>freeData?totalData-freeData:0; //汇总打印:包括姓名,入网日期,统计月份,通话清单,费用清单,总费用。 System.out.println(name + "," + DateUtil.formatDateToDay(joinTime) + "入网."); System.out.println(" 操作清单如下-----"); for(int i=0;i<actionRecords.size();i++){ System.out.println(" " + actionRecords.get(i)); } System.out.println(" 统计清单如下-----"); System.out.println(" 通话:" + phoneRecords + "分钟:短信" + messageRecords + "条:数据" + dataRecords + "k"); System.out.println(" 通话累计:" + totalPhone + "分钟,减除新开户" + freePhone + "分钟,实际收费" + chargePhone + "分钟"); System.out.println(" 短信累计:" + totalMessage + "条,减除新开户" + freeMessage + "条,实际收费" + chargeMessage + "条"); System.out.println(" 数据累计:" + totalData + "k,减除新开户" + freeData + "k,实际收费" + chargeData + "k"); ComputeStrategy phoneStrategy = packStrategy.getValidPhonePack(currentMonth); ComputeStrategy messageStrategy = packStrategy.getValidMessagePack(currentMonth); ComputeStrategy dataStrategy = packStrategy.getValidDataPack(currentMonth); int sum = 0; //VIP用户才有月租金或基本费 Rent rent = packStrategy.getValidRent(currentMonth); if(rent != null){ int rentMoney = rent.coputeRent(joinTime,currentMonth); sum += rentMoney; System.out.println(" 月租费或基本费:" + rentMoney + "厘钱"); } sum += phoneStrategy.computeMoney(chargePhone); sum += messageStrategy.computeMoney(chargeMessage); sum += dataStrategy.computeMoney(chargeData/10); System.out.println(" 总计:" + sum/1000f + "元钱"); return sum; } private int gatherRecords(ArrayList records){ int sum = 0; for(int i=0;i<records.size();i++){ sum += (Integer)(records.get(i)); } return sum; } public abstract void randomCancelPack(Date month); public abstract void randomOrderPack(Date month); } public abstract class Customer { protected String name; //用户入网的时间 private Date joinTime; private int customerType = 0; protected List<ActionRecord> actionRecords = new ArrayList<ActionRecord>(); //积累的结果只表示当月的所有通话记录,不代表所有历史记录 private ArrayList phoneRecords = new ArrayList(); //积累的结果只表示当月的所有短信记录,不代表所有历史记录 private ArrayList messageRecords = new ArrayList(); //积累的结果只表示当月的所有数据传送记录,不代表所有历史记录 private ArrayList dataRecords = new ArrayList(); protected PackStrategy packStrategy; public void monthBegin(){ phoneRecords.clear(); messageRecords.clear(); dataRecords.clear(); actionRecords.clear(); } public Customer(String name,Date joinTime,int customerType){ this.name = name; this.joinTime = joinTime; this.customerType = customerType; } public String toString(){ return name; } public void callPhone(int times){ phoneRecords.add(times); actionRecords.add(new ActionRecord("打电话",times + "分钟")); } public void sendMessage(int numbers){ messageRecords.add(numbers); actionRecords.add(new ActionRecord("发短信",numbers + "条")); } public void transferData(int size){ dataRecords.add(size); actionRecords.add(new ActionRecord("传数据",size + "k")); } /** * * @param currentMonth 正在被计费处理的当月的日期, * 注意:日字段设置为1,以便于方法内部计算是否是新用户 */ public int countMonthMoney(Date currentMonth){ boolean newcome = !joinTime.before(currentMonth);//joinTime.after(currentMonth); int totalPhone = gatherRecords(phoneRecords); int totalMessage = gatherRecords(messageRecords); int totalData = gatherRecords(dataRecords); int freePhone = 0; int freeMessage = 0; int freeData = 0; if(newcome){ freePhone = ConfigManager.getNewCustomerFree(customerType,0); freeMessage = ConfigManager.getNewCustomerFree(customerType,1); freeData = ConfigManager.getNewCustomerFree(customerType,2); } int chargePhone = totalPhone>freePhone?totalPhone-freePhone:0; int chargeMessage = totalMessage>freeMessage?totalMessage-freeMessage:0; int chargeData = totalData>freeData?totalData-freeData:0; //汇总打印:包括姓名,入网日期,统计月份,通话清单,费用清单,总费用。 System.out.println(name + "," + DateUtil.formatDateToDay(joinTime) + "入网."); System.out.println(" 操作清单如下-----"); for(int i=0;i<actionRecords.size();i++){ System.out.println(" " + actionRecords.get(i)); } System.out.println(" 统计清单如下-----"); System.out.println(" 通话:" + phoneRecords + "分钟:短信" + messageRecords + "条:数据" + dataRecords + "k"); System.out.println(" 通话累计:" + totalPhone + "分钟,减除新开户" + freePhone + "分钟,实际收费" + chargePhone + "分钟"); System.out.println(" 短信累计:" + totalMessage + "条,减除新开户" + freeMessage + "条,实际收费" + chargeMessage + "条"); System.out.println(" 数据累计:" + totalData + "k,减除新开户" + freeData + "k,实际收费" + chargeData + "k"); ComputeStrategy phoneStrategy = packStrategy.getValidPhonePack(currentMonth); ComputeStrategy messageStrategy = packStrategy.getValidMessagePack(currentMonth); ComputeStrategy dataStrategy = packStrategy.getValidDataPack(currentMonth); int sum = 0; //VIP用户才有月租金或基本费 Rent rent = packStrategy.getValidRent(currentMonth); if(rent != null){ int rentMoney = rent.coputeRent(joinTime,currentMonth); sum += rentMoney; System.out.println(" 月租费或基本费:" + rentMoney + "厘钱"); } sum += phoneStrategy.computeMoney(chargePhone); sum += messageStrategy.computeMoney(chargeMessage); sum += dataStrategy.computeMoney(chargeData/10); System.out.println(" 总计:" + sum/1000f + "元钱"); return sum; } private int gatherRecords(ArrayList records){ int sum = 0; for(int i=0;i<records.size();i++){ sum += (Integer)(records.get(i)); } return sum; } public abstract void randomCancelPack(Date month); public abstract void randomOrderPack(Date month); }
(五)ActionRecord类(代表用户的一项操作)
1.代表用户的一项操作信息,包含操作名称和数量。
源码如下:
view
plaincopy to
clipboardprint?
public class ActionRecord {
private String name;
private String value;
public ActionRecord(String name, String value) {
this.name = name;
this.value = value;
}
public String toString(){
return name +
":" + value;
}
}
view plaincopy to clipboardprint? public class CommonCustomer extends Customer { public CommonCustomer(String name,Date joinTime){ super(name,joinTime,0); packStrategy = new PackStrategy(0,0,null); actionRecords.add(new ActionRecord(name, DateUtil.formatDateToDay(joinTime)+"入网")); } public void randomCancelPack(Date orderedMonth){ int rand = new Random().nextInt(3); switch(rand){ case 0: /*if(packStrategy.getOrderedPhonePack() == null || packStrategy.getOrderedPhonePack().getPackType() == 0){ System.out.println(name + "试图退订根本就没有订过的电话套餐"); return; }*/ packStrategy.cancelPhonePack(orderedMonth); System.out.println(name + "退订了" + "电话套餐" + "(从" + DateUtil.formatDateToMonth(orderedMonth) + "开始)"); actionRecords.add(new ActionRecord("退订电话套餐","")); break; case 1: /*if(packStrategy.getOrderedMessagePack() ==null || packStrategy.getOrderedMessagePack().getPackType() == 0){ System.out.println(name + "试图退订根本就没有订过的短信套餐"); return; }*/ packStrategy.cancelMessagePack(orderedMonth); System.out.println(name + "退订了" + "短信套餐" + "(从" + DateUtil.formatDateToMonth(orderedMonth) + "开始)"); actionRecords.add(new ActionRecord("退订短信套餐","")); break; case 2: /* if(packStrategy.getOrderedDataPack()==null || packStrategy.getOrderedDataPack().getPackType() == 0){ System.out.println(name + "试图退订根本就没有订过的数据套餐"); return; } */ packStrategy.cancelDataPack(orderedMonth); System.out.println(name + "退订了" + "数据套餐" + "(从" + DateUtil.formatDateToMonth(orderedMonth) + "开始)"); actionRecords.add(new ActionRecord("退订数据套餐","")); break; } } public void randomOrderPack(Date month){ int rand = new Random().nextInt(3); switch(rand){ case 0: //if(packStrategy.getOrderedPhonePack()==null || packStrategy.getOrderedPhonePack().getPackType() == 0){ packStrategy.orderPhonePack(month,1); System.out.println(name + "订购了" + "电话套餐" + "(从" + DateUtil.formatDateToMonth(month) + "开始)"); actionRecords.add(new ActionRecord("定电话套餐","")); //} break; case 1: //if(packStrategy.getOrderedMessagePack() == null || packStrategy.getOrderedMessagePack().getPackType() == 0){ packStrategy.orderMessagePack(month,1); System.out.println(name + "订购了" + "短信套餐" + "(从" + DateUtil.formatDateToMonth(month) + "开始)"); actionRecords.add(new ActionRecord("定短信套餐","")); //} break; case 2: //if(packStrategy.getOrderedDataPack() == null || packStrategy.getOrderedDataPack().getPackType() == 0){ packStrategy.orderDataPack(month,1); System.out.println(name + "订购了" + "数据套餐" + "(从" + DateUtil.formatDateToMonth(month) + "开始)"); actionRecords.add(new ActionRecord("定数据套餐","")); //} break; } } } public class CommonCustomer extends Customer { public CommonCustomer(String name,Date joinTime){ super(name,joinTime,0); packStrategy = new PackStrategy(0,0,null); actionRecords.add(new ActionRecord(name, DateUtil.formatDateToDay(joinTime)+"入网")); } public void randomCancelPack(Date orderedMonth){ int rand = new Random().nextInt(3); switch(rand){ case 0: /*if(packStrategy.getOrderedPhonePack() == null || packStrategy.getOrderedPhonePack().getPackType() == 0){ System.out.println(name + "试图退订根本就没有订过的电话套餐"); return; }*/ packStrategy.cancelPhonePack(orderedMonth); System.out.println(name + "退订了" + "电话套餐" + "(从" + DateUtil.formatDateToMonth(orderedMonth) + "开始)"); actionRecords.add(new ActionRecord("退订电话套餐","")); break; case 1: /*if(packStrategy.getOrderedMessagePack() ==null || packStrategy.getOrderedMessagePack().getPackType() == 0){ System.out.println(name + "试图退订根本就没有订过的短信套餐"); return; }*/ packStrategy.cancelMessagePack(orderedMonth); System.out.println(name + "退订了" + "短信套餐" + "(从" + DateUtil.formatDateToMonth(orderedMonth) + "开始)"); actionRecords.add(new ActionRecord("退订短信套餐","")); break; case 2: /* if(packStrategy.getOrderedDataPack()==null || packStrategy.getOrderedDataPack().getPackType() == 0){ System.out.println(name + "试图退订根本就没有订过的数据套餐"); return; } */ packStrategy.cancelDataPack(orderedMonth); System.out.println(name + "退订了" + "数据套餐" + "(从" + DateUtil.formatDateToMonth(orderedMonth) + "开始)"); actionRecords.add(new ActionRecord("退订数据套餐","")); break; } } public void randomOrderPack(Date month){ int rand = new Random().nextInt(3); switch(rand){ case 0: //if(packStrategy.getOrderedPhonePack()==null || packStrategy.getOrderedPhonePack().getPackType() == 0){ packStrategy.orderPhonePack(month,1); System.out.println(name + "订购了" + "电话套餐" + "(从" + DateUtil.formatDateToMonth(month) + "开始)"); actionRecords.add(new ActionRecord("定电话套餐","")); //} break; case 1: //if(packStrategy.getOrderedMessagePack() == null || packStrategy.getOrderedMessagePack().getPackType() == 0){ packStrategy.orderMessagePack(month,1); System.out.println(name + "订购了" + "短信套餐" + "(从" + DateUtil.formatDateToMonth(month) + "开始)"); actionRecords.add(new ActionRecord("定短信套餐","")); //} break; case 2: //if(packStrategy.getOrderedDataPack() == null || packStrategy.getOrderedDataPack().getPackType() == 0){ packStrategy.orderDataPack(month,1); System.out.println(name + "订购了" + "数据套餐" + "(从" + DateUtil.formatDateToMonth(month) + "开始)"); actionRecords.add(new ActionRecord("定数据套餐","")); //} break; } } }
(七)VIPCustomer类(代表普通用户)
1.在构造方法中为策略存储对象赋初始值,即用户类型为1、各项功能的套餐类别为0,月租金或月基本费对象为按天计算月租金。
2.实现订购套餐和退订套餐的功能,通过产生随机数来决定是订购和退订套餐1或套餐2。订购套餐1或套餐2就是将各个功能套餐相应的策略对象的类型都设置为1或2,并设置相应的Rent对象;退订套餐就是将各个功能套餐相应的策略对象的类型都设置为0,并设置相应的Rent对象为按天计算月租金。其中的代码省略了一些琐碎的细节,包括阻止用户再次订购已经订购了的功能套餐、用户退订根本没有订购过的套餐。
源码如下:
view
plaincopy to
clipboardprint?
public class VIPCustomer extends Customer {
public VIPCustomer(String name,Date joinTime){
super(name,joinTime,1);
packStrategy = new PackStrategy(1,0,new Rent(200,RentUnit.DAY));
actionRecords.add(new ActionRecord(name, DateUtil.formatDateToDay(joinTime)+"入网"));
}
public
void orderPack1(Date month){
/* if(packStrategy.getOrderedPhonePack()==null || packStrategy.getOrderedPhonePack().getPackType() != 1)
{ */
packStrategy.orderRent(month,new Rent(10000,RentUnit.MONTH));
packStrategy.orderPhonePack(month,1);
packStrategy.orderMessagePack(month,1);
packStrategy.orderDataPack(month,1);
System.out.println(name+
"订购了套餐1" + "(从" + DateUtil.formatDateToMonth(month) +
"开始)" );
actionRecords.add(new ActionRecord("订购套餐1",""));
//}
}
public
void orderPack2(Date month){
/* if(packStrategy.getOrderedPhonePack() ==null || packStrategy.getOrderedPhonePack().getPackType() != 2)
{*/
packStrategy.orderRent(month,new Rent(20000,RentUnit.MONTH));
packStrategy.orderPhonePack(month,2);
packStrategy.orderMessagePack(month,2);
packStrategy.orderDataPack(month,2);
System.out.println(name+
"订购了套餐2" + "(从" + DateUtil.formatDateToMonth(month) +
"开始)" );
actionRecords.add(new ActionRecord("订购套餐2",""));
//}
}
public
void randomCancelPack(Date orderedMonth){
/*
if(packStrategy.getOrderedPhonePack() ==null ||
packStrategy.getOrderedPhonePack().getPackType() == 0){
System.out.println(name + "试图退订根本就没有订过的套餐"); return; }
*/
packStrategy.orderRent(orderedMonth, new Rent(200, RentUnit.DAY));
packStrategy.orderPhonePack(orderedMonth, 0);
packStrategy.orderMessagePack(orderedMonth, 0);
packStrategy.orderDataPack(orderedMonth, 0);
System.out.println(name +
"退订了" + "套餐" +
"(从"
+ DateUtil.formatDateToMonth(orderedMonth) + "开始)");
actionRecords.add(new ActionRecord("退定套餐",
""));
}
public void randomOrderPack(Date month){
//如果以前订购过某套餐,现在仍然可以重新订购该套餐
int randType = (new Random().nextInt(10))%2;
if(randType == 0){
orderPack1(month);
}else
if(randType == 1){
orderPack2(month);
}
}
}
view plaincopy to clipboardprint? public class DateUtil { public static String formatDateToMonth(Date date){ SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月"); String result = sdf.format(date); return result; } public static String formatDateToDay(Date date){ SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日"); String result = sdf.format(date); return result; } } public class DateUtil { public static String formatDateToMonth(Date date){ SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月"); String result = sdf.format(date); return result; } public static String formatDateToDay(Date date){ SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日"); String result = sdf.format(date); return result; } }
(九)ComputeStrategy类(计算某项功能费用的策略类)
1.该类用于根据用户类型、套餐类型、业务功能类别来计算当月的某项业务功能的费用,其内部采用同一种算法公式来处理各种情况,仅仅参数值不同,所以,该中定义了三个属性来分别记住是哪种客户的哪种套餐下的哪种服务项,这样,要计算某种用户类型的某种套餐的某种业务功能的当月费用,只需要将这三个属性设置为相应的值即可。
2.如果VIP客户和common客户的不同套餐的每种服务费的算法公式区别很大,那就需要为每种客户的每种套餐的每种服务方式各设计一个子类,并将它们的共同之处抽象成为父类,譬如,定义PhoneComputeStrategy、MessageComputeStrategy、DataComputeStrategy等子类继承ComputeStragtegy。由于本系统可以采用同一种算法公式来处理各种情况,仅仅参数值不同,所以不需要采用多个子类的方式来做。
源码如下:
view
plaincopy to
clipboardprint?
public class ComputeStrategy {
private int customerType;
private
int packType;
private int businessType;
private String businessName =
"";
public ComputeStrategy(int customerType,
int packType, int businessType) {
this.customerType = customerType;
this.packType = packType;
this.businessType = businessType;
switch(businessType){
case 0:businessName =
"电话";break;
case 1:businessName =
"短信";break;
case 2:businessName =
"数据";break;
}
}
public int computeMoney(int quantity){
int price = ConfigManager.getPrice(customerType, packType,businessType);
int freeQuantity = ConfigManager.getFree(customerType, packType,businessType);
int chargeQuantity = quantity - freeQuantity;
if(chargeQuantity < 0){
chargeQuantity = 0;
}
int phoneBaseMoney = ConfigManager.getRent(customerType, packType,businessType);
System.out.print(businessName +
"功能费:" + phoneBaseMoney + "厘钱,");
int fee = price * chargeQuantity;
System.out.println(businessName +
"计价费:" + quantity + "-" + freeQuantity +
"=" + chargeQuantity + ","
+ chargeQuantity + "*" + price +
"=" + fee +"厘钱");
return phoneBaseMoney + fee;
}
}
view plaincopy to clipboardprint? public class Rent { private int price; private RentUnit unit = RentUnit.MONTH; public Rent(int price,RentUnit unit){ this.price = price; this.unit = unit; } public int coputeRent(Date startTime,Date currentMonth){ //首先应该想到去找开源的日期运算类 if(unit == RentUnit.DAY){ Calendar start = Calendar.getInstance(); start.setTime(startTime); Calendar end = Calendar.getInstance(); end.setTime(currentMonth); //将日期设置为当月的最后一天 end.set(Calendar.DAY_OF_MONTH, 1); end.add(Calendar.MONTH, 1); end.add(Calendar.DAY_OF_MONTH, -1); int days = end.get(Calendar.DAY_OF_MONTH) ; if(end.get(Calendar.MONTH) == start.get(Calendar.MONTH)){ days -= start.get(Calendar.DAY_OF_MONTH) + 1; } return price*days; } else{ return price; } } } public class Rent { private int price; private RentUnit unit = RentUnit.MONTH; public Rent(int price,RentUnit unit){ this.price = price; this.unit = unit; } public int coputeRent(Date startTime,Date currentMonth){ //首先应该想到去找开源的日期运算类 if(unit == RentUnit.DAY){ Calendar start = Calendar.getInstance(); start.setTime(startTime); Calendar end = Calendar.getInstance(); end.setTime(currentMonth); //将日期设置为当月的最后一天 end.set(Calendar.DAY_OF_MONTH, 1); end.add(Calendar.MONTH, 1); end.add(Calendar.DAY_OF_MONTH, -1); int days = end.get(Calendar.DAY_OF_MONTH) ; if(end.get(Calendar.MONTH) == start.get(Calendar.MONTH)){ days -= start.get(Calendar.DAY_OF_MONTH) + 1; } return price*days; } else{ return price; } } }
Rent类中所使用的枚举类RentUnit非常简单,其中仅仅是定义了MONTH和DAY两个成员, 其源码如下:
view
plaincopy to
clipboardprint?
public enum RentUnit {
DAY,MONTH;
}
view plaincopy to clipboardprint? public class PackStrategy { private int customerType; private int packType = 0; private ComputeStrategy currentStrategies[] = new ComputeStrategy[3]; private Rent rent; private OrderedStrategyHolder<ComputeStrategy> orderedStrategies[] = new OrderedStrategyHolder[]{ new OrderedStrategyHolder<ComputeStrategy>(), new OrderedStrategyHolder<ComputeStrategy>(), new OrderedStrategyHolder<ComputeStrategy>(), }; private OrderedStrategyHolder<Rent> orderedRent = new OrderedStrategyHolder<Rent>(); public PackStrategy(int customerType,int packType,Rent rent){ this.customerType = customerType; this.packType = packType; this.rent = rent; for(int i=0;i<3;i++){ currentStrategies[i] = new ComputeStrategy(customerType, packType,i); } } public Rent getValidRent(Date month){ Rent validRent = orderedRent.getValidComputeStrategy(month); return validRent==null?rent:validRent; } public void orderRent(Date orderedMonth,Rent rent){ Rent oldRent = orderedRent.order(orderedMonth, rent); if(oldRent != null){ this.rent = oldRent; } } public void cancelRent(Date orderedMonth,Rent rent){ orderRent(orderedMonth,null); } public ComputeStrategy getValidPhonePack(Date month){ ComputeStrategy computeStrategy = (ComputeStrategy)orderedStrategies[0].getValidComputeStrategy(month); return computeStrategy==null?currentStrategies[0]:computeStrategy; } public ComputeStrategy getValidMessagePack(Date month){ ComputeStrategy computeStrategy = (ComputeStrategy)orderedStrategies[1].getValidComputeStrategy(month); return computeStrategy==null?currentStrategies[1]:computeStrategy; } public ComputeStrategy getValidDataPack(Date month){ ComputeStrategy computeStrategy = (ComputeStrategy)orderedStrategies[2].getValidComputeStrategy(month); return computeStrategy==null?currentStrategies[2]:computeStrategy; } public void orderPhonePack(Date orderedMonth,int packType){ ComputeStrategy oldComputeStrategy = orderedStrategies[0].order(orderedMonth, new ComputeStrategy(customerType, packType, 0)); if(oldComputeStrategy != null){ this.currentStrategies[0] = oldComputeStrategy; } } public void orderMessagePack(Date orderedMonth, int packType){ ComputeStrategy oldComputeStrategy = orderedStrategies[1].order(orderedMonth, new ComputeStrategy(customerType, packType, 1)); if(oldComputeStrategy != null){ this.currentStrategies[1] = oldComputeStrategy; } } public void orderDataPack(Date orderedMonth, int packType){ ComputeStrategy oldComputeStrategy = orderedStrategies[2].order(orderedMonth, new ComputeStrategy(customerType, packType, 2)); if(oldComputeStrategy != null){ this.currentStrategies[2] = oldComputeStrategy; } } public void cancelPhonePack(Date orderedMonth){ orderPhonePack(orderedMonth, 0); } public void cancelMessagePack(Date orderedMonth){ orderMessagePack(orderedMonth, 0); } public void cancelDataPack(Date orderedMonth){ orderDataPack(orderedMonth, 0); } /* public PhoneComputeStrategy getOrderedPhonePack(){ PhoneComputeStrategy phoneHolderStrategy = (PhoneComputeStrategy)phoneOrderedStrategyHolder.getOrderedStrategy(); return phoneHolderStrategy; } public MessageComputeStrategy getOrderedMessagePack(){ MessageComputeStrategy messageHolderStrategy = (MessageComputeStrategy)messageOrderedStrategyHolder.getOrderedStrategy(); return messageHolderStrategy; } public DataComputeStrategy getOrderedDataPack(){ DataComputeStrategy dataHolderStrategy = (DataComputeStrategy)dataOrderedStrategyHolder.getOrderedStrategy(); return dataHolderStrategy; } */ } public class PackStrategy { private int customerType; private int packType = 0; private ComputeStrategy currentStrategies[] = new ComputeStrategy[3]; private Rent rent; private OrderedStrategyHolder<ComputeStrategy> orderedStrategies[] = new OrderedStrategyHolder[]{ new OrderedStrategyHolder<ComputeStrategy>(), new OrderedStrategyHolder<ComputeStrategy>(), new OrderedStrategyHolder<ComputeStrategy>(), }; private OrderedStrategyHolder<Rent> orderedRent = new OrderedStrategyHolder<Rent>(); public PackStrategy(int customerType,int packType,Rent rent){ this.customerType = customerType; this.packType = packType; this.rent = rent; for(int i=0;i<3;i++){ currentStrategies[i] = new ComputeStrategy(customerType, packType,i); } } public Rent getValidRent(Date month){ Rent validRent = orderedRent.getValidComputeStrategy(month); return validRent==null?rent:validRent; } public void orderRent(Date orderedMonth,Rent rent){ Rent oldRent = orderedRent.order(orderedMonth, rent); if(oldRent != null){ this.rent = oldRent; } } public void cancelRent(Date orderedMonth,Rent rent){ orderRent(orderedMonth,null); } public ComputeStrategy getValidPhonePack(Date month){ ComputeStrategy computeStrategy = (ComputeStrategy)orderedStrategies[0].getValidComputeStrategy(month); return computeStrategy==null?currentStrategies[0]:computeStrategy; } public ComputeStrategy getValidMessagePack(Date month){ ComputeStrategy computeStrategy = (ComputeStrategy)orderedStrategies[1].getValidComputeStrategy(month); return computeStrategy==null?currentStrategies[1]:computeStrategy; } public ComputeStrategy getValidDataPack(Date month){ ComputeStrategy computeStrategy = (ComputeStrategy)orderedStrategies[2].getValidComputeStrategy(month); return computeStrategy==null?currentStrategies[2]:computeStrategy; } public void orderPhonePack(Date orderedMonth,int packType){ ComputeStrategy oldComputeStrategy = orderedStrategies[0].order(orderedMonth, new ComputeStrategy(customerType, packType, 0)); if(oldComputeStrategy != null){ this.currentStrategies[0] = oldComputeStrategy; } } public void orderMessagePack(Date orderedMonth, int packType){ ComputeStrategy oldComputeStrategy = orderedStrategies[1].order(orderedMonth, new ComputeStrategy(customerType, packType, 1)); if(oldComputeStrategy != null){ this.currentStrategies[1] = oldComputeStrategy; } } public void orderDataPack(Date orderedMonth, int packType){ ComputeStrategy oldComputeStrategy = orderedStrategies[2].order(orderedMonth, new ComputeStrategy(customerType, packType, 2)); if(oldComputeStrategy != null){ this.currentStrategies[2] = oldComputeStrategy; } } public void cancelPhonePack(Date orderedMonth){ orderPhonePack(orderedMonth, 0); } public void cancelMessagePack(Date orderedMonth){ orderMessagePack(orderedMonth, 0); } public void cancelDataPack(Date orderedMonth){ orderDataPack(orderedMonth, 0); } /* public PhoneComputeStrategy getOrderedPhonePack(){ PhoneComputeStrategy phoneHolderStrategy = (PhoneComputeStrategy)phoneOrderedStrategyHolder.getOrderedStrategy(); return phoneHolderStrategy; } public MessageComputeStrategy getOrderedMessagePack(){ MessageComputeStrategy messageHolderStrategy = (MessageComputeStrategy)messageOrderedStrategyHolder.getOrderedStrategy(); return messageHolderStrategy; } public DataComputeStrategy getOrderedDataPack(){ DataComputeStrategy dataHolderStrategy = (DataComputeStrategy)dataOrderedStrategyHolder.getOrderedStrategy(); return dataHolderStrategy; } */ }
(十二)OrderedStrategyHolder类(用于存储一项业务套餐订购信息)
1.用户订购一项业务功能套餐时需要记录生效的年月份和相应的业务功能套餐策略等两个信息,为此,需要设计一个订购记录类来管理这些信息,该类包含两个字段:套餐生效的年月份和订购的业务功能套餐策略。由于VIP用户订购套餐时,除了要记录各项业务功能的计费策略外,还要记录月租金或月基本费,月租金或月基本费的记录方式与记录一项业务功能的套餐方式完全一样,只是记录的参数类型不同,一个是ComputeStrategy策略对象,一个是Rent对象,所以,可以采用泛型技术来设计订购记录类,让它可以记录业务功能套餐策略的订购,也可以记录月租金或月基本费的订购。
2.本订购记录类中提供了一个order()方法来更新其中记录的套餐订购信息。前面讲解PackStrategy类的设计原理时已经提到过:因为原来订购的套餐策略的订购日期是存储在订购记录类中的,根据面向对象的封装特性,当我们订购新的计费策略时,原来订购的计费策略是否对当前月有效,即是否将原来订购的套餐信息赋值给PackStrategy中的用于记录原先或当前的套餐计费策略的变量,
这个功能不是在PackStrategy类中完成,而是在订购记录对象中完成。要防止设置下个月的计费策略时把当前月的计费策略冲掉,这个逻辑是在order方法中完成的,order方法中要根据原来记录的计费策略的生效年月份来判断是否要把原来记录的计费策略返回出去,如果原来记录计费策略的生效年月份已经是下个月了,则不能将此计费策略返回出去,这时候返回null,表示订购记录中原来存储的计费策略对当前月无效,PackStrategy类应使用其中的原先或当前的套餐计费策略的变量来计算当前月的费用,否则,
PackStrategy类应使用order方法返回的计费策略对象来计算当前月的费用。
3.
在计算某月的账单费用时,是否采用订购的从某个月开始的套餐计费策略,同样还是根据面向对象的封装特性,这一功能也应由订购记录类来提供。所以,本订购记录类中提供了一个getValidStrategy()方法来判断其中记录的套餐是否对某月有效,如果有效则返回该套餐策略对象,否则返回null。
源码如下:
view
plaincopy to
clipboardprint?
public class OrderedStrategyHolder<T> {
private Date orderedMonth;
private T computeStrategy;
public T order(Date orderedMonth,T computeStrategy){
T oldComputeStrategy = null;
if(this.orderedMonth!=null &&
this.orderedMonth.before(orderedMonth)){
oldComputeStrategy = this.computeStrategy;
}
this.orderedMonth = orderedMonth;
this.computeStrategy = computeStrategy;
return oldComputeStrategy;
}
public T getValidComputeStrategy(Date month){
if(this.orderedMonth!=null && !month.before(orderedMonth)){
return computeStrategy;
}
return null;
}
}
public class OrderedStrategyHolder<T> {
private Date orderedMonth;
private T computeStrategy;
public T order(Date orderedMonth,T computeStrategy){
T oldComputeStrategy = null;
if(this.orderedMonth!=null && this.orderedMonth.before(orderedMonth)){
oldComputeStrategy = this.computeStrategy;
}
this.orderedMonth = orderedMonth;
this.computeStrategy = computeStrategy;
return oldComputeStrategy;
}
public T getValidComputeStrategy(Date month){
if(this.orderedMonth!=null && !month.before(orderedMonth)){
return computeStrategy;
}
return null;
}
}
相关文章推荐
- 预习和思考《万元月薪面试题:移动用户资费统计系统》
- 一道面试题--模拟实现简易的移动用户资费统计系统逻辑
- 一道面试题--模拟实现简易的移动用户资费统计系统逻辑
- 累病倒了我两次的面试题--移动用户资费统计系统
- 累病倒了我两次的面试题--移动用户资费统计系统
- 累病倒了我两次的面试题--移动用户资费统计系统
- 累病倒了我两次的面试题--移动用户资费统计系统
- 累病倒了我两次的面试题--移动用户资费统计系统
- 黑马程序员---移动用户资费统计系统学习笔记
- 累病倒了我两次的面试题--移动用户资费统计系统
- 简易移动用户资费统计系统 数据库设计方案
- 黑马程序员_移动用户资费统计系统
- 模拟实现简易的移动用户资费统计系统
- 模拟实现简易的移动用户资费统计系统
- 移动用户资费统计系统
- 移动用户资费统计系统
- 黑马程序员__移动用户资费统计系统
- 【黑马程序员】 学习笔记 - 直击7K面试题 - 张孝祥老师的银行业务调度管理系统
- 黑马程序员-张孝祥-7K月薪面试题破解之一_交通灯管理系统
- 学习_张孝祥-7K月薪面试题破解之二_银行业务调度系统视频教程