10.1 Обєкти і класи

10.07.2015

1 0. «Епізодичне» об’єктно-орієнтоване програмування

ООП – Організація Визволення Палестини.

Абревіатура.

Резонне питання – чому так пізно приступаємо до знайомства з ООП? Я теж вважаю, що деякі глави книги тільки б виграли від їх викладу в об’єктно-орієнтованої нотації. Але, сказавши «а», слід було б сказати і «б», тобто довелося б повністю викласти принципи ООП, а це було б не зовсім правильно:

ця технологія орієнтована на створення досить великих проектів, хоча окремі частини проекту (наприклад, методи класів) все одно розробляються в рамках традиційної технології структурного програмування. Тому, на мій консервативний погляд, щоб відчути переваги технології ООП, треба мати досвід розробки проектів певної складності в традиційній технології;

багато механізми об’єктно-орієнтованого Сі чудово ілюструються засобами класичного Сі, щоб розуміти перше, потрібно знати друге;

всі попередні голови ілюстровані невеликими за обсягом програмами, для яких об’єктно-орієнтована нотація (саме як для прикладів) не обов’язкова;

ООП – це постановка процесу програмування «з ніг на голову», (або з голови на ноги), а це краще зробити не в середині викладу матеріалу;

І, нарешті, такий «монстр» як Сі++, що намагається поєднувати в собі все і вся, має не зовсім зручну, зайво відкриту і досить громіздку об’єктно-орієнтовану нотацію. Тому даний матеріал слід розглядати як запрошення до знайомства з тотальними середовищами ООП, наприклад, Java або C#.

10.1 Об’єкти і класи
Об’єкт, метод, клас: визначення і властивості

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

5.3 ми вже зробили спробу прив’язати до структурованого типу термінологію «клас – властивість – метод — об’єкт». Тепер настав час остаточно розставити «всі крапки над е ». Для початку розглянемо кілька визначень класу.

Технологічне визначення класу. Технологія ООП, насамперед, накладає обмеження на способи подання даних в програмі і їх взаємодія з алгоритмічної компонентою (функціями). Будь-яка програма відображає у своїх даних стан зовнішніх об’єктів програмування. Це можуть бути як фізичні об’єкти зовнішнього середовища, так і логічні програмні сутності (наприклад, файли). Для цього можна використовувати різні способи, можна «розмазати» властивості об’єкта за різних структур даних. Можна виходити з того, що кожному об’єкту буде відповідати своя власна структура даних, в якій містяться всі елементи опису властивостей зовнішнього об’єкта програмування. Таку структуру даних аналогічно можна назвати об’єктом. Функції, що працюють з об’єктом і отримують в якості обов’язкового параметра покажчик на структуру даних, називаються методами. Сукупність опису об’єктів одного типу і методів роботи з ними називається класом.

Об’єкт – структура даних, яка містить опис властивостей зовнішнього об’єкта програмування. Метод – функція, яка працює з об’єктом. Клас – опис структури об’єкта і методів роботи з ним.

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

Синтаксичне визначення об’єкта і класу — структура з вбудованими функціями. Структурована змінна з вбудованими в неї функціями (див. 5.3 ) ідеально підходить під визначення об’єкта. Не потрібно намагатися винаходити велосипед, достатньо назвати речі своїми іменами. Структурована змінна – це об’єкт, функції, вбудовані в структурований тип – це методи класу, а саме визначення структурованого типу (що включає і визначення вбудованих функцій) – є клас.

Синтаксичне визначення класу – тип даних, який визначається програмістом. Клас являє собою опис структури об’єктів одного виду з набором методів їх обробки. Аналогія з типом даних тут напрошується сама собою. Тип даних – це форма подання даних з набором операцій. Відмінність полягає в тому, що тип даних які вже визначено у мові, або формально складається з вже існуючих (але без внутрішнього програмування). Значить, клас можна визначити як тип даних, який визначається програмістом. Тоді об’єкт – це змінна класу. Ці трактування закріплена у мові: синтаксис визначення змінних і об’єктів майже повністю ідентичний.

Клас тип даних, який визначається програмістом, об’єкт змінна класу

Прописні істини об’єктно-орієнтованого підходу

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

Контекст класу (поточного об’єкта). Поняття контексту ми вже вводили стосовно функції (1.6 ). Контекст (оточення) – це набір імен (об’єктів мови), які можна використовувати безпосередньо, без вказівки шляху доступу». Контекстом функції є її формальні параметри та локальні змінні. Кожен клас визначає свій контекст – це імена елементів даних і методів класу. Цей контекст виникає, оскільки програмні компоненти класу мають справу з поточним об’єктом, замовчування стосується саме його. Будь-який елемент контексту може адресуватися як просто по імені, так і через покажчик this,, наприклад n this->n.

Класи і породження об’єктів. Клас – це опис об’єктів, об’єкт – це інсталяція (відображення) класу в пам’яті. Можна, звичайно, це розуміти буквально, як створення екземпляра класу з усіма його «потрохами» — даними і методами. В реальності ж проекція класу на традиційну комп’ютерну архітектуру виглядає таким чином:

для кожного об’єкта створюється екземпляр даних;

методи класу, з якими працює об’єкт, являють собою єдиний екземпляр програмного коду в сегменті команд, який виконується однаково для всіх об’єктів (поділяється ними);

при виклику методу об’єкт, для якого він виконується, ідентифікується вказівником поточного об’єкта this, задає контекст поточного об’єкта.

Таким чином, зв’язка «об’єкт-метод» перетворюється на традиційну послідовність дій: «виклик функції – методи класу з фактичним параметром – покажчиком на поточний об’єкт».

class A < // Еквівалентно:

int a; // struct A ;

public: void F() // void A::F(A *this) a++; >

// A DD;

A DD; DD.F(); // A::F(&DD); this=ⅅ

Клас – межа відповідальності транслятора і програми. При наявності різноманітності визначень класу залишився без відповіді основне питання: де проходить межа відповідальності між транслятором і програмою (отже, і програмістом, її вклад). Ця межа між технологічним і синтаксичним визначенням класу, тобто між структурованої змінної – синтаксичним об’єктом і пов’язаної з нею структурою даних – технологічним об’єктом. Виконавець несе відповідальність за синтаксичний об’єкт, забезпечуючи, перш за все, дії, пов’язані з його копіюванням: присвоювання, передачу за значенням у функцію і повернення його у вигляді значення результату. За все інше несе відповідальність програміст, точніше написаний ним програмний код класу:

якщо елементи даних класу мають взаємопов’язані значення, то клас повинен підтримувати встановлені для них угоди;

