- Lektsia - бесплатные рефераты, доклады, курсовые работы, контрольные и дипломы для студентов - https://lektsia.info -

Появление ООП — реакция на кризис



Появление ООП — реакция на кризис

Программного обеспечения

Объектно-ориентированное программирование (ООП) — это технология, возникшая

как реакция на очередную фазу кризиса программного обеспечения, когда

методы структурного программирования уже не позволяли справляться с растущей

сложностью промышленного программного продукта. Следствия — срыв

сроков проектов, перерасход бюджета, урезанная функциональность и множество

ошибок.

Существенная черта промышленной пропзаммы — ее сложность: один разработчик

не в состоянии охватить все аспекты системы, поэтому в ее создании участвует

целый коллектив. Следовательно, к первичной сложности самой задачи, вытекающей

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

с учетом необходимости координации действий в команде разработчиков.

Так как сложные системы разрабатываются в расчете на длительную эксплуатацию,

то появляются еще две проблемы: сопровождение системы (устранение обнаруженных

ошибок) и ее модификация, поскольку у заказчика постоянно появляются

новые требования и пожелания. Иногда затраты на сопровождение и модификацию

сопоставимы с затратами на собственно разработку системы.

Способ управления сложными системами был известен еще в древности — divide

et impera (разделяй и властвуй). То есть выход — в декомпозиции системы на все

меньшие и меньшие подсистемы, каждую из которых можно совершенствовать

независимо. Здесь вы, наверное, вспомните о методе нисходящего проектирования,

которым мы активно пользовались в первой книге практикума. Но если

в рамках структурного подхода декомпозищтя понимается как разбиение алгоритма,

когда каждый из модулей системы выполняет один из этапов общего процесса,

то ООП предлагает совершенно другой подход.

Критерии качества декомпозиции проекта 13

Суть его в том, что в качестве критерия декомпозиции принимается принадлежность

ее элементов к различным абстракциям проблемной области. Откуда

же берутся эти абстракции? Исключительно из головы программиста, который,

анализируя предметную область, вычленяет из нее отдельные объекты. Для каждого

из этих объектов определяются свойства, существенные для решения задачи.

Затем каждому реальному объекту предметной области ставится в соответствие

программный объект.

Почему обгуектно-ориентированная декомпозиция оказалась более эффективным

средством борьбы со сложностью процессов проектирования и сопровождения

программных систем, чем функциональная'^ декомпозиция? Тому есть много причин.

Чтобы в них разобраться, рассмотрим критерии качества проекта, связанные

с его декомпозицией.

Критерии качества декомпозиции

Проекта

Со слолсностъю приложения трудно что-либо сделать — она определяется целью

создания программы. А вот сложность реализации можно попытаться контролировать.

Первый вопрос, возникающий при декомпозиции: на какие компоненты

(модули, функции, классы) нужно разбить программу? Очевидно, что с ростом

числа компонентов сложность программы растет, поскольку необходима кооперация,

координация и коммуникация между компонентами. Особенно негативны

последствия неоправданного разбиения на компоненты, когда оказываются

разделенными действия, по сути тесно связанные между собой.

Вторая проблема связана с организацией взаимодействия между компонентами.

Взаимодействрте упрощается и его легче взять под контроль, если каждый

компонент рассматривается как некий «черный ящик», внутреннее устройство

которого неизвестно, но известны выполняемые им функции, а также «входы»

и «выходы» этого ящика. Вход компонента позволяет ввести в него значение некоторой

входной переменной, а выход — получить значение некоторой выход?юй

переменной. В программировании совокупность входов и выходов черного ящика

определяет интерфейс компонента. Интерфейс реализуется как набор некоторых

функций (или запросов к компоненту), вызывая которые клиент либо получает

какую-то информацию, либо меняет состояние компонента.

Модное нынче словечко «клиент» означает просто-напросто компонент, которому

понадобились услуги другого компонента, исполняющего в этом случае роль

сервера. Взаимоотношение клиент/сервер на самом деле очень старо и использовалось

уже в рамках структурного программирования, когда функция-клиент

пользовалась услугами функции-сервера путем ее вызова.

Синонимами являются термины структурная, процедурная и алгоритмически-ориентированная

декомпозиция.

1 4 Семинар 1. Классы

Подытожим сказанное о проблемах разбиения программы на компоненты и организации

их взаимодействия. Для оценки качества программного проекта нужно

учитывать, кроме всех прочих, следующие два показателя:

Сцепление {cohesion) внутри компонента — показатель, характеризующий

степень взаимосвязи отдельных его частей. Простой пример: если внутри

компонента решаются две подзадачи, которые легко можно разделить, то

компонент обладает слабым (плохим) сцеплением.

Связанность {coupling) между компонентами — показатель, описывающий интерфейс

между компонентом-клиентом и компонентом-сервером. Общее число

входов и выходов сервера есть мера связанности. Чем меньше связанность

между двумя компонентами, тем проще понять и отслеживать в будущем их

взаимодействие. А так как в больших проектах эти компоненты часто разрабатываются

разными людьми, то очень важно уменьшать связанность между

компонентами.

Заметим, что описанные показатели, конечно, имеют относительный характер,

и пользоваться ими следует благоразумно. Например, фанатичное следование

первому показателю (сильное сцепление) может привести к дроблению проекта

на очень большое количество мелких функций, и сопровождающий программист

вряд ли помянет вас добрым словом.

Почему в случае функциональной декомпозиции трудно достичь слабой связанности

между компонентами? Дело в том, что интерфейс между компонентами

в таком проекте реализуется либо через глобальные переменные, либо через

механизм формальных/фактических параметров. В сложной программной системе,

реализованной в рамках структурной парадигмы, практически невозможно

обойтись без связи через глобальные структуры данных, а это означает, что фактически

любая функция в случае ошибки может испортить эти данные. Подобные

ошибки очень трудно локализуются в процессе отладки. Добавьте к этому

головную боль для сопровождающего программиста, который должен помнить

десятки (если не сотни) обращений к общим данным из разных частей проекта.

Соответственно, модификация существующего проекта в связи с новыми требованиями

заказчика также потребует очень большой работы, так как возникнет

необходимость проверить влияние внесенных изменений практически на все

компоненты проекта.

Другой проблемой в проектах с функциональной декомпозицией было «проклятие

» общего глобального пространства имен. Члены команды, работающей над

проектом, должны были тратить немалые усилия по согласованию применяемых

имен для своих функций, чтобы они были уникальными в рамках всего проекта.

Что принесло с собой ООП

Первым бросающимся в глаза отличием ООП от структурного программирования

является использование классов. Класс — это тип, определяемый программистом,

в котором объединяются структуры данных и функции их обработки.

Что принесло с собой ООП 1 5

Конкретные переменные типа данных «класс» называются экземплярами класса,

или объектами. Программы, разрабатываемые на основе концепций ООП, реализуют

алгоритмы, описывающие взаимодействие между объектами.

Класс содержит константы и переменные, называемые полями, а также выполняемые

над ними операции и функции. Функции класса называются методами^

Предполагается, что доступ к полям класса возможен только через вызов соответствующих

методов. Поля PI методы являются элементами, или членами класса.

Эффективным механизмом ослабления связанности между компонентами в случае

объектно-ориентированной декомпозиции является так называемая инкапсуляция.

Инкапсуляция — это ограничение доступа к данным и их объединение с методами,

обрабатывающими эти данные. Доступ к отдельным частям класса регулируется

с помощью специальных ключевых слов: public (открытая часть), private

(закрытая часть) и protected (защищенная часть)1

Методы, расположенные в открытой части, формируют интерфейс класса и могут

свободно вызываться клиентом через соответствующий объект класса. Доступ

к закрытой секции класса возможен только из его собственных методов, а к защищенной

— из его собственных методов, а также из методов классов-потомков'^

Инкапсуляция повышает надежность программ, предотвращая непреднамеренный

ошибочный доступ к полям объекта. Кроме этого, программу легче модифицировать,

поскольку при сохранении интерфейса класса можно менять его реализацию,

и это не затронет внешний программный код (код клиента).

С понятием инкапсуляции тесно связано понятие сокрытия информации. С другой

стороны, понятие сокрытия информации соприкасается с понятием разделения

ответственности между клиентом и сервером. Клиент не обязан знать, как

реализованы те или иные методы в сервере. Для него достаточно знать, что делает

данный метод и как к нему обратиться. При хорошем проектировании имена

методов обычно отражают суть выполняемой ими работы, поэтому чтение кода

клиента для сопровождающего программиста превращается просто в удовольствие,

если не сказать — в наслаждение.

Заметим, что класс одаривает своего программиста-разработчика надежным «укрытием

», обеспечивая локальную (в пределах класса) область видимости имен.

Теперь можно сократить штат бригады программистов: специалист, отвечающий

за согласование имен функций и имен глобальных структур данных между членами

бригады, стал не нужен. В разных классах методы, реализующие схожие

подзадачи, могут преспокойно иметь одинаковые имена. То же относится и к полям

разных классов.

С ООП связаны еще два инструмента, грамотное использование которых повышает

качество проектов: наследование классов и полиморфизм.

^ Широко используется и другое название — функции-члены, возникшее при подстрочном

переводе англоязычной литературы.

^ Последний вид доступа имеет значение при наследовагши классов и будет рассмотрен на

втором семинаре.

^ Подробнее об этом — на втором семинаре.

1 6 Семинар 1. Классы

Наследование •— механизм получения нового класса из существующего. Производный

класс создается путем дополнения или изменения существующего класса.

Благодаря этому реализуется концепция повторного использования кода.

С помощью наследования может быть создана иерархия родственных типов, которые

совместно используют код и интерфейсы.

Полиморфизм дает возможность создавать множественные определения для операций

и функций. Какое именно определение будет использоваться, зависит от

контекста программы. Вы уже знакомы с одной из разновидностей полиморфизма

в языке C++ — перефузкой функций. Программирование с ютассами предоставляет

еще две возможности: перегрузку операций и использование так называемых

виртуальных методов. Перегрузка операций позволяет применять для собственных

классов те же операции, которые используются для встроенных типов C++.

Виртуальные методы обеспечивают возможность выбрать на этапе выполнения

нужный метод среди одноименных методов базового и производного классов.__

 

От структуры — к классу

Прообразом класса в C++ является структура в С. В то же время в C++ структура

обрела новые свойства и теперь является частным видом класса, все элементы

которого по умолчанию являются открытыми. Со структурой struct в C++ можно

делать все, что можно делать с классом. Тем не хменее в C++ структуры обычно

используют лишь для удобства работы с небольшими наборами данных без

какого-либо собственного поведения.

Новые понятия легче изучать, отталкиваясь от уже освоенного материала. Давайте

возьмем задачу 6.1 из первой книги практикума и посмотрим, что можно

с ней сделать, применяя средства ООП.

ЧТО ТАКОЕ ПОЛИМОРФИЗМ

Полиморфный объект представляет собой такой объект, который может изменять форму во время выполнения программы. Предположим, например, что вы программист, работающий в телефонной компании, и вам необходимо написать программу, которая эмулирует телефонные операции. Поскольку вы знакомы с тем, как люди используют телефон, вы быстро выберете общие операции, такие как набор номера, звонок, разъединение и индикация занятости. С помощью этих операций вы определите следующий класс:

class phone

{
public:
void dial(char "number) { cout << "Набор номера " << number << endl; }
void answer(void) { cout << "Ожидание ответа" << endl; }
void hangup(void) { cout << "Звонок выполнен - повесить трубку" << endl; }
void ring(void) { cout << "Звонок, звонок, звонок" << endl;)
phone(char *number) { strcpy(phone::number, number); };
private:
char number[13];
);

Следующая программа PHONEONE.CPP использует класс phone для создания объекта-телефона:

#include <iostream.h>

#include <string.h>

class phone

{
public:
void dial(char *number) { cout << "Набор номера " << number << endl; }
void answer(void) { cout << "Ожидание ответа" << endl; }
void hangup(void) { cout << "Звонок выполнен - повесить трубку" << endl; }
void ring(void) { cout << "Звонок, звонок, звонок" << endl; }
phone(char *number) { strcpy(phone::number, number); };
private:
char number[13];
};

void main(void)

{
phone telephone("555-1212");
telephone.dial("212-555-1212");
}

Если вы продемонстрируете программу вашему боссу, то он или она скажет, что ваша программа не делает различий между дисковым и кнопочным телефонами, и что она не поддерживает платные телефоны, когда пользователь должен заплатить 25 центов, чтобы позвонить.

Поскольку вы знаете наследование, то примете решение породить классы touch_tone иpay_phone из класса phone, как показано ниже:

class touch_tone : phone

{
public:
void dial(char * number) { cout << "Пик пик Набор номера " << number << endl; }
touch_tone(char *number) : phone(number) { }
};

class pay_phone : phone

{
public:
void dial(char * number)

{
cout << "Пожалуйста, оплатите " << amount << " центов" << endl;
cout << "Набор номера " << number << endl;
}

pay_phone(char *number, int amount) : phone(number) { pay_phone::amount = amount; }
private:
int amount;
};

Как видите, классы touch_tone и pay__phone определяют свой собственный метод dial. Если вы предположите, что метод dial класса, phone основан на дисковом телефоне, то вам не потребуется создавать класс для дискового телефона. Следующая программа NEWPHONE.CPP использует эти классы для создания объектов rotary, touch_tone иpay_phone:

#include <iostream.h>

#include <string.h>

class phone

{
public:
void dial(char *number) { cout << "Набор номера " << number << endl; }
void answer(void) { cout << "Ожидание ответа" << endl; }
void hangup(void) { cout << "Звонок выполнен - повесить трубку" << endl; }
void ring(void) { cout << "Звонок, звонок, звонок" << endl; }
phone(char *number) { strcpy(phone::number, number); };
protected:
char number[13];
);

class touch_tone : phone

{
public:
void dial(char *number) { cout << "Пик пик Набор номера " << number << endl; }
touch_tone(char *number) : phone(number) { }
};

class pay_phone : phone

{
public:
void dial(char * number) { cout << "Пожалуйста, оплатите " << amount << " центов"<< endl; cout << "Набор номера " << number << endl; }
pay_phone(char * number, int amount) : phone(number) { pay_phone::amount = amount; }
private:
int amount ;
};

void main (void)

{
phone rotary("303-555-1212");
rotary.dial("602-555-1212");
touch_tone telephone("555-1212");
telephone.dial("212-555-1212");
pay_phone city_phone("555-1111", 25);
city_phone.dial("212-555-1212");
}

Если вы откомпилируете и запустите эту программу, на экране дисплея появится следующий вывод:

С:> NEWPHONE <Enter>

Набор номера 602-555-1212

Набор номера 212-555-1212

Как уже упоминалось, полиморфный объект представляет собой такой объект, который изменяет форму во время выполнения программы. Предыдущая программа, например, не использовала полиморфные объекты. Иначе говоря, в ней нет объектов, которые бы изменяли форму.

Набор номера 818-555-1212

Набор номера 212-555-1212

Поскольку объект poly_phone изменяет форму по мере выполнения программы, он является полиморфным.

Полиморфный объект представляет собой такой объект, который может изменять форму во время выполнения программы. Для создания полиморфного объекта ваша программа должна использовать указатель на объект базового класса. Далее программа должна присвоить этому указателю адрес объекта производного класса. Каждый раз, когда программа присваивает указателю адрес объекта другого класса, объект этого указателя (который является полиморфным) изменяет форму. Программы строят полиморфные объекты, основываясь на виртуальных функциях базового класса.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Полиморфизм представляет собой способность объекта изменять форму во время выполнения программы. В этом уроке рассмотрены шаги, которые вам необходимо выполнить для создания полиморфных объектов. Из урока 39 вы узнаете, как использовать исключительные ситуации в C++ для обеспечения надежности вашей программы. Прежде чем приступить к уроку 39 убедитесь, что вы освоили следующие основные концепции:

  1. Полиморфный объект может изменять форму во время выполнения программы.
  2. Вы создаете полиморфные объекты, используя классы, порожденные от существующего базового класса.
  3. В базовом для полиморфного объекта классе вы должны определить одну или несколько функций как виртуальные (virtual).
  4. В общем случае полиморфные объекты отличаются использованием виртуальных функций базового класса.
  5. Для создания полиморфного объекта вам необходимо создать указатель на объект базового класса.
  6. Для изменения формы полиморфного объекта вы просто направляете указатель на различные объекты, присваивая новый адрес объекта указателю на полиморфный объект.
  7. Чисто виртуальная функция — это виртуальная функция базового класса, для которой в базовом классе не определены операторы. Вместо них базовый класс присваивает такой функции значение 0.
  8. Производные классы должны обеспечить определение функции для каждой чисто виртуальной функции базового класса.

 

Появление ООП — реакция на кризис

Программного обеспечения

Объектно-ориентированное программирование (ООП) — это технология, возникшая

как реакция на очередную фазу кризиса программного обеспечения, когда

методы структурного программирования уже не позволяли справляться с растущей

сложностью промышленного программного продукта. Следствия — срыв

сроков проектов, перерасход бюджета, урезанная функциональность и множество

ошибок.

Существенная черта промышленной пропзаммы — ее сложность: один разработчик

не в состоянии охватить все аспекты системы, поэтому в ее создании участвует

целый коллектив. Следовательно, к первичной сложности самой задачи, вытекающей

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

с учетом необходимости координации действий в команде разработчиков.

Так как сложные системы разрабатываются в расчете на длительную эксплуатацию,

то появляются еще две проблемы: сопровождение системы (устранение обнаруженных

ошибок) и ее модификация, поскольку у заказчика постоянно появляются

новые требования и пожелания. Иногда затраты на сопровождение и модификацию

сопоставимы с затратами на собственно разработку системы.

Способ управления сложными системами был известен еще в древности — divide

et impera (разделяй и властвуй). То есть выход — в декомпозиции системы на все

меньшие и меньшие подсистемы, каждую из которых можно совершенствовать

независимо. Здесь вы, наверное, вспомните о методе нисходящего проектирования,

которым мы активно пользовались в первой книге практикума. Но если

в рамках структурного подхода декомпозищтя понимается как разбиение алгоритма,

когда каждый из модулей системы выполняет один из этапов общего процесса,

то ООП предлагает совершенно другой подход.

Критерии качества декомпозиции проекта 13

Суть его в том, что в качестве критерия декомпозиции принимается принадлежность

ее элементов к различным абстракциям проблемной области. Откуда

же берутся эти абстракции? Исключительно из головы программиста, который,

анализируя предметную область, вычленяет из нее отдельные объекты. Для каждого

из этих объектов определяются свойства, существенные для решения задачи.

Затем каждому реальному объекту предметной области ставится в соответствие

программный объект.

Почему обгуектно-ориентированная декомпозиция оказалась более эффективным

средством борьбы со сложностью процессов проектирования и сопровождения

программных систем, чем функциональная'^ декомпозиция? Тому есть много причин.

Чтобы в них разобраться, рассмотрим критерии качества проекта, связанные

с его декомпозицией.