Чистая архитектура. Искусство разработки программного обеспечения - стр. 18
>point.h
>class Point {
>public:
> Point(double x, double y);
> double distance(const Point& p) const;
>private:
> double x;
> double y;
>};
>point.cc
>#include "point.h"
>#include
>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.h
знают о переменных-членах >x
и >y
! Компилятор не позволит обратиться к ним непосредственно, но клиент все равно знает об их существовании. Например, если имена этих членов изменятся, файл >point.cc
придется скомпилировать заново! Инкапсуляция оказалась разрушенной.
Введением в язык ключевых слов >public
, >private
и >protected
инкапсуляция была частично восстановлена. Однако это был лишь грубый прием (хак), обусловленный технической необходимостью компилятора видеть все переменные-члены в заголовочном файле.
Языки Java и C# полностью отменили деление на заголовок/реализацию, ослабив инкапсуляцию еще больше. В этих языках невозможно разделить объявление и определение класса.
По описанным причинам трудно согласиться, что ОО зависит от строгой инкапсуляции. В действительности многие языки ОО практически не имеют принудительной инкапсуляции[13].
ОО безусловно полагается на поведение программистов – что они не станут использовать обходные приемы для работы с инкапсулированными данными. То есть языки, заявляющие о поддержке OO, фактически ослабили превосходную инкапсуляцию, некогда существовавшую в C.
Наследование?
Языки ОО не улучшили инкапсуляцию, зато они дали нам наследование.
Точнее – ее разновидность. По сути, наследование – это всего лишь повторное объявление группы переменных и функций в ограниченной области видимости. Нечто похожее программисты на C проделывали вручную задолго до появления языков ОО[14].
Взгляните на дополнение к нашей исходной программе point.h на языке C:
>namedPoint.h
>struct NamedPoint;
>struct NamedPoint* makeNamedPoint(double x, double y, char* name);
>void setName(struct NamedPoint* np, char* name);
>char* getName(struct NamedPoint* np);
>namedPoint.c
>#include "namedPoint.h"
>#include
>struct NamedPoint {
> double x,y;
> char* name;
>};
>struct NamedPoint* makeNamedPoint(double x, double y, char* name) {
> struct NamedPoint* p = malloc(sizeof(struct NamedPoint));
> p->x = x;
> p->y = y;
> p->name = name;
> return p;
>}
>void setName(struct NamedPoint* np, char* name) {
> np->name = name;
>}
>char* getName(struct NamedPoint* np) {
> return np->name;
>}
>main.c
>#include "point.h"
>#include "namedPoint.h"
>#include
>int main(int ac, char** av) {
> struct NamedPoint* origin = makeNamedPoint(0.0, 0.0, "origin");
> struct NamedPoint* upperRight = makeNamedPoint
> (1.0, 1.0, "upperRight");
> printf("distance=%f\n",
> distance(
> (struct Point*) origin,
> (struct Point*) upperRight));
>}
Внимательно рассмотрев основной код в файле >main.c
, можно заметить, что структура данных >NamedPoint
используется, как если бы она была производной от структуры >Point
. Такое оказалось возможным потому, что первые два поля в >NamedPoint
совпадают с полями в >Point
. Проще говоря, >NamedPoint
может маскироваться под >Point
, потому что >NamedPoint
фактически является надмножеством >Point
и имеет члены, соответствующие структуре >Point
, следующие в том же порядке.