Добавить в цитаты Настройки чтения

Страница 9 из 20



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

Давайте исследуем эти понятия по очереди.

Инкапсуляция?

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

Эта идея определенно не уникальная для ОО. Например, в языке C имеется превосходная поддержка инкапсуляции. Рассмотрим простую программу на C:

<b>point.h</b>

struct Point;

struct Point* makePoint(double x, double y);

double distance (struct Point *p1, struct Point *p2);

<b>point.c</b>

#include &quot;point.h&quot;

#include &lt;stdlib.h&gt;

#include &lt;math.h&gt;

struct Point {

 double x,y;

};

struct Point* makepoint(double x, double y) {

 struct Point* p = malloc(sizeof(struct Point));

 p-&gt;x = x;

 p-&gt;y = y;

 return p;

}

double distance(struct Point* p1, struct Point* p2) {

 double dx = p1-&gt;x – p2-&gt;x;

 double dy = p1-&gt;y – p2-&gt;y;

 return sqrt(dx*dx+dy*dy);

}

Пользователи point.h не имеют доступа к членам структуры Point. Они могут вызывать функции makePoint() и distance(), но не имеют никакого представления о реализации структуры Point и функций для работы с ней.

Это отличный пример поддержки инкапсуляции не в объектно-ориентированном языке. Программисты на C постоянно использовали подобные приемы. Мы можем объявить структуры данных и функции в заголовочных файлах и реализовать их в файлах реализации. И наши пользователи никогда не получат доступа к элементам в этих файлах реализации.

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

По техническим причинам[12] компилятор C++ требует определять переменные-члены класса в заголовочном файле. В результате объектно-ориентированная версия предыдущей программы Point приобретает такой вид:

<b>point.h</b>

class Point {

public:

 Point(double x, double y);

 double distance(const Point&amp; p) const;

private:



 double x;

 double y;

};

<b>point.cc</b>

#include &quot;point.h&quot;

#include &lt;math.h&gt;

Point::Point(double x, double y)

: x(x), y(y)

{}

double Point::distance(const Point&amp; p) const {

 double dx = x-p.x;

 double dy = y-p.y;

 return sqrt(dx*dx + dy*dy);

}

Теперь пользователи заголовочного файла point.h знают о переменных-членах x и y! Компилятор не позволит обратиться к ним непосредственно, но клиент все равно знает об их существовании. Например, если имена этих членов изменятся, файл point.cc придется скомпилировать заново! Инкапсуляция оказалась разрушенной.

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

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

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

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

Наследование?

Языки ОО не улучшили инкапсуляцию, зато они дали нам наследование.

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

Взгляните на дополнение к нашей исходной программе point.h на языке C:

<b>namedPoint.h</b>

struct NamedPoint;

struct NamedPoint* makeNamedPoint(double x, double y, char* name);

void setName(struct NamedPoint* np, char* name);

char* getName(struct NamedPoint* np);

<b>namedPoint.c</b>

12

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

13

Например, Smalltalk, Python, JavaScript, Lua и Ruby.

14

И не только программисты на C: большинство языков той эпохи позволяли маскировать одни структуры данных под другие.