目录
1.前言
2.设计与分析
(1)面向对象设计原则
(2)继承
(3)多态
(4)接口
(5)容器
3.踩坑心得
4.改进
5.总结
1. 前言
这一阶段主要涵盖了类的设计(航空货运管理系统1.0),面向对象的设计原则,继承与多态(航空货运管理系统2.0),抽象类(航空货运管理系统2.0),接口(雨刷程序功能扩展设计),重构,容器类(点线面问题再重构),再加上对前面题目的迭代练习。相较于算法而言,本次题目的难度相对下降一点,没有上一阶段的那么痛苦,但还是不能掉以轻心。
2.设计与分析
- 单一职责原则(Single Responsibility Principle, SRP):每个类只负责一项单一的职责,即一个类或模块应该只有一个引起它变化的原因,各司其职,互不影响。可以提高代码的可维护性和可扩展性。在航空货运管理系统中,客户类除了一些构造方法外,只负责展示客户的信息,并没有其他多余的职责,符合单一职责原则。

-
开闭原则(Open/Closed Principle, OCP):软件实体应该对扩展开放,对修改关闭。关键在于抽象化设计。打造更健壮、灵活的软件系统。在航空货运管理系统中,定义了支付类(抽象类),微信支付类继承于该类,并override其中的方法。如果客户的需求发生变化,想要增加支付宝支付,只需要让支付宝支付类继承支付类即可,无须修改order类的代码,实现了对扩展开放,对修改关闭。

- 里氏代换原则(Liskov Substitution Principle, LSP):所有使用父类的地方一定能够使用其子类的对象。要求子类兼容父类的行为。在航空货运管理系统中,以支付类为例,其子类微信支付类和支付宝支付类都实现了父类中的方法,且方法签名相同。无论使用wechatPay或AliPay对象,都可以使用Payment的引用,具有良好的扩展性。符合里氏代换原则。
- 合成复用原则(Composite Reuse Principle, CRP):优先使用对象组合或聚合来实现代码复用,少用继承,降低类与类之间的耦合。其中组合的生存期一致,聚合的生存期不一致。在航空货运管理系统中,order类与orderDetails类之间是聚合关系,与客户之间是组合客户,它们实现了其他类的功能,实现解耦。一个订单可以有多个订单明细,订单明细也可以独立存在。一个订单必然有一个客户。

- 依赖倒转原则(Dependency Inversion Principle, DIP):高层模块(稳定)不应该依赖于低层模块(变化),他们都应该应该依赖于抽象(稳定)。抽象(稳定)不应该依赖于细节(变化),细节应该依赖于抽象(稳定)。代码要依赖于抽象的类,而不要依赖于具体的类;要针对接口或抽象类编程,而不是针对具体类编程。在航空货运管理系统中,order类依赖于Payment抽象类,而不是依赖于具体的支付类(如wechatPay, AliPay);orderDetails类依赖于Product类,而非具体的货物类(如普通货物,加急货物)。低层模块通过继承抽象类来完成功能,Alipay,WeChat Pay,Cash类都继承于Payment抽象类,并实现的pay方法。Normal、Dangerous、Expedite类继承自Product抽象类,并实现了getRate方法。


- 迪米特法则(Law of Demeter, LoD):不要和陌生人讲话,只和你的直接朋友通信,最少知识原则。一个对象应当对其他对象有尽可能少的理解,从而降低对象之间的耦合程度。在航空货运管理系统中,通过设置控制类来解耦,使客户与订单之间有尽可能少的联系。

(2)继承:从已经存在的类中定义新的类,父类与子类形成了一种 “is-a” 的关系。当类与类之间,存在相同(共性)的内容,并满足子-类是父类的一种,就可以考虑使用继承。在继承的过程中,需要注意一些问题。为了多态而用继承(解耦)。java支持单继承,不支持多继承,但支持多层继承。父类的构造方法不能被继承。父类的成员变量(非私有)能被子类继承,私有的不能被子类继承。成员方法(虚方法表)能被子类继承,不在虚方法表中则不能被子类继承。在航空货运管理系统中,普通货物、加急货物、危险货物之间存在相同的内容,可以抽象出他们的父类货物类,并满足普通货物、加急货物、危险货物是货物的一种。


