Хотел было кинуть ответ в «приват», но как ни искал «приват», не нашёл. Вероятно, в движке форума этого нет? В «приват», т.к. думаю мой пост вряд ли будет интересен другим. Там, ниже – я почти по всем пунктам согласен.
******************** >>Небольшое замечание: на самом деле такой "нудный код" тоже является признаком плохого >>стиля. Уже хотя бы потому, что при добавлении нового объекта, менять нужно не только в том >>месте, где производится собственно добавление (что неизбежно), но и в данном (и других, >>потому как код нужно добавлять не только для ClearControls) обработчике. То же самое и с >>удалением.
Согласен – натяжка;)). Ну уж очень хотелось;)))
******************** >>Вывод: везде, где родителю необходимо что-то сделать со ВСЕМИ своими детьми (если >>количество больше 2-3-х), я ПРОЕКТИРУЮ класс так, чтобы родитель мог ПЕРЕЧИСЛИТЬ всех >>своих детей в цикле. Количество кода стремительно уменьшается. И при добавлении/удалении >>детей изменениям подвергается только само место добавления (удалять можно тоже >>перечислением).
Согласен при ~ >= 3. А так, можно и «нудным кодом».
******************** >>Еще раз напомню, что развитие парадигм программирования шло именно по пути уменьшения >>связей между частями программы. И даже если ООП ничего не говорит о таком подходе, важно >>понимать, куда именно движется программирование и соответсвовать, так сказать. То, что при >>подходе с перечислением уменьшается количество связей, думаю объяснять не стоит. Тем >>более что в этом вопросе никто не собирается вставлять палки в колеса. Все системные >>контейнеры (взять окна, tab control-ы и др.) хранят список дочерних объектов и предоставляют >>возможность их перечисления, если ты при проектировании своего класса будешь поступать так >>же - будет тебе счастье :-)
Понятно. Твой пример о TabCtrl, для которого я ранее сделал перепост, думаю, всё равно, будет полезен для многих программеров. Хотя, сам я делаю немного по другому, т.к. мне недостаточно только lparam – мов для пейджей. В неком CTabPageCtrl:public CTabCtrl у меня есть мембер-массив структур, который хранит кроме указателя на диалог пейджи ещё разную ерунду, которая мне необходима для работы с БД. Понятно, что и в этом случае, можно было обойтись lparam, присваивая ему адрес этой структуры, для которой делаем new в ран-тайм, когда вставляем пейдж. Но для такой мелочёвки мне было удобнее тупо, по быстрому и надёжно. Как обрабатывать перечисления – дело вкуса, имхо.
******************** >>Причем сказанное выше касается КАЖДОГО из родителей. Вплоть до диалога, который может >>выключить всех своих детей при помощи EnumChildWindows. Кроме того, как ленивый >>программист, я пытаюсь вынести общий код как можно выше в базовые классы. То есть >>добавлять/перечислять всех детей однотипных объектов одной и той же функцией (а не >>похожей, или даже copy/paste-нутой). То есть сделать, например, класс страницы, которая умеет >>выключать всех своих детей перечислением в for-е и отнаследовать все страницы от нее. >>Главное не переусердствовать и не вводить в иерархию ничего натянутого. Например, если >>способ перечисления для окна существенно отличается от способа перечисления для >>табконтрола, то не стоит пытаться сделать им общую базу только для того чтоб "вынести как >>можно выше". Связь база-наследник должна появляться только в том случае если анализ >>выявил такую связь. То есть надо стараться, но знать меру.
В начале 90-х мне надо было прикрутить графической «интерфейс» кастомизированного приложения в среде AutoCad 11.0 (досова штука). Писал в то время исключительно plain C,
хотя уже и был Турбо С++ 1.0. Поскольку это был плайн-С, а без абстракций – никуда, то многообразие данных моделировалось указателями на структуры данных, а многообразие методов – указателями на структуры функций. Для моделирования эламентов GUI ( в графике!!!! , под ДОСом. В то время – кул!!!) я именно ЭТО и делал (то, о чём ты говоришь). Т.е. выстраивались двусвязанные списки, которые были «контейнерами», и в которые «ставлялись» кнопки, диалоги и т.д. Там и были перечисления «контролов», которые обрабатывались в цикле. Были там и FillControls вместе с Дисплеем;))
Для чего я это рассказываю? Было всё это непросто. Хотя и работало. Не поверишь – даже продать удалось в одну контору (в единственном экземпляре)!!! Но вот посоветовать пойти по этому пути другим – у меня не хватает духу. Очень уж тернистый путь. Опасаюсь я, что это простое решение, которое можно отсоветовать кому-либо. Опасаюсь, т.к. помню ту головную боль. Но согласен – если сделать, то будет счастя. Может и так. Всёж С++ на дворе, не плайн-С.
******************** >> Если в «ЧИСТО ООП» переход в новое состояние всегда
>>выполняется, начиная с рут (например, theApp), как было
>>Самое интересное, что в данном случае переход в новое состояние начинается тоже с рута, >>которым в данном случае является мессенгер и дальше вниз по иерархии. Такой подход имеет >>право на жизнь. Так как хотя в принципе могут регистрироваться все (расплющивая иерархию в >>одну горизонталь), но реально регистрируются только ключевые объекты, которые на самом >>деле можно считать и так находящимися на одном уровне (и в мессенгер они попадают тоже на >>один). А иерархия при этом не разрушается.
Плохо то, что для «…иерархия при этом не разрушается…» очень много перекладывается на «вменяемость» программера;))
******************** >>НО!!! Единственный реально необходимый пример такой архитектуры, я вижу в том, чтобы >>иметь несколько мессенгеров. То есть иметь несколько разных частей приложения в разных >>состояниях.
Если ты заметил, то мессангер я для этого и сделал без сингентона. Хотя последнее очень красивое слово (это тоже паттерн). С Singleton–ном – это у того программера, для кода которого я сделал перепост в «шагах».
******************** >>Добавлять в список нужно не путем регистрации, а прямо при создании объекта. То есть >>родитель (приложение) создает детей (виды и др.) и тут же добавляет их в список. >>Терминология немного прихрамывает, потому как динамические родитель-ребенок отличаются >>от статических база-наследник, по крайней мере, то что Я называю так, имеет разную основу. >>Родитель - это контейнер, хранящий детей (и умеющий добавлять/удалять их), а база - это >>просто базовый класс, который выражает утверждение, что наследник ЯВЛЯЕТСЯ кроме всего >>прочего и базой. То есть родитель-ребенок это связь типа мессенгер-подписчики (то есть >>содержит), а база-наследник - типа окно-диалог (то есть является). Короче родитель и ребенок >>это объекты, а база и наследник - классы. Так вот эти дочерние объекты должны иметь общий >>базовый класс и переопределять виртуальные функции реакции на некоторые события (о >>которых им будет сообщать родитель путем вызова этих функций).
Если это совет мне, то - хороший.
Для этого надо сделать так, чтобы объекты умели быть одновременно и мессангерами и подписчиками. Т.е. если я правильно понял, то что было в начале твоего поста, и что здесь – ключевые объекты должны уметь выступать в роли «контейнера» (т.е. мессангера), если они включают в контейнерном мессангере «списки» других объектов-подписчиков, И одновременно должны уметь быть подписчиками, т.к. могут входить в «вышележащий» контейнер (мессангер).
Если это совет другим, кто читает пост – то ???????????? Люди поделают, поделают, а потом будут пилюваться в нас и костерить.
******************** >>От разрегистрации тоже можно избавиться используя замечательную особенность двусвязного >>списка. Особенность заключается в том, что любой элемент двусвязного списка можно удалить >>без знания из какого же собственно списка он удаляется (есть связи и вперед и назад - нужно >>просто связать элемент, предшествующий удаляемому, с элементом, последующим).
Это и есть ОБЯЗАТЕЛЬНАЯ разрегистрация, когда подписчик уходит со сцены;)))
******************** >> Как это делается смотри виндовый LIST_ENTRY и сопутствующие макросы.
Думаю, что это мелкософт у меня «посмотрел»;)))
Когда я это делал для досовых задач в плайн-С, виндовз ещё не было. Я постил, чуть выше.
Шучу, конечно.
******************** >>А ребенок в деструкторе (деструкторе той самой базы) сам удалится из списка.
Мессангер так и работает, в смысле в деструкторе.
******************** >> Разрегистрацию можно и ввести, но только для динамического изменения топологии дерева, по >>которому проходят сообщения (то есть для случая: ребенок остался, но не хочет получать >>сообщения).
Кстати да. Хотя мне трудно придумать, где это может быть на практике – «…ребенок остался, но не хочет получать сообщения ….»
******************** >>Я же предпочитаю не надеяться на сознательность. Может быть даже я сам когда нибудь забуду >>о том, из каких побуждений я исходил и какие предположения делал. Если мне просто запретят >>делать, то что нежелательно - я вспомню быстрее :-)
То, что и меня это смущает, я уже постил.
******************** >>Но вот даже при "чистом ООП" можно ограничиться одним вызовом, а не "нудным кодом". Я >>привел пример с CPage-ем. Оставаясь иерархичным этот пример делал все в одном цикле. >>Точно так же любой контейнер может хранить однородный список своего содержимого, а не 20 >>указателей на разные типы. И это не будет отходом от ООП, потому что данный список >>создается и поддерживается самим контейнером, и в него не может попасть никто посторонний. >>В случае с мессенгером - может, но ты лично решил этого не делать, что и придает твоему >>приложению видимость ООП.
1) Если контейнер будет мессангером, то для такого КЛАССА можно в принципе по ООП-вски определить его поведение – каких деток он умеет включать в свой список подписчиков.
2) Если сделать п.1, ДАЖЕ ЕСЛИ ОБЪЕКТЫ БУДУТ РАЗНЫХ КЛАССОВ, то всё равно будет по ООП-вски, т.к. грубо говоря, будем в цикле говорить всем – «нарисоваться», и все нарисуются в соответствие с поведением своих классов.
******************** >>Вывод: текущий вариант можно доработать, подвергнув минимальным изменениям. То что >>рассылка сообщений вынесена в отдельный класс - хорошо (как сказал далай-Страутсруп: как >>можно большее количество сущностей должно иметь свой класс :-) ). Но то, что мессенгер один >>и глобальный - плохо. Нужно отнаследовать приложение от мессенгера и регистрировать >>подписчиков прямо при создании (этих подписчиков). Причем регистрироваться должен не сам >>подписчик, а родитель может зарегистрировать своего ребенка в качестве подписчика на >>сообщения СВОЕГО мессенгера.
>>Стоит упомянуть, что классы всех контейнеров, которые не могут напрямую перечислить свое >>содержимое (как табконтрол или окно) следует отнаследовать от мессенгера (ну или мессенгер->>подобного класса).
>>Иерархия сохраняется, код упрощается, количество связей уменьшается, добавление объектов >>с разных уровней иерархии в один мессенгер не НЕЖЕЛАТЕЛЬНО, а НЕВОЗМОЖНО, короче >>все счастливы и все работает.
Да. Надо попробовать. То, что от мессангера можно будет отнаследлваться, в любом классе, понятно. Надо подумать, как сделать так, чтобы такой класс будет одновременно отнаследоваться от подписчика. Это надо, так как сам контейнер, может быть включён в контейнер «выше».
PS. В очередной раз спасибо.
|