Дизайн: Наличие ссылок в подклассе поля в суперклассе - PullRequest
2 голосов
/ 29 октября 2009

Прежде всего, я понятия не имею, отражает ли заголовок мой вопрос. Я прошу прощения и прошу изменить его, если у вас есть идея получше.

Теперь у меня проблема в основном с дизайном. Я не уверен, что то, что я хочу, это хорошая практика, но это очень удобно.

У меня есть интерфейс с именем IMovement .

Два класса реализуют IMovement : VelocityMovement , SplineMovement .

У меня также есть класс под названием Sprite . Спрайт имеет IMovement .

Наконец, у меня есть два подкласса Sprite: Игрок и Враг .

Я ЗНАЮ , что Игрок будет ВСЕГДА имеют VelocityMovement и
Я ЗНАЮ , что Враг будет ВСЕГДА иметь SplineMovement .

Неправильно ли (по замыслу или из практики) иметь, кроме ссылки на суперклассы на IMovement, атрибут для VelocityMovement или SplineMovement от Player или Enemy соответственно?

Другими словами:

Interface IMovement {
  public Vector2 getPosition(int time);
}

class VeloctiyMovement implements IMovement { 
   public Vector2 velocity;

   public Vector2 getPosition(int time) { ... }
}

class SplineMovement implements IMovement { 
   public Spline spline;

   public Vector2 getPosition(int time) { ... }
}

class Sprite {
  IMovement movement;
}

class Player extends Sprite {
  public VelocityMovement velocityMovement;

  public Player() {
    velocityMovement = new VelocityMovement();
    movement = velocityMovement;
  }
}

class Enemy extends Sprite {
  public SplineMovement splineMovement;

  public Enemy() {
    splineMovement = new SplineMovement();
    movement = splineMovement();
  }
}

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

Причина, по которой я указываю 100 раз, заключается в том, что я также обеспокоен, стоит ли кастовать дорого.

Пример, вместо:

// When I need the velocity
Vector2 velocity = ((VelocityMovement)player.movement).velocity;
...

// When I don't care
Vector2 position = player.movement.GetPosition(time);
...

Теперь я могу сделать это:

Vector2 velocity = player.VelocityMovement.velocity;
...

Vector2 position = player.movement.GetPosition(time);
...

Итак, мой вопрос: должен ли игрок иметь поле VelocityMovement, а у врага - SplineMovement? Или это плохой дизайн?

Мне бы хотелось, чтобы ваши мысли.

UPDATE

Теперь я вспомнил, почему я выбрал поле IMovement вместо того, чтобы Sprite реализовывал IMovement (как правильно подсказывает JacobM ).

Это потому, что в некоторых случаях у врага может быть SplineMovement, а в других - VelocityMovement.

Хотя я неправильно сказал «Я ЗНАЮ, что Враг ВСЕГДА будет иметь SplineMovement», я должен был сказать, что я знаю, что враг имеет VelocityMovement в местах, где я собираю всех врагов с VelocityMovements. Надеюсь, вы меня понимаете (иначе я чувствую себя идиотом).

Например:

List<Enemy> enemies = GetAllVelocityMovementEnemies();

Я делаю это потому, что где-то в игровой логике я хочу воздействовать только на группу врагов, у которых есть VelocityMovements.

Итак, хотя ответ JacobM совершенно верен как дизайн, это не для моего случая. В противном случае мне пришлось бы создать подкласс Enemy с помощью EnemyVelocityMovement, EnemySplineMovement и т. Д. И т. Д.

Итак, извините за недоразумение, я решил, что Ответ Брайана Агнью является лучшим для моего дизайна.

О, и да, опасения за кастинг - это действительно преждевременная оптимизация. ;)

Ответы [ 2 ]

3 голосов
/ 29 октября 2009

Не беспокойтесь об оптимизации на данный момент. Преждевременная оптимизация вызовет у вас горе, которое, скорее всего, не оправдано.

Итак, чтобы ответить на ваш вопрос, не дублируйте поля по всей иерархии - это головная боль при обслуживании, и ваша предполагаемая экономия времени будет незначительной. Если вы будете следовать принципу DRY , вы избавите себя от многих головных болей.

Попросите ваши классы двигаться (скажем) через что-то вроде:

player.move()

в противном случае (как вы обнаруживаете), вы должны найти их IMovement, а затем разыграть их, а затем спросить это для скорости, и это действительно нарушает инкапсуляцию. Ваши классы Player / Enemy, вероятно, будут иметь различные реализации move(), в зависимости от их особенностей.

Возможно, вам не нужен метод move(), но я пытаюсь продемонстрировать, что интеллект содержится в в классе , а не в вызывающем коде. Так что вам не нужно вообще выставлять свои IMovement объекты.

Наконец, я бы оптимизировал только после того, как он заработал, и , измерив его, чтобы определить, есть ли у вас проблемы!

2 голосов
/ 29 октября 2009

У меня нет времени, чтобы попытаться написать код, но, возможно, если используемый вами язык поддерживает универсальные типы, вы могли бы сделать что-то вроде

class Sprite<T extends IMovement> {
   T movement;
}

class Player extends Sprite<VelocityMovement> {
}

class Enemy extends Sprite<SplineMovement>{
}

Таким образом, когда вы запрашиваете объект движения игрока, вы уже знаете, что это VelocityMovement (но, конечно, вы также можете называть его IMovement).

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