JAVA 使用继承复用代码的陷阱与补救措施

来源:互联网 发布:淘宝 未上架 抢购技巧 编辑:程序博客网 时间:2024/06/05 22:36

学习了java,自然知道了面向对象编程,找出有共性的类创建父类实现代码的复用也成了敲代码的家常便饭。继承的使用极大的方便了程序员的开发,你可以抽取出共有的代码放在父类中,避免了子类中大量的重复代码,而且更炫酷的是,一旦这些共有的部分需要改动,你只需要在父类中改动就可以了,而不必一个接一个的复制粘贴修改子类。
举个栗子,马老板决定开发一款RPG游戏,里面的角色(character)有自己的名字(name),都有自己的职业(getOccupation()),都会说话(talking()),都会走路(walking())。接到这个用心创造快乐的任务的程序猿小李在对需求进行了分析后,决定使用继承。他写下了如下代码:

public abstract class Character {    public abstract String getName();    public abstract String getOccupation();    public void talking(){    }    public void walking(){    }}

Character是一个抽象的父类,考虑到每个角色的名字和职业不一定相同,小李把getName(),getOccupation()方法写成了抽象的,具体的实现由子类继承时实现。大功告成,小李喝着咖啡开始了美滋滋的生活,for now.
但是很快在高尔夫球场挥洒汗水的马老板在挥杆见灵光一线:这个角色扮演游戏中的角色应该拥有战斗(fight())能力,而且职业为医生的角色不应该具有战斗能力
马老板把灵感告诉了项目经理,项目经理向马老板保证很快就可以完成,然后把任务交给了小李。
凭着开发人员的直觉,小李预感到会有更多的角色不具备战斗功能,比如萝莉,正太。所以将fight()方法写在父类中然后在子类中覆盖这个方法让fight()什么都不做并不是个好方法。小李决定让每个可以战斗的角色实现如下接口

public interface FightingCharacter {    public void fight();}

但小李发现这个决定同样很糟糕,因为可战斗的角色同样也会增加,而且因为接口中只有抽象的方法无法实现代码复用,在不久的将来增长到100个战斗角色时,会造成大量复用的fight()代码。陷入苦恼的小李很快迎来了第二个噩梦。
马老板在挥杆间再次灵感一现:作为不能战斗的补偿,角色应该获得治疗(treat())这项能力,而且治疗这项能力很多角色都可以拥有,只是治疗的效果不同。这意味着部分角色即可战斗又可以治疗。
怎么办,怎么办,该如何解决部分角色拥有战斗或者治疗能力的同时又避免代码复用的问题。陷入绝望的小李决定去找大师。
听完小李的哭诉,大师微微一笑,说道:你需要把代码中可能会改变的部分抽取出来,让它们和不会变化的代码分离开,并且只暴露出需要被调用的部分
小李恍然大悟:大师,你的意思是让我把可能变化的代码抽取成可供选择调用的方法写在父类里然后供子类调用吗吗?
大师:不,是抽取成对象,并且有共性的对象继承同一个接口。
是时候把战斗(fight()),治疗(treat())从角色character中解放出来了。

public interface FightAbility {    public void fight();}class ArrowFightAbility implements FightAbility{    @Override    public void fight() {        // 战斗方式,弓箭    }}class LoliFightAblity implements FightAbility{    @Override    public void fight() {        // 战斗方式,什么都不做    }}

然后在Character类中添加fightAbility字段

public abstract class Character {    private FightAbility fightAbilty;    public void setFightAbilty(FightAbility fightAbilty) {        this.fightAbilty = fightAbilty;    }    public void doFight(){        if(fightAbilty!=null)            //对于父类来说,只需要知道调用此方法是执行战斗行为            //即可,具体的实现不需要关心            fightAbilty.fight();    }    public abstract String getName();    public abstract String getOccupation();    public void talking(){    }    public void walking(){    }}

这让骑士(KnightCharacter)只需要set一个ArrowFightAbility 对象,需要战斗时调用doFight()方法即可实现用弓箭战斗,对萝莉来说她需要的是set一个LoliFightAblity ,然后doFight()时什么都不做。对于治疗这项技能同理。
更加妙不可言的是,角色可以通过setFightAbilty方法动态的切换自己的技能,比如小萝莉黑化变成最强战力,骑士中毒不能战斗等等。
是时候介绍一些高大上的理念了:角色的行为,例如战斗,治疗,我们此时不应该称其为一系列的行为(a set of behaviors),而是 一族算法(a family of algorithms).在本例中,算法代表的角色可以做的事情,比如fight(),treat()。我们通过实现接口FightAbility,生成 用于执行战斗的类,并提供接口供角色调用。此时Character和FightAbility 的关系是has-a关系。如果Character的子类继承了FightAbility,自己来实现fight()方法,那么就是is-a关系,当然子类继承接口的缺点上文也讨论过了。
如果你有耐心读到这里,congratulation,我相信你已经学会了策略模式。没错,本篇博客讨论的就是策略模式。策略模式定义了算法族,分别封装起来, 而且让它们之间可以相互替换(实现同一接口的原因)。策略模式令可变化的算法独立于调用它们的客户端(Strategy lets the algorithm vary independently from clients that use it).
路漫漫其修远兮,具体的使用还需骚年在敲代码中自行领会~

原创粉丝点击