К. Арнольд, Д. Гослинг - Язык программирования Java (1160779), страница 33
Текст из файла (страница 33)
Преобразование оказывается успешным лишь в том случае, если объект может использоваться в качестве объекта типа, ккоторому преобразуется ссылка.Object.clone инициализирует каждое поле объекта-дубликата, присваивая ему значение соответствующего поля исходного объекта. Вам остаетсятолько написать специализированный код для обработки тех полей, для которых копирование по значению не подходит.
IntegerStack.clone некопирует поле top, поскольку в него уже занесено правильное значение во время стандартного копирования значений полей.После появления специализированного метода clone содержимое памяти для нашего примера будет выглядеть так:Иногда добиться правильной работы clone оказывается настолько сложно, что игра не стоит свеч, и некоторые классы отказываются отподдержки clone. В таких случаях ваш метод clone должен возбуждать исключение Clone NotSupportedException, чтобы его вызов никогда неприводил к созданию неправильных объектов.Вы также можете потребовать, чтобы метод clone поддерживался во всех подклассах данного класса, — для этого следует переопределить методclone так, чтобы в его сигнатуру не входило объявление о возбуждении исключения CloneNotSupportedException.
В результате подклассы, вкоторых реализуется метод clone, не смогут возбуждать исключение CloneNotSupported Exception, поскольку методы подкласса не могут вводитьновые исключения. Аналогично, если метод clone в вашем классе объявлен public, то во всех расширенных классах он также будет иметь атрибутpublic — вспомним о том, что метод подкласса не может быть менее доступным, чем соответствующий ему метод суперкласса.Упражнение 3.8Реализуйте интерфейс Cloneable в классах Vehicle и PassengerVehicle.
Какой из четырех описанных выше вариантов отношения к дублированиюдолжен быть реализован в каждом из этих классов? Сработает ли простое копирование, осуществляемое в Object.clone, при дублированииобъектов этих классов?Упражнение 3.9Напишите класс Garage (гараж), объекты которого будут сохранять в массиве некоторое число объектов Vehicle.
Реализуйте интерфейс Cloneableи напишите для него соответствующий метод clone. Напишите метод Garage.main для его тестирования.Упражнение 3.10Реализуйте в классе LinkedList интерфейс Cloneable; метод clone должен возвращать новый список со значениями исходного списка (а не с ихдубликатами). Другими словами, изменения в одном списке не должны затрагивать второго списка, однако изменения в объектах, ссылки накоторые хранятся в списке, должны проявляться в обоих списках.3.9. Расширение классов: когда и какВозможность создания расширенных классов — одно из главных достоинств объектно-ориентированного программирования.
Когда вырасширяете класс, чтобы наделить его новыми функциями, то при этом возникает так называемое отношение подобия (IsA relationship) —расширение создает новый тип объектов, которые “подобны” исходному классу. Отношение подобия существенно отличается от отношенияпринадлежности (HasA relationship), при котором один объект пользуется другим для хранения информации о своем состоянии или длявыполнения своих функций — ему “принадлежит” ссылка на данный объект.Давайте рассмотрим пример. Допустим, у нас имеется класс Point, представляющий точку в двумерном пространстве в виде пары координат (x, y).Класс Point можно расширить и создать на его основе класс Pixel для представления цветного пикселя на экране.
Pixel “подобен” Point; все, чтосправедливо по отношению к простой точке, будет справедливо и по отношению к пикселю. В класс Pixel можно включить механизм дляхранения информации о цвете пикселя или ссылку на объект-экран, на котором находится пиксель. Пиксель “подобен” точке (то есть является еечастным случаем), так как он представляет собой точку на плоскости (экране) с расширенным контрактом (для него также определено понятиецвета и экрана).С другой стороны, круг нельзя считать частным случаем точки.
Хотя в принципе круг можно описать как точку, имеющую радиус, он обладаетрядом свойств, которые отсутствуют у точки. Например, если у вас имеется метод, который помещает центр некоторого прямоугольника взаданной точке, то какой смысл будет иметь этот метод для круга? Каждому кругу “принадлежит” центр, который является точкой, но круг неявляется точкой с радиусом и не должен представляться в виде подкласса Point.Правильный выбор иногда оказывается неочевидным — в зависимости от конкретного приложения могут применяться различные варианты.Единственное требование заключается в том, чтобы приложение работало, и притом осмысленно.Налаживание связей подобия и принадлежности — задача нетривиальная и чреватая осложнениями.
Например, при использовании объектноориентированных средств для проектировании базы данных о работниках фирмы можно пойти по наиболее очевидному и общепринятомупути — создать класс Employee, в котором хранятся общие сведения для всех работников (например, имя и табельный номер) и затем расширитьего для работы с определенными категориями работников — Manager, Engineer, FileClerk и т.
д.Такой вариант не подходит в реальной ситуации, когда одно и то же лицо выполняет несколько функций. Скажем, инженер может одновременноявляться менеджером группы и, следовательно, присутствует сразу в двух качествах. Возьмем другой пример — аспирант может являться иучащимся, и преподавателем.Более гибкий подход состоит в том, чтобы создать класс Role (функция работника) и расширить его для создания специализированных классов —например, Manager.
В этом случае можно изменить класс Employee и превратить его в набор объектов Role. Тогда конкретное лицо может бытьсвязано с постоянно изменяющимся набором функций внутри организации. От концепции “менеджер является работником” мы переходим кконцепции “менеджер является функцией”, при которой работнику может принадлежать функция менеджера наряду с другими функциями.Неправильный выбор исходной концепции затрудняет внесение изменений в готовую систему, поскольку они требуют значительных переделокв текстах программ.
Например, в методах, реализованных с учетом первой концепции базы данных работников, несомненно будетиспользоваться тот факт, что объект Manager может применяться в качестве объекта Employee. Если переключиться на вторую концепцию, то этоутверждение станет ложным, и все исходные программы перестанут работать.3.10.
Проектирование расширяемого классаТеперь можно оправдать сложность класса Attr. Почему бы не сделать name и value простыми и общедоступными полями? Тогда можно было быполностью устранить из класса целых три метода, поскольку открывается возможность прямого доступа к этим полям.Ответ заключается в том, что класс Attr проектировался с учетом возможного расширения. Хранение его данных в открытых полях имеет дванежелательных последствия:●●Значение поля name в любой момент может быть изменено программистом — это плохо, так как объект Attr представляет собой(переменное) значение для конкретного (постоянного) имени. Например, изменение имени после внесения атрибута в список,отсортированный по имени, приведет к нарушению порядка сортировки.Не остается возможностей для расширения функциональности класса.
Включая в класс методы доступа, вы можете переопределить их итем самым усовершенствовать класс. Примером может служить класс Color Attr, в котором мы преобразовывали новое значение в объектScreen Color. Если бы поле value было открытым и программист мог в любой момент изменить его, то нам пришлось бы придумыватьдругой способ для получения объекта ScreenColor — запоминать последнее значение и сравнивать его с текущим, чтобы увидеть, ненуждается ли оно в преобразовании.
В итоге программа стала бы значительно более сложной и, скорее всего, менее эффективной.Класс, не являющийся final, фактически содержит два интерфейса. Открытый (public) интерфейс предназначен для программистов,использующих ваш класс. Защищенный (protected) интерфейс предназначен для программистов, расширяющих ваш класс. Каждый из нихпредставляет отдельный контракт и должен быть тщательно спроектирован.Например, допустим, что вы хотите создать оболочку для определения свойств различных алгоритмов сортировки. Обо всех алгоритмахсортировки можно сказать кое-что общее: у них имеются данные, с которыми они работают; для этих данных должен быть предусмотренмеханизм упорядочения; количество сравнений и перестановок, необходимых для выполнения алгоритма, является важным фактором дляопределения его параметров.Можно написать абстрактный метод, который учитывает все эти свойства, но создать универсальный метод анализа сортировки невозможно —он определяется для каждого порожденного класса.
Приведем класс SortDouble, который сортирует массивы значений double и при этомподсчитывает количество перестановок и сравнений, которое понадобится для определяемого ниже класса SortMetrics:abstract class SortDouble {private double[] values;private SortMetrics curMetrics = new SortMetrics();/** Вызывается для проведения полной сортировки */public final SortMetrics sort(double[] data) {values = data;curMetrics.init();doSort();return metrics();}public final SortMetrics metrics() {return (SortMetrics)curMetrics.clone();}protected final int datalength() {return values.length;}/** Для выборки элементов в порожденных классах */protected final double probe(int i) {curMetrics.probeCnt++;return values[i];}/** Для сравнения элементов в порожденных классах */protected final int compare(int i, int j) {curMetrics.compareCnt++;double d1 = values[i];double d2 = values[j];if (d1 == d2)return 0;elsereturn (d1 << d2 ? -1 : 1);}/** Для перестановки элементов в порожденных классах */protected final void swap(int i, int j) {curMetrics.swapCnt++;}double tmp = values[i];values[i] = values[j];values[j] = tmp;/** Реализуется в порожденных классах и используется в sort */protected abstract void doSort();}В классе имеются поля для хранения сортируемого массива (values) и ссылки на объект-метрику (curMetrics), в котором содержатся измеряемыепараметры.