简体   繁体   中英

Is there any way to avoid if-else and dynamic casting in this case?

For example, for a game I have some Skill, which is data object:

public interface Skill{
public String getName();
}

public class Attack implements Skill{
    public String getName(){ return "Attack"; }
    public int power;
}

public class Speak implements Skill{
    public String getName(){ return "Speak"; }
    public String speech;
}

To apply the skills during the game, I need some SkillHandler for each corresponding skill:

public interface SkillHandler{
    public void apply(Skill skill);
}

public class AttackHandler{
    @Override
    public void apply(Skill skill){
        Attack attack=(Attack)skill;
        Player player=Global.getPlayer();
        Enemy enemy=Global.getEnemy();
        enemy.hp=enemy.hp-attack.power;
        //some other code for follow up handle
    }
}

public class SpeakHandler{
    @Override
    public void apply(Skill skill){
        Speak speak=(Speak)skill;
        Label label=new Label(speech);
        this.displayOnTop(label);
    }
}

I use one SkillHandler for each Skill because I don't want the Skill depend on SkillHandler, and PlayerAttackStateHandler would apply each skill:

public class PlayerAttackStateHandler{
    public PlayerAttackHandler(){
        Skill[] skills=Global.getSkills();
        for(int i=0;i<skills.length;i++){
            SkillHandler skillHandler=null;

            if(skills[i].getName().equals("Attack")){
                skillHandler=new AttackHandler();
            }else if(skills[i].getName().equals("Speak")){
                skillHandler=new SpeakHandler();
            }

            skillHandler.apply(skills[i]);
        }
    }
}

I know this design is ill-formed because it has at least 2 problems:

  1. I need to update the long if-else chain if a new Skill as well as new SkillHandler is added, which seems does't follow open-closed principle

  2. it has a dynamic cast in each SkillHandler

My question is, is there any design pattern to eliminate both if-else and dynamic cast (if possible) in this case, while keep the Skill not depend on SkillHandler?

It seems that your implementation has lot in common with visitor pattern, something like this:

public interface ISkillable //this is your Skill
{
    public int GetPower();
    public string GetSpeak();
}

public interface IVisitable //player or npc
{
    public void Accept(IVisitor visitor)
}

public interface IVisitor //AttackHandler or SpeakHandler
{
    public void ApplySkill(ISkillable skillable)
}

public class Player implements ISkillable, IVisitable
{
    ...
    public void Accept(IVisitor visitor)
    { 
        visitor.Visit(this); 
    }
}

public class AttackVisitor implements IVisitor
{
    public void Visit(ISkillable skillable)
    {
        //do something with power
    }
}

and then example how it can be used is

player.Accept(new AttackVisitor(/*you can provide additional info like enemy*/));
player.Accept(new SpeakVisitor());

See Attack and Speak as capabilities some agent could possess.

I would consider testing capabilities/features:

interface Attacking { void attack(); }
interface Speaking { void speak(); }

Animal animal = ...

Optional<Attacking> attacker = animal.lookup(Attacking.class);
attacker.ifPresent(a -> a.attack());

Optional<Speaking> speaker = animal.lookup(Speaking.class);
speaker.ifPresent(sp -> sp.speak());

Animal need not implement any interface, but you can look up (lookup or maybe as) capabilities. This is extendible in the future, dynamic: can change at run-time.

Implementation as

private Map<Class<?>, ?> map = new HashMap<>();

public <T> Optional<T> lookup(Class<T> type) {
     Object instance = map.get(type);
     if (instance == null) {
         return Optional.empty();
     }
     return Optional.of(type.cast(instance));
}

<S> void register(Class<S> type, S instance) {
    map.put(type, instance);
}

The implementation does a safe dynamic cast, as register ensures the safe filling of (key, value) entries.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM