Вопрос дизайна и ответственности объекта - PullRequest
1 голос
/ 06 августа 2009

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

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

Куда движется движение единицы на сетке.

Я сделал очень простой дизайн в 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;      
    }
}

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

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

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

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

Спасибо за ваше терпение и отзывы; заблаговременно.

Ответы [ 5 ]

4 голосов
/ 06 августа 2009

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

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);
    }
}
2 голосов
/ 06 августа 2009

Вы спрашиваете, как упростить. Если я могу предложить что-то смелое, почему бы не использовать непрозрачный 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 для хранения направления, в котором он направлен.

1 голос
/ 06 августа 2009

Моя непосредственная мысль при взгляде на это - некоторая путаница. Класс Rover имеет 4 состояния и направление, что кажется немного противоречивым. Я ожидал бы позицию и направление (для государства я, возможно, ожидал бы, ON / OFF / RECHARGING или что-то подобное).

Итак, я бы исследовал перечисления 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 голос
/ 06 августа 2009

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

Кажется, также есть проблема с именованием: возможно, NORTH, EAST, WEST и SOUTH должны быть в перечислении, называемом Direction (вместо Compass), а directionOnRight () и т. Д. В текущей реализации Direction должны быть статическими. методы (получение текущего направления в качестве единственного аргумента и возвращение правого / левого / обратного направления)? Вам не нужно хранить их в дополнительных полях ИМХО (помните высказывание о преждевременной оптимизации; -).

0 голосов
/ 24 августа 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...