Читаем Чистая архитектура. Искусство разработки программного обеспечения полностью

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


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, следующие в том же порядке.

Этот прием широко применялся[15] программистами до появления ОО. Фактически именно так C++ реализует единственное наследование.

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

Перейти на страницу:

Все книги серии Библиотека программиста

Программист-фанатик
Программист-фанатик

В этой книге вы не найдете описания конкретных технологий, алгоритмов и языков программирования — ценность ее не в этом. Она представляет собой сборник практических советов и рекомендаций, касающихся ситуаций, с которыми порой сталкивается любой разработчик: отсутствие мотивации, выбор приоритетов, психология программирования, отношения с руководством и коллегами и многие другие. Подобные знания обычно приходят лишь в результате многолетнего опыта реальной работы. По большому счету перед вами — ярко и увлекательно написанное руководство, которое поможет быстро сделать карьеру в индустрии разработки ПО любому, кто поставил себе такую цель. Конечно, опытные программисты могут найти некоторые идеи автора достаточно очевидными, но и для таких найдутся темы, которые позволят пересмотреть устоявшиеся взгляды и выйти на новый уровень мастерства. Для тех же, кто только в самом начале своего пути как разработчика, чтение данной книги, несомненно, откроет широчайшие перспективы. Издательство выражает благодарность Шувалову А. В. и Курышеву А. И. за помощь в работе над книгой.

Чед Фаулер

Программирование, программы, базы данных / Программирование / Книги по IT

Похожие книги