如图所示,这几种货物之间存在一些相同的内容,我们就可以把他们抽象出来,形成一个共同的父类货物类。客户类也是类似的。个人与集团之间也存在一些相同的地方。
(3)多态:意味着父类型的变量可以引用子类型的对象。例如Shape c1 = new Circle()。Shape父类,Cirlce是子类。父类类型 对象名称 = 子类对象,向上转型。优点就是使用父类型作为参数,可以接受所有子类的对象。弊端在于不能使用子类特有的功能。在航空货运管理系统中,定义了支付的父类,等运行时却指向子类对象。货物也是如此,定义了货物的父类,运行时也是指向子类的对象。


(4)拥抱变化。在航空货运系统中,客户的需求发生变化。在基础运费的基础上增加折扣率,个人用户享受订单的9折优惠,集团用户享受订单的8折优惠。我是把折扣这个属性放在了客户类中,在order类中,计算总额的时候调用该方法。


(5)接口的使用(inferface)。接口是一种抽象类型,定义了一组方法的签名,但不提供具体的实现,弥补了Java中单继承的局限性。在雨刷功能程序扩展设计中,定义了控制杆接口和刻度盘接口。


如图所示,只提供方法的签名,不提供方法的具体实现。雨刷系统1和雨刷系统2需要implement它们。


(6)容器:可以用来存储和管理多个对象的类。在点线面问题在重构中,增加了一个容器类,其属性为ArrayList<Element>类型的对象,我们可以增加Element类的对象,删除Element类的对象等操作方法。其中Array List中的操作方法也十分丰富,可借助API帮助文档进行查看。


(1)一定要深刻理解客户的需求。就拿航空货运管理系统来说,搞混了计费重量和航班的载重量,我以为航班载重量是实际重量的相加,结果确实计费重量的相加,导致了我的输出跟样例的输出存在差别。
(2)在设计类与类之间的关系时一定要想清楚,这个类跟这个类之间是什么关系合适。就拿航班类为例,我最终是订单类与航班类之间是关联关系。在设计航班类时,其实我刚开始有想过把他与订单明细类联系起来,但是写着写着就不对了,踩坑了。我们可以先把类图大概的画在草稿之上,并思考他们之间的关系是否合适。
(3)增加代码的复用性。在这两次的题目集中,如果你的代码的复用性好,那么你就只需要在原来的代码上进行修改,不需要从头开始写,可以为我们节省大量的时间。
(4)在创建一个类时,最好是把它的无参构造方法加上,以免后续出现问题。
(1)main方法过于冗长,有些东西可以把他包装成一个方法。如图所示,可以把判定它是哪种类型做成一个方法,就不需要在main方法中如何过多的if-else语句。


(2)在航空货运管理系统中,最复杂的方法是Dangerous.getRate() 。使用了太多的if-else进行判断。


(3)代码结构存在问题。深度为1和2的语句数较多。有大量顺序执行的代码块,缺乏模块化设计。
(4)分支语句占比异常,分支语句占比仅 7.4%(正常范围约 15%-25%)。可能在于代码逻辑简单,或存在未被发现的漏洞。
(5)根据SourceMomitor,注释行占比仅 6.4%,可能会影响代码的可读性,需要增加注释。
这一阶段的题目集相较于上一阶段的题目集来说,相对简单一点,这一阶段的角色主要是designer。通过这一阶段的练习,对类与类之间的关系有了进一步的了解,在写代码的过程中,要遵守面向对象的设计原则,要注意解耦,关系越轻越好,我们是为了多态而去使用继承,少用继承,多用合成复用原则,可以使用接口实现多层继承,当有相同的方法时,我们可以定义抽象方法,并让子类去覆写(override),当我们需要对对象进行操作时,我们可以使用容器类,例如ArrayList,以及要注意代码的复用性,可拓展性和可维护性。以这次航空货运管理系统为例,如果在第一次写代码的时候注意了复用性,那么在第二次和第三次写航空货运管理系统时,就可以节省很多时间。在以后的实践中中,心中要始终想着这些原则和方法。
浙公网安备 33010602011771号