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

15.1.3. Поведінка

Що таке поведінка. Об'єкти не існують ізольовано, а піддаються впливу або самі впливають на інші об'єкти.

Поведінка - це те, як об'єкт діє й реагує; поведінка виражається в термінах стану об'єкта й передачі повідомлень.

Іншими словами, поведінка об'єкта - це його спостережувана й перевірена ззовні дія.

Операцією називається певний вплив одного об'єкта на іншій з метою викликати відповідну реакцію. Наприклад, клієнт може активізувати операції append і pop для того, щоб керувати об'єктом-чергою (додати або вилучити елемент). Існує також операція length, що дозволяє визначити розмір черги, але не може змінити це значення. У чисто об’єктно-орієнтованій мові, такій як Smalltalk, прийнято говорити про передачу повідомлень між об'єктами. У мовах типу C++, в яких чіткіше відчувається процедурне минуле, ми говоримо, що один об'єкт викликає функцію-член іншого. В основному поняття повідомлення збігається з поняттям операції над об'єктами, хоча механізм передачі різний. Для наших цілей ці два терміни можуть використовуватися як синоніми.

В об’єктно-орієнтованих мовах операції, що виконуються над даним об'єктом, називаються методами й входять у визначення класу об'єкта. В C++ вони називаються функціями-членами. Ми будемо використовувати ці терміни як синоніми.

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

Стан об'єкта представляє сумарний результат його поведінки.

Найцікавіші ті об'єкти, стан яких не статичний: їхній стан змінюється й запитується операціями.

Приклади. Опишемо мовою C++ клас Queue (черга):

class Queue {

public:

Queue();

Queue(const Queue&);

virtual ~Queue();

virtual Queue& operator=(const Queue&);

virtual int operator==(const Queue&) const;

int operator!=(const Queue&) const;

virtual void clear();

virtual void append(const void*);

virtual void pop();

virtual void remove(int at);

virtual int length() const;

virtual int isEmpty() const;

virtual const void* front() const;

virtual int location(const void*);

protected:

...

};

У визначенні класу використовується звичайна для С ідіома посилання на дані невизначеного типу за допомогою void*, завдяки чому в чергу можна вставляти об'єкти різних класів. Ця техніка не безпечна - клієнт повинен розуміти, з яким (якого класу) об'єктом він має справу. Крім того, під час використання void* черга не "володіє" об'єктами, які до неї поміщені. Деструктор Queue() знищує чергу, але не її учасників. У наступному розділі ми розглянемо параметризовані типи, які допомагають справлятися з такими проблемами.

Оскільки визначення Queue задає клас, а не об'єкт, ми повинні оголосити екземпляри класу, з якими можуть працювати клієнти:

Queue a, b, c, d;

Ми можемо виконувати операції над об'єктами:

a.append(&deb);

a.append(&karen);

a.append (&denise);

b = a;

a.pop();

Тепер черга а містить двох співробітників (першим стоїть karen), а черга b - трьох (першим стоїть deb). Таким чином, черги мають певний стан, який впливає на їхню майбутню поведінку - наприклад, одну чергу можна безпечно просунути (pop) ще два рази, а іншу - три.

Операції. Операція - це послуга, яку клас може надати своїм клієнтам. На практиці типовий клієнт здійснює над об'єктами операції п'яти видів. Нижче наведені три найпоширеніші операції:

Модифікатор

Операція, яка змінює стан об'єкту

Селектор

Операція, яка читає стан об'єкту, але не міняє його

Ітератор

Операція, що дозволяє організувати доступ до всіх частин об'єкта в строгій послідовності

Оскільки логіка цих операцій досить різна, корисно вибрати такий стиль програмування, що враховує ці розходження в коді програми. У нашій специфікації класу Queue ми спочатку перелічили всі модифікатори (функції-члени без специфікаторів const - clear, append, pop, remove), а потім всі селектори (функції зі специфікаторами const - length, isEmpty, front і location).

Дві операції є універсальними; вони забезпечують інфраструктуру, необхідну для створення й знищення екземплярів класу:  

Конструктор

Операція створення об'єкта і/або його ініціалізації

Деструктор

Операція, що звільняє стан об'єкта і/або руйнує сам об'єкт

У мові C++ конструктор і деструктор становлять частину опису класу, тоді як в Smalltalk і CLOS ці оператори визначені в протоколі метакласу (тобто в класі класу).

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

void copyUntilFound(Queue& from, Queue& to, void* item)

{

while ((!from.isEmpty()) && (from.front() != item))

{

to.append(from.front());

from.pop();

}

}

Зміст полягає в тому, що вміст однієї черги переходить в іншу доти, поки на початку першої черги не виявиться заданий об'єкт. Це операція високого рівня; вона будується на операціях-примітивах класу Queue.

В C++ (і Smalltalk) прийнято збирати всі логічно зв'язані вільні підпрограми й повідомляти їх частиною деякого класу, що не має стану. Всі такі функції будуть статичними.

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

Ролі й відповідальності. Сукупність всіх методів і вільних процедур, що відносяться до конкретного об'єкта, утворює протокол цього об'єкта. Протокол, таким чином, визначає поведінку об'єкта, що охоплює всі його статичні й динамічні аспекти. У самих нетривіальних абстракціях корисно підрозділяти протокол на часткові аспекти поведінки, які ми будемо називати ролями. Роль - це маска, яку носить об'єкт; вона визначає контракт між абстракцією та її клієнтами.

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

Дійсно більшість цікавих об'єктів виконують у своєму житті різні ролі, наприклад:

  • Банківський рахунок може бути в хорошому або поганому стані (дві ролі), і від цієї ролі залежить, що відбудеться при спробі зняття з нього грошей.

  • Для фондового брокера пакет акцій - це товар, який можна купувати або продавати, а для юриста це знак володіння певними правами.

  • На протязі дня одні і та ж сама особа може відігравати роль матері, лікаря, садівника й кінокритика.

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

Об'єкти як автомати. Наявність внутрішнього стану об'єктів означає, що порядок виконання операцій має істотне значення. Це наводить на думку представити об'єкт як маленьку незалежну машину. Дійсно, для деяких об'єктів такий часовий порядок настільки істотний, що найкращим способом їхнього формального опису буде кінцевий автомат.

Продовжуючи аналогію з машинами, можна сказати, що об'єкти можуть бути активними й пасивними. Активний об'єкт має свій потік керування, а пасивний - немає. Активний об'єкт у загальному випадку автономний, тобто він може проявляти свою поведінку без впливу зі сторони інших об'єктів. Пасивний об'єкт, навпаки, може змінювати свій стан тільки під впливом інших об'єктів. Таким чином, активні об'єкти системи - джерела керуючих впливів. Якщо система має кілька потоків керування, то й активних об'єктів може бути кілька. У послідовних системах звичайно в кожний момент часу існує тільки один активний об'єкт, наприклад, головне вікно, диспетчер який опрацьовує всі повідомлення. У такому випадку інші об'єкти пасивні: їхня поведінка проявляється, коли до них звертається активний об'єкт. В інших видах послідовних архітектур (системи опрацювання транзакцій) немає явного центру активності, і керування розподілене серед пасивних об'єктів системи.