三次电信计费作业的总结
一、设计与分析
1. 电信计费系列1-座机计费(PTA212019班-题目集09)
TASK:实现一个简单的电信计费程序:
假设南昌市电信分公司针对市内座机用户采用的计费方式:
月租20元,接电话免费,市内拨打电话0.1元/分钟,省内长途0.3元/分钟,国内长途拨打0.6元/分钟。不足一分钟按一分钟计。
南昌市的区号:0791,江西省内各地市区号包括:0790~0799以及0701。
设计策略:几道复杂的多边形题目之后,我们迎来了电信计费这个新题型,虽然看似麻烦,但在参考类图中已给出用户类,计费类与记录类的设计方式,极大的降低了本次题目的难度,对于本次题目来说,结构的设计要占大头,格式的判断居其次,涉及的计算量非常小。因此本次题目的设计策略主要是围绕如何判断输入信息的格式,以及判断后是如何将信息存储进记录类,这是我们在进行此次题目的设计中要着重、优先考虑的。
此次格式判断,本人引入了上次多边形的格式判断类,即一个类分管所有的格式判断,并另有一个内容分析类,在进行格式判断后,将内容有效的存储到记录类中,最后又加入了一个计算类,依据用户类中的记录进行费用计算,以上是除参考类图之外笔者自行引入的类设计。
类图如下:
方法和类复杂度分析:
优点:
挺好拓展的,但好像也没那么好拓展,除此之外想不出有什么优点。
缺点:
- 控制类的输入方法很复杂,这点我在第二次作业中也没处理好,弄得复杂度过高的原因是因为输入本身是在一个while循环里面,而循环里面又有多个if else判断,其实归根到底,还是单一职责没弄好,而且这是个累活。
- 代码的结构多少有些臃肿了,主要是因为格式类与输入分析类没设计好,理想的状况下这两个类应该是简单易懂的,但我的设计显得很散,很乱。
- 格式类有些笨拙,之前自己做了一堆时间判断,最后还是用了一个正则表达式,致使以前的判断没必要了。
控制类代码:
class Controller { private ParseInput parseInput = new ParseInput(); public static Scanner in = new Scanner(System.in); public void input(){ ArrayList<User> users = new ArrayList<User>(); ArrayList<CallRecord> callRecords = new ArrayList<CallRecord>(); String end = "end"; String u = "u-"; String t = "t-"; String str; str = in.nextLine(); boolean inputU = true; boolean inputT = false; while (!end.equals(str)) { if (parseInput.inputInvalid(str)) { String type = str.substring(0,2); String information = str.substring(2); if (u.equals(type) && inputU) { User user; user = parseInput.parseUser(information); if (user != null) { users.add(user); inputT = true; } } else if (t.equals(type) && inputT) { CallRecord callRecord; ArrayList<String> list = parseInput.parseStrT(information); callRecord = parseInput.parseCallByTelephone(list); if (callRecord != null) { callRecords.add(callRecord); inputU = false; } } } str = in.nextLine(); } in.close(); }
格式类与分析类代码:
class Format { public String inputU = "^u-((079[0-9])|(0701))\\d{7,8} [0-2]$"; public String inputT = "[t]-0791[0-9]{7,8}\\s" + "0[0-9]{9,11}\\s((([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]|[0-9][1-9][0-9]{2}|[1-9][0-9]{3})\\.(((0?[13578]|1[02])\\.(0?[1-9]|[12][0-9]|3[01]))|(([469]|11)\\.([1-9]|[12][0-9]|30))|(2\\.([1-9]|[1][0-9]|2[0-8]))))|((([0-9]{2})([48]|[2468][048]|[13579][26])|(([48]|[2468][048]|[3579][26])00))\\.2\\.29))\\s([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])\\s((([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]|[0-9][1-9][0-9]{2}|[1-9][0-9]{3})\\.((([13578]|1[02])\\.([1-9]|[12][0-9]|3[01]))|(([469]|11)\\.([1-9]|[12][0-9]|30))|(2\\.([1-9]|[1][0-9]|2[0-8]))))|((([0-9]{2})([48]|[2468][048]|[13579][26])|(([48]|[2468][048]|[3579][26])00))\\.2\\.29))\\s([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])"; private String jiangXiNum = "^((079[0-9])|(0701))\\d{7,8}$"; private String landNum = "\\d{3,4}\\d{7,8}"; private String jiangXiArea = "^((079[0-9])|(0701))"; private String nanChangNum = "^0791\\d{7,8}$"; private String nanChangArea = "0791"; private String dateRegex = "^\\d{4}.\\d{1,2}.\\d{1,2} \\d{1,2}:\\d{2}:\\d{2}$"; public Format() { } public boolean verifyPhoneNum(String s) { boolean flag; flag = s.matches(jiangXiNum); return flag; } public boolean receivePhoneNum(String s) { boolean flag; flag = s.matches(landNum); return flag; } public boolean verifyUserType(String s) { String pattern = "^[0-2]$"; boolean flag; flag = s.matches(pattern); return flag; } public boolean verifyDate(String s) { boolean flag; flag = s.matches(dateRegex); return flag; } public boolean inCity(String s1, String s2) { boolean flag; flag = s1.equals(s2); return flag; } public boolean inProvince(String s1, String s2) { boolean flag; flag = s1.matches(jiangXiArea) && s2.matches(jiangXiArea); return flag; } public boolean inLand(String s1, String s2) { boolean flag1, flag2; flag1 = s1.matches(jiangXiArea) && !s2.matches(jiangXiArea); flag2 = !s1.matches(jiangXiArea) && s2.matches(jiangXiArea); return flag1 || flag2; } } class ParseInput { private Format format = new Format(); public boolean inputInvalid(String s) { boolean flag1 = s.matches(format.inputT); boolean flag2 = s.matches(format.inputU); return flag1 || flag2; } public User parseUser(String s) { User user = null; String[] strings = s.split(" "); String num = strings[0]; String userNum = parseUserNum(num); String type = strings[1]; ChargeMode mode = parseUserType(type); if (userNum != null && mode != null) { user = new User(new UserRecords(), mode, userNum); } return user; } public String parseUserNum(String s) { if (format.verifyPhoneNum(s)) { return s; } return null; } public ChargeMode parseUserType(String s) { String landPhone = "0"; String mobilePhone = "1"; String mobilePhoneA = "2"; if (landPhone.equals(s)) { return landPhoneMode(); } else if (mobilePhone.equals(s)) { return new ChargeMode(null) { @Override public double calCost(UserRecords userRecords) { return 0; } @Override public double getMonthlyRent() { return 0; } }; } else if (mobilePhoneA.equals(s)) { return new ChargeMode(null) { @Override public double calCost(UserRecords userRecords) { return 0; } @Override public double getMonthlyRent() { return 0; } }; } return null; } public ChargeMode landPhoneMode() { ArrayList<ChargeRule> chargeRules = new ArrayList<ChargeRule>() { { add(new LandPhoneInCityRule()); add(new LandPhoneProvinceRule()); add(new LandPhoneInlandRule()); } }; ChargeMode landlinePhoneCharging; landlinePhoneCharging = new LandlinePhoneCharging(chargeRules); return landlinePhoneCharging; } public CallRecord parseCallByTelephone(ArrayList<String> list){ CallRecord callRecord = null; if (list != null) { String callingNum = list.get(0); String answerNum = list.get(1); String startTimeS = list.get(2); String endTimeS = list.get(3); SimpleDateFormat sf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss"); try { sf.setLenient(false); Date startTime = sf.parse(startTimeS); Date endTime = sf.parse(endTimeS); callRecord = new CallRecord(callingNum, answerNum, startTime, endTime, callingNum.substring(0,4), answerNum.substring(0,4)); } catch (ParseException ignored) { } } return callRecord; } public ArrayList<String> parseStrT(String s) { ArrayList<String> list = null; String[] strings = s.split("\\s+", 3); String dataTime = strings[2]; int index = dataTime.indexOf(" "); index = dataTime.indexOf(" ", index + 1); String dataTime2 = dataTime.substring(index + 1); String dataTime1 = dataTime.substring(0, index); boolean flag1 = format.verifyPhoneNum(strings[0]) || format.receivePhoneNum(strings[1]); boolean flag2 = format.verifyDate(dataTime1) && format.verifyDate(dataTime2); if (flag1 && flag2) { list = new ArrayList<String>() { { add(strings[0]); add(strings[1]); add(dataTime1); add(dataTime2); } }; } return list; } }
2. 电信计费系列2-手机+座机计费(PTA212019班-题目集10)
TASK:实现南昌市电信分公司的计费程序,假设该公司针对手机和座机用户分别采取了两种计费方案,分别如下:
1、针对市内座机用户采用的计费方式(与电信计费系列1内容相同):
月租20元,接电话免费,市内拨打电话0.1元/分钟,省内长途0.3元/分钟,国内长途拨打0.6元/分钟。不足一分钟按一分钟计。
假设本市的区号:0791,江西省内各地市区号包括:0790~0799以及0701。
2、针对手机用户采用实时计费方式:
月租15元,市内省内接电话均免费,市内拨打市内电话0.1元/分钟,市内拨打省内电话0.2元/分钟,市内拨打省外电话0.3元/分钟,省内漫游打电话0.3元/分钟,省外漫游接听0.3元/分钟,省外漫游拨打0.6元/分钟;
注:被叫电话属于市内、省内还是国内由被叫电话的接听地点区号决定,比如以下案例中,南昌市手机用户13307912264在区号为020的广州接听了电话,主叫号码应被计算为拨打了一个省外长途,同时,手机用户13307912264也要被计算省外接听漫游费:
u-13307912264 1
t-079186330022 13307912264 020 2022.1.3 10:00:25 2022.1.3 10:05:11
设计策略:
实际上就是一次简单的拓展,整个代码过程不超过两个小时,但这没什么光彩的,大刀阔斧的直接拓展导致代码量暴增,实际上这其中是有很多优化的必要。
类图如下:
方法和类复杂度分析:
优点:
依然符合开闭原则,想拓展的话可以接着拓展,虽然感觉有点不对。
缺点:
- 没有解决上次作业中提到的臃肿问题,如果再一次面对这样的PTA作业,有时间的话,我一定对自己说三个字:要简洁。
- 代码体积过大,类过多:感觉很臃肿,很混乱。
- 可读性非常低:注释写的太少。
这次的控制类中的输入方法更复杂了,设计的不好
public void input() { String end = "end"; String u = "u-"; String t = "t-"; String str; str = in.nextLine(); boolean inputU = true; boolean inputT = false; while (!end.equals(str)) { if (parseInput.inputInvalid2(str) || parseInput.inputInvalid(str)) { String type = str.substring(0, 2); String information = str.substring(2); if (u.equals(type) && inputU) { User user; user = parseInput.parseUser(information); if (user != null) { users.add(user); inputT = true; } } else if (t.equals(type) && inputT) { CallRecord callRecord; if (str.matches(format.inputT_MM)) { ArrayList<String> list = parseInput.parseStrT_MM(information); callRecord = parseInput.storageRecord_MM(list); } else if (str.matches(format.inputT_ML) || str.matches(format.inputT_LM)) { ArrayList<String> list = parseInput.parseStrT_LM_ML(information); callRecord = parseInput.storageRecord_ML_LM(list); } else { ArrayList<String> list = parseInput.parseStrT_LL(information); callRecord = parseInput.parseCallByTelephone(list); } if (callRecord != null) { callRecords.add(callRecord); inputU = false; } } } str = in.nextLine(); } }
3. 电信计费系列3-短信计费(PTA212019班-题目集11)
TASK:实现一个简单的电信计费程序,针对手机的短信采用如下计费方式:
1、接收短信免费,发送短信0.1元/条,超过3条0.2元/条,超过5条0.3元/条。
2、如果一次发送短信的字符数量超过10个,按每10个字符一条短信进行计算。
设计策略:最后一次电信作业,这次非常的简单,减去了时间格式的判断,大大降低了题目的难度,于是重新进行了一次类设计,因为这次题目所涉及到的方法非常简单,体量也非常的小,如果接着上一次拓展的话,代码体积就太大了,PTA直接过不去,这次类设计我直接减去了各个抽象父类以及格式类与分析类,代码总共才五个类。
类图如下:
方法与类复杂度分析:
优点:
这次代码量才200行,可以说非常的简洁,了却了我前两次题目的夙愿。
缺点:
- 简洁的代价是:不易拓展,关于java的类设计,我觉得最难想的是抽象父类该怎么设计,这直接决定了代码是如何进行拓展的。
- 经典问题:单一职责,单一职责不仅是个累活,我想,巧妙的代码往往非常简洁易懂,关键就在于单一职责实现的好。这也是个设计的过程。像这种题目设计好一次就能用三次。
二、总结
- 题目中的有些问题很有意思,比如区号是四位也可能是三位,这就像一个冷知识一样穿插在题目中,前面我没说,我认为这种东西就没什么好说的,我实在想不出有什么技术含量。
- PTA的意义,我认为主要是如何设计,有些极其繁琐的细节问题,我认为主要归结于出题人如何写测试点,而不是我如何设计,我设计有多好也不可能面面俱到,就像时间判断一样,我用时间类判断竟然不对,只能用正则表达式,如果这样的问题在之后的项目中乃至以后的工作中遇到,有关于繁琐细节的问题那就非常值得思考了。
- 做一次这样的迭代作业之后,我首先一个感悟是,最好在第一次作业就把类设计的非常完善与简洁,现在我想简洁这个东西大概是java的最大难题,不仅是迭代作业,日常作业我认为也应当追求简洁,简洁易于交流也易于代码的身心健康,简洁应该成为一种习惯。