Некоторые, чтобы объяснить природу ОО, прибегают к трем волшебным словам: инкапсуляция, наследование и полиморфизм. Они подразумевают, что ОО является комплексом из этих трех понятий или, по крайней мере, что объектно-ориентированный язык должен их поддерживать.
Давайте исследуем эти понятия по очереди.
Инкапсуляция?
Инкапсуляция упоминается как часть определения ОО потому, что языки ОО поддерживают простой и эффективный способ инкапсуляции данных и функций. Как результат, есть возможность очертить круг связанных данных и функций. За пределами круга эти данные невидимы и доступны только некоторые функции. Воплощение этого понятия можно наблюдать в виде приватных членов данных и общедоступных членов-функций класса.
Эта идея определенно не уникальная для ОО. Например, в языке 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 "point.h"#include <stdlib.h>#include <math.h>struct Point { double x,y;};struct Point* makepoint(double x, double y) { struct Point* p = malloc(sizeof(struct Point)); p->x = x; p->y = y; return p;}double distance(struct Point* p1, struct Point* p2) { double dx = p1->x – p2->x; double dy = p1->y – p2->y; return sqrt(dx*dx+dy*dy);}Пользователи
point.hPointmakePoint()distance()Это отличный пример поддержки инкапсуляции не в объектно-ориентированном языке. Программисты на C постоянно использовали подобные приемы. Мы можем объявить структуры данных и функции в заголовочных файлах и реализовать их в файлах реализации. И наши пользователи никогда не получат доступа к элементам в этих файлах реализации.
Но затем пришел объектно-ориентированный C++ и превосходная инкапсуляция в C оказалась разрушенной.
По техническим причинам [12] компилятор C++ требует определять переменные-члены класса в заголовочном файле. В результате объектно-ориентированная версия предыдущей программы Point приобретает такой вид:
<b>point.h</b>class Point {public: Point(double x, double y); double distance(const Point& p) const;private: double x; double y;};<b>point.cc</b>#include "point.h"#include <math.h>Point::Point(double x, double y): x(x), y(y){}double Point::distance(const Point& p) const { double dx = x-p.x; double dy = y-p.y; return sqrt(dx*dx + dy*dy);}Теперь пользователи заголовочного файла
point.hxypoint.ccВведением в язык ключевых слов
publicprivateprotectedЯзыки 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>