抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

知道继承,接口,多态和虚类不能让你学会面向对象程序设计。理解设计模式,因为设计模式是面向对象开发工程师们多年以来程序设计的经验总结,是为了解决问题而产生的历经验证的工程通用解决方案。良好的设计模式会包含可复用,可扩充,可维护三个特性,而模式可以帮我们做到这点。

首先来举个简单的例子,来展示继承所不能解决的问题。如图,某游戏公司程序员为自己游戏的职业系统设计了一个 Player 超类,所有的职业都会继承这个超类。在继承时,也会继承超类内所有的方法。如果需要新的职业,那么我们就添加一个新的类。完美!

美丽的UML图
美丽的UML图

现在公司抛出了新的需求:要求玩家应该会闪避。这很好办,只需要在超类里加入一个 dodge() 方法即可。但是在测试的时候发现了新的问题:牧师会闪避导致了他过于强大的生存能力,以至于不需要队友保护他。这和游戏的设计理念不太符合,公司要求程序员让牧师不再会闪避。这也很简单,把 Priest 的 dodge() 方法的具体实现覆盖成什么都不做就可以了。到目前为止,一切都很好…

直到程序员意识到,公司为了赚钱每个月都会推出一款新职业,他们有的专注攻击不能防御,有的利用防御来攻击,有的……噢还有,公司还会推出氪金装备,使得 Priest 可以有限次的进行闪避动作。天啊,这要怎么办?来看看我们从最初的设计原则可以推出什么。

变与不变

应用设计的第一个原则:找出应用中需要变化之处,将其独立,不要和不需要变化的代码写在一起。这样,每次新的需求来到,我们就可以不修改固定的部分,只修改变化的部分。

对于我们的游戏设计来说,我们知道 dodge() 是会随着职业的不同而改变的(当然 attack() 和 defense() 也会变,但是原理是一样的 )。那么我们把闪避行为拿出来,并为它创建一个类。所有的职业都将 HAS-A 闪避行为。

为谁而写

应用设计的第二个原则:针对接口编程,不要针对实现编程。 说真的,我觉得这句话刚一看到非常难理解。

什么是实现?职业类如果实现了 dodge() 接口,那么就意味着所有职业都要实现这个接口,因为他们都继承自原始的超类。我们不要把什么类能实现什么在最开始的时候写死。

怎么针对接口编程?我设计一个 DodgeBehaviour 接口,此接口有 dodge() 方法。现在,我们让具体的类实现这个接口。DodgeNormal 类会正常的躲避,而 DodgeNaught 类什么都不做。现在,闪避行为和 Player 类没有任何关系了。我们可以随意添加闪避行为或者修改行为,比如我们为法师设计一个 DodgeKUXUAN,让闪避的时候会增加粒子效果。

另一个美丽的UML图
另一个美丽的UML图

完成设计

现在事情可能会变得有点奇怪,我们如何让某个职业进行闪避?请看代码:

1
2
3
4
5
6
7
8
9
10
public class Player {
healthType Health;
DodgeBehaviour dodgeBehaviour;

//...

public void performDodge() {
dodgeBehaviour.dodge();
}
}

这是我们的超类。看,我们通过让职业 HAS-A 行为的方式成功地托管了闪避行为!这也就引出了我们的第三个设计原则:多用组合,少用继承。HAS-A is better than IS-A!

那么相对应的,子类的代码:

1
2
3
4
5
public class Priest extends Player {
public Priest() {
dodgeBehaviour = new DodgeNaught();
}
}

在构造函数里面,我们写好 Priest 会使用哪种 实现了 DodgeBahaviour 的类。不仅如此,如果我们对 dodgeBehaviour 设定一个 setter 函数,我们就可以在程序运行时改变牧师躲避的行为,比如牧师装备了氪金道具,那么就将 dodgeBehaviour 设定成可以闪避并计数,结束后改回去。这比写一个 ”可以进行10次闪避” 的 Priest 职业好太多了!

同理,攻击和防御方法都可以用相同的方式实现。以上的设计模式我们称为策略模式。策略模式定义且封装了独立于使用算法客户的算法族,使他们相互之间能够替换。这也不意味着继承方法真的没用了,比如所有的职业都需要走路,那么 walk() 方法就可以继承。关键点是确立好需求,并选择你需要的设计模式。

所以,这位程序员该好好跟他的产品经理吵一架 :P

总结

来看看我们目前学到的东西:

  • 设计原则一:找出应用中需要变化之处,将其独立,不要和不需要变化的代码写在一起。
  • 设计原则二:针对接口编程,不要针对实现编程。
  • 设计原则三:多用组合,少用继承。
  • 策略模式:定义且封装了独立于使用算法客户的算法族,使他们相互之间能够替换。

评论