Файл: Основные понятия объектно-ориентированного программирования.pdf
Добавлен: 20.05.2023
Просмотров: 951
Скачиваний: 13
Пример тесной связи между динамической библиотекой GUI и объектно-ориентированного языка программирования можно найти посмотрев на Фреймворк Cocoa на Mac OS X, который был написан на Objective-C, объектно-ориентированом расширении к C#, основанном на Smalltalk с поддержкой динамических сообщений.
Инструментарии ООП повлияли на популярность событийно-ориентированного программирования (хотя, эта концепция не ограничивается одним ООП).
Некоторые даже думали, что кажущаяся или реальная связь с графическими интерфейсами – это то, что вынесло ООП на передний план технологий.
В ETH Zürich, Никлаус Вирт и его коллеги тоже исследовали такие предметы, как абстрация данных и модульное программирование, хотя эти подходы широко использовались и в 60-х и ранее. Modula-2 вышедшая в 1978 включала оба эти подхода, а ее последователь Oberon имел собственный подход к объектно-ориентированности, классам и прочему, непохожий на подход Smalltalk и совсем не похожий на подход C++.
Возможности ООП добавлялись во многие языки того времени, включая Ada, BASIC, Fortran, Pascal и другие. Их добавление в языки, изначально не разрабатывавшиеся для поддержки ООП часто приводило к проблемам с совместимостью и поддержкой кода.
Позднее стали появляться языки, поддерживающие как объектно-ориентированный подход, так и процедурный вроде Python и Ruby. Пожалуй, самыми коммерчески успешными объектно-ориентированными языками можно назвать Visual Basic.NET, C# и Java. И .NET и Java демонстрируют превосходство ООП.
- Основные понятия
1.Абстракция данных
2.Инкапсуляция
3.Наследование
4.Полиморфизм подтипов
5.Класс
6.Объект
2.1. Абстрагирование данных.
Абстракция в объектно-ориентированном программировании — это придание объекту характеристик, которые чётко определяют его концептуальные границы, отличая от всех других объектов.
Основная идея состоит в том, чтобы отделить способ использования составных объектов данных от деталей их реализации в виде более простых объектов, подобно тому, как функциональная абстракция разделяет способ использования функции и деталей её реализации в терминах более примитивных функций, таким образом, данные обрабатываются функцией высокого уровня с помощью вызова функций низкого уровня. Это важный инструмент ООП наряду с полиморфизмом, наследованием и инкапсуляцией.
Абстракция является основой объектно-ориентированного программирования и позволяет работать с объектами, не вдаваясь в особенности их реализации.
Абстракция данных — популярная и в общем неверно определяемая техника программирования. Фундаментальная идея состоит в разделении несущественных деталей реализации подпрограммы и характеристик, существенных для корректного её использования.
Такое разделение может быть выражено через специальный «интерфейс», сосредотачивающий описание всех возможных применений программы.
Интерфейс — программная/синтаксическая структура, определяющая отношение между объектами, которые разделяют определенное поведенческое множество и не связаны никак иначе. При проектировании классов, разработка интерфейса тождественна разработке спецификации (множества методов, который каждый класс, использующий интерфейс должен реализовывать).
Интерфейсы, наряду с абстрактными классами и протоколами, устанавливают взаимные обязательства между элементами программной системы, что является фундаментом концепции программирования по контракту (англ. design by contract (DbC). Интерфейс определяет границу взаимодействия между классами или компонентами, специфицируя определенную абстракцию, которую осуществляет реализующая сторона.
Интерфейс в ООП является строго формализованным элементом объектно-ориентированного языка и широко используется в исходном коде программ.
Интерфейсы позволяют наладить множественное наследование объектов и в то же время решить проблему ромбовидного наследования. В языке C++ она решается через наследование классов с использованием ключевого слова virtual.
Интерфейсы и абстрактные классы.
Можно заметить, что интерфейс, с формальной точки зрения, — это просто чистый абстрактный класс, то есть класс, в котором не определено ничего, кроме абстрактных методов. Если язык программирования поддерживает множественное наследование и абстрактные методы (как, например, C++), то необходимости во введении в синтаксис языка отдельного понятия «интерфейс» не возникает. Данные сущности описываются с помощью абстрактных классов и наследуются классами для реализации абстрактных методов.
Однако поддержка множественного наследования в полном объёме достаточно сложна и вызывает множество проблем, как на уровне реализации языка, так и на уровне архитектуры приложений. Введение понятия интерфейсов является компромиссом, позволяющим получить многие преимущества множественного наследования (в частности, возможность удобно определять логически связанные наборы методов в виде сущностей, подобных классам и допускающих наследование и реализацию), не реализуя его в полном объёме и не сталкиваясь, таким образом, с большинством связанных с ним трудностей.
На уровне исполнения классическая схема множественного наследования вызывает дополнительный ряд неудобств:
- если объект может параллельно наследовать n классов, существует n независимых способов к нему обращаться, а значит должно существовать (n — 1) дополнительных указателей на него; с точки зрения автоматического управления памятью это будет означать, что возникают ссылки, указывающие в середину объекта;
- поддержка виртуальных вызовов подразумевает, что в объекте хранится ссылка на его виртуальную таблицу, а в случае множественного наследования n ссылок; активное использовании множественного наследования сильно увеличит объём памяти, занимаемый каждым объектом (экземпляром).
Использование схемы с интерфейсами (вместо множественного наследования) позволяет отбросить эти проблемы, если не считать вопроса о вызове интерфейсных методов (то есть виртуальных вызовов методов при множественном наследовании, см. выше). Классическое решение состоит в том (например, в JVM для Java или CLR для C#), что интерфейсные методы вызываются менее эффективным способом, без помощи виртуальной таблицы: при каждом вызове сначала определяется конкретный класс объекта, а затем в нём ищется нужный метод (разумеется, с многочисленными оптимизациями).
Абстрактный класс в объектно-ориентированном программировании — базовый класс, который не предполагает создания экземпляров. Абстрактные классы реализуют на практике один из принципов ООП — полиморфизм. Абстрактный класс может содержать (и не содержать) абстрактные методы и свойства. Абстрактный метод не реализуется для класса, в котором описан, однако должен быть реализован для его неабстрактных потомков. Абстрактные классы представляют собой наиболее общие абстракции, то есть имеющие наибольший объём и наименьшее содержание.
В одних языках создавать экземпляры абстрактных классов запрещено, в других это допускается (например, Delphi), но обращение к абстрактному методу объекта этого класса в процессе выполнения программы приведёт к ошибке. Во многих языках допустимо объявить любой класс абстрактным, даже если в нём нет абстрактных методов (например, Java), именно для запрещения создания экземпляров. Абстрактный класс можно рассматривать в качестве интерфейса к семейству классов, порождённому им, но, в отличие от классического интерфейса, абстрактный класс может иметь определённые методы, а также свойства.
2.2.Инкапсуляция
Инкапсуляция (англ. encapsulation,от лат. incapsula) в информатике
упаковка данных и функций в единый компонент.
Инкапсуляция зачастую рассматривается как понятие, присущее исключительно объектно-ориентированному программированию (ООП), но в действительности обширно встречается и в других (см. подтипизация на записях и полиморфизм записей и вариантов).
В ООП инкапсуляция тесно связана с принципом абстракции данных (не путать с абстрактными типами данных, реализации которых предоставляют возможность инкапсуляции, но имеют иную природу).
Это, в частности, приводит к другому распространённому заблуждению — рассмотрению инкапсуляции неотрывно от сокрытия. В частности, в сообществе С++ или Java принято рассматривать инкапсуляцию без сокрытия как неполноценную.
Однако, некоторые языки (например, Smalltalk, Python) реализуют инкапсуляцию в полной мере, но не предусматривают возможности сокрытия в принципе.
Другие (Standard ML, OCaml) жёстко разделяют эти понятия как ортогональные и предоставляют их в семантически различном виде.
В общем случае в разных языках программирования термин «инкапсуляция» относится к одной или обеим одновременно следующим нотациям:
- механизм языка, позволяющий ограничить доступ одних компонентов программы к другим;
- языковая конструкция, позволяющая связать данные с методами, предназначенными для обработки этих данных.
Пренебрегая формализмом и способствуя интуитивному восприятию, инкапсуляцию можно определить с помощью латинского in capsula — размещение в оболочке, изоляция, закрытие чего-либо инородного с целью исключения влияния на окружающее, обеспечение доступности главного, выделение основного содержания путём помещения всего мешающего, второстепенного в некую условную капсулу (чёрный ящик). Однако данное определение является лишь приближением.
Примеры.
Ada
package Stacks is
type Stack_Type is private;
procedure Push (Stack : in out Stack_Type; Val : Integer);
private
type Stack_Data is array (1 .. 100) of Integer;
type Stack_Type is record
Max : Integer := 0.3;
Data : Stack_Data;
end record;
end Stacks;
C++
class A
{
public:
int a, b; //данные открытого интерфейса
int ReturnSomething(); //метод открытого интерфейса
private:
int Aa, Ab; //скрытые данные
void Do_Something(); //скрытый метод
};
Класс А инкапсулирует свойства Aa, Ab и метод Do Something(), представляя внешний интерфейс Return Something, a, b.
C
Целью инкапсуляции является обеспечение согласованности внутреннего состояния объекта. В C# для инкапсуляции используются публичные свойства и методы объекта. Переменные, за редким исключением, не должны быть публично доступными. Проиллюстрировать инкапсуляцию можно на простом примере. Допустим, нам необходимо хранить вещественное значение и его строковое представление (например, для того, чтобы не производить каждый раз конвертацию в случае частого использования). Пример реализации без инкапсуляции таков:
class NoEncapsulation
{
public double ValueDouble;
public string ValueString;
}
При этом мы можем отдельно изменять как само значение Value, так и его строковое представление, и в некоторый момент может возникнуть их несоответствие (например, в результате исключения). Пример реализации с использованием инкапсуляции:
class EncapsulationExample
{
private double valueDouble;
private string valueString;
public double ValueDouble
{
get { return valueDouble; }
set
{
valueDouble = value;
valueString = value.ToString();
}
}
public string ValueString
{
get { return valueString; }
set
{
double tmp_value = Convert.ToDouble(value); //здесь может возникнуть исключение
valueDouble = tmp_value;
valueString = value;
}
}
}
Здесь доступ к переменным ValueDouble и ValueString возможен только через свойства Value Double и ValueString. Если мы попытаемся присвоить свойству ValueString некорректную строку и возникнет исключение в момент конвертации, то внутренние переменные останутся в прежнем, согласованном состоянии, поскольку исключение вызывает выход из процедуры.
Delphi
В Delphi для создания скрытых полей или методов их достаточно объявить в секции private.
TMyClass = class
private
FMyField: Integer;
procedure SetMyField(const Value: Integer);
function GetMyField: Integer;
public
property MyField: Integer read GetMyField write SetMyField;
end;
Для создания интерфейса доступа к скрытым полям в Delphi введены свойства.
PHP
class A
{
private $a; // скрытое свойство
private $b; // скрытое свойство
private function DoSomething() //скрытый метод
{
//actions
}
public function ReturnSomething() //открытый интерфейс
{
//actions
}
}
В этом примере закрыты свойства $a и $b для класса A с целью предотвращения повреждения этих свойств другим кодом, которому необходимо предоставить только права на чтение.
Java
class A {
private int a;
private int b;
private void doSomething() { //скрытый метод
//actions
}
public int getSomething() { //открытый метод
return a;
}
}
JavaScript
var A = function() {
// private
var _property;
var _privateMethod = function() { /* actions */ } // скрытый метод
// public
this.getProperty = function() { // открытый интерфейс
return _property;
}
this.setProperty = function(value) { // открытый интерфейс
_property = value;
_privateMethod();
}
}
или
var A = function() {
// private
var _property;
var _privateMethod = function() { /* actions */ } // скрытый метод
// public
return {
getProperty: function() { // открытый интерфейс
return _property;
},
setProperty: function(value) { // открытый интерфейс
_property = value;
_privateMethod();
}
}
}