Главная страница
Навигация по странице:

  • // Вырожденные классы, подобные этому, // не должны быть открытыми

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

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

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

  • /* Яблочный соус какой-то! */

  • /* Это не группа, а жидкости */

  • // Шаблон перечисления·целых чисел (int enum) // весьма сомнительный

  • // Шаблон typesafe enum

  • Effective Java tmprogramming Language GuideJ o s h u a b lo c h


    Скачать 1.05 Mb.
    НазваниеEffective Java tmprogramming Language GuideJ o s h u a b lo c h
    Дата03.04.2018
    Размер1.05 Mb.
    Формат файлаpdf
    Имя файлаBlokh_Dzh_-_Java_Effektivnoe_programmirovanie.pdf
    ТипДокументы
    #40178
    страница12 из 25
    1   ...   8   9   10   11   12   13   14   15   ...   25
    3 а меняйте структуру классом Конструкция struct языка Сне была принята в языке программирования Java потому, что класс выполняет все тоже самое, что может делать структура, и даже более того. Структура группирует несколько полей данных в один общий объект, тогда как класс связывает с полученным объектом операции, а также позволяет скрывать поля данных от пользователей объекта. Иными словами, класс может инкапсулировать

    92
    encapsulate) свои данные в объекте, доступ к которому осуществляется только через его методы. Тем самым у разработчика появляется возможность менять внутреннее представление объекта (статья 12). После первого знакомства с языком Java некоторые программисты, ранее пользовавшиеся языком С, приходят к заключению, что в некоторых случаях класс слишком тяжеловесен, чтобы заменить структуру, однако это не так. Вырожденный класс, состоящий исключительно из полей данных, примерно равнозначен структуре из языка С
    // Вырожденные классы, подобные этому,
    // не должны быть открытыми
    class Point { public float х public float у Поскольку доступ к таким классам осуществляется через поле данных, они лишены преимуществ инкапсуляции. Вы не можете поменять структуру такого класса, не изменив его API. Вы не можете использовать каких-либо инвариантов. Вы не можете предпринять каких-либо дополнительных действий, когда меняется значение поля. для программистов, строго придерживающихся объектно- ориентированного подхода, такой класс заслуживает осуждения, ив любом случае его следует заменить классом с закрытыми полями и открытыми методами доступа
    // Класс с инкапсулированной структурой class Point { private float х private float уху. х = х this.y = уху х) { this.x = х }
    public void setY(float у) { this.y = у В отношении открытых классов борцы за чистоту языка программирования совершенно правы если класс доступен за пределами пакета, то предусмотрительный программист создает соответствующие методы доступа, оставляя возможность изменения внутреннего представления этого класса. Если открытый класс показал клиенту свои поля данных, то всякая возможность менять это представление может быть потеряна, поскольку программный код клиентов открытого класса может оказаться где угодно.
    Однако если класс доступен только в пределах пакета или является закрытым вложенным классом, то никакого настоящего ущерба от прямого доступа к его полям сданными не будет, при условии, что эти поля действительно описывают выстраиваемую этим классом абстракцию. По сравнению с методами доступа такой подход создает меньше визуального беспорядка ив декларации класса, и у клиентов, пользующихся этим классом. И хотя программный код клиента зависит от внутреннего представления класса, он может располагаться лишь в том же пакете, где находится класс. В том редком случае, когда необходимо поменять внутреннее представление класса, изменения можно произвести так, чтобы за пределами пакета они никого не коснулись. В случае же с закрытым вложенным классом область изменений ограничена еще больше внешним классом. Несколько классов в библиотеках для платформы Java нарушают совет, касающийся запрещения непосредственного доступа к полям открытого класса. В частности, это классы Point и Dimension из пакета java.awt. Не следует подражать этим классам, лучше рассматривать их как предупреждение. В статье 37 показано, как раскрытие внутреннего содержания класса Dimension привело к серьезным проблемам с производительностью, которые нельзя было разрешить, не затрагивая клиентов.
    3 а м е и я й те об ъ ед и и е и и е иерархией классов В языке С конструкция union чаще всего служит для построения структур, в которых можно хранить более одного типа данных. Обычно такая структура содержит по крайней мере два поля объединение
    (union) и тeг (tag). Тег - это обыкновенное поле, которое используется для указания, какие из возможных типов можно хранить в объединении. Чаще всего тег представлен перечислением (unum) какого-либо типа. Структуру, которая содержит объединение и тег, иногда называют явным объединением. в приведенном ниже примерена языке С тип 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, есть какие-либо поля данных, эти поля представляют данные, которые едины для всех типов, а потому их нужно перенести в корневой класс. В приведенном примере нет операций и полей данных, которые бы не зависели от типа. Далее, для каждого типа, который может быть представлен объединением, определите неабстрактный подкласс корневого класса. В примере такими типами являются круги прямоугольник. В каждый подкласс поместите те поля данных, которые характерны для соответствующего типа. Так, радиус является характеристикой круга,
    а длина и ширина описывают прямоугольник. Кроме того, в каждый подкласс поместите соответствующую реализацию для всех абстрактных методов в корневом классе. Представим иерархию классов для нашего примера явного объединения 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 соответствует прямоугольнику, а в объединении описывается круг, все пропало. И даже если явное объединение инициализировано правильно, оно может быть передано не той функции, которая соответствует значению тега. Второе преимущество иерархии классов заключается в простоте и ясности программного кода. Явное объединение загромождено шаблонами декларация типа перечисления, декларация поля тега, поле переключения тега, обработка непредвиденных значений тега и т. Д. Программный код объединения неудобно читать даже из-за того, что операции для различных типов перемешаны, а не разделены по типу. Третьим преимуществом иерархии классов является простота ее расширяемости, даже если над ней работают независимо несколько групп программистов. для расширения иерархии классов достаточно добавить в нее новый подкласс. Если вы забыли переопределить один из абстрактных методов суперкласса, компилятор напомнит об этом недвусмысленным образом. для расширения явного объединения в С потребуется
    доступ к его исходному коду. Придется добавить новое значение в представленный тип перечисления, а также новую альтернативу в оператор 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 = е
    /* Помещает данные водно из полей объединения ... */
    ргiпtf("%х\n", sleaze.bits); /* ... и читает их из другого Это непортируемое решение можно использовать, особенно в системном программировании, но его нельзя реализовать в языке программирования а. Фактически это противоречит самому духу языка, который гарантирует контроль типов и готов на все, чтобы изолировать программистов от машинно- зависимого внутреннего представления программ. В пакете jаvа.lапg есть методы преобразования чисел с плавающей точкой в двоичную форму, однако в целях переносимости эти методы определены в соответствии со строго регламентированным двоичным представлением. Следующий фрагмент кода, который почти равнозначен предыдущему фрагменту на языке С, гарантирует, что программа будет всегда печатать один и тот же результат независимо оттого, где она запущена
    Sуstет.оut.ргintln(
    Intеgег.tоНехStгing(Flоаt.flоаtТolпtВits(6. 69ge-41f)));
    3 а меняйте конструкцию классом Конструкция enum языка С отсутствует в языке программирования Jаvа. Обычно эта конструкция определяет тип, соответствующий перечислению (enumerated type), те. тип, допустимыми значениями которого являются константы из фиксированного набора. К сожалению, конструкция enum не очень хорошо определяет перечисления. Она лишь задает набор именованных целочисленных констант, не обеспечивая ни безопасности типов, ни минимального удобства использования. Так, в С допустимы не только записи typedef enum { FUJI, PIPPIN, GRANNY_SMITH } apple_t; /* Сорта яблок typedef enum { NAVEL, TEMPLE, BLOOD } огапgе_t; /* Сорта апельсинов огапgе_t myFavorite = PIPPIN; /* Путает яблоки и апельсины */ но и этот кошмар огаngе_t х = (FUJI - РIРРIN)/ТЕМРLЕ;
    /* Яблочный соус какой-то! */ Конструкция enum не образует пространства имен для генерируемых ею констант. Поэтому следующая декларация, в которой одно из названий используется вновь, вступает в конфликт с предыдущей декларацией огаngе_t: typedef е { BLOOD, SWEAT, TEARS } fluid_t; /* Это не группа, а жидкости */
    Типы, декларируемые конструкцией enum, ненадежны. Если не позаботиться о сохранении значений всех имеющихся констант, можно получить непредсказуемые изменения в работе системы, если добавить в перечисление новые константы, ноне выполнить повторную компиляцию клиентов. Разные группы разработчиков не могут добавлять в перечисление новые константы независимо друг от друга, поскольку новые константы перечисления скорее всего будут конфликтовать между собой. Конструкция не предоставляет простых способов перевода констант перечисления в печатные строки или подсчета количества констант в каком-либо типе. К сожалению, в языке программирования Jаvа большое распространение имеет шаблон перечислений, который, как показано ниже, перенимает указанные недостатки конструкции е из языка С
    // Шаблон перечисления·целых чисел (int enum)
    // весьма сомнительный
    public class PlayingCard { public static final int SUIT_CLUBS =0;
    public static final int SUIT_DIAMONDS =1’
    public static final п SUIT_HEARTS =2’
    public static final int SUIT_SPADES Вы можете встретить другой вариант этого шаблона, когда вместо констант п применяются константы String. Его тоже не следует использовать. Хотя для своих констант он передает печатные строки, это может привести к проблемам с производительностью, поскольку связано с операцией сравнения строк. Более того, неискушенные пользователи могут получить программный код клиентов, жестко привязанный к строковым константам, вместо того, чтобы использовать названия соответствующих полей. И если в жестко заданные строковые константы вкрадутся опечатки, это не будет выявлено на этапе компиляции и повлечет сбои при выполнении программы. К счастью, язык программирования Jаvа предлагает альтернативное решение, которое лишено недостатков распространенных шаблонов для int и String и имеет множество преимуществ. Это шаблон, называемый перечислением типов (typesafe enum). К сожалению, он пока мало известен. Основная идея шаблона проста определите класс, представляющий отдельный элемент перечисления, ноне создавайте для него никаких открытых конструкторов. Вместо них создайте поля public static final, по одному для каждой константы перечисления. Покажем, как этот шаблон выглядит в простейшем случае
    // Шаблон typesafe enum
    public class Suit { private final String name; private Suit(String name) { this.name = name; }

    99
    public String toString() { return name;}
    public static final Suit CLUBS= new Suit("clubs"); // трефы static final Suit DIAMONDS = new Suit("diamonds"); // бубны static final Suit HEARTS= new Suit("hearts"); // черви static final Suit SPADES= new Suit("spades"); // пики
    }
    Поскольку клиенты не могут создавать экземпляров данного класса или расширять его, не будет никаких объектов этого типа, за исключением тех, что доступны в полях public static final. И хотя класс даже не декларирован как final, расширять его невозможно конструктор подкласса должен вызывать конструктор суперкласса, атакой конструктор ему недоступен. Как и подразумевает название, шаблон typesafe enum обеспечивает безопасность типов уже на стадии компиляции. Декларируя метод с параметром типа Suit, вы имеете гарантию того, что любая переданная вам ссылка на объект, отличная от null, представляет одну из четырех правильных карточных мастей. Любая попытка передать объект неправильного типа будет обнаружена на стадии компиляции также, как и любая попытка присвоить результат вычислений, соответствующий одному перечислению, переменной, которая соответствует другому типу перечисления Различные классы перечислений с одинаковыми названиями констант могут мирно сосуществовать благодаря тому, что каждый класс имеет собственное пространство имен. Добавлять константы в класс перечисления можно, не компилируя вновь всех его клиентов, поскольку открытые статические поля со ссылкой на объекты, в которых содержатся константы перечисления, образуют уровень изоляции между клиентом и классом перечисления. Сами константы никогда не компилируются в классе клиента, как это было в случае с более распространенным шаблоном, использующим int или String. Поскольку перечисления типов - это вполне самостоятельные классы, допустимо переопределение метода toString таким образом, чтобы можно было преобразовывать значения в печатные строки. При желании вы сможете сделать еще один шаг в этом направлении и с помощью стандартных средств связать класс перечисления с региональными настройками. Заметим, что названия строк используются лишь в методе toString, при проверке равенства они не применяются, поскольку реализация метода equals, унаследованная от класса Object, выполняет сравнение, проверяя равенство ссылок. В общем случае вы можете усилить класс перечислений любым методом, который покажется вам подходящим. Например, наш класс Sui t мог бы выиграть от добавления метода, который возвращает цвет или изображение масти. Класс может начать свою жизнь как простое перечисление и со временем развиться в абстракцию с полным набором свойств. Поскольку в класс перечисления разрешается добавлять любые методы, его можно заставить реализовать любой интерфейс. Предположим, вы хотите, чтобы класс Suit реализовывал интерфейс
    Comparable, и клиенты могли сортировать сдачу
    карт в бридже по мастям. Представим слегка видоизмененный первоначальный шаблон, который поддерживает такой трюк. Статическая переменная nextOrdinal используется для того, чтобы каждому экземпляру класса в момент его создания назначать порядковый номер. Эти номера применяются в методе compareTo для упорядочения экземпляров.
    1   ...   8   9   10   11   12   13   14   15   ...   25


    написать администратору сайта