Залепа №9. Microsoft друзей не признает.
Автор будет очень признателен, если Вы кликнете по одной из белых ссылок выше.
Вам это ничего не стоит, а автору сайта будет приятно ;)
Начну с цитаты из одной полезной книжки Алена И. Голуба "Правила программирования в С и С++" (речь идет именно о C++):
Как-то раз я видел интерфейс, в котором объект "календарь" позволял пользователю интерактивно выбирать дату, щелкая мышью на каком-либо из дней, показанных на изображении календаря. "Календарь" затем экспортирует эту дату в другие части программы, помещая ее в объект "дата", который возвращается из сообщения get_date(). Проблема здесь в том, что проектирование выполнено наизнанку. Программист мыслил структурными категориями, а не объектно-ориентированными.
При выполнении должным образом единственным видимым в других частях программы объектом был бы объект "дата". "Дата" использовала бы объект "календарь" для реализации сообщения "инициализируй_себя" (которое могло бы быть конструктором), но "календарь" бы содержался внутри "даты". Определение класса "календарь" можно было бы даже вложить в определение класса "дата". Объект "дата" также мог бы поддерживать другие инициализирующие сообщения, такие как "инициализируй_себя_от_редактируемого_ввода" или "инициализируй_себя_из_строки", но во всех случаях объект "дата" отвечает за нужное для инициализации взаимодействие с пользовательским интерфейсом. Остальная часть программы просто бы непосредственно использовала "дату"; никто, кроме "даты", даже бы не знал о существовании объекта "календарь". То есть вы бы объявили "дату" и приказали ей себя инициализировать. Затем вы можете передавать объект "дата" всюду, куда необходимо. Конечно, "дата" должна также уметь себя вывести, переслать в файл или из файла, сравнить себя с другими датами и так далее.
Другими словами, правильное проектирование в ООП - это создание классов, АБСОЛЮТНО НЕЗАВИСИМЫХ от других классов и объектов, инкапсулирующих все необходимые для работы классы внутри себя. Только такой тип проектирования позволяет создавать действительно переносимый код, который без всякой адаптации можно встроить в свою программу.
Следуя описанному выше работа с классом "дата" должна выглядеть примерно так:
CDate cd = new CDate();
// создали объект "дата" с текущей датой внутри
cd = new CDate("15.06.2007");
// инициализировали строкой
cd = new CDate(15, 6, 2007);
// инициализировали числовыми значениями
cd = my_date;
// инициализировали другим объектом класса CData (присваивание)
cd.SaveToFile("c:\\autoexec.bat");
// сохранили дату в файл
cd.LoadFromFile("c:\\autoexec.bat");
// прочи дату из файла
int days = cd - new CDate("28.02.2006");
// получили количество дней между датами
cd += 365;
// получили дату, на год большую исходной
int dw = cd.DayOfWeek();
// вернуло номер дня недели
cd.ShowCalendar();
// отображение календаря для ввода даты пользователем.
... ну и так далее.Естественно, класс CDate должен быть порожден от System.Windows.Control или иметь некий другой механизм, чтобы (при необходимости) без проблем встраиваться в пользовательский интерфейс. Оцените удобство такого контрола в сравнении с предлагаемым аналогом из библиотеки .NET. Думаю, не надо доказывать, что описываемый здесь на порядок удобнее для программиста. Вот это и есть грамотное, правильное проектирование в стиле ООП.
Но я просто так в этом блоге ничего не пишу. Давайте посмотрим, получится ли с помощью "самой передовой" технологии нормально реализовать нечто подобное описанному.
Итак, упростим задачу до предела:
- есть класс CDate, представляющий ни что иное как обычную календарную дату.
- есть класс CCalendar, представляющий собой вспомогательное окно, отображающее календарь и принимающее ввод от юзера.
Требования к реализации:
- 1) все операции касающиеся даты, должны выполняться в классе CDate. И это есть логично.
- 2) класс календаря, фактически являясь окном, имеет только "оконную" функциональность. Т.е. никаких собственных вычислений дат и периодов он не делает - у нас для этого есть CDate. И это тоже есть логично.
- 3) не забываем про инкапсуляцию, т.е. пользователь нашего класса CDate НЕ должен иметь доступ к функциям внутренней обработки. Для него предоставлен интерфейс взаимодействия в виде открытых свойств и методов, им пусть и пользуется.
Отсюда вытекают пункты:
- 3а) класс календаря не должен быть виден пользователю. Более того, пользователь не должен даже догадываться о том, что CDate использует внутри еще что-то.
- 3б) проект должен иметь некую "модульную" структуру, чтобы встраивание класса CDate в реальную программу было максимально простым.
Что ж, давайте немного углубимся и попробуем осмыслить, как все это будет работать.
Из третьего пункта следует, что объекты класса CCalendar будут создаваться не пользователем, а только классом CDate, им же они будут контролироваться в течении всей жизни календаря и, в конце-концов, он же их будет и уничтожать. Хорошо. Идем дальше.
Судя по первым двум пунктам, наши объекты CDate и CCalendar будут активно вызывать методы друг друга. Причем, скорее всего случится так, что календарю потребуется доступ к некоторым методам CDate, которые предназначены для внутренних механизмов самого CDate. Такое вполне вероятно.
К чему я веду? А к тому, что тут мы уперлись головой в очередной косяк C# - отсутствие в языке понятия "дружественности". :(
В C++ мы бы просто сделали класс CCalendar другом класса CDate, разрешив тем самым календарю использовать внутренние механизмы в виде вызовов защищенных методов CDate. Согласен, решение не самое элегантное, но, поскольку оба класса разрабатываются нами, а пользователи о таком "разделении труда" даже не подозревают (и соответственно не смогут, например, породить наследников от календаря), то такое решение вполне приемлемо.
Выигрыш от него на лицо: календарь может использовать скрытые возможность CDate, при этом пользователь все так же остается ограниченным рамками предоставленного нами открытого интерфейса CDate (пункт 3 выполняется в полной мере).
Что же мы имеем в шарпе?
А там мы лишний раз убеждаемся в том, что технология .NET - это технология, в которой ничего хорошего NЕТ! В данном случае, в ней НЕТ дружественности. И поэтому, чтобы открыть доступ календарю к скрытой внутренней функциональности CDate, нам придется открыть эту функциональность и для всех остальных. Подозреваю, что именно поэтому классы .NET набиты таким большим количеством лишних методов и обработчиков - мелкомягкие вступили в собственную ловушку.
Чем грозит такое "открытие внутренней функциональности" наверное объяснять не нужно: вот есть класс, вот его методы, причем ОТКРЫТЫЕ методы, но пользоваться ими нельзя, т.к. они предназначены для внутренних нужд. Гы-гы-гы! :)
Получается чисто майкрософтовский подход: уверен, что каждому не раз попадались в мелкомягких библиотеках методы, свойства и (особенно!) константы которые "не реализованы" и "введены для дальнейшего расширения" и "будут реализованы в следующих версиях". Смех да и только!
Ладно, время покажет, может все же я зря смеюсь. Давайте лучше закончим с нашим примером.
Пункты 3а и 3б в C# так же вызывают некоторые проблемы. А именно, если распространять свой класс CDate в виде исходников, то он потянет за собой и класс CCalendar, а мы бы не хотели, чтобы пользователь знал о нем. Если же скомпоновать наш класс в сборку (assembly), то класс календаря можно сделать ненаследуемым, но при таком подходе пользователю уже не удастся сделать программу из одного exe-файла - придется тягать за собой еще и нашу сборку.
Сборки - это здорово по отношению к DLL, но убого по отношению к обычным библиотечным файлам, которые до .NET существовали в любом компилирующем языке и функции из которых встраивались прямо в exe-шник, не создавая никаких проблем. Причем, встраивалась не вся библиотека, а только функции/классы, реально использующиеся в приложении. В случае же сборок (как и в случае DLL), нам приходится тянуть за собой мегабайты сборочного барахла даже если мы используем из нее только одну функцию, размером в 300 байт. Почему майкрософт отказалась от использования библиотек объектных модулей мне лично не понятно.
Но возможно они еще вернут такую возможность. По крайней мере у меня есть повод так думать. А откуда взялась такая уверенность напишу в одном из следующих постов.
Адрес заметки: http://fit-media.com/post_1199568462.html
