Как применять наследование

From AsIsWiki
Revision as of 11:54, 4 April 2015 by Alex (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search
Форум

Назад | Оглавление


Contents

Размещайте общие операции и поля в суперклассе

По этой причине мы поместили поле name в классе Person и не стали повторять его в классах Employee и Student.


Старайтесь не использовать защищенные поля

Некоторые разработчики полагают, что следует "на всякий случай" объявлять большинство полей защищенными, чтобы по мере необходимости подклассы могли к ним обратиться. Однако, есть две причины, по которым этот механизм не гарантирует достаточной защиты. Во-первых, множество подклассов не ограничено - любой программист может создать подкласс вашего класса и написать программу, получающую непосредственный доступ к защищенным полям экземпляра, нарушая инкапсуляцию. Во-вторых, в Java к защищенным полям имеют доступ все классы, размещенные в том же пакете, независимо от того, являются они подклассами данного класса или нет.

Однако, специализированные методы, которые переопределяются в подклассах, защищенными объявлять полезно. Ярким примером служит метод clone().


Используйте наследование для моделирования отношения "является"

Наследование позволяет экономить время и усилия при разработке программ, поэтому некоторые программисты им злоупотребляют. Допустим, нам нужен класс Contractor. Сотрудник, нанимаемый по констракту, имеет имя и дату заключения договора, однако у него нет оклада. У него почасовая оплата, причем он работает не так давно, чтобы возникла необходимость повышать оплату его труда. Ниже показано, как можно сделать класс Contractor подклассом класса Employee, добавив поле hourlyWage.

class Contractor extends Employee {
    ...
    private double hourlyWage;
}

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

Отношение "сотрудник с почасовой оплатой / постоянный сотрудник" не удовлетворяет отношению "является". Сотрудники, нанятые по контракту, не являются разновидностью постоянных сотрудников.


Не используйте наследование, если не все наследуемые методы имеют смысл

Допустим, что нам нужно написать класс Holiday. Разумеется, праздники - это разновидность календарных дней, а дни можно представить в виде объектов GregorianCalendar, поэтому можно применять наследование.

class Holiday extends GregorianCalendar( ... )

К сожалению, множество праздников при наследовании не закрыто. Среди открытых методов класса GregorianCalendar есть метод add(), который может превратить праздники в будни.

Holiday christmas;

christmas.add(GregorianCalendar.DAY, 12);

Итак, наследование в этом случае не подходит.


Переопределяя метод, не изменяйте ожидаемое поведение класса

При переопределении методов необходимо не только соблюдать синтаксис, но, что гораздо важнее, учитывать поведение класса. В этом компилятор не поможет вам; он не в состоянии проверить, оправдано ли переопределение метода. Предположим, что вы захотели устранить проблему с методом add() в классе Holiday, переопределив этот метод так, чтобы он, например, не выполнял никаких действий либо возвращал следующий праздничный день. Однако, такое переопределение не оправдано. При выполнении приведенной ниже последовательности команд, пользователь вправе ожидать определенного результата, независимо от того, является ли x экземпляром GregorianCalendar или Holiday.

int d1 = x.get(Calendar.DAY_OF_MONTH);

x.add(Calendar.DAY_OF_MONTH, 1);

int d2 = x.get(Calendar.DAY_OF_MONTH);

System.out.println(d2 - d1);

Очевидно, что приведенный пример несколько надуман. Разные пользователи посчитают естественным различное поведение программы. Так, например, можно услышать мнение, что в методе Manager.equals() не должна учитываться премия, поскольку метод Employee.equals() игнорирует ее. Подобные споры могут длиться бесконечно и не дать никакого результата. Принимая конкретное решение, следует руководствоваться целями, для которых создается программа.


Используйте полиморфизм, а не информацию о типе

Вспомните о полиморфизме, как только увидите код, имеющий следующий вид:

if (x имеет_тип 1) {
    действие_1(x);
} else {
    if (x имеет_тип 2) {
        действие_2(x);
    }
}

Носят ли действие_1 и действие_2 общий характер? Если да, поместите соответствующие методы в общий суперкласс или интерфейс обоих типов. Тогда можно просто вызвать приведенный ниже метод, и, с помощью механизма динамического связывания, присущего полиморфизму, выполнить правильное действие.

x.действие();

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


Не злоупотребляйте механизмом отражения

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



Форум

Назад | Оглавление

Personal tools
Namespaces

Variants
Actions
Navigation
Tools