“过家家”版的移动离线计费系统实现

  • 时间:
  • 浏览:0

public class NormalUserPhoneCallRule extends Rule {

用 Java 写了 Rule 基类,并用 Groovy 脚本语言实现它,有之前 写原来 RuleFactory 来动态地调入脚本语言并编译执行。

整个计费算法的依赖关系如下,可见普通用户人太好是按服务种类计费,而 VIP 用户按套餐计费:

Rules 之间有依赖关系,你你这人 依赖关系直接写在 rule 有一种的代码里,不需要单独配置,可能 groovy 有一种只要配置文件。

                 [ kPipsPhoneCallPackage, kNoChargeMinutesInPackage, kPipsPhoneCallPerMinuteWithPackage ]);

整个实现(不为什么在么在是计费逻辑)是根据 test cases 一步步重构出来的,我一之前 始于只设计了几只简单的框架 class。

            assert !isNew;

有之前 限于实际情況,我只好根据另一方的理解来做了。

            return phoneCall.getMoneyInPips(input);

            Rule phoneCall = factory.create("package_phone_call",

电信计费系统是个有挑战的项目。经过原来以前做过例如系统的同事的讲解,我大致明白:电信计费系统分为“离线计费”和“在线计费”两大类。“离线计费”只要本题要求的那种应用进程,在月末根据用户当月的消费情況生成对账单;“在线计费”是当用户通话时,实时地从账户里扣钱,可能账户余额存在问题或欠费不要 ,就直接掐断通话。可见在线计费的可靠性与实时性要求要高得多。“离线计费”系统的挑战之一在于需求多变,电信公司可能随时增减套餐,推出各项组合优惠,实施积分奖励等等,计费逻辑错综复杂,更在意系统的“灵活性”。

这道题目的先要,任何学过应用进程设计的人(不需用学过面向对象)都应该能做出来——大不了只要用 if-else 判断各种情況嘛。我这里分发了几只流程图,用于厘清需求与代码逻辑:

普通用户测试用例:

当然,原题要求四天做出来,就只能用重型武器了,我把它当成一道普通的招聘面试题来做,把题目中的需求用普通代码实现了就算完事儿。

面向对象的经典例子之一是员工薪酬支付系统,Uncle Bob 的《敏捷软件开发》就拿它举过例子。

        } else {

用多重继承吗?原来 Java 不支持多重继承。那把 Coffee 和 Tea 改成 interface?如此整个项目的代码完全前会改(extends 改为 implements)。

公司把员工分三类:工程师(领月薪)、销售(有提成)、合同工(按工作时数付费);初学面向对象常犯的错误是设计出如下的继承体系:

根据以上分析,任何学过基本的形状化应用进程设计的同学都应该能写出代码,实现需求。当然,搞不好有的人会把完全计费逻辑写进原来函数里 :(

而 normal_user.groovy 又用到了 normal_user_newjoiner.groovy 和 normal_user_{phone_call, short_message, internet}.groovy:

你你这人 设计在应付有一种新需求时是合理的,比如增加“经理”类型,经理除了月薪还有奖金可拿。

哪几种就留给面向对象爱好者去操心了。

  

完全的代码见 https://github.com/chenshuo/recipes/tree/master/java/ 

其中 billing/ 目录是 Java 代码,groovy/ 目录是计费规则。这份代码依赖 Groovy、JUnit、Joda date time (JSR-310) 等第三方库,见 run.sh 中 class path 的设置。

    public static final long kPipsPhoneCallPerMinute = 8000L;

以上对象模型运转得很好,直到有一天,星巴克决定进军香港市场,推出本地化产品。

            return phoneCall.getMoneyInPips(input);

简言之,根据题目当前的需求,普通用户是有一种服务分别计费,再相加;VIP 用户是先按套餐 switch-case,各套餐所村里人 计费。

大伙目前公认的能适应你你这人 需求的设计是用 strategy 模式:

为了灵活性,我把计费逻辑用内嵌的 Groovy 脚本语言实现,以脚本语言为系统的配置文件。原来一来,系统部署以前,只需用升级配置文件(配置只要一定是文件,还可能是数据库的某个表,表里存放 Groovy 代码),不需用改 Java 代码,就能实现新的套餐可能调整已有套餐的价格。

                 [ 0L, noChargeMinutes, kPipsPhoneCallPerMinute ]);

根据当前的题目需求,我归纳如下:

1. 首先,普通用户和 VIP 用户有完全不同的计费逻辑;

最后,base_phone_call.groovy 实现如下,遍历所有的电话呼叫,把费用加起来:

星巴克拓展业务,之前 始于卖茶,有有一种:伯爵红茶、英国早茶、薄荷茶。

        if (packages.contains(PackageType.kNormalUserPhoneCall)) {

}

可能需求变更,会破坏以上假设,在设计的以需用适当留有余地,但又只能做得过于通用,可能你别问我需求会为什么在么在在么在变。悖论:过于通用的代码常反而只能应对需求变更。

可能 Groovy 会编译成 Java bytecode,其运行强度和 Java 一样快,如此几只额外的开销(最多第一次 load in 的以前慢其他。)

    public static final long kPipsPhoneCallPerMinuteWithPackage = 8000L;

我的感觉,你你这人 极度灵活的系统用面向对象往往适得其反,比方说,要不需要说细分用户类型或服务类型?要不需要说为不同的服务类型细分输入数据?可能将来通话计费要用到短信消费的数据,会不需要推翻整个设计?

幸运的是,“薪酬支付系统”所村里人 及都做过,你你这人 设计错误前人也犯过了,读读书就能出理 重蹈覆辙。可能在原来全新的领域,别问我将来需求会为什么在么在在么在变,还信心满满用面向对象来做设计,真不怕落入 Linus 的诅咒“可能在两年以前让我注意到其他抽象效果不为什么在么在在么在样,有之前 所有代码可能依赖于围绕它设计的‘漂亮’对象模型了,可能不重写应用应用进程,就无法改正”吗?

我的设计很简单,基本如此面向对象,用了其他“基于对象”的东西。

Linus 当年炮轰 C++ 的理由之一只要“低效的抽象编程模型”——“可能在两年以前让我注意到其他抽象效果不为什么在么在在么在样,有之前 所有代码可能依赖于围绕它设计的‘漂亮’对象模型了,可能不重写应用应用进程,就无法改正。”

星巴克卖其他其他种咖啡:美式咖啡、浓缩咖啡、拿铁、香草拿铁、榛果拿铁、卡布奇诺、摩卡咖啡。

http://blog.csdn.net/zhangxiaoxiang/archive/2011/04/06/6804117.aspx

3b. 对于 VIP 用户,主只要根据套餐类型来计费,每个套餐有另一方一套计费规则;

拿到题目,我的第一感觉是需求不清晰,其他其他地方需用请出题人阐明:

            Rule phoneCall = factory.create("package_phone_call",

在这道题目里,我如此做不要 对象建模,可能根据我的经验,“面向对象的灵活性”只对应用进程员有价值(还不一定),对用户没价值。不需要面向对象要能达到相同的灵活性,有之前 有以前成本更低。

可能让 CoffeeTea 直接继承更高层的 Object class?如此它的逻辑又和 Coffee 和 Tea 有重复,将来 Coffee 升级人太好 要两头改?

在那篇 blog 的回复蕴含人一针见血地指出了哪几种的什么的问题的关键。konyel:“再错综复杂的业务完全前会 内存数据库+规则引擎 进行配置方案的,把原来的业务写进类里只要个悲剧。”

    @Override

    long getMoneyInPips(UserMonthUsage input) {

在之前 始于编码以前,把测试用例分发一下。这是我拍脑门想到的其他测试用例,只覆盖了基本情況。

可能如此学过 decorator 模式,可能会设计出如下的继承体系:

有的 Rule 是有情況的,其情況可能是“计费单价”可能“免费额度”例如,其他其他 Rule 是 Cloneable。

2. 新用户有赠送的额度,扣除赠送额度之和,计费规则不变;

干脆,我采取有一种以数据为中心的做法,把可能用到的完全数据都交给计费逻辑,让它另一方选用需用的数据来用。

        UserType type = (UserType)input.get(UserField.kUserType);

有之前 一旦出現 原来新需求“销售人员都需用被提拔为经理”,如此原来的设计就得推倒重来,可能原来对象一旦创建,其 type 就无法改变。

比如 root.groovy 的实现用到了 vip_user.groovy 和 normal_user.groovy:

        assert type == UserType.kNormal;

    public static final long kPipsPhoneCallPackage = 20*8000L;

        }

说到这里,星巴克还有原来小小的需求:星巴克的店员是煮咖啡的高手,但大伙不懂编程,更不懂面向对象,何如设计系统让店员能另一方上加咖啡种类?(比如,考虑牛奶质量有哪几种的什么的问题,星巴克打算在内地推出“豆奶咖啡”SoyCoffee,如此你你这人 事情是完全前会要重新编译部署整个系统呢?)

为出理 版权纠纷,我这里就不引用原文了。

package_phone_call.groovy 用 base_phone_call.groovy 来计算价格,并扣除免费额度:

            boolean isNew = input.getBoolean(UserField.kIsNewUser);

    本文转自 陈硕  博客园博客,原文链接:http://www.cnblogs.com/Solstice/archive/2011/04/22/2024791.html,如需转载请自行联系原作者

悲剧的以前到了,香港有有一种“鸳鸯奶茶”,是咖啡和奶茶兑到一起去,为什么在么在在么在设计对象模型?

今天让我 讲的重点完全前会 decorator,人太好用 decorator 也只要表表皮上好看,骨子里一样脆弱。

首先,让我 声明,我如此做过真正的电信系统,这里给出的是原来“过家家”的实现(toy project),用来满足面试题的需求,完全前会真正的生产环境的电信系统。

举原来小例子:员工薪酬支付与星巴克咖啡。

VIP 用户测试用例:

我的做法估计所村里人 及不需要接受,“原来简单的逻辑搞得如此错综复杂,不如把代码写一起去”。我认为我的做法能更好地适应可能出現 的新需求,有之前 不增加目前实现的难度。

以上留作思考题吧。

看一遍一道热烈讨论的“移动用户资费统计系统”编程面试题,本文给出我的做法。

        List<Package> packages = input.getPackages();

这体现了“代码只要数据、数据只要代码”的思想。

我只要根据哪几种测试用例一步一把代码写出来的,边写边重构,最后只要现在的样子。对于你你这人 类型的任务,TDD 或许是个不错的法律最好的依据。

你你这人 思想在下一篇《分布式应用进程的自动回归测试》还有用到,大伙都需用用 Groovy 来编写原来个错综复杂的测试用例,而用 Java 来实现 test harness。

    public static final long kNoChargeMinutesInPackage = 80L;

    }

3a. 对于普通用户,有一种服务(电话、短信、上网)是分别计费的,每种服务完全前会可选的套餐,有之前 把有一种服务的费用加起来;

根据以上分析,用户当月消费额是个纯函数(输出完全由输入决定,函数自身无情況),本文只实现你你这人 计算用户当月消费额度的纯函数,其他功能从略。你你这人 函数的输入是:用户的类型,用户消费的原始记录(即原始话单),用户套餐的使用情況,算是新用户,各个套餐的计费规则。是的,我把套餐计费规则(自然包括各项服务的单价)也当成输入了,听上去大伙要写原来高阶函数?待后文揭晓。

            long noChargeMinutes = (Long)state;

星巴克咖啡也是原来经典例子,《Head First 设计模式》以它为例讲 decorator 模式。

可能是我,我不需要如此设计,而会把计算薪酬的逻辑里装可嵌入的脚本语言中,原来修改系统的功能就不需要重新编译并发布代码,只能改改配置文件就行了。