Размер шрифта
-
+

Чистая архитектура. Искусство разработки программного обеспечения - стр. 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, следующие в том же порядке.

Страница 18