Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


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



 

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

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

рг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)));

 


Поделиться:



Последнее изменение этой страницы: 2019-04-11; Просмотров: 230; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.017 с.)
Главная | Случайная страница | Обратная связь