Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Проектування інформаційних систем.doc
Скачиваний:
95
Добавлен:
21.09.2019
Размер:
28.77 Mб
Скачать

15.1.4. Ідентичність

Семантика.

Ідентичність - це така властивість об'єкта, що відрізняє його від всіх інших об'єктів.

У більшості мов програмування й керування базами даних для ідентифікації тимчасових об'єктів їх іменують, тим самим інтегруючи адресованість та ідентичність. Більшість баз даних розрізняють постійні об'єкти за ключовим атрибутом, тим самим змішуючи ідентичність і значення даних. Джерелом помилок в об’єктно-орієнтованому програмуванні є невміння відрізняти ім'я об'єкта від самого об'єкта.

Приклади. Почнемо з визначення крапки на площині.

struct Point {

int x;

int y;

Point() : x(0), y(0) {}

Point(int xValue, int yValue) : x(xValue), y(yValue) {}

};

Ми визначили Point як структуру, а не як повноцінний клас. Правило, на підставі якого ми так здійснили, дуже просте. Якщо абстракція представляє собою сукупність інших об'єктів без якої-небудь власної поведінки, ми робимо її структурою. Однак, коли наша абстракція визначає складнішу поведінку, ніж простий доступ до полів структури, то потрібно визначати клас. У цьому випадку абстракція Рoint - це просто пара координат (x,y). Для зручності передбачено два конструктори: один ініціалізує точку нульовими значеннями координат, а інший - деякими заданими значеннями.

Тепер визначимо екранний об'єкт (DisplayItem). Це абстракція досить звична для систем із графічним інтерфейсом (GUI) - вона є базовим класом для всіх об'єктів, які можна відображати у вікні. Ми хочемо зробити її чимось значнішим, ніж просто сукупністю точок. Треба, щоб клієнти могли рисувати, вибирати об'єкти й переміщати їх по екрані, а також запитувати їх місце знаходження та стан. Ми записуємо нашу абстракцію у вигляді такого оголошення на C++:

class DisplayItem {

public:

DisplayItem();

DisplayItem(const Point& location);

virtual ~DisplayItem();

virtual void draw();

virtual void erase();

virtual void select();

virtual void unselect();

virtual void move(const Point& location);

int isSelected() const;

Point location() const;

int isUnder(const Point& location) const;

protected:

};

У цьому оголошенні ми навмисно опустили конструктори, а також оператори для копіювання, присвоювання й перевірки на рівність. Їх ми залишимо до наступного розділу.

Ми очікуємо, що в цього класу буде багато спадкоємців, тому деструктор і всі модифікатори оголошені віртуальними. Особливо це відноситься до draw. Селектори швидше за все не будуть перевизначатися в підкласах. Зверніть увагу, що один з них, isUnder, повинен обчислити, чи накриває цей об'єкт дану точку, а не просто повертати значення якоїсь властивості.

Оголосимо екземпляри вказаних класів:

DisplayItem item1;

DisplayItem* item2 = new DisplayItem(Point(75, 75));

DisplayItem* item3 = new DisplayItem(Point(100, 100));

DisplayItem* item4 = 0;

Рис. 15.1а показує, що при виконанні цих операторів виникають чотири імені й три різних об'єкти. Конкретно, у пам'яті будуть відведені чотири місця під імена item1, item2, item3, item4. При цьому item1 буде іменем об'єкту класу DisplayItem, а три інших будуть показниками. Крім того, лише item2 і item3 будуть насправді вказувати на об'єкти класу DisplayItem. В об'єктів, на які вказують item2 і item3, до того ж немає імен, хоча на них можна посилатися: наприклад, *item2. Тому ми можемо сказати, що item2 вказує на окремий об'єкт класу DisplayItem, на ім'я якого ми можемо опосередковано посилатися через *item2. Унікальна ідентичність (але не обов'язкове ім'я) кожного об'єкта зберігається на весь час його існування, навіть якщо його внутрішній стан змінився. Ця ситуація нагадує парадокс Зенона про ріку: чи може ріка бути тієї ж самою, якщо в ній щодня тече різна вода?

Розглянемо результат виконання таких операторів (рис. 15.1б):

item1.move(item2->location());

item4 = item3;

item4->move(Point(38, 100));

Об'єкт item1 і об'єкт, на який вказує item2, тепер відносяться до однієї й тієї ж точки екрану. Показник item4 став вказувати на той же об'єкт, що й item3. До речі, помітьте різницю між виразами "об'єкт item2" і "об'єкт, на який вказує item2". Другий вираз більше точніший, хоча для стислості ми часто будемо використовувати їх як синоніми.

Рис. 15.1. Ідентичність об'єктів

Хоча об'єкт item1 і об'єкт, на який вказує item2, мають однаковий стан, вони залишаються різними об'єктами. Крім того, ми змінили стан об'єкта *item3, використавши його нове непряме ім'я item4. Ця ситуація, яку ми називаємо структурною залежністю, маючи на увазі під цим ситуацію, коли об'єкт іменується більше, ніж одним способом декількома синонімічними іменами. Структурна залежність породжує в об’єктно-орієнтованому програмуванні багато проблем. Складність розпізнання побічних ефектів при діях із синонімічними об'єктами часто приводить до "витоків пам'яті", неправильному доступу до пам'яті, і, гірше того, до непрогнозованих змін станів. Наприклад, якщо ми знищимо об'єкт через показник item3, то значення показника item4 виявиться безглуздим; ця ситуація називається повислим посиланням.

На рис. 15.1в ілюструється результат виконання таких дій:

item2 = &item1;

item4->move(item2->location());

У першому рядку створюється синонім: item2 вказує на той же об'єкт, що й item1. У другій доступ до стану item1 отриманий через цей новий синонім. На жаль, при цьому відбувся витік пам'яті, - об'єкт, на який спочатку вказувало посилання item2, ніяк не йменується ні прямо, ні побічно, і його ідентичність загублена. В Smalltalk і CLOS пам'ять, відведена під об'єкти, буде знову повернута системі збирачем сміття. У мовах типу C++ така пам'ять не звільняється, поки не завершиться програма, що створила об'єкт. Такі витоки пам'яті можуть викликати незручність, великі збої, особливо, якщо програма повинна безупинно працювати тривалий час.

Копіювання, присвоювання й рівність. Структурна залежність має місце, коли об'єкт має кілька імен. У складних системах, реалізованих за допомогою об’єктно-орієнтованого підходу використання синонімів просто неминуче. Наприклад, розглянемо такі дві функції:

void highLight(DisplayItem& i);