Программирование >>  Арифметические и логические операции 

1 ... 27 28 29 [ 30 ] 31 32 33 ... 53


лизации, то первая реализация менее инкапсулирована, чем вторая. Если мы применим эти рассуждения к описанной выше структуре, то увидим, что изменение ее элементов может разрушить неопределенно большое количество функций, а именно: каждую функцию, использующую эту структуру. В общем случае мы не можем рассчитать количество таких функций, потому что не имеется никакого способа выявить весь код, который использует специфику структуры. Это особенно видно, если изменения касаются кода библиотек. Однако число функций, которые могли бы быть разрушены, если изменить данные, являющиеся элементами класса, подсчитать просто: это все функции, которые имеют доступ к закрытой части класса. В данном случае, изменятся только четыре функции (не включая объявлений в закрытой части класса). И мы знаем об этом, потому что все они удобно перечислены при определении класса. Так как они - единственные функции, которые имеют доступ к закрытым частям класса, они также - единственные функции, на которые можно воздействовать, если эти части изменяются.

Инкапсуляция и функции - не члены

Приемлемый способом оценки инкапсуляции является количество функций, которые могли бы быть разрушены, если изменяется реализация класса. В этом случае становится ясно, что класс с n методами более инкапсулирован, чем класс с n+1 методами. И это наблюдение поясняет предпочтение в выборе функций, не являющихся ни друзьями, ни методами: если функция f может быть выполнена как метод или как функция, не являющаяся другом, то создание ее в виде метода уменьшило бы инкапсуляцию, тогда как создание ее в виде недруга инкапсуляцию не уменьшит. Так как функциональность здесь не обсуждается (функциональные возможности f доступны классам клиентов независимо от того, где эта f размещена), мы естественно предпочитаем более инкапсулированный проект.

Важно, что мы пытаемся выбрать между методами класса и внешними функциями, не являющимися друзьями. Точно так же, как и методы, функции-друзья могут быть подвержены разрушениям при изменении реализации класса. Поэтому, выбор между методами и функциями-друзьями можно правильно сделать только на основе анализа поведения. Кроме того, общее мнение о том, что функции-друзья нарушают инкапсуляцию - не совсем истина. Друзья не нарушают инкапсуляцию, они только уменьшают ее точно таким же способом, что и методы класса.

Этот анализ применяется к любому виду методов, включая и статические. Добавление статического метода к классу, когда его функциональные возможности могут быть реализованы как не члены и не друзья

уменьшают инкапсуляцию точно так же, как это делает добавление нестатического метода. Перемещение свободной функции в класс с оформлением ее в виде статического метода, только для того, чтобы показать, что она соприкасается с этим классом, является плохой идеей. Например, если имеется абстрактный класс для виджетов (Widgets) и затем используется функция фабрики классов, чтобы дать возможность клиентам создавать виджеты, можно использовать следующий общий, но худший способ организовать это:

a design less encapsulated than it could be class Widget {

внутренее наполнение Widget; может быть:

public, private, или protected public:

может быть также недругом и не членом static Widget* make(/* params */);

Лучшей идеей является создание вне Widget, что увеличивает совокупную инкапсуляцию системы. Чтобы показать, что Widget и его создание (make) все-таки связаны, используется соответствующее пространство имен (namespace):

более инкапсулированный проект namespace WidgetStuff {

class Widget {

Widget* make( /* params */ );

Увы, у этой идеи имеется своя слабость, когда используются шаб-

лоны.

Синтаксические проблемы

Возможно что вы, как и многие люди, имеете представление относительно синтаксического смысла утверждения, что не методы и не друзья предпочтительнее методов. Возможно, что вы даже купились на аргументы относительно инкапсуляции. Теперь, предположим, что класс Wombat поддерживает функциональные возможности поедания и засыпания. Далее предположим, что функциональные возможности, связанные с поеданием, должны быть выполнены как метод, а засыпание может быть выполнено как член или как не член и не друг. Если вы следуете советам, описанным выше, вы создадите описания подобные этим:

class Wombat { public:

void eat(double tonsToEat);



void sleep(Wombat& w, double hoursToSnooze);

Это привело бы к синтаксическому противоречию для клиентов класса, потому что для

Wombat w;

они напишут:

w.eat(.564); при вызове eat. Но они написали бы:

sleep(w, 2.57);

для выполнения sleep. При использовании только методов класса можно было бы иметь более опрятный код:

class Wombat { public:

void eat(double tonsToEat);

void sleep(double hoursToSnooze);

w.eat(.564); w.sleep(2.57);

Ах, эта всеобщая однородность! Но эта однородность вводит в заблуждение, потому что в мире имеется огромное количество функций, которые мечтают о вашей философии.

Чтобы непосредственно ее использовать, нужны функции - не методы. Позвольте нам продолжить пример Wombat. Предположим, что вы пишете программу моделирования этих прожорливых созданий, и воображаете, что одним из методов, в котором вы часто нуждаетесь при использовании вомбатов, является засыпание на полчаса. Ясно, что вы можете засорить ваш код обращениями w.sleep (.5), но этих (.5) будет так много, что их будет трудно напечатать. А что произойдет, если это волшебное значение должно будет измениться? Имеется ряд способов решить эту проблему, но возможно самый простой заключается в определении функции, которая инкапсулирует детали того, что вы хотите сделать. Понятно, что если вы не являетесь автором Wombat, функция будет обязательно внешней, и вы будете вызвать ее таким образом:

может быть inline, но это не меняет сути void nap(Wombat& w) { w.sleep(.5); } Wombat w;

nap(w);

И там, где вы используете это, появится синтаксическая несогласованность, которой вы так боитесь. Когда вы хотите кормить ваши желудки (wombats), вы обращаетесь к методу класса, но когда вы хотите их усыпить, вы обращаетесь к внешней функции.

Если вы самокритичны и честны сами с собой, вы увидите, что имеете эту предполагаемую несогласованность со всеми нетривиальными классами, вы используете ее потому, что класс не может имеет любую функцию, которую пожелает какой-то клиент. Каждый клиент добавляет, по крайней мере, несколько своих функций для собственного удобства, и эти функции всегда не являются методами классов. Программисты на C++ используют это, и они не думают ничего об этом. Некоторые вызовы используют синтаксис методов, а некоторые синтаксис внешних вызовов. Клиенты только ищут, какой из синтаксисов является соответствующим для тех функций, которые они хотят вызвать, затем они вызывают их. Жизнь продолжается. Это происходит особенно часто в STL (Стандартной библиотеки C++), где некоторые алгоритмы являются методами (например, size), некоторые - не методами (например, unique), а некоторые - тем и другим (например, find). Никто и глазом не моргает.

Интерфейсы и упаковка

Интерфейс класса (подразумевая, функциональные возможности, обеспечиваемые классом) включает также внешние функции, связанные с классом. Им также показано, что правила области видимости имен в C++ поддерживают эти изменения понятия интерфейса . Это означает, что решение сделать функцию, зависимую от класса, в виде не друга - не члена вместо члена даже не изменяет интерфейс этого класса! Кроме того, вывод функций интерфейса класса за границы определения класса ведет к некоторой замечательной гибкости упаковки, которая была бы иначе недоступна. В частности, это означает, что интерфейс класса может быть разбит на множество заголовочных файлов.

Предположим, что автор класса Wombat обнаружил, что клиенты Wombat часто нуждаются в ряде дополнительных функций, связанных, с едой, сном и размножением. Такие функции по определению не строго необходимы. Те же самые функциональные возможности могли бы быть получены через вызов других (хотя и более громоздких) методов. В результате, каждая дополнительная функция должна быть не другом и не методом.

Но предположим, что клиенты дополнительных функций, используемых для еды, редко нуждаются в дополнительных функциях для сна или размножения. И предположим, что клиенту, использующему до-



полнительные функции для сна и размножения, также редко нужны дополнительные функции для еды. То же самое можно развить на функции размножения и сна.

Вместо размещения всех Wombat-зависимых функций в одном заголовочном файле, предпочтительнее было бы разместить элементы интерфейса Wombat в четырех отдельных заголовках: один для функций ядра Wombat (описания функций, связанных с определением класса), и по одному для каждой дополнительной функции, определяющей еду, сон, и размножение. Клиенты включают в свои программы только те заголовки, в которых они нуждаются. Возникающее в результате программное обеспечение не только более ясное, оно также содержит меньшее количество зависимостей, пустых для трансляции. Этот подход, использующий множество заголовков, был принят для стандартной библиотеки (STL). Содержание namespace std размещено в 50 различных заголовочных файлах. Клиенты включают заголовки, объявляющие только части библиотеки, необходимые им, и они игнорирует все остальное.

Кроме этого, такой подход расширяем. Когда объявления функций, составляющих интерфейс класса, распределены по многим заголовочным файлам, это становится естественным для клиентов, которые размещают свои наборы дополнительных функций, специфические для приложения, в новых заголовочных файлах, подключаемых дополнительно. Другими словами, чтобы обработать специфические для приложения дополнительные функции, они поступают точно так же, как и авторы класса. Это то, что и должно быть. В конце концов, это только дополнительные функции.

Минимальность и инкапсуляция

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

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

Стандартная мудрость, несмотря на использование недругов и не методов улучшает инкапсуляцию класса, и предпочтительнее для таких функций над методами, потому что делает решение проще, когда надо проектировать и разрабатывать классы с интерфейсами, которые являются полными и минимальными (или близкими к минимальному). Возражения, связанные с неестественностью, возникающей в результате изменения синтаксиса вызова, являются совершенно необоснованными, а склонность к недругам и не методам ведет к пакующим стратегиям для организации интерфейсов класса, которые минимизируют клиентские зависимости при трансляции, сохраняя доступ к максимальному числу дополнительных функций.

Пришло время отказаться от традиционной, но неточной идеи, связанной с тем, что означает быть объектно-ориентированным . Действительно ли вы - истинный сторонник инкапсуляции? Если так, то вы ухватитесь за недружественные внешние функции с пылом, которого они заслуживают.

Все изложенное выше показывает, что не все так гладко в чистых методах объектно-ориентированного проектирования, если приходится прибегать к ухищрениям, присущим чисто процедурному программированию. Конечно, эффект от использования будет очевиден лишь при разработке достаточно больших программных систем, когда программу приходится развивать и модифицировать, а не создавать заново. Отсюда следует, что чисто объектные языки и методы могут оказаться в этом случае весьма неудобными. А значит: прощай Java и Си? Или их ждет ревизия?

С другой стороны оказывается, что инкапсуляция лучше всего удается процедурным языкам!? Ведь любую структуру данных можно окружить внешними функциями и через них осуществлять доступ. А методы в классе вообще не нужны!?



1 ... 27 28 29 [ 30 ] 31 32 33 ... 53

© 2006 - 2025 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика