У меня вопрос, связанный с дизайном и структурированием объекта. Вот постановка проблемы:

  1. У меня есть объект-робот, который должен самостоятельно перемещаться по земле. Ему будут предоставлены инструкции по перемещению, и он должен быть проанализирован соответствующим образом. Например, входной образец будет: a. RotateRight | Move | RotateLeft | Move | Move | Move

Где движение - это движение юнита по сетке.

Я сделал очень простой дизайн на java. (Полный код вставлен ниже)

package com.roverboy.entity;

import com.roverboy.states.RotateLeftState;
import com.roverboy.states.RotateRightState;
import com.roverboy.states.State;

public class Rover {

    private Coordinate roverCoordinate;
    private State roverState;

    private State rotateRight;
    private State rotateLeft;
    private State move;

    public Rover() {
        this(0, 0, Compass.NORTH);
    }

    public Rover(int xCoordinate, int yCoordinate, String direction) {
        roverCoordinate = new Coordinate(xCoordinate, yCoordinate, direction);
        rotateRight = new RotateRightState(this);
        rotateLeft = new RotateLeftState(this);
        move = new MoveState(this);
    }

    public State getRoverState() {
        return roverState;
    }

    public void setRoverState(State roverState) {
        this.roverState = roverState;
    }

    public Coordinate currentCoordinates() {
        return roverCoordinate;
    }

    public void rotateRight() {
        roverState = rotateRight;
        roverState.action();
    }

    public void rotateLeft() {
        roverState = rotateLeft;
        roverState.action();
    }

    public void move() {
        roverState = move;
        roverState.action();
    }
}


package com.roverboy.states;

public interface State {

    public void action();
}

package com.roverboy.entity;

import com.roverboy.states.State;

public class MoveState implements State {

    private Rover rover;

    public MoveState(Rover rover) {
        this.rover = rover;
    }

    public void action() {
        rover.currentCoordinates().setXCoordinate(
                (Compass.EAST).equalsIgnoreCase(rover.currentCoordinates()
                        .getFacingDirection()) ? rover.currentCoordinates()
                        .getXCoordinate() + 1 : rover.currentCoordinates()
                        .getXCoordinate());

        rover.currentCoordinates().setXCoordinate(
                (Compass.WEST).equalsIgnoreCase(rover.currentCoordinates()
                        .getFacingDirection()) ? rover.currentCoordinates()
                                .getXCoordinate() - 1 : rover.currentCoordinates()
                                .getXCoordinate());

        rover.currentCoordinates().setYCoordinate(
                (Compass.NORTH).equalsIgnoreCase(rover.currentCoordinates()
                        .getFacingDirection()) ? rover.currentCoordinates()
                                .getYCoordinate() + 1 : rover.currentCoordinates()
                                .getYCoordinate());

        rover.currentCoordinates().setYCoordinate(
                (Compass.SOUTH).equalsIgnoreCase(rover.currentCoordinates()
                        .getFacingDirection()) ? rover.currentCoordinates()
                                .getYCoordinate() - 1 : rover.currentCoordinates()
                                .getYCoordinate());
    }
}


package com.roverboy.states;

import com.roverboy.entity.Rover;

public class RotateRightState implements State {

    private Rover rover;

    public RotateRightState(Rover rover) {
        this.rover = rover;
    }

    public void action() {
        rover.currentCoordinates().directionOnRight();
    }

}

package com.roverboy.states;

import com.roverboy.entity.Rover;

public class RotateLeftState implements State {

    private Rover rover;

    public RotateLeftState(Rover rover)
    {
        this.rover = rover;
    }

    public void action() {
        rover.currentCoordinates().directionOnLeft();
    }

}


package com.roverboy.entity;

public class Coordinate {

    private int xCoordinate;
    private int yCoordinate;
    private Direction direction;
    {
        Direction north = new Direction(Compass.NORTH);
        Direction south = new Direction(Compass.SOUTH);
        Direction east = new Direction(Compass.EAST);
        Direction west = new Direction(Compass.WEST);
        north.directionOnRight = east;
        north.directionOnLeft = west;
        east.directionOnRight = north;
        east.directionOnLeft = south;       
        south.directionOnRight = west;
        south.directionOnLeft = east;
        west.directionOnRight = south;
        west.directionOnLeft = north;
        direction = north;
    }

    public Coordinate(int xCoordinate, int yCoordinate, String direction) {
        this.xCoordinate = xCoordinate;
        this.yCoordinate = yCoordinate;
        this.direction.face(direction);
    }

    public int getXCoordinate() {
        return xCoordinate;
    }
    public void setXCoordinate(int coordinate) {
        xCoordinate = coordinate;
    }
    public int getYCoordinate() {
        return yCoordinate;
    }
    public void setYCoordinate(int coordinate) {
        yCoordinate = coordinate;
    }

    public void directionOnRight()
    {
        direction.directionOnRight();
    }

    public void directionOnLeft()
    {
        direction.directionOnLeft();
    }

    public String getFacingDirection()
    {
        return direction.directionValue;
    }
}

class Direction
{
    String directionValue;
    Direction directionOnRight;
    Direction directionOnLeft;

    Direction(String directionValue)
    {
        this.directionValue = directionValue;
    }

    void face(String directionValue)
    {
        for(int i=0;i<4;i++)
        {
            if(this.directionValue.equalsIgnoreCase(directionValue))
                break;
            else
                directionOnRight();
        }
    }

    void directionOnRight()
    {
        directionValue = directionOnRight.directionValue;
        directionOnRight = directionOnRight.directionOnRight;
        directionOnLeft = directionOnRight.directionOnLeft;             
    }

    void directionOnLeft()
    {
        directionValue = directionOnLeft.directionValue;
        directionOnRight = directionOnLeft.directionOnRight;
        directionOnLeft = directionOnLeft.directionOnLeft;      
    }
}

Теперь я сомневаюсь в этом последнем классе «Направление» и «Координата». Координата представляет собой объект координат для ровера, который помогает ему сохранять направление. В настоящее время для отслеживания направления я использую двусвязный список объектов направления, которые в значительной степени работают как компас. Поверните влево или вправо.

Вот вопросы, которые у меня есть. 1. Я использовал шаблон состояний и показанный дизайн для отслеживания направления. Есть ли лучший подход, чтобы упростить даже это? Рем. Мне нужно правильно вести координаты; так что если вы двигаетесь к оси + y, мои координаты должны быть в + иначе в минусе. То же самое для оси X.

  1. В настоящее время ответственность за изменение лица марсохода косвенно делегируется координатам и классу направления. Это правда правильно? Разве марсоход не отвечает за сохранение направления? Действительно ли я прав в своем дизайне, делегируя эту ответственность классу координации и направления; только потому, что там проще манипулировать?

  2. Мы будем приветствовать любые простые улучшения дизайна и предложения по коду. Не стесняйтесь критиковать.

Благодарим за терпение и отзывы. заранее, авансом.

1
Priyank 6 Авг 2009 в 22:39

5 ответов

Лучший ответ

Вы спрашиваете, как упростить. Если я могу предложить что-то смелое, почему бы не использовать непрозрачный int для направления и не иметь статический класс, чтобы справиться с этим? Под «непрозрачным int» я подразумеваю, что ваш код никогда не будет использовать его напрямую, а только как аргумент класса Direction.

Вот частичный псевдокод в стиле java, чтобы показать, что я имею в виду.

// 0 = east, 1 = north, 2 = west, ...
public class Direction {
  static int [] moveX = [ 1, 0, -1, 0];
  static final int NORTH = 1;
  // coordinates after moving one step in the given direction
  static Pair move(int direction, Pair old) {
     return new Pair( old.x + moveX[direction] , old.y + moveY[direction] );
  }
  static int turnLeft(int direction) { 
     return (direction+1) % 4;
  }
  static int turnRight(int direction) {
     return (direction+3) % 4;
  }
}

Преимущество такого способа работы заключается в использовании меньшего количества выделений, поэтому сборщику мусора не нужно будет запускаться так часто. Еще одно преимущество заключается в том, что дизайн остается объектно-ориентированным в том смысле, что вы можете легко изменить класс направления, если позже вы захотите иметь возможность вращаться, например, 45 градусов за раз.

