Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 [ 49 ] 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76

class xxx { private:

int x;

public:

void setx ( int ix ){ x = ix; int getx ( void ){return x; }

что что-то не так.

Реализация в листинге 7 исправляет это, заменив преобразование char* на обработчиков сообщений типа метода самовывода (print()). Я бы вывел строку при помощи:

string s; s.print( cout )

или:

cout << s;

а не используя printf(). При этом совсем нет открытого доступа к внутреннему буферу. Функции окружения могут меньше беспокоиться о том, как хранятся символы, до тех пор, пока строковый объект правильно отвечает на сообщение о самовыводе. Вы можете менять свойства представления строки как хотите, не влияя на отправителя сообщения print() . Например, строковый объект мог бы содержать два буфера - один для строк Unicode и другой для строк char* - и обеспечивать перевод одной строки в другую. Вы могли бы даже добавить для перевода на французский язык сообщение translate to French() и получить многоязыкую строку. Такая степень изоляции и является целью объектно-ориентированного программирования, но вы ее не добьетесь, если не будете непреклонно следовать этим правилам. Здесь нет места ковбоям от программирования.

110.1. Не пользуйтесь функциями типа get/set (чтения и присваивания значений)

Это правило в действительности то же, что и предыдущее "все данные должны быть закрытыми". Я выделил его, потому что есть такая распространенная ошибка среди начинающих программистов на Си++. Нет разницы между:

struct xxx

int x;



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

• Сообщение реализует свойство. Открытая (public) функция реализует обработчик сообщения. Поля данных - лишние во внешнем мире; вы добавляете их лишь для того, чтобы иметь возможность реализовать свойство. Доступ к ним должен быть невозможен.

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

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

Закончу этот раздел более реальным примером. Как-то раз я видел интерфейс, в котором объект "календарь" позволял пользователю интерактивно выбирать дату, щелкая мышью на каком-либо из дней, показанных на изображении календаря. "Календарь" затем экспортирует эту дату в другие части программы, помещая ее в объект "дата", который возвращается из сообщения get date(). Проблема здесь в том, что проектирование выполнено выполнено наизнанку. Программист мыслил структурными категориями, а не объектно-ориентированными.

При выполнении должным образом единственным видимым в других частях программы объектом был бы объект "дата". "Дата" использовала бы объект "календарь" для реализации сообщения "инициализируЙ себя" (которое могло бы быть конструктором), но "календарь" бы содержался внутри "даты". Определение класса



"календарь" можно было бы даже вложить в определение класса "дата". Объект "дата" также мог бы поддерживать другие инициализирующие сообщения, такие как

"инициализируй себя от редактируемого ввода" или

"инициализируй себя из строки", но во всех случаях объект "дата" отвечает за нужное для инициализации взаимодействие с пользовательским интерфейсом. Остальная часть программы просто бы непосредственно использовала "дату"; никто, кроме "даты", даже бы не знал о существовании объекта "календарь". То есть вы бы объявили " дату" и приказали ей себя инициализировать. Затем вы можете передавать объект "дата" всюду, куда необходимо. Конечно, "дата" должна также уметь себя вывести, переслать в файл или из файла, сравнить себя с другими датами и так далее.

111. Откажитесь от выражений языка Си, когда программируете на Си++

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

Хорошим примером этой проблемы в Си++ является char*. Большинство программистов на Си ни за что не соглашаются отказаться от использования строк в виде char*. Проблема заключается в том, что вы привыкли смотреть на char* и думать, что это строка. Это не строка. Это указатель. Убежденность в том, что указатель - это строка, обычно вызывает проблемы, некоторые из которых я уже рассматривал, а другие будут рассмотрены позднее.

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



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 [ 49 ] 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76