【问题标题】:A question of design and object responsibility设计和对象责任的问题
【发布时间】:2009-08-06 18:39:35
【问题描述】:

我有一个设计和对象结构相关的问题。这是问题陈述:

  1. 我有一个机器人对象,它可以自行穿越地面。它将被提供运动指令,并且它必须相应地解析。例如样本输入将是: 一个。向右旋转|移动|向左旋转|移动|移动|移动

其中 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;      
    }
}

现在我的疑问是最后一类“方向”和“坐标”。坐标代表流动站的坐标对象,帮助它保持方向。目前,为了跟踪方向,我正在使用 Direction 对象的双向链表,它的工作原理非常类似于指南针。向左或向右旋转。

这是我的问题。 1. 我使用了状态模式并展示了方向跟踪的设计。有没有更好的方法来简化这个?雷姆。我需要正确维护坐标;这样,如果您向 +y 轴移动,我的坐标应该在 + 中,否则在减号中。 X轴也一样。

  1. 目前,改变流动站表面的责任间接委托给坐标和方向类。这真的正确吗?漫游者不负责保持方向吗?在我的设计中,我是否真的正确地将这个责任委托给协调和指导课程?只是因为在那里更容易操纵它?

  2. 欢迎对代码进行任何简单的设计改进和建议。随意批评。

感谢您的耐心和反馈;提前。

【问题讨论】:

  • 编辑可以解决这个问题吗?标题太宽泛了,当 OP 看起来像是在处理算法问题时,他正在寻求设计帮助。

标签: java design-patterns oop


【解决方案1】:

这是我前几天想出的一个方向枚举,我可能非常喜欢它。也许您会发现它在您的代码中很有用。

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】:

    您在询问如何简化。如果我可以提出一些大胆的建议,为什么不使用不透明的 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 字段来存储它所面对的方向。

    【讨论】:

    • 你认为垃圾回收在这种情况下是一个实际问题吗?
    • 你知道,Brian,再次查看原始代码我认为它不会出现垃圾收集问题,因为它不会分配。所以在这种情况下它很好。如果你问的是一般情况,那么根据我的经验是的,要获得流畅的游戏,你真的希望尽可能减少 GC。
    【解决方案3】:

    当我看到这段代码时,首先想到的是Direction不应该有一个String字段directionValue,而是一个存储Compass的字段(即Compass.EAST,Compass.WEST)。这将使您摆脱 MoveState.action() 中的字符串比较,因此应该使您的代码相当干净。

    命名似乎也有问题:也许NORTH,EAST,WEST和SOUTH应该在一个名为Direction(而不是Compass)的枚举中,并且当前Direction实现中的directionOnRight()等应该是它的静态方法(获取当前方向作为单个参数,并返回右/左/反向)?恕我直言,您实际上并不需要将它们存储在额外的字段中(记住关于过早优化的说法 ;-)。

    【讨论】:

      【解决方案4】:

      看到这个,我的第一反应是有些困惑。 Rover 类有 4 个状态和一个方向,这似乎有点违反直觉。我会期待一个位置和一个方向(对于我可能会期待的状态,ON/OFF/RECHARGING 或类似的东西)。

      所以,我会调查 Java enums 并有一个 NORTH/SOUTH/EAST/WEST Direction 枚举作为方向。位置(坐标)具有 x/y 位置,要移动,我只需在面对枚举上实现 deltaX()deltaY()(看起来 Carl 刚刚发布了类似的内容)

      那么您的移动代码将如下所示:

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

      无论您面向哪个方向。请注意,这委派运动。 Rover 总是在移动,但 Direction 枚举为它提供了要更改的 dx/dy。

      枚举还可以有方法clockwise()counterClockwise(),因此调用NORTH.clockwise() 将返回您的新面值EAST。每个枚举实例只有 delta 和 顺时针/逆时针方法,而您的 Rover 仅具有以下内容:

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

      这似乎更直观,也是我所期望的。我已经分别表示了 x 和 y,但您可能希望包含在一个类中。如果你这样做了,那么 Direction 枚举应该处理这样一个对象,而不是依赖它被再次分解成 x 和 y。

      【讨论】:

        【解决方案5】:

        这对我来说似乎太复杂了。我认为应该这样做:让你的机器人知道他的转角。然后,如果他被要求左转或右转,他只会改变这个角度。当他被要求移动时,他将根据 x,y 坐标中的这个角度移动。角度可以像指南针一样存储,甚至可以更简单地使用真实角度(0、90、180、270)。通过将 sin(angle) 和 cos(angle) 的移动步长相乘,可以很容易地在角度方向移动机器人。为什么t it be that simple? It will also handle more directions that just 4 and you可以在任意步长范围内移动。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2010-12-27
          • 2010-10-04
          • 2011-05-07
          • 1970-01-01
          • 2013-03-23
          • 2014-08-10
          相关资源
          最近更新 更多