Чтобы ответить на ваши другие вопросы, я думаю, что совершенно нормально делегировать классу Direction задачу изменения координаты в определенном направлении. Марсоход будет отвечать за поддержание направления только в том смысле, что объект марсохода будет содержать поле int для хранения направления, в котором он смотрит.

2
redtuna 6 Авг 2009 в 18:54

Вот перечисление направлений, которое я придумал на днях и которое, возможно, мне очень нравится. Возможно, вы найдете это полезным в своем коде.

import java.awt.Point;

public enum Direction {
    E(1, 0), N(0, 1), W(-1, 0), S(0, -1);
    private final int   dy;
    private final int   dx;

    private Direction(int dx, int dy) {
        this.dx = dx;
        this.dy = dy;
    }

    public Direction left() {
        return skip(1);
    }

    public Direction right() {
        return skip(3);
    }

    public Direction reverse() {
        return skip(2);
    }

    private Direction skip(int n) {
        final Direction[] values = values();
        return values[(ordinal() + n) % values.length];
    }

    public Point advance(Point point) {
        return new Point(point.x + dx, point.y + dy);
    }
}
4
Carl Manaster 6 Авг 2009 в 19:06

Первое, что приходит мне в голову, когда я вижу этот код, - это то, что в Direction не должно быть строкового поля directionValue, а должно быть поле, в котором хранится компас (то есть Compass.EAST, Compass.WEST). Это избавит вас от сравнений строк в MoveState.action () и, следовательно, сделает ваш код значительно чище.

Также, похоже, есть проблема с именованием: возможно, СЕВЕР, ВОСТОК, ЗАПАД и ЮГ должны быть в перечислении под названием Direction (вместо Compass), а directionOnRight () и т. Д. В текущей реализации Direction должны быть его статическими методами (получение текущее направление в качестве единственного аргумента и возвращающее направление вправо / влево / назад)? ИМХО (помните поговорку о преждевременной оптимизации ;-) не нужно хранить их в дополнительных полях.

1
Roland Ewald 6 Авг 2009 в 18:54

При взгляде на это я сразу же подумал, что это некоторая путаница. Класс Rover имеет 4 состояния и направление, что кажется немного нелогичным. Я бы ожидал позицию и направление (для State я, возможно, ожидал бы ВКЛ / ВЫКЛ / ПЕРЕЗАРЯДКА или что-то подобное).

Итак, я бы исследовал перечисления Java и есть перечисление NORTH / SOUTH / EAST / WEST Direction для направления. Позиция (координата) имеет позиции x / y, и для перемещения я бы просто реализовал deltaX() и deltaY() в перечислении лицевых сторон (похоже, что Карл только что опубликовал что-то подобное)

Тогда ваш код движения будет выглядеть просто так:

x += facing.deltaX()
y += facing.deltaY()

В каком бы направлении вы ни смотрели. Обратите внимание, что это не делегирует движение. Ровер всегда движется, но перечисление Direction дает ему dx / dy для изменения.

В перечислении также могут быть методы clockwise() и counterClockwise(), поэтому вызов NORTH.clockwise() вернет ваше новое значение лицевой стороны EAST. Каждый экземпляр перечисления будет иметь только методы дельты и по часовой / против часовой стрелки, а ваш Rover просто имеет следующее:

private Direction facing;
private int x;
private int y;

Который кажется гораздо более интуитивным и ожидаемым. Я выразил x и y отдельно, но вы можете захотеть обернуть его в один класс. Если вы это сделаете, то перечисление Direction должно обрабатывать такой объект, а не полагаться на его повторное разбиение на x и y.

1
Brian Agnew 6 Авг 2009 в 19:15

Мне это кажется слишком сложным. Я думаю, что это нужно сделать так: пусть ваш робот знает угол поворота. Затем, если его попросят повернуть налево или направо, он просто изменит этот угол. Когда его просят переместиться, он будет двигаться в соответствии с этим углом в координатах x, y. Угол может быть сохранен как компас или, что еще проще, с реальным углом (0, 90, 180, 270). Робота легко переместить в угловом направлении, умножив шаг движения на sin (угол) и cos (угол). Почему t it be that simple? It will also handle more directions that just 4 and you сможет двигаться в любом диапазоне шагов.

0
Yaroslav Yakovlev 24 Авг 2009 в 10:40