Главная » Java, Советы » 3амеияйте объедииеиие иерархией классов

0

 

В языке С конструкция 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;

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;

 

sleaze.f = 6.699е-41;

/* Помещает данные в одно из полей объединения … */

ргiпtf("%х\n", sleaze.bits); /* … и читает их из другого */

 

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

В пакете jаvа.lапg есть методы преобразования чисел с плавающей точкой в двоичную форму, однако в целях переносимости эти методы определены в соответствии со строго регламентированным двоичным представлением. Следующий фрагмент кода, который почти равнозначен предыдущему фрагменту на языке С, гарантирует, что программа будет всегда печатать один и тот же результат независимо от того, где она запущена:

 

Sуstет.оut.ргintln(

Intеgег.tоНехStгing(Flоаt.flоаtТolпtВits(6. 69ge-41f)));

 

Источник: Джошуа Блох, Java TM Эффективное программирование, Издательство «Лори»

По теме:

  • Комментарии