设计简单的勇者游戏角色
在一款勇者冒险的游戏中,主角团的每个角色会有不同的职业King,Queen, Knight, Troll
……,而且每个角色都可以战斗,但是每个人的相貌不一样
那么如果我们想设计这样的游戏角色,可以设置一个抽象超类Character
超类Character
中有个方法:每个职业都会的行为fight()
另一个是方法:display()
,由于每个职业的外貌不同,所以设置为抽象类,由不同职业自己实现自己的外貌
abstract class Character{
// 长相
abstract public void display();
// 战斗方式
public void fight() {
System.out.println("Hit"); // 打击
};
}
之后的每个职业都继承这个Character
类,同时实现自己的长相display()
这样各自的外貌(display()
)有区别,又因为每个职业继承了父类,每个职业都会战斗(fight()
),
class King extends Character{
@Override
public void display() {
System.out.println("I wearing a crown"); //国王带着皇冠
}
}
class Knight extends Character{……//其他职业省略
到此为止我们就设计了一套简单的勇者游戏角色
战斗方式发生变化
当当当当!游戏更新了,出现一种船新职业——Minstrel
(吟游诗人)
就像其他职业一样,我们把她继承Character
……实现自己的外貌……会战斗……等等!
本次更新的Minstrel
不会战斗啊!她的作用是给队友加Buff
class Minstrel extends Character{
@Override
public void display() {
System.out.println("Minstrel hold a lute"); //吟游诗人抱着鲁特琴
}
}
这可怎么办,而且之后出现的很多个角色如果战斗方式都不一样,这个问题就更复杂了,很容易会想到下面的解决方法
将Character
的fight()
变成抽象方法,让每个角色自己实现战斗
abstract class Character{
// 这里表示其他方法…………
// 战斗
abstract public void fight();
}
class Minstrel extends Character{
// 这里表示其他方法…………
@Override
public void fight() {
System.out.println("Dont know how to fight");
}
}
class King extends Character{
// 这里表示其他方法…………
@Override
public void fight() {
System.out.println("Hit");
}
}
可以是可以,但是如果游戏中有几十个几百个职业,难道我要把每个职业的fight()
都实现改一遍吗?那也太恐怖了,换方法
将会战斗(Fightable
)和不会战斗(Fightless
)设置为两个接口,让两类角色实现两类接口
interface Fightable{
void fight();
}
interface Fightless{
void fight();
}
class Minstrel extends Character implements Fightless{
// 这里表示其他方法…………
@Override
public void fight() {
System.out.println("Dont know how to fight");
}
}
class King extends Character{
// 这里表示其他方法…………
@Override
public void fight() {
System.out.println("Hit");
}
}
这个方法和上个方法一样,虽然实现了松耦合,但是代码重复率更高了,也没有起到什么实质性作用
把战斗方式提取出来
如果我们把职业中会发生变化的方法,单独提取出来封装,不和其他不变化的混合在一起,那么我们就可以只关心变化的部分
这里会变化的方法是战斗方法fight()
,那我们就把所有战斗方法都提取出来
不妨设置一个Fight
接口,这个接口中包装着战斗方法fight()
interface Fight{
void fight();
}
比如会出现Cut,Hit, AddBuff, Raid
等战斗方式
class CutFight implements Fight{
public void fight(){
System.out.println("Cut!");
}
}
class HitFight implements Fight{
public void fight(){
System.out.println("Hit!");
}
}
class AddBuffFight implements Fight{
public void fight(){
System.out.println("AddBuff!");
}
}
class RaidFight implements Fight{
public void fight(){
System.out.println("Raid!");
}
}
这样我们就可以在职业的超类Character
中装入一个Fight
接口变量,战斗的执行交给Fight
接口变量的fight()
方法执行
(了解单例模式的朋友也可以使用单例模式,这段代码并未使用)
interface Fight{
void fight();
}
// 提取出的Cut战斗方式
class CutFight implements Fight{
public void fight(){
System.out.println("Cut!");
}
}
abstract class Character{
// Fight接口的变量
Fight fightRef;
// 长相
abstract public void display();
// 战斗方式
public void fight() {
fightRef.fight();
};
}
class King extends Character{
@Override
public void display() {
System.out.println("King wearing a crown"); //国王带着皇冠
}
public King(){
fightRef = new Cut();
}
}
这样就把每个方法提取出来,将变化的方法与不变方法的分离,使系统更有弹性
还可以在Character
中对fightRef
加入set()
方法,这样就可以在运行时改变战斗方式
abstract class Character{
// Fight接口的变量
Fight fightRef;
public setFightRef(Fight newFight){
fightRef = newFight;
}
// 其他方法……
}
public static void main(){
Character king = new King(); // Fight方式使用构造函数中的Cut
king.fight();
king.setFightRef(new RaidFight); // 修改Fight方式为Raid
king.fight();
}
输出结果:
Cut!
Raid!
策略模式
恭喜你,你已经使用了策略模式
在刚刚我们修改角色设计时,用到的思想就是策略模式,它的正式定义为
策略模式:定义的算法族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户
我们还用了以下设计原则
- 找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起
- 针对接口(超类型)编程,而不是针对实现编程
- 多用组合,少用继承
设计模式更像是一种思想,如果工程中找不到该使用哪种设计模式,就多考虑设计原则,学会设计模式也方便业内人士交流
参考:《Head first 设计模式》(封面真的chou)