Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Effective Java TM.doc
Скачиваний:
3
Добавлен:
28.09.2019
Размер:
2.11 Mб
Скачать

3Амеияйте объедииеиие иерархией классов

В языке С конструкция union чаще всего служит для построения структур, в кото­рых можно хранить более одного типа данных. Обычно такая структура содержит по крайней мере два поля: объединение (union) и тeг (tag). Тег - это обыкновенное поле, которое используется для указания, какие из возможных типов можно хранить в объединении. Чаще всего тег представлен перечислением (unum) какого-либо типа. Структуру, которая содержит объединение и тег, иногда называют явным объедине­нием (discriminated union).

в приведенном ниже примере на языке С тип shape_t - это явное объединение, которое можно использовать для представления как прямоугольника, так и круга. Функция area получает указатель на структуру shape_t и возвращает площадь фигуры либо -1. О, если структура недействительна:

/* Явное объединение */

#include "math.h"

typedef enum { RECTANGLE, CIRCLE } shapeType_t;

typedef struct {

double length;

double width; }

rectangleDimensions_t;

94

typedef struct {

double radius;

} circleDimensions_t;

typedef struct {

shapeType_t tag;

union {

rectangleDimensions_t rectangle;

circleDimensions_t circle;

} dimensions;

}shape_t;

double area(shape_t *shape){

switch(shape->tag) {

case RECTANGLE: {

double length = shape->dimensions. rectangle.length;

double width = shape->dimensions. rectangle.width;

return length * width;

}

case CIRCLE: {

double r = shape->dimensions.circle.radius;

return M_PI * (r*r); }

default: return -1.0;

/* Неверный тег */

}

}

Создатели языка программирования Java решили исключить конструкцию union, поскольку имеется лучший механизм определения типа данных, который можно ис­пользовать для представления объектов разных типов: создание подклассов. Явное объединение в действительности является лишь бледным подобием иерархии классов.

Чтобы преобразовать объединение в иерархию классов, определите абстрактный класс, в котором для каждой операции, чья работа зависит от значения тега, представ­лен отдельный абстрактный метод. В предыдущем примере единственной такой опера­цией является area. Полученный абстрактный класс будет корнем иерархии классов. При наличии операции, функционирование которой не зависит от значения тега, пред­ставьте ее как неабстрактный метод корневого класса. Точно так же, если в явном объединении, помимо tag и union, есть какие-либо поля данных, эти поля представля­ют данные, которые едины для всех типов, а потому их нужно перенести в корневой класс. В приведенном примере нет операций и полей данных, которые бы не зависели от типа.

Далее, для каждого типа, который может быть представлен объединением, опре­делите неабстрактный подкласс корневого класса. В примере такими типами являются круг и прямоугольник. В каждый подкласс поместите те поля данных, которые ха­рактерны для соответствующего типа. Так, радиус является характеристикой круга,

95

а длина и ширина описывают прямоугольник. Кроме того, в каждый подкласс помес­тите соответствующую реализацию для всех абстрактных методов в корневом классе. Представим иерархию классов для нашего примера явного объединения:

abstract class Shape {

abstract double агеа(); }

class Circle extends Shape {

final double radius;

Circle(double radius) { this.radius = radius; }

double агеа() { return Math.PI * radius*radius; }

}

class Rectangle extends Shape {

final double length;

final double width;

Rectangle(double length, double width) {

this.length = length;

this.width = width;

}

double а геа() { return length * width; }

}

По сравнению с явным объединением, иерархия классов имеет множество пре­имуществ. Главное из них заключается в том, что иерархия типов обеспечивает их безопасность. В данном примере каждый экземпляр класса Shape является либо пра­вильным экземпляром Circle, либо прав ильным экземпляром Rectangle. Поскольку язык С не устанавливает связь между тегом и объединением, возможно создание структуры shape_t, в которой содержится мусор. Если в теге указано, что shape_t соответствует прямоугольнику, а в объединении описывается круг, все пропало. И даже если явное объединение инициализировано правильно, оно может быть пере­дано не той функции, которая соответствует значению тега.

Второе преимущество иерархии классов заключается в простоте и ясности про­граммного кода. Явное объединение загромождено шаблонами: декларация типа пере­числения, декларация поля тега, поле переключения тега, обработка непредвиденных значений тега и т. Д. Программный код объединения неудобно читать даже из-за того, что операции для различных типов перемешаны, а не разделены по типу.

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

96

доступ к его исходному коду. Придется добавить новое значение в представленный тип перечисления, а также новую альтернативу в оператор switch для каждой опера­ции в объединении. И наконец, нужно снова компилировать программу. И если для какого-либо метода вы забыли указать новую альтернативу, это не будет обнаружено до тех пор, пока программа не будет запущена на исполнение, и лишь при условии, что вы тщательно проверяете неидентифицированные значения тега и генерируете соответ­ствующее сообщение об ошибке.

Четвертое преимущество иерархии классов связано с ее способностью отражать естественные иерархические отношения между типами, что обеспечивает повышенную гибкость и улучшает проверку типов на этапе компиляции. Допустим, что явное объединение в исходном примере допускает также построение квадратов. В иерархии классов можно показать, что квадрат - это частный случай прямоугольника (при условии, что оба они неизменны):

class Square extends Rectangle {

Square (double side) {

super(side, side);

}

double side() {

return length; // Возвращает длину или, что то же самое, ширину

}

}

Иерархия классов, представленная в этом примере, не является единственно

возможной для явного объединения. Данная иерархия содержит несколько конструк­торских решений, заслуживающих особого упоминания. для классов в иерархии, за исключением класса Square, доступ к полям обеспечивается непосредственно, а не че­рез методы доступа. Это делается для краткости, и было бы ошибкой, если бы классы были открытыми (статья 19). Указанные классы являются ·неизменяемыми, что не всегда возможно, но это обычно хорошее решение (статья 13).

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

Другой вариант использования конструкции union из языка С не связан с явными объединениями и касается внутреннего представления элемента данных, что нарушает систему типизации. Например, следующий фрагмент программы на языке С печатает машинно-зависимое шестнадцатеричное представление поля типа float:

union {

float f;

int bits;

}sleaze;

97

sleaze.f = 6.699е-41;

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]