якщо об’єкт даних класу посилається на зовнішні структури даних, то при аналізі копіювання об’єкта необхідно забезпечити незалежність пов’язаної структури даних в об’єкті-копії (створити її копію або забезпечити поділ – див. «конструктор копіювання»;

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

10.1 Обєкти і класи

рис. 101-1. Об’єкт: межа відповідальності транслятора і програми

Модульне проектування і ООП. Об’єктно-орієнтоване програмування за своєю природою є модульним. На поверхні лежить очевидний факт: наявність великої кількості примітивних методів роботи з об’єктом дозволяє, комбінуючи їх, отримувати нові при мінімумі витрат. Але є ще інша можливість: при розробці методу можна створювати локальні об’єкти того ж самого класу, викликаючи для них вже відомі методи, що також забезпечує повторне використання вже написаного коду.

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

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

c lass poly<

int n;

double * pd ; // Внутрішня СД – дін. масив коефіцієнтів

public. void add ( double D 2[], int n 2) // Порушення закритості – параметр – внутрішня СД

void add ( poly & T ) // Правильно: параметр – об’єкт того ж класу

Незалежність. Внутрішні дані об’єкта повинні бути «особистою власністю» об’єкта. При виконанні операцій, пов’язаних з копіюванням його вмісту (привласнення, передача за значенням) клас повинен забезпечувати створення незалежних копії пов’язаних з об’єктом даних. Можливий і інший варіант: кілька об’єктів можуть розділяти загальну структуру даних. Але тоді в класі повинен бути прописаний механізм такого розподілу і синхронізації доступу.

Універсальність. По відношенню до даних універсальність розуміється як відсутність обмежень на їх розмірності і, як наслідок, використання динамічних структур даних у внутрішньому поданні об’єктів. Клас при цьому повинен вирішувати проблеми, пов’язані з динамічним розподілом пам’яті, її заняттям, звільненням і перерозподілом.

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

Цілісність. З одержимі об’єкта повинно бути завжди коректно з точки зору угод, встановлених класом для можливих варіантів його внутрішнього представлення, за цим насамперед повинні стежити конструктори і деструктори, інші методи теж не повинні залишати після себе» неправильних об’єктів.

порада: бажано уникати різноманіття форм представлення внутрішніх даних об’єкта. Чим їх менше, тим простіше забезпечити його цілісність і коректність. Наприклад, краще мати фіктивний динамічний масив, ніж NULL- покажчик. У прикладі з класом степеневого полінома «порожній» поліном краще уявити динамічним масивом з єдиним нульовим коефіцієнтом.

c lass poly <

int n ;

double * pd ; // Внутрішня СД – дін. масив коефіцієнтів

public. poly () // Небажано: NULL – відсутність масиву

poly()< n =0; // Правильно: поліном з a (0)=0

pd=new double[1];

pd[0]=0;

> ;

10.1 Обєкти і класи
«Лягаючи спати, програміст ставить поруч дві склянки: один повний – якщо захоче пити, і один порожній – якщо не захоче». Анекдот в тему.

Об’єкт – замкнуті, логічно несуперечливі, завжди коректні дані з чітко певним універсальним інтерфейсом доступу до них

Настав час подивитися, як перераховані вимоги врахувати на практиці. В якості прикладу розглянемо клас степеневого полінома. Він має нетривіальне предметне наповнення, а з іншого боку, досить простий в реалізації. Поліном n -ой ступеня являє собою вираз виду a 0 + a 1 x + a 2 x 2 +…+ an x n або Σ aі x i . Для його подання і маніпуляцій з ним в об’єкті достатньо зберігати масив його коефіцієнтів. Вимога універсальності відразу ж передбачає довільну розмірність і динамічний масив, вимога незалежності – кожен об’єкт володіє власним динамічним масивом (поділ не допускається), вимога закритості – цей масив не доступний ззовні. Якщо в операції беруть участь більше одного полінома, то всі вони передаються як об’єкти, а не як динамічні масиви. І нарешті, в класі є поточний об’єкт, над яким покладається виконання методів, він не може бути «весільним генералом».

// Клас степеневого полінома – заголовок класу (оголошення)

int n; // ступінь полінома

double & get ( int k ); // отримання посилання на коефіцієнт

void add ( poly & T ); // додавання об’єктів (1=1+2)

void mul ( poly & T ); // множення об’єктів об’єктів (1=1+2)

…>;

Цілісність об’єкта. Конструктор. Деструктор

Вимога цілісності та коректності об’єкта означають, що об’єкт – це щось більше, ніж просто мінлива. При створенні змінної її ініціалізація зовсім не обов’язкова, в той час як створення об’єкта повинно супроводжуватися встановленням його початкового стану (ініціалізація даних, резервування пам’яті, ресурсів, встановлення зв’язків і т. д.). Аналогічні зворотні дії необхідно виконати при його знищенні перед звільненням пам’яті. З цією метою в класі вводяться спеціальні методи – конструктори та деструктор. Їх імена збігаються з ім’ям класу. Конструкторів для даного класу може бути як завгодно багато, вони відрізняються формальними параметрами, деструктор ж завжди має ім’я, яка символом «

«. Якщо конструктор має формальні параметри, то у визначенні змінної-об’єкта після її імені повинні бути присутніми в дужках значення фактичних параметрів.

int n; // ступінь полінома

double *pd; // динамічний масив коефіцієнтів

poly()< // "порожній," поліном - нульової ступеня

n=0; // з нульовим коефіцієнтом

pd=new double[1];

pd[0]=0;>

poly(int m)< // поліном заданої ступеня

n=m; // з нульовими коефіцієнтами

pd=new double[n+1];

for(int i=0;i < =n;i++)

pd[i]=0; >

poly(int n0,double p[])< // конструктор з масиву коефіцієнтів

load(n0,p); > // використовується допоміжний метод load

poly(poly &T)< // конструктор "об'єкт з об'єкта"

load(T.n, T. pd); > // (конструктор копіювання)

poly()< // деструктор

delete []pd; >

Момент виклику конструктора і деструктора визначається часом створення і знищення об’єктів:

для статичних і зовнішніх об’єктів — конструктор викликається перед входом у main. деструктор — після виходу з main(). Конструктори викликаються до порядку визначення об’єктів, деструктори — в зворотному порядку;

для автоматичних об’єктів — конструктор викликається при вході у функцію (блок), деструктор — при виході з нього;

для динамічних об’єктів — конструктор викликається при виконанні оператора new. деструктор — при виконанні оператора delete .

В Сі++ можливе визначення масиву об’єктів класу. При цьому конструктор і деструктор автоматично викликаються в циклі для кожного елемента масиву і не повинні мати параметрів. При виконанні оператора delete для вказівника на масив об’єктів його необхідно перед дужками.

struct poly ; // визначення класу

double D []=;

poly a,b(6), c (3, D ); // Статичні об’єкти – конструктори

// порожній поліном, заданої розмірності та з масиву

poly *p,* q ; // посилання на об’єкт

void main()<

poly c,d( c ); // Автоматичні об’єкти

p = new poly ; // Динамічний об’єкт

q = new poly [ n ]; // Динамічний масив об’єктів

delete p; // Знищення динамічного об’єкта

delete [] q ; // Знищення динамічного масиву об’єктів

> // Знищення автоматичних об’єктів

Зауваження: процес конструювання «вкладений» в процес розподілу пам’яті під змінну. Конструктор викликається відразу ж після виділення пам’яті, а деструктор – перед її звільненням.

Безіменні об’єкти. Програмі найчастіше потрібні «одноразові» об’єкти, для яких тільки й потрібно, що передати їх на вхід функції або методу, або використовувати їх у вираженні в якості аналога константи. Такий об’єкт можна створити, вказавши ім’я класу і в дужках фактичні параметри конструктора.

//— Безіменні об’єкти

struct A<

int a;

A(int a1) // Конструктор

A &INC() // Метод класу — інкремент

Короткий опис статті: об’єктно орієнтоване програмування

Джерело: 10.1 Об’єкти і класи

Також ви можете прочитати