Чтение онлайн

на главную - закладки

Жанры

Как функции, не являющиеся методами, улучшают инкапсуляцию

Мейерс Скотт

Шрифт:

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

Простой способ измерить, сколько кода может быть разрушено,

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

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

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

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

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

// 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). Никто и глазом не моргает. Даже Вы.

Поделиться:
Популярные книги

Адвокат Империи 3

Карелин Сергей Витальевич
3. Адвокат империи
Фантастика:
городское фэнтези
попаданцы
аниме
фэнтези
фантастика: прочее
5.00
рейтинг книги
Адвокат Империи 3

Кротовский, может, хватит?

Парсиев Дмитрий
3. РОС: Изнанка Империи
Фантастика:
попаданцы
альтернативная история
аниме
7.50
рейтинг книги
Кротовский, может, хватит?

Дурная жена неверного дракона

Ганова Алиса
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Дурная жена неверного дракона

Вонгозеро

Вагнер Яна
1. Вонгозеро
Детективы:
триллеры
9.19
рейтинг книги
Вонгозеро

Ведьма Вильхельма

Шёпот Светлана
Любовные романы:
любовно-фантастические романы
8.67
рейтинг книги
Ведьма Вильхельма

Папина дочка

Рам Янка
4. Самбисты
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Папина дочка

Законы Рода. Том 6

Flow Ascold
6. Граф Берестьев
Фантастика:
юмористическое фэнтези
аниме
5.00
рейтинг книги
Законы Рода. Том 6

Как я строил магическую империю 7

Зубов Константин
7. Как я строил магическую империю
Фантастика:
попаданцы
постапокалипсис
аниме
фантастика: прочее
5.00
рейтинг книги
Как я строил магическую империю 7

Лучший из худший 3

Дашко Дмитрий
3. Лучший из худших
Фантастика:
городское фэнтези
попаданцы
аниме
6.00
рейтинг книги
Лучший из худший 3

Штурмовик из будущего 3

Политов Дмитрий Валерьевич
3. Небо в огне
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Штурмовик из будущего 3

Последний попаданец 2

Зубов Константин
2. Последний попаданец
Фантастика:
юмористическая фантастика
попаданцы
рпг
7.50
рейтинг книги
Последний попаданец 2

Идеальный мир для Лекаря 14

Сапфир Олег
14. Лекарь
Фантастика:
юмористическое фэнтези
попаданцы
аниме
5.00
рейтинг книги
Идеальный мир для Лекаря 14

Безумный Макс. Поручик Империи

Ланцов Михаил Алексеевич
1. Безумный Макс
Фантастика:
героическая фантастика
альтернативная история
7.64
рейтинг книги
Безумный Макс. Поручик Империи

Вдова на выданье

Шах Ольга
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Вдова на выданье