Файл: История и развитие методологии объектно-ориентированного программирования. Сферы применения.pdf
Добавлен: 14.05.2023
Просмотров: 413
Скачиваний: 2
СОДЕРЖАНИЕ
Часть I Объектно-ориентированное программирование
Глава 1 Эволюция методологий программирования
1.1 Поколения языков программирования
1.1.1 Начало начал, или первое поколение языков программирования
1.1.2 Развитие алгоритмических абстракций. Второе поколение языков программирования.
1.2. ЗАРОЖДЕНИЕ ОБЪЕКТНОЙ МОДЕЛИ
1.2.1 Объектные языки программирования
1.2.2 Объектно-ориентированные языки
1.2.3 Объектно-ориентированный анализ, дизайн и проектирование
1.3 Парадигмы программирования
Глава 2 Составные части объектного подхода
3.1 Что такое объект с точки зрения ООП
3.3.1 Классификация методов объектов
3.3.3 Связь объектов и автоматов, активные и пассивные объекты
• Абстракция сущности Объект представляет собой полезную модель некой сущности в предметной области
• Абстракция поведения Объект состоит из обобщенного множества операций
• Абстракция виртуальной машины
Объект группирует операции, которые вместе используются более высоким уровнем управления, либо сами используют некоторый набор операций более низкого уровня
• Произвольная абстракция
Объект включает в себя набор операций, не имеющих друг с другом ничего общего В качестве примера, рассмотрим автомобиль. Каждый человек имеет набор представлений и ассоциаций, связанных с этим понятием. Возьмем, например, двигатель. Это объект, который во взаимодействии с другими объектами (системой зажигания, карбюратором, коробкой передач) обеспечивает определенную функциональность. Эта функциональность заключается в способности создания крутящего момента вала, который передается коробке передач. При этом, двигатель использует при работе другие системы: систему зажигания и систему подачи топлива. В нашем случае двигатель является абстракцией, выделенной на основе своей наиболее существенной характеристики, а именно, способности превращать химическую энергию топлива в механическую энергию вращения распределительного вала. Продолжая анализ и декомпозицию автомобиля, можно выделить набор абстракций, элементами которого будут двигатель, коробка передач, система зажигания и т.д. Каждая из этих абстракций выделяется на основе своего интерфейса и функциональности. 2.2 Инкапсуляция Следующим важным принципом, который непосредственно призван осуществлять поддержку абстрагирования, является инкапсуляция. Процесс выделения ключевых абстракций при решении поставлен- ной задачи в большей степени относится к объектно-ориентированному проектированию. Инкапсуляция же, напротив, является главенствующим принципом объектно-ориентированного программирования.
2.2. ИНКАПСУЛЯЦИЯ
Абстрагирование направлено на наблюдаемое поведение (контракт) объекта, а инкапсуляция обеспечивает сокрытие его реализации. Барбара Лисков прямо утверждает, что «абстракция будет работать только вместе с инкапсуляцией». Практически же, это означает, что в любом классе присутствуют две части: интерфейс и реализация. Интерфейс отражает внешнее поведение абстракции, специфицируя поведение всех объектов данного класса. Внутренняя реализация описывает представление этой абстракции и механизмы достижения желаемого поведения объекта. Принцип разделения интерфейса и реализации соответствует сути вещей: в интерфейсной части собрано все, что касается взаимодействия данного объекта с другими объектами, а реализация скрывает от других объектов все детали, не имеющие отношения к процессу взаимодействия объектов. Бритон и Парнас назвали такие детали «тайнами абстракции». Буч определяет инкапсуляцию следующим образом: Инкапсуляция — это процесс отделения друг от друга элементов объекта, определяющих его устройство и поведение; инкапсуляция служит для того, чтобы изолировать контрактные обязательства абстракции от их реализации. Современные объектно-ориентированные языки, такие как C++ и Java, имеют развитые средства поддержки принципа инкапсуляции. Эти средства выражаются в наличии механизмов управления доступом к методам и данным объекта 1. Разумная инкапсуляция позволяет локализовать части реализации программной системы, которые могут подвергнуться изменениям. Например, по мере развития программного продукта, разработчики могут принять решение изменить внутреннее устройство тех или иных объектов с целью улучшения производительности или экономии памяти. При этом, важным преимуществом наличия механизмов разграничения доступа (механизмов инкапсуляции), является возможность внесения изменений в реализацию объекта (класса) без изменения других объектов (классов). Если вернуться к рассмотрению автомобиля, то можно продемонстрировать принцип инкапсуляции на примере реализации акселератора. В одном случае механизм связи между педалью газа и заслонкой карбюратора может быть выполнен с помощью сложной системы тросов и рычагов, а в другом — с помощью датчика, сигнал с которого подается на электронное устройство управления карбюратором. В любом случае мы имеем дело с инкапсулированной реализацией этого механизма, при которой водителю нет дела до того как все устроено — он управляет подачей топлива через интерфейс, заключающийся в возможности нажатия на педаль акселератора.
2.3 Модульность
При разработке большой объектно-ориентированной программной системы одной из основных проблем становится наличие очень большого количества абстракций (классов и интерфейсов). Так как абстракции не существуют независимо, а взаимодействуют друг с другом, то ситуация усугубляется сложностью графа зависимостей между ними. Как одно из средств преодоления сложностей такого рода выступает принцип модульности. Разбиение программы на модули не только позволяет бороться со сложностью, но и заставляет определять и хорошо документировать интерфейсы (или интерфейсные классы) между модулями, что в свою очередь, облегчает процесс объектной декомпозиции системы. Наличие четко определенных и хорошо задокументированных интерфейсов во многом способствует формированию цельного представления о программной системе и ее составляющих частях (подсистемах). Использование модулей характерно не только для объектно-ориентированного программирования. В традиционном структурном программировании модули играют роль контейнеров в которые сгруппированы процедуры и данные, которыми они оперируют. Основная задача при этом состоит в том, чтобы в один модуль попадали подпрограммы, использующие друг друга или изменяемые вместе. В объектно-ориентированном программировании модули выполняют роль физических контейнеров и областей определения типов. Модули могут содержать определения классов, интерфейсов (Java), а также глобальные объекты и данные (C++). Разные языки имеют разную поддержку модульности. Согласно Барбаре Лисков «модульность — это разделение программы на фрагменты, которые компилируются по отдельности, но могут устанавливать связи с другими модулями». В языке C++ это определение по прежнему может быть признано актуальным. В языке C и раннем C++, препроцессор и раздельная компиляция служили основными средствами поддержки рассматриваемого принципа. В процессе эволюции и стандартизации в С++ были введены пространства имен, которые являются средством логического разделения области видимости включаемых в них классов и данных. Программы на Java, как правило, обладают более высокой степенью модульной гранулированности нежели программы на C++. Это связано с тем, что в Java принцип модульности с самого начала был положен в основу модели стандартной библиотеки и является основополагающим принципом при формировании физической модели размещения компонент Java программ. Этот язык имеет развитые средства поддержки принципа модульности и поощряет разработчиков на активное следование этому принципу. Правильное разбиение программы на модули зачастую является почти столь же важной задачей, что и выбор правильного набора абстракций. Как и выделение ключевых абстракций, выделение модулей и определение зависимостей и взаимосвязей между ними — основная задач объектно-ориентированного проектирования. Ситуация зачастую осложняется тем, что уже на начальном этапе проектирования, когда выделены далеко не все абстракции, встает задача разбиения системы на модули. Вероятность возникновения потребности во внесении изменений в интерфейсные элементы модулей должна быть сведена к минимуму. Так как такие изменения могут существенным образом затрагивать части системы, использующие данный модуль. Всегда нужно стремиться к минимизации интерфейсных частей модулей, естественно в разумных пределах, с целью уменьшения сцепления между разными частями системы. Модули должны объединять логически связанные абстракции. Следует помнить, что модуль является минимальной единицей переиспользования и размещения программной системы. Использование даже одного класса как правило означает зависимость от всего модуля. Формализуя все выше сказанное, можно дать следующее определение модульности (по Бучу): Модульность — это свойство системы, которая была разложена на внутренне связные, но слабо связные между собой модули. Возвращаясь к нашему автомобилю, можно выделить такие примеры модулей, как электрическая система (электропитание, освещение и т.п.), силовая установка (двигатель, карбюратор и др.) ходовая часть (трансмиссия, распределительный вал, колеса, тормозная система) и так далее. 2.4 Иерархия Инкапсуляция и модульность позволяют в значительной степени упростить описание и процесс разработки системы абстракций, но зачастую, еще лучше позволяет бороться со сложностью принцип иерархии. Инкапсуляция убирает из поля зрения внутренние содержание абстракций, модульность объединяет логически связанные абстракции в группы, а иерархия позволяет разделить абстракций на уровни, т.е. образует из абстракций иерархическую структуру. Буч определяет иерархию следующим образом: Иерархия — это упорядочение абстракций путем расположения их по уровням.
2.4. ИЕРАРХИЯ
В объектно-ориентированных системах используются два вида иерархических структур: структуры классов ( иерархические отношения «is a») и структуры объектов (отношения вида «part of»). Отношения вида «is a» реализуются в объектно-ориентированных языках с помощью наследования или генерализации. Наследование означает такое отношение между классами (отношение родитель/потомок), когда один класс заимствует, а также расширяет и/или специализирует (уточняет) структуру и функциональный контракт одного или нескольких родительских классов. Иными словами, наследование создает такую иерархию абстракций, в которой подклассы наследуют строение и функциональность от одного или нескольких суперклассов. Часто подкласс достраивает или переписывает компоненты вышестоящего класса. В наследственной иерархии общая часть структуры и поведения сосредоточена в наиболее общем суперклассе. По этой причине говорят о наследовании, как об иерархии обобщение специализация. Суперклассы, при этом, отражают наиболее общие, а подклассы — более специализированные абстракции, в которых члены суперкласса могут быть дополнены, модифицированы и даже скрыты. При одиночном наследовании класс может иметь только одного родителя, но реализовывать несколько интерфейсов. При этом интерфейсы могут наследовать от нескольких родительских интерфейсов. При множественном наследовании у класса может быть несколько суперклассов. Структурно-агрегационные иерархические отношения («part of») мы рассмотрим, когда будем вести разговор об объектах и отношениях между ними. При этом класс-родитель является суперклассом для класса-потомка (подкласса)
2.5 Типизация
Понятие типа пришло в ООП из теории абстрактных типов данных.
Дойч определяет тип, как «точную характеристику свойств, включая структуру и поведение, относящуюся к некоторой совокупности объектов» Несмотря на то, что в некоторых языках существуют отличия между типом и классом, в современных объектно-ориентированных языках (в нашем случае C++ и Java) эти понятия неразделимы. Мы будем подразумевать под типами как примитивные типы (char, int, float), так и языковые средства абстракций, определяемых пользователем (классы, структуры и интерфейсы). Типизация, как и инкапсуляция, больше относится к области объектно-ориентированного программирования, нежели к области объектно-ориентированного проектирования. Тем не менее, следует учитывать особенности языка при проектировании системы абстракций с тем, чтобы язык программирования обеспечивал соблюдение выработанных проектных решений. Ниже приведено определение типизации по Бучу: Типизация — это способ защититься от использования объектов одного класса вместо другого, или по крайней мере управлять таким использованием. Центральное место в типизации занимают механизмы согласования типов. Конкретный язык программирования может иметь сильный или слабый механизм типизации, или даже не иметь его вовсе. Сильно типизированные языки непреклонно следуют правилам использования типов. Так, в языках C++ и Java нельзя вызвать метод у объекта, если он не зарегистрирован в его классе, суперклассе или интерфейсе. Причем нарушение такого рода будет обнаружено уже на стадии компиляции. В Smalltalk, напротив, во время исполнения любое сообщение может быть послано любому объекту, при этом возникнет ошибочная ситуация, если объект не в состоянии обработать это сообщение (т.е. в его классе или суперклассе нет соответствующего метода).
2.5. ТИПИЗАЦИЯ
Не смотря на то, что и C++ и Java являются сильно типизированными языками, механизмы согласования типов у них различны. В Java определяемые пользователем типы (классы и интерфейсы) могут приводиться друг к другу только согласно иерархии наследова- ния (ссылка на объект определенного класса может быть приведена к ссылке на совместный тип — суперкласс или интерфейс, реализуемый классом или суперклассом) 3. В языке C++ существует механизм неявного приведения типов, которые могут быть не связаны друг с другом иерархией наследования (конструкторы классов от одного аргумента приводимого типа, не объявленные explicit). Также возможно приведение экземпляра класса к примитивным типам (например, можно определить для класса operator bool() с целью использования объекта этого класса в логических условиях). И в C++, и в Java есть средства явного преобразования и проверки типов во время исполнения. Сильная типизация заставляет разработчика соблюдать правила использования абстракций, поэтому она приносит неоценимую помощь в больших проектах. Однако у нее есть теневая сторона, заключающаяся в необходимости перекомпиляции всех подклассов, а также классов, использующих данный класс при внесении изменений в его интерфейс. Вторым важным аспектом, который следует упомянуть в связи с сильной типизацией, является задача реализации контейнеров, и, в особенности, контейнеров, способных содержать разнотипные элементы. В Java контейнеры стандартной библиотеки хранят ссылки на хранимые объекты как на экземпляры класса Object, являющегося суперклассом. В этом правиле есть одно исключение, а именно, возможность приведения любого объекта к типу String посредством неявного вызова метода to String() объявленного в корневом классе Object Следует заметить, однако, что если Java всегда гарантирует совместимость переменной ссылочного типа и объекта, на который эта переменная ссылается, то в C++ существуют средства полностью обойти или подавить правила типизации с помощью явных преобразований типа, таких как reinterpret_castсом для всех классов. Это заставляет разработчика заботиться об обратном приведении типов при извлечении объектов из контейнеров. В С++ задача решается с помощью шаблонов, но в этом случае в контейнере могут храниться только объекты типов, приводимых к типу параметра, заданного при инстанцировании шаблона. Не следует путать понятия сильной (строгой) и статической типизации. Строгая типизация призвана обеспечить соответствие типов, а статическая типизация (так же называемая статическим или ранним связыванием), определяет время, когда имена (переменные ссылочного типа или указатели) связываются с типами адресуемых ими объектов. При статическом связывании тип адресуемого объекта, ровно как и тип результата любого выражения, определяется уже на стадии компиляции. При динамическом (или позднем) связывании тип результата выражения или объекта, на который ссылается указатель или переменная ссылочного типа определяется во время исполнения программы. При этом указатель (или ссылка) могут ссылаться на объект любого типа совместимого по иерархии наследования с типом указателя или ссылки, соответственно. Данная особенность называется полиморфизмом: одно и то же имя может означать объекты разных типов, но имея общего предка, все они имеют и общее подмножество операций (контракт), которые можно над ними выполнить. Противоположность полиморфизму называется мономорфизмом, который характерен для языков с сильной типизацией и статическим связыванием. Формулировка, приведенная выше, определяет «виртуальный полиморфизм» (результат взаимодействия наследования и динамического связывания), наряду с которым различают еще и «параметрический полиморфизм» — свойство языков позволяющее объявлять и использовать функции с одинаковыми именами, но отличающиеся типами и/или количеством своих аргументов. Процесс выбора конкретной функции(метода) во время исполнения, в зависимости от типа объекта или аргументов, называется разрешением полиморфизма.
2.6. ПАРАЛЛЕЛИЗМ
Виртуальный полиморфизм — одно из самых мощных и эффективных средств объектно-ориентированного программирования, отличающее его от программирования на основе абстрактных типов данных. Таким образом, C++ и Java являются сильно типизированными языками с динамическим связыванием. Параллелизм в объектно-ориентированном программировании, как и другие принципы, возник не на пустом месте, а явился результатом привнесения объектной идеи в теорию параллельных вычислений. Необходимость в разработке теории параллельных вычислений возникла давно. Задачи автоматизации очень часто требуют одновременной обработки нескольких событий. При решении задач, связанных с большой вычислительной трудоемкостью, очень часто бывает недостаточно мощности одного процессора и приходится искать решение основанное на распараллеливании вычислений на многопроцессорных системах. Существует очень много классов задач, где возможность использования параллелизма может сильно улучшить характеристики разрабатываемой информационной системы. Фундаментальным понятием и единицей действия в теории параллельных вычислений является поток управления. Традиционно, многопоточность бывает двух видов — тяжелая (основанная на процессах в операционной системе) и легкая (основанная на потоках в рамках одного процесса). Потоки управления при тяжелой многопоточности существуют каждый в своем отдельном адресном пространстве в рамках операционной системы (с каждым процессом ассоциируется ровно один поток управления). Переключение контекстов исполняемых потоков при операциях межпроцессного взаимодействия в таких системах, как правило, сопряжено с большими накладными расходами. При легкой многопоточности потоки внутри процесса разделяют общее адресное пространство. При этом возникает проблема обеспечения конкурентного доступа к данным из разных потоков. Программа, работающая в системе с легкой многопоточностью, представляет из себя совокупность из нескольких потоков управления и точек синхронизации. Точки синхронизации обеспечивают целостность совместно используемых данных и взаимодействие потоков между собой. В объектно-ориентированной системе, использующей принципы и средства параллелизма, потоки управления представляются активными объектами, которые являются инициаторами всех происходящих в системе действий. Таким образом, объекты делятся на активные (являющиеся своего рода отдельными вычислительными центрами) и пассивные, на которые направленно воздействие активных объектов. Параллелизм дает возможность объектам действовать одновременно. На основе этой идеи, Буч дает следующее определение параллелизма: Параллелизм — это свойство, отличающее активные объекты от пассивных. Как только в систему введен параллелизм, сразу возникает вопрос о синхронизации активных объектов друг с другом и последовательными пассивными объектами. Например, если два объекта посылают сообщения третьему, то должен существовать какой-то механизм, гарантирующий, что объект, на который направлено действие, не разрушится при одновременной попытке двух активных объектов изменить его состояние. В этом вопросе соединяются абстракция, инкапсуляция и параллелизм. В параллельных системах недостаточно определить поведение объекта, надо еще принять меры, гарантирующие, что он не будет «растерзан на части» несколькими независимыми процессами. Параллелизм может обеспечиваться как средствами языка (Java, Ada, Smalltalk), так и специально написанными библиотеками, которые используются при написании параллельной системы с использованием языков, не имеющих встроенной поддержки этого принципа (C++). Следует, однако, отметить, что даже если язык имеет встроенную поддержку параллелизма, все равно, необходимо учитывать устройство и особенности организации многопоточности в конкретных операционных системах, под управлением которых планируется работа разрабатываемой программы.