Бьерн Страуструп.
Язык программирования С++
333
Здесь
функции-члены образуют интерфейс на слишком низком уровне абстракции. Как правило классы
с интерфейсом такого уровня относятся к специфике реализации большого компонента, если они
вообще могут к чему-нибудь относиться. В
идеале параметр функции из интерфейса должен
сопровождаться такой информацией, которой достаточно для его понимания. Можно сформулировать
такое правило: надо уметь передавать запросы на обслуживание удаленному серверу по узкому каналу.
Язык С++ раскрывает представление класса как часть интерфейса. Это представление может быть
скрытым (с помощью private или protected), но обязательно доступным транслятору, чтобы он мог
разместить автоматические (локальные) переменные, сделать подстановку тела функции и т.д.
Отрицательным следствием этого является то, что использование типов классов в
представлении
класса может привести к возникновению нежелательных зависимостей. Приведет ли использование
членов типа Y и Z к проблемам, зависит от того, каковы в действительности типы Y и Z. Если это
достаточно простые типы, наподобие complex или String, то их использование будет вполне
допустимым в
большинстве случаев. Такие типы можно считать устойчивыми, и необходимость
включать определения их классов будет вполне допустимой нагрузкой для транслятора. Если же Y и Z
сами являются классами интерфейса большого компонента (например, типа графической системы или
системы обеспечения банковских счетов), то прямую зависимость от них можно считать неразумной. В
таких случаях предпочтительнее использовать член, являющийся указателем или ссылкой:
class X {
Y*
a;
Z&
b;
//
...
};
При этом способе определение X отделяется от определений Y и Z, т.е. теперь определение X зависит
только от имен Y и Z. Реализация X, конечно, будет по-прежнему зависеть от определений Y и Z, но это
уже не будет оказывать неблагоприятного влияния на пользователей X.
Вышесказанное иллюстрирует важное утверждение: У интерфейса, скрывающего значительный объем
информации (что и должен делать полезный интерфейс), должно быть существенно меньше
зависимостей, чем у
реализации, которая их скрывает. Например, определение класса X можно
транслировать без доступа к определениям Y и Z. Однако, в определениях функций-членов класса X,
которые работают со ссылками на объекты Y и Z, доступ к определениям Y и Z необходим. При анализе
зависимостей следует рассматривать раздельно зависимости в интерфейсе и в реализации. В идеале
для обоих видов зависимостей граф зависимостей системы должен быть направленным нецикличным
графом, что облегчает понимание и тестирование системы. Однако, эта цель более важна и чаще
достижима для реализаций, чем для интерфейсов.
Отметим, что класс определяет три интерфейса:
class X {
private:
// доступно только для членов и друзей
protected:
// доступно только для членов и друзей, а также
// для членов и друзей производных классов
public:
// общедоступно
};
Члены должны образовывать самый ограниченный из возможных интерфейсов. Иными словами, член
должен быть описан как private, если нет причин для более широкого доступа к нему; если же таковые
есть, то член должен быть описан как protected, если нет дополнительных причин задать его как public.
В
большинстве случаев плохо задавать все данные, представляемые членами, как public. Функции и
классы, образующие общий интерфейс, должны быть спроектированы таким образом, чтобы
представление класса совпадало с его ролью в проекте как средства представления понятий.
Напомним, что друзья являются частью общего интерфейса.
Отметим, что абстрактные классы можно использовать для представления
понятия упрятывания более
высокого уровня ($$1.4.6, $$6.3, $$13.3).