Лекция №1. Введение

Вид материалаЛекция

Содержание


Выделение памяти.
Выделение минимальной статической памяти.
Выделение максимальной статической памяти.
Динамическое выделение памяти.
Проверка на равенство.
Преобразование типов.
Полиморфные переменные.
Отложенные методы.
Обобщенные функции и шаблоны.
Полиморфизм в различных языках.
Соответствие между методами и сообщениями.
Классы как типы.
Классы как объекты.
Подобный материал:
  1   2   3   4   5

Лекция №1. Введение.


Объектно – ориентированное программирование представляет собой новую технологию программирования, основанную на высоком уровне абстракции и модульности. Другое название этой технологии – программирование в классах. Все современные системы программирования основаны на применении этой технологии. Наиболее широко применяемые системы - это C++, Delphi, Java.

ООП характеризуют три основные свойства.
  1. Инкапсуляция. Это объединение структур данных с действиями по их обработке (методами обработки) в новую структуру – класс.
  2. Наследование. Это определение некоторого класса, а затем использование его для построения иерархии порожденных классов с наследованием доступа каждого из порожденных объектов к методам и данным предка.
  3. Полиморфизм. Это задание одного наименования действия, которое передается вверх и вниз по иерархии классов с реализацией этого действия способом, соответствующим каждому классу в иерархии.

Основные причины широкого распространения технологии ООП в настоящее время заключаются в следующем:
  • Технология ООП может просто и быстро привести к росту продуктивности и улучшению надежности программного обеспечения;
  • Технология ООП позволяет легко переходить от традиционных языков программирования к интегрированным средам разработки;
  • Технология ООП имеет много сходства с идеями, имеющимися в различных предметных областях.

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


Из истории.


Первоначально программирование являлось скорее искусством, чем наукой. Оно было основано на изучении и применении различных языков программирования, таких как COBOL, FORTRAN, Assembler ALGOL и.т.п.

Все эти языки в основном обеспечивали формализацию алгоритмов различного типа, в основном вычислительных. (Кнут, «Искусство программирования»). Были формализованы различные типовые алгоритмы; численные ( решение уравнений, интегрирование), сортировки и.т. п. Основное внимание уделялось точности и скорости вычислений и экономии оперативной памяти. Было введено понятие модульности и разработан аппарат процедур и функций. Хорошая программа, как правило, должна была состоять из головного модуля и набора процедур и функций, к которым происходили обращения. Основными структурами данных были массив и файл. В силу этого перечисленные языки программирования не имели гибких возможностей для работы со сложными структурами данных. Структуры данных подгонялись под алгоритм решения задачи.

Никлаус Вирт сформулировал тезис, который лег в основу следующего шага в развитии программирования – структурного программирования.

Структура данных + алгоритм = программа.


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

Пример. Пусть имеется последовательность целочисленных значений длиной N, где N – велико. Необходимо проверить, является ли какой – либо отрезок (подпоследовательность) данной последовательности длиной М повторяющимся в данной последовательности.

Простая линейная программа, основанная на структуре одномерного массива (вектора), потребует большого количества времени для выполнения этой работы. Однако, если работать с двумерным массивом (матрицей) размерностью NM и отсортировать эту матрицу по строкам, то время счета значительно сократится. В этом случае, если какой – либо отрезок окажется повторяющимся, то в отсортированной матрице две соседние строки окажутся идентичными.


ФСЕСППФЕСЕЕПСФЕЕЕСППСФЕЕППФСССЕПФСЕЕ………

М = 6.

ФСЕСПП позиции 1 – М.

СЕСППФ позиции 2 – М+1.

ЕСППФЕ позиции 3 – М+2.

---------------------- и.т.п.


Во втором случае появилась более сложная структура данных, которую можно трактовать как массив размерностью (N/M, М), или массив векторов размерностью М. Для каждого вектора определена операция сортировки и операция сравнения векторов.

Для реализации нового подхода к разработке программного обеспечения были созданы языки программирования следующего поколения, такие, как «С», Pascal, Modula – 2, ADA и др. Все они характеризуются развитыми средствами конструирования структур данных любой сложности, базирующихся на элементарных типах данных, встроенных в язык. Однако, по-прежнему, данные и средства их обработки - процедуры и функции существовали по отдельности.

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

Главное средство контроля над сложностью программного обеспечения - это абстрагирование, т. е. способность отделить логический смысл фрагмента программы от проблемы его реализации. Процедуры и функции были первыми механизмами абстрагирования. Они позволяют сконцентрировать в одном месте работу, которая выполнялась многократно, и затем использовать этот код в нужных местах. Кроме того, процедуры обеспечивали возможность скрытия (маскирования) информации. Один программист мог написать процедуру, а другие – ее использовать, причем даже необязательно зная детали используемого алгоритма. Нужно было только знать интерфейс нужной процедуры. Но процедуры не решили всех проблем. В частности, они не обладали эффективным механизмом маскировки деталей организации данных и только отчасти снимали проблему использования разными программистами одинаковых имен.

Следующий шаг состоял в появлении и разработке концепции модуля. Модули можно рассматривать как улучшенный метод создания и управления совокупностями имен и связанными с ними значениями. Если рассматривать модуль как абстрактную концепцию, то ее суть состоит в разбиении пространства имен на две части. Открытая (public) часть является доступной извне модуля, закрытая (private) часть доступна только внутри модуля.

Типы, данные (переменные) и процедуры могут быть отнесены к любой из двух частей. Справедливы следующие два принципа использования модулей:
  • Пользователя следует снабдить всей информацией, необходимой для корректного использования модуля, но не более того;
  • Разработчика следует снабдить всей необходимой информацией для разработки модуля, но не более того.

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

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

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

Модули часто используются при реализации абстрактных типов данных. Чтобы использовать абстрактный тип данных, необходимо:
  1. Экспортировать определение типа данных;
  2. Делать доступным набор операций, использующихся для манипулирования экземплярами типа данных;
  3. Защищать данные, связанные с типом данных, чтобы с ними можно было бы работать только через указанные подпрограммы;
  4. Создавать необходимое количество экземпляров (объявлять переменные) абстрактного типа данных.

Модули служат только как механизм маскировки информации и непосредственно связаны только со свойствами 2 и 3. Остальные свойства могут, вообще говоря, быть реализованы с помощью соответствующей техники программирования.

ООП добавляет несколько важных идей к концепции абстрактных типов данных. Главная из них - это пересылка сообщений. Действие инициируется по запросу, обращенному к конкретному объекту, а не через вызов функции. Вторая важная идея – это механизм переопределения имен и совместного (многократного) использования кода. Здесь самым важным является то, что интерпретация сообщения может меняться для различных объектов, и зависят от объекта – получателя сообщения. Поскольку имена операций не обязаны быть уникальными, могут использоваться простые и явные формы. Это приводит к более простому и понятному коду.

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


Лекция №2. Основные принципы объектно – ориентированного программирования.


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

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

Во – вторых, интерпретация сообщения зависит от получателя и является различной для разных получателей.

Например, необходимо доставить N байт информации. Это сообщение может быть по-разному интерпретировано такими объектами, как канал связи, дисковый накопитель или устройство печати. У каждого из них свои методы обслуживания данного сообщения.

Таким образом, различие между вызовом процедуры и пересылкой сообщения состоит в том, что в последнем случае существует определенный получатель и интерпретация (т. е., выбор определенного метода, который запускается в ответ на сообщение) может быть различной для разных получателей. Обычно конкретный получатель неизвестен вплоть до выполнения программы, так что определить заранее, какой метод будет вызван, вообще говоря, невозможно. В этом случае говорят, что имеет место позднее связывание между сообщением (именем метода) и фрагментом кода. Эта ситуация в корне отличается от раннего связывания (на этапе компоновки программы) имени с фрагментом кода, что происходит при традиционных вызовах процедур.

Фундаментальной концепцией ООП является понятие обязанности или ответственности за выполнение действия. В этом смысле запрос выражает только стремление получить желаемый результат. Объект – получатель свободен в выборе способа, который приведет к этому результату. Это увеличивает уровень абстракции и позволяет иметь большую независимость между объектами, что является критическим фактором при решении сложных задач. Полный набор обязанностей, связанных с определенным объектом, часто определяется термином «протокол».


Классы и экземпляры.


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

В современных языках программирования (C++, Java, Delphi) категория называется классом, а экземпляры классов (представители) называются объектами.

Иерархия классов и наследование.


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





Внутри категории (класса) «Звезды» есть, например, две основные ветви: видимые звезды и невидимые звезды. Невидимые звезды имеют подкатегории (подклассы) «Черные дыры» и «Нейтронные звезды». Видимые звезды имеют подкатегории (подклассы) с переменной и постоянной яркостью, и т. п.

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

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

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

Связывание и переопределение методов.


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

Поиск метода, который вызывается в ответ на определенное сообщение, начинается с методов, принадлежащих классу получателя. Если подходящий метод не найден, то поиск продолжается для надкласса. Поиск продвигается вверх по цепочке надклассов до тех пор, пока не будет исчерпана последовательность надклассов, или пока не будет найден походящий метод. В первом случае выдается сообщение об ошибке, во втором – выполняется нужный метод. Если выше в иерархии классов существуют методы с тем же именем, что и текущий, то говорят, что данный метод переопределяет (перекрывает) наследуемое поведение (свойство).

Факт реакции на одно и то же сообщение различных объектов по-разному является одним из примеров полиморфизма.

Один из основателей ООП Алан Кей сформулировал следующие фундаментальные принципы ООП:
  1. Все является объектами.
  2. Вычисления осуществляются путем взаимодействия (обмена данными) между объектами, при котором один объект требует, чтобы другой объект выполнил некоторое действие. Объекты взаимодействуют, посылая и принимая сообщения. Сообщение -–это запрос на выполнение действия, дополненный набором аргументов, которые могут понадобиться при выполнении действия.
  3. Каждый объект имеет независимую память, которая состоит из других объектов.
  4. Каждый объект является представителем класса, который выражает общие свойства объектов (таких, как целые числа, списки и т.п.).
  5. В классе задается функциональность (поведение) объекта. Тем самым все объекты, которые являются экземплярами одного класса, могу выполнять одни и те же действия.
  6. Классы организованы в единую древовидную структуру с общим корнем, называемую иерархией наследования.
  7. Память и поведение, связанные с экземпляром определенного класса, автоматически доступны любому классу, расположенному ниже в иерархическом дереве.


Резюме.

  • ООП - это не просто несколько новых свойств, добавленных в уже существующие языки программирования. Это новый шаг в осмыслении процессов декомпозиции задач и разработке ПО.
  • ООП рассматривает программы как совокупность свободно (гибко) связанных между собой агентов, называемых объектами. Каждый объект отвечает за конкретные задачи. Вычисление осуществляется посредством взаимодействия объектов. ООП таким образом моделирует программируемый процесс.
  • Объект получается в результате инкапсуляции состояния (данных) и поведения (операций). Тем самым объект во многих отношениях аналогичен модулю, или абстрактному типу данных.
  • Поведение объекта диктуется его классом. Каждый объект является экземпляром некоторого класса. Все объекты одного класса будут вызывать одни и те же методы в ответ на одинаковые запросы.
  • Объект проявляет свое поведение путем вызова метода в ответ на сообщение. Интерпретация сообщения (т.е., конкретный используемый метод), зависит от объекта и может быть различной для различных классов объектов.
  • Классы и объекты расширяют понятие абстрактного типа данных путем введения наследования. Классы могут быть организованы в виде иерархического дерева наследования. Данные и методы, связанные с классами, расположенными выше в иерархическом дереве, доступны для нижележащих классов. Происходит наследование поведения от надклассов.
  • С помощью уменьшения взаимозависимости между компонентами программного обеспечения ООП позволяет разрабатывать системы, пригодные для многократного использования. Такие компоненты могут быть созданы и отлажены как независимые программные единицы, в изоляции от других частей прикладной программы.
  • Многократно используемые программные компоненты позволяют разработчикам иметь дело с проблемами на более высоком уровне абстракции. Возможно определять объекты и манипулировать ими просто в терминах сообщений, которые они распознают, и работы, которую они выполняют, игнорируя детали реализации.



Лекция №3. Классы и методы.

Ранее было показано, что объекты могут рассматриваться как абстрактные типы данных. В программировании, основанном на абстракции данных, информация сознательно прячется в небольшой части программы. В частности, каждый объект из набора абстрактных типов данных, разрабатываемых программистом, имеет «два лица». С внешней точки зрения (пользователя) абстрактный тип данных представляет из себя всего лишь совокупность операций, которые определяют поведение абстракций (интерфейс). С противоположной стороны, за фасадом интерфейса, программист, который определяет абстрактный тип, видит значения переменных, которые используются для поддержки внутреннего состояния объекта.

Пусть, например, имеется абстрактный тип Stack. Пользователь знает только описание допустимых операций, например: push, pop, top. С другой стороны, программист, реализующий (разрабатывающий) тип Stack, должен манипулировать с конкретными структурами данных. Конкретные детали инкапсулированы в более абстрактный объект:

Пользователь Разработчик

Push Const Limit = 300;

Pop Var CurrentTop: 0..Limit;

Top Values: array [1..Limit] of integer;


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

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

Таким образом, можно сформулировать основные принципы для классов и объектов.
  • Объявление класса должно обеспечивать пользователя всей информацией, необходимой для успешной работы с классом, и никакой другой.
  • Методам должна быть доступна вся информация, необходимая для выполнения их обязанностей, и никакой другой.

Эти принципы делят объект на две части. Имеется внешний образ, наблюдаемый пользователем объекта – это представление об образе называется интерфейсом (interface), поскольку оно описывает, как объект взаимодействует с внешним миром. Другая часть связана с его реализацией (implementation). Пользователю разрешен доступ только к тому, что описано в интерфейсной части. Реализация определяет, как достигается выполнение обязанностей, заявляемых в интерфейсной части.

Все современные языки программирования - Delphi, C++, Java поддерживают разбиение класса на блок интерфейса и блок реализации. Здесь важно иметь в виду, что разделение интерфейса и реализации не является в точности инкапсуляцией данных, рассмотренной выше. Первое является абстрактным понятием, второе – лишь механизмом его воплощения. Таким образом, модули используются в процессе реализации объектов, принадлежащих к абстрактным типам данных, но сами по себе модули не являются абстрактными типами данных. Все вышеперечисленные языки программирования рассматривают классы как расширенную форму структуры данных типа «запись».

Далее на одном примере будет рассмотрена реализация классов в языках Delphi, C++ и Java.

Пусть необходимо разработать класс «игральная карта», который далее может быть использован в моделировании любой карточной игры (класс Card). Класс Card должен:
  1. Хранить ранг и масть карты;
  2. Возвращать атрибуты карты (цвет, ранг, масть);
  3. Хранить состояние «картинка вверх» или «картинка вниз»;
  4. Уметь отображать карту на экране;
  5. Уметь удалять карту с экрана.