Абстрактный базовый класс или класс? - PullRequest
5 голосов
/ 14 мая 2010

Для моего семестрового проекта моя команда и я должны создать файл .jar (библиотека, не запускаемая), которая содержит среду разработки игры и демонстрирует концепции ООП. Предполагается, что это FRAMEWORK, а другая команда должна использовать нашу платформу и наоборот. Поэтому я хочу знать, как мы должны начать. Мы подумали о нескольких подходах:
1. Начните с простого класса

public class Enemy {
    public Enemy(int x, int y, int health, int attack, ...) {
        ...
    }
    ...
}
public class UserDefinedClass extends Enemy {
    ...
}

2. Начните с абстрактного класса, который пользовательские враги должны наследовать абстрактные члены

public abstract class Enemy {
    public Enemy(int x, int y, int health, int attack, ...) {
        ...
    }
    public abstract void draw();
    public abstract void destroy();
    ...
}
public class UserDefinedClass extends Enemy {
    ...
    public void draw() {
        ...
    }
    public void destroy() {
        ...
    }
}

3. Создайте супер-ABC (абстрактный базовый класс), который ВСЕ наследуется от

public abstract class VectorEntity {
    ...
}
public abstract class Enemy extends VectorEntity {
    ...
}
public class Player extends VectorEntity {
    ...
}
public class UserDefinedClass extends Enemy {
    ...
}

Что я должен использовать? Или есть лучший способ?

Ответы [ 4 ]

6 голосов
/ 14 мая 2010

Что ж, трудно сказать наверняка, не зная глубоко, что вы делаете, и даже тогда это довольно субъективно. Тем не менее, есть несколько вещей, о которых стоит подумать.

  1. Собираются ли они на самом деле создать врага или все враги действительно должны быть производного типа? Если вы на самом деле не собираетесь создавать экземпляры Enemies, а не производных типов, то, скорее всего, это должен быть либо интерфейс, либо абстрактный класс.

  2. Если вы хотите обеспечить реальное поведение в своем базовом классе, то, очевидно, это должен быть класс, а не интерфейс.

  3. Методы, которые должны присутствовать для API, но для которых нет смысла предоставлять какие-либо реализации, должны быть абстрактными.

  4. Методы, в которых имеет смысл иметь реализации для них в базовом классе, должны иметь реализации в базовом классе. И если для них не имеет смысла их переопределять, сделайте их окончательными.

  5. Создание классов, разделяющих общий базовый класс, действительно имеет смысл только в том случае, если они действительно разделяют поведение или вам нужно иметь возможность обрабатывать их одинаково где-то в вашем коде. Если они не очень похожи друг на друга, то, вероятно, им не следует использовать базовый класс. Например, если предполагается, что Enemy и Player должны отображаться, может иметь смысл иметь общий базовый класс, который обрабатывает их функциональные возможности отображения. Но если бы враг был чем-то отображаемым, а Player был более абстрактным понятием - например, контроллером игры - и не отображался, то для них, вероятно, не имело бы смысла разделять базовый класс. В целом, при создании классов лучше отдавать предпочтение композиции, а не наследованию, поэтому, если рассматриваемые классы на самом деле не будут разделять поведение и на самом деле не будут иметь отношения «есть» с общим базовым классом, то они не должны иметь общий базовый класс.

  6. Предпочитают, чтобы ваши базовые классы обменивались только методами, а не данными. Другими словами, в дереве наследования лучше всего создавать экземпляры только листьев. Существуют различные вещи, такие как equals(), которые ломаются, когда у вас есть базовые классы с фактическими данными в них. Это не значит, что вы не можете этого делать - люди делают это постоянно - но это может вызвать проблемы, и лучше избегать этого, если в этом нет необходимости.

  7. Предпочитают переопределять абстрактные методы. В противном случае в производных классах вы рискуете не вызывать метод базового класса или полностью изменить его метод.

Я уверен, что смогу придумать больше, но, не зная вашего проекта, он обязательно будет довольно общим. Из 3 вариантов, которые вы дали, я бы, вероятно, пошел с 2. 3, похоже, что вы, вероятно, создадите базовый класс для несвязанных классов, а 1 приведет к тому, что Enemy будет инстанцируемым, что вам, вероятно, не нужно определенно сделал бы так, чтобы больше чем листья в вашей иерархии наследования были бы инстанцируемыми. Вероятно, вы все равно будете получать данные в базовых классах с 2, но вы, скорее всего, будете переопределять только абстрактные методы, и у вас будет меньше проблем с измененным поведением в производных классах.

1 голос
/ 14 мая 2010

Четвертым вариантом будет использование интерфейсов.

interface Enemy {

    public void draw();

    . . .

}

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

0 голосов
/ 14 мая 2010

Просто небольшой ответ из книги «Более эффективный с ++», страница 271:

"Сделать базовые классы абстрактными, которые не находятся в конце иерархии". Я слишком ленив, чтобы дать вам весь заголовок, но автор перечисляет несколько веских причин для этого.

0 голосов
/ 14 мая 2010

Мое правило поведения заключается в том, что до тех пор, пока существует более одного класса, совместно использующего одни и те же операции / данные / методы / функциональные возможности, они должны быть расширениями одного и того же абстрактного класса.

Итак, если бы это сделал я:

  • Если у ВСЕХ классов есть что-то общее, есть верхний уровень abstract class, который собирает эти функции / поля / данные в одном месте.
  • Если нет, то только те классы, которые действительно имеют что-то общее, должны расширять нижний уровень abstract class.

Если только классы являются общими для классов, можно использовать interface s. Однако я всегда нахожу, что рано или поздно я вижу, что классы, которые реализуют interface, имеют одинаковые поля private. На этом этапе я преобразую interface в abstract class, который содержит эти закрытые поля (чтобы сэкономить на строках кода, если ничего больше).

...