Объектно-ориентированное программирование

Объектно-ориентированное или объектное программирование (ООП) — парадигма программирования, в которой основными концепциями являются понятия объектов и классов. Под парадигмой программирования понимается система идей и понятий, определяющая стиль написания компьютерных программ, а также образ мышления программиста.

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

Основные понятия. Абстракция данных — подход к обработке данных по принципу «черного ящика». Данные обрабатываются функцией высокого уровня с помощью вызова функций более низкого уровня. Это позволяет работать с объектами, не вдаваясь в особенности их реализации.

Пример задания абстрактной функции (без ее реализации в данном классе) приведен в листинге 6.2 (синтаксис зависит от языка программирования). Функция уже известна, но ее конкретная реализация будет выполнена на более низком уровне — в классах-потомках.

Листинг 6.2

Пример объявления абстрактного метода

int Calculate();

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

Пример определения класса приведен в листинге 6.3 (синтаксис зависит от языка программирования).

Листинг 6.3

Пример объявления класса

class Client {

}

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

В большинстве объектно-ориентированных языков программирования поддерживаются следующие области доступа:

  • private (закрытый, внутренний член класса) — обращения допускаются только из методов класса, в котором этот член определен; наследники класса уже не смогут получить доступ к этому члену;
  • protected (защищенный, внутренний член иерархии классов) — обращения допускаются из методов класса, в котором этот член определен, или из любых его классов-наследников;
  • public (открытый член класса) — обращения допускаются из любого кода.

Пример задания членов класса приведен в листинге 6.4 (синтаксис зависит от языка программирования).

Листинг 6.4

Пример задания членов класса

class Client {

private string firstName; private string lastName; private DateTime birthday;

public Client(string parFirstName, string par LastName, DateTime parBirthday)

{

firstName = parFirstName; lastName = parLastName; birthday = parBirthday;

}

public int CalculateAge(DateTime parDate)

{

return (parDate - birthday).Year;

}

Интерфейс класса — это набор публичных полей и методов класса, к которым можно обращаться из других классов, частей программы. Для примера в листинге 6.4 в интерфейс класса будут входить конструктор класса: Client (string parFirstName, string par LastName, DateTime parBirthday) и метод вычисления возраста: public int CalculateAge (DateTime parDate).

В некоторых языках программирования появились интерфейсы, объявляемые как типы данных; например, в C# можно объявить интерфейс класса, который будет содержать объявления методов, обязательных для реализации в классах, наследующих данный интерфейс.

Прототип — это объект-образец, по образу и подобию которого создаются другие объекты. Если в класс, описанный в предыдущем примере, добавить метод Clone, который будет возвращать экземпляр объекта, инициированный текущими значениями полей, то можно сказать, что класс является прототипом (листинг 6.5).

Листинг 6.5

Пример создания класса-прототипа

class Client {

• M

public Client Clone()

{

return new Client(this.firstName, this.lastName, this.birthday);

}

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

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

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

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

Пример объявления абстрактного класса представлен в листинге 6.6.

Пример объявления абстрактного класса

abstract class Client {

private string firstName; private string lastName; private DateTime birthday;

public Client(string parFirstName, string par LastName,

DateTime parBirthday)

{

firstName = parFirstName; lastName = parLastName; birthday = parBirthday;

}

public abstract int CalculateAge(DateTime parDate);

}

Объект, наряду с понятием «класс», является основополагающим понятием объектно-ориентированного подхода. Под объектом подразумевается некоторая сущность, обладающая состоянием и поведением. Объекты могут принадлежать одному или нескольким классам, которые, в свою очередь, определяют поведение объекта. Время с момента создания объекта до его уничтожения называется временем жизни объекта.

Инстанцирование — создание экземпляра класса; в отличие от слова «создание» применяется не к объекту, а к классу: говорят «создать экземпляр класса» или «инстанцировать класс».

Экземпляр класса — это конкретный описанный объект (существующий в памяти). Класс описывает свойства и методы, которые будут доступны объекту, относящемуся к этому классу. Экземпляры используют для представления конкретных сущностей реального мира. Создание экземпляра класса может осуществляться операцией new (листинг 6.7).

Листинг 6.7

Пример объявления экземпляра класса

Client client = new Client("Иван", "Иванов", DateTime.

Parse("10.04.1983"));

Концепции объектно-ориентированного программирования.

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

может на них реагировать, используя свои данные. Данные объекта скрыты от остальной программы. Сокрытие данных (объявление для них области видимости «protected» или «private») называется инкапсуляцией. Однако наличие инкапсуляции еще не означает объектной ориентированности языка, для этого требуется наличие наследования. Но в полной мере преимущества ООП проявляются только в том случае, когда в языке программирования реализован полиморфизм — возможность объектов с одинаковой спецификацией иметь различную реализацию.

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

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

Класс, от которого произошло наследование, называется базовым или родительским. Классы, которые произошли от базового, называются потомками, наследниками или производными классами (листинг 6.8).

Листинг 6.8

Простейший пример наследования

class Classl {

}

class Class2: Classl {

}

Инкапсуляция — свойство языка программирования, позволяющее объединить данные и код в объект и скрыть реализацию объекта от пользователя. При этом пользователю предоставляется только спецификация (интерфейс) объекта. Пользователь может взаимодействовать с объектом только через этот интерфейс. Инкапсуляция может быть достигнута простейшими организационными мерами. Знание того, что «вот так делать нельзя», иногда является самым эффективным средством инкапсуляции (листинги 6.9 и 6.10).

Листинг 6.9

Класс реализации комплексного числа

// Класс комплексного числа class Complex {

// Целая часть числа private double re;

// Мнимая часть числа private double im;

// Конструктор с инициализацией public Complex(double i_re, double i_im)

{

re = i_re; im = i_im;

}

// Сложение комплексных чисел

// parComplexl - Первое комплексное число

// parComplex2 - Второе комплексное число

public static Complex operator+(Complex parComplexl,

Complex parComplex2)

{

return new Complex(parComplexl.re + parComplex2.re, parComplexl.im + parComplex2.im);

}

}

Листинг 6.10

Использование класса комплексного числа

Complex complexl = new Complex(1,1);

Complex complex2 = new Complex(2,2);

Complex complex3 = complexl + complex2;

Следует особо отметить, что одна из наиболее распространенных ошибок заключается в попытке делать сокрытие реализации только ради сокрытия. Целями, достойными усилий, являются: достижение предельной локализации изменений при их необходимости; прогно-зируемость изменений (какие изменения в коде надо сделать для заданного изменения функциональности); прогнозируемость последствий изменений.

Полиморфизм — взаимозаменяемость объектов с одинаковым интерфейсом. Язык программирования поддерживает полиморфизм, если классы с одинаковой спецификацией могут иметь различную реализацию, например реализация класса может быть изменена в процессе наследования. Кратко смысл полиморфизма можно выразить фразой: «один интерфейс, множество методов».

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

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

Листинг 6.11

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

// Класс для определения произвольной арифметической операции //с использованием двух переменных class Operation {

// Вычисление произвольной операции с участием двух переменных public virtual float Calculate(float parArgumentl, float parArgument2);

}

// Сложение двух переменных class Add: Operation {

// Сложение с использованием двух переменных public override float Calculate(float parArgumentl, float parArgument2)

{

return parArgumentl + parArgument2;

}

}

// Сложение двух переменных class Sub: Operation {

// Сложение с использованием двух переменных public override float Calculate(float parArgumentl, float parArgument2)

{

return parArgumentl - parArgument2;

}

В приведенном примере листинга 6.11 класс «Operation» определяет виртуальный метод, который подразумевает какое-то арифметическое вычисление с использованием двух переменных и возвращение результата. Класс «Add» является наследником «Operaion» и перегружает метод «Calculate» как операцию сложения двух переменных. Класс «Sub» также является наследником «Operation» и перегружает метод «Calculate» как операцию вычитания.

Листинг 6.12

Простейший пример использования полиморфизма

Operation operation = new Add();

float resultl = operation.Calculate(l.Of, 1.Of);

operation = new Sub();

float result2 = operation.Calculate(l.Of, 1.Of);

В листинге 6.12 представлен простейший пример использования полиморфизма. В примере объявляется локальная переменная «operation» с типом родительского класса «Operation» и ей присваивается экземпляр класса «Add». Данное присвоение разрешается и является одним из свойств наследования: переменным с типом родительского класса можно присваивать экземпляры классов-наследников, но не наоборот. В результате вызова метода «Calculate» переменная «resultl» примет значение 2.Of, так как в данный момент переменная «operation» является экземпляром класса «Add», где вычисление определено как сложение. Далее переменной «operation» присваивается значение экземпляра «Sub» и вызов метода «Calculate» возвратит значение O.Of, как результат вызова метода из класса «Sub».

Конструктор — специальный метод в объектно-ориентированном программировании, служащий для инициализации объекта при его создании (например, выделения памяти). В языках программирования C++, C# или Java конструктором класса называется функция, имеющая то же имя, что и сам класс, и не возвращающая никакого значения. Можно сказать, что конструктором называется тот метод класса, который вызывается автоматически при создании экземпляра класса. В зависимости от варианта объявления конструктора различают следующие их виды (листинг 6.13):

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

Листинг 6.13

Пример объявления конструкторов различного вида

// Класс комплексного числа class Complex {

double re; double im;

// Конструктор по умолчанию public Complex()

{

re = 0.0; im = 0.0;

}

// Конструктор копирования public Complex(Complex obj)

{

re = obj.re; im = obj.im;

}

// Конструктор с инициализацией (обычный конструктор) public Complex(double i_re, double i_im)

{

re = i_re; im = i_im;

}

}

Деструктор — специальный метод класса, служащий для деинициализации объекта (например, освобождения памяти). Имя деструктора должно совпадать с именем класса и иметь префикс ~.

У класса может быть только один деструктор. Деструктор не имеет модификатора доступа и параметров (листинг 6.14).

Листинг 6.14

Пример объявления деструктора

// Класс комплексного числа class Complex {

double re; double im;

// Конструктор с инициализацией (обычный конструктор) public Complex(double i_re, double i_im)

{

re = i_re; im = i_im;

}

// Деструктор -Complex()

{

}

}

Виртуальный метод — метод/функция класса, который может быть переопределен в классах-наследниках так, что конкретная реализация метода будет определяться во время исполнения (листинг 6.15).

Листинг 6.15

Пример объявления виртуальных и невиртуальных функций

class Ancestor {

public virtual void functionl ()

{

Console.WriteLine(«Ancestor.functionl()»);

}

public void function2 ()

{

Console.WriteLine(«Ancestor.function2()»);

}

}

class Descendant: Ancestor {

public override void functionl () {

}

public void function2 ()

{

Console.WriteLine(«Descendant.function2()»);

}

}

Descendant descendant = new Descendant(); Ancestor ancestor = descendant;

descendant.Functionl(); descendant.Function2();

ancestor.Functionl(); ancestor.Function2();

Программисту необязательно знать тип объекта для работы с ним через виртуальные методы, достаточно лишь знать, что объект принадлежит классу или наследнику класса, в котором метод объявлен. Базовый класс может и не предоставлять реализации виртуального метода, а только декларировать его существование. Такие методы без реализации называются «чисто виртуальными» (англ, pure virtual) или «абстрактными». Класс, содержащий хотя бы один такой метод, тоже будет абстрактным. Объект такого класса создать нельзя (в некоторых языках допускается, но вызов абстрактного метода приведет к ошибке). Наследники абстрактного класса должны предоставить реализацию для всех его абстрактных методов, иначе они, в свою очередь, будут абстрактными классами.

В примере листинга 6.15 класс «Ancestor» определяет две функции, одна из них виртуальная, другая — нет. Класс «Descendant» переопределяет обе функции, но одинаковое обращение к функциям дает разные результаты. Результат, получаемый на выходе, представлен листингом 6.16.

Листинг 6.16

Пример вызова виртуальных и невиртуальных функций

Descendant.Functionl Descendant.Function2 Descendant.Functionl Ancestor.Function2

В случае виртуальной функции для определения реализации функции используется информация о типе объекта и вызывается «правильная» реализация независимо от типа указателя. При вызове невиртуальной функции компилятор руководствуется типом переменной, поэтому вызываются две разные реализации «ГшкЛюп2()», несмотря на то что используется один и тот же объект.

 
< Пред   СОДЕРЖАНИЕ     След >