Проектування расширяемого класу

Тепер можна виправдати складність класу 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 curMetricsinit() doSort()

return metrics()

}

public final SortMetrics metrics() {

return (SortMetrics)curMetricsclone()

}

protected final int datalength() {

return valueslength

}

/ ** Для вибірки елементів у породжених класах * /

protected final double probe(int i) {

curMetricsprobeCnt++

return values[i]

}

/ ** Для порівняння елементів в породжених класах * /

protected final int compare(int i, int j) {

curMetricscompareCnt++ double d1 = values[i] double d2 = values[j]

if (d1 == d2)

return 0

else

}

return (d1 &lt&lt d2 -1 : 1)

/ ** Для перестановки елементів в породжених класах * /

protected final void swap(int i, int j) {

curMetricsswapCnt++ double tmp = values[i] values[i] = values[j] values[j] = tmp

}

/ ** Реалізується в породжених класах і використовується в sort * /

protected abstract void doSort()

}

У класі є поля для зберігання сортованого масиву (values) і посилання на обєкт-метрику (curMetrics), в якому містяться вимірювані параметри Щоб забезпечити правильність підрахунків, SortDouble містить методи, використовувані розширеними класами при вибірці даних або виконанні порівнянь і перестановок

При проектуванні класу потрібно вирішити, якою мірою можна довіряти породженим класам Клас SortDouble НЕ довіряє їм, і найчастіше при розробці класів, призначених для подальшого розширення, такий підхід виправдовує себе Безпечне проектування не тільки запобігає зловмисне використання класу, але і бореться з помилками в програмі

SortDouble ретельно обмежує доступ до кожного члена класу до відповідного рівня Всі неабстрактне методи оголошуються final Всі вони входять до контракт класу SortDouble, який має на увазі захист алгоритму вимірювання від втручання ззовні Оголошення методів з ключовим словом final допомагає компілятору згенерувати оптимальний код і запобігає перевизначення в породжених класах

Обєкти SortMetrics описують параметри виконуваної сортування Даний клас містить три відкритих поля Його єдине призначення полягає в передачі даних, так що приховувати дані за методами доступу немає сенсу SortDoublemetrics повертає копію даних, щоб не видавати стороннім посилання на свої внутрішні дані Завдяки цьому запобігається зміна даних як в коді, що створює обєкти Sort Double, так і в коді розширених класів Клас SortMetrics виглядає наступним чином:

final class SortMetrics implements Cloneable {

public long probeCnt, compareCnt, swapCnt

public void init() {

probeCnt = swapCnt = compareCnt = 0

}

public String toString() {

return probeCnt + &quot probes &quot + compareCnt + &quot compares &quot + swapCnt + &quot swaps"

}

/ ** Даний клас підтримує clone () * /

public Object clone() {

try {

return superclone () / / Механізм за замовчуванням

catch CloneNotSupportedException e) {

/ / Неможливо: і this, і Object підтримують clone throw new InternalError (etoString ())

}

}

}

Наведемо приклад класу, що розширює SortDouble Клас BubbleSort Double виробляє сортування бульбашковим методом – надзвичайно неефективний, але простий алгоритм сортування, основна перевага якого полягає в тому, що його легко запрограмувати і зрозуміти:

class BubbleSortDouble extends SortDouble {

protected void doSort() {

for (int i = 0 i &lt&lt dataLength() i++) {

for (int j = i + 1 j &lt&lt dataLength() j++) {

if (compare(i, j) &gt&gt 0)

swap(i, j)

}

}

}

static double[] testData = {

03, 13e-2, 79, 317

}

static public void main(String[] args) { BubbleSortDouble bsort = new BubbleSortDouble() SortMetrics metrics = bsortsort(testData) Systemoutprintln(&quotBubble Sort: &quot + metrics) for (int i = 0 i &lt&lt testDatalength i++)

Systemoutprintln(&quot\t&quot + testData[i])

}

}

На прикладі методу main можна побачити, як працює фрагмент програми, який проводить вимірювання: він створює обєкт класу, породженого від Sort Double, передає йому дані для сортування і викликає sort Метод sort инициализирует лічильники параметрів, а потім викликає абстрактний метод doSort Кожен розширений клас реалізує свій варіант doSort для проведення сортування, користуючись в потрібні моменти методами dataLength, compare і swap При поверненні з функції doSort стан лічильників відображає кількість виконаних операцій кожного виду

BubbleSortDouble містить метод main, в якому виконується тестування ось як виглядають результати його роботи:

Bubble Sort: 0 probes 6 compares 2 swaps

0013

03

317

79

Тепер давайте повернемося до розгляду проектування класів, призначених для розширення Ми ретельно спроектували захищений інтерфейс SortClass з розрахунком на те, щоб надати розширеним класам більш тісний доступ до даних

обєкту – але лише до тих з них, до яких потрібно Доступ до інтерфейсів класу обраний

наступним чином:

Відкритий: члени класу з атрибутом public використовуються тестуючим кодом –

тобто фрагментом програми, який обчислює тимчасові витрати алгоритму Прикладом може служити метод Bubble Sortmain він надає сортовані дані і отримує результати тестування До лічильників з нього можна звертатися тільки для читання Відкритий метод sort, створений нами для

тестового коду, забезпечує правильну ініціалізацію лічильників перед їх

використанням

Оголошуючи метод doSort з атрибутом protected, тестуючий код тим самим дозволяє звертатися до нього тільки побічно, через головний метод sort таким чином ми можемо гарантувати, що лічильники завжди будуть ініціалізовані, і уникнемо можливої ​​помилки

Ми скористалися методами і обмеженням доступу, щоб сховати все, що виходить за межі відкритої частини класу Єдине, що може зробити з класом тестуючий код, – це виконати тестування для конкретного алгоритму сортування і отримати результат

Захищений: члени класу з атрибутом protected використовуються під час сортування для отримання виміряних параметрів Захищений контракт дозволяє алгоритму сортування переглянути та модифікувати дані з тим, щоб отримати відсортований список (засоби для цього визначаються алгоритмом) Крім того, такий спосіб надає алгоритмом сортування контекст виконання, в якому будуть вимірюватися необхідні параметри (Метод doSort)

Ми домовилися, що довіряти розширеним класам в нашому випадку не слід, – ось чому вся робота з даними здійснюється побічно, через використання спеціальних методів доступу Наприклад, щоб приховати операції порівняння за рахунок відмови від виклику compare, алгоритму сортування доведеться користуватися методом probe для того, щоб дізнатися, що знаходиться в масиві Оскільки виклики probe також підраховуються, ніяких незареєстрованих звернень не виникне Крім того, метод metrics повертає копію обєкта з лічильниками, тому при сортуванні змінити значення лічильників неможливо

Закритий: закриті дані класу повинні бути заховані від доступу ззовні – конкретно, мова йде про сортируемих даних і лічильниках Зовнішній код не зможе отримати до них доступ, прямо або побічно

Як згадувалося вище, клас SortDouble проектувався так, щоб не довіряти розширеним класам і запобігти будь-яке випадкове або навмисне втручання з їхнього боку Наприклад, якби масив SortDouble values ​​(сортовані дані) був оголошений protected замість private, можна було б відмовитися від використання методу probe, оскільки зазвичай алгоритми сортування обходяться операціями порівняння і перестановки Але в цьому випадку програміст може написати розширений клас, який здійснюватиме перестановку даних без використання swap Результат виявиться невірним, але виявити це буде нелегко Підрахунок звернень до даних і оголошення масиву private запобігає деякі можливі програмні помилки

Якщо класи не проектується з урахуванням його подальшого розширення, то він з великою ймовірністю буде неправильно використовуватися підкласами Якщо ж клас повинен розширюватися, слід особливо ретельно підійти до проектування захищеного інтерфейсу (хоча в результаті, можливо, вам доведеться включити в нього захищені члени, якщо доступ з розширених класів повинен проводитися спеціальним чином) В іншому випадку, ймовірно, слід оголосити клас final і спроектувати захищений інтерфейс, коли настане час зняти обмеження final

Вправа 311

Знайдіть у SortDouble щонайменше одну лазівку, яка дозволяє алгоритму сортування непомітно змінювати значення виміряних параметрів Закрийте її Передбачається, що автор алгоритму сортування не збирається писати метод main

Вправа 312

Напишіть універсальний клас SortHarness, який може сортувати обєкти будь-якого типу Як у даному випадку вирішується проблема з упорядкуванням обєктів – адже для їх порівняння не можна буде користуватися оператором <

Глава 4

ІНТЕРФЕЙСИ

“Диригування – це коли ви малюєте

свої проекти прямо в повітрі, паличкою або руками,

і намальоване стає інструкціями для хлопців у краватках, які в даний момент воліли б

опинитися де-небудь на риболовлі

Френк Заппа

Основною одиницею проектування в Java є відкриті (public) методи, які можуть викликатися для обєктів Інтерфейси призначені для оголошення типів, що складаються лише з абстрактних методів і констант вони дозволяють задати для цих методів довільну реалізацію Інтерфейс є вираженням чистої концепції проектування, тоді як клас являє собою суміш проектування і конкретної реалізації

Методи, що входять в інтерфейс, можуть бути реалізовані в класі так, як вважатиме за потрібне проектувальник класу Отже, інтерфейси мають значно більше можливостей реалізації, ніж класи

Джерело: Арнольд К, Гослінг Д – Мова програмування Java (1997)

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*