Использование интерфейса, реализованного подклассом из коллекции суперклассов без instanceof - PullRequest
1 голос
/ 12 апреля 2019

Я пишу небольшую 2D-игру, все игровые элементы являются подклассами GameObject. В классе Game есть коллекция GameObject. Моя проблема в том, что когда игрок выполняет действие, я просматриваю коллекцию GameObject, чтобы найти то, что находится перед игроком, и затем я хочу использовать ТОЛЬКО методы интерфейсов, реализованные подклассами GameObject, без использования instanceof и приведения.

Вот (очень) упрощенная диаграмма класса ситуации

Я пытался реализовать шаблон посетителя, но я хочу, чтобы функция visit() принимала Activable или Obstacle в качестве аргумента, а не TV или Wall.

Вот пример кода:

class Game {
    private ArrayList<GameObject> gameObjects;
    ...
    public void actionPerformed(...) {
        GameObject current;
        //find the focused Game object
        ...

        //What's easiest but I don't want
        if(gameObjects instanceOf Obstacle) {
            ((Obstacle) current).aMethod()
            ...
        } else if (gameObjects instanceOf Activable) {
            ((Activable) current).activate()
            ...
        }
        ...

        //What visitor allow me
        public void watchVisit(TV tv) {
            tv.watch();
        }
        public void hitVisit(Wall w) {
            //...
        }

        //What I want
        public void activableVisit(Activable a) {
            a.activate();
        }
        public void walkVisit(Obstacle o) {
            //...
        }
        ...
}

GameObject:

class GameObject {
    public void acceptWatch(Game g) {
        //Do nothing, only implemented in class TV
    }
    //Same with wall
    ...
}

ТВ:

class TV extends Item implements Activable {
    public void acceptWatch(Game g) {
        //this works if watchVisit take a "TV" but not if it's a "Activable"
        g.watchVisit(this);
    }
    public void watch() {
        ...
    }
    ...
}

Как я могу решить эту проблему?

Ответы [ 2 ]

0 голосов
/ 12 апреля 2019

Я думаю, будет сложно найти хоть один ответ на ваш вопрос. Это входит в дизайн и намерения, и в этом пространстве всегда есть компромиссы, редко определенные ответы.

Однако я думаю, что вы должны взглянуть на Composite Pattern . Составной шаблон по существу берет группу объектов, которые отличаются, и обрабатывает их одинаково. Это достигается путем реализации всех интерфейсов для всех объектов более высокого уровня, Компонента, так что все объекты наследуют общий набор методов и атрибутов.

В вашем примере вы можете определить GameObject в качестве компонента и реализовать интерфейсы Obstacle и Activatable. Также добавьте некоторые вспомогательные методы, такие как isObstacle и isActivatable, чтобы вы могли тестировать без приведения. Теперь вы можете попросить посетителя пройти по списку и выполнять действия только с определенными объектами, что, как я думаю, вам нужно.

0 голосов
/ 12 апреля 2019

Вместо создания всех этих отдельных методов для watchTV() или hitWall() в GameObject, сделайте GameObject Abstract с любыми общими переменными на нем (name, activatable, obstacle,и т. д.) с помощью одного Abstract метода, называемого doButtonOneActivity().

. Затем заставьте другие ваши объекты, такие как TV или Wall, расширить GameObject и переопределите метод doButtonOneActivity() любым конкретным элементом.будет делать при нажатии.

Теперь ваш класс Game может просто вызвать doButtonOneActivity() на GameObject, и сам объект выяснит, что ему нужно делать, без необходимости вручную управлять этим.

Надеюсь, это поможет!

Игра:

public class Game implements Serializable, IClusterable {
    private static final long serialVersionUID = 1L;

    private ArrayList<GameObject> gameObjects;

    public void actionPerformed(GameObject current) {

        // Let the object do whatever it's supposed to do on button press, in either case.
        current.doButtonOneActivity();

        if(current.isActivatable()){
            // Do whatever extra thing you need to do if this one is Activatable...
            System.out.println("Hey, this thing is activatable!");
        } else if (current.isObstacle()){
            // Do something an obstacle needs you to do
            System.out.println("Hey, this thing is an obstacle!");
        }

    }
}

GameObject, который является абстрактным.

public abstract class GameObject implements Serializable, IClusterable {
    private static final long serialVersionUID = 1L;

    public String name;
    private boolean activatable;
    private boolean obstacle;

    public GameObject(String name, boolean activatable, boolean obstacle) {
        super();
        this.name = name;
        this.activatable = activatable;
        this.obstacle = obstacle;
    }

    public abstract void doButtonOneActivity();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isActivatable() {
        return activatable;
    }

    public void setActivatable(boolean activatable) {
        this.activatable = activatable;
    }

    public boolean isObstacle() {
        return obstacle;
    }

    public void setObstacle(boolean obstacle) {
        this.obstacle = obstacle;
    }
}

Телевизор, расширяющий GameObjectи заполняет нужный метод.

public class TV extends GameObject implements Serializable, IClusterable {
    private static final long serialVersionUID = 1L;

    public TV(String name, boolean activatable, boolean obstacle) {
        super(name, activatable, obstacle);
    }

    @Override
    public void doButtonOneActivity() {
        if(isActivatable()){
            // do whatever I need to do as a TV when I am activated...
           }
           if (isObstacle()){
            // do whatever I need to do as a TV when I am activated as an obstacle...
           }
           System.out.println("I'm a TV and I was called. My name is: " + getName()); 

    }
}

Стена, которая расширяет GameObject и заполняет необходимый метод.

public class Wall extends GameObject implements Serializable, IClusterable {
    private static final long serialVersionUID = 1L;

    public Wall(String name, boolean activatable, boolean obstacle) {
        super(name, activatable, obstacle);
    }

    @Override
    public void doButtonOneActivity() {    
        if(isActivatable()){
         // do whatever I need to do as a Wall when I am activated...
        }
        if (isObstacle()){
         // do whatever I need to do as a Wall when I am activated as an obstacle...
        }
        System.out.println("I'm a wall and I was called. My name is: " + getName()); 

    }
}
...