ezus
Опытный
Offline
|
|
« : 18-05-2010 07:47 » |
|
То, что ты пытаешься делать, это форменное надругательство над логикой.
Очевидно, что я приводил не кусок программы, а адаптированный код. Но тем не менее я не буду оспаривать данное утверждение, очень может быть, что оно верное. Тем более я буду благодарен за любые комментарии, т.к. именно тема дизайна наиболее мне привлекательна. Итак имеем 1. Структуру системного вектора, содержащего указатели на отдельные модули системы struct ZSysVector { ZGui* _gui; ZReports* _reports; .... AProject* _prj; }
Главной идеей системного вектора - локализация описания среды исполнения кода. Польза: унификация инициализации модулей, сокращение числа передоваемых параметров, сокращение числа интерфейсных прозрачных функций в головных модулях, нет необходимости в многочисленных мелких изменениях при изменении среды. Недостаток: Отсутствие явного указания в коде программы связей между модулями. Есть еще один фактор - дань личным традициям. 2. Необходимо разработать новую подсистему (Simulation), которой требуется добавить к системному вектору пару переменных. Раньше я просто бы добавил эти переменные в системный вектор, но: а) в больших системах системный вектор разрастается до неприятных размеров б) есть очень красивое понятие инкапсуляция, и не хотелсь бы от него отказываться. Поэтому мне показалось естественным использовать наследование struct ZSysVectorSimulation : public ZSysVector { SRand* _rand; SMainTree* _tree; }
В этом случае мы унифицируем обращение к внешним модулям из внутреннего кода, и внутри модуля нам нет необходимости знать структуру всей программы. В чем здесь ошибка, и где нарушение логики дизайна? 3. Возможны и другие варианты Например Проще некуда struct Sb { Sa aa; int ib; };
или создать отдельный вектор для каждого модуля или ... Но все известные мне варианты не соответствуют отмеченным выше критериям. 4. Естественно при инициализации новой структуры ZSysVectorSimulation и возникла проблемы, вынесенная в заголовок данной темы.
|
|
|
Записан
|
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #1 : 18-05-2010 07:51 » |
|
инициализировать можно через список инициализации конструктора struct ZSysVectorSimulation : public ZSysVector { SRand* _rand; SMainTree* _tree;
ZSysVectorSimulation():ZSysVector(...параметры для передачи в конструктор ZSysVector...)//,_rand(0),_tree(0) { } }
|
|
« Последнее редактирование: 18-05-2010 07:52 от Алексей1153++ »
|
Записан
|
|
|
|
Вад
|
|
« Ответ #2 : 18-05-2010 08:09 » |
|
Главной идеей системного вектора - локализация описания среды исполнения кода.
Слишком много существительных Можно поподробнее, для чего это делается? Если модули независимые - то зачем их держать в какой-то одной структуре? Если кто-то внешний управляет их временем жизни - почему бы этого кого-то не инкапсулировать в объект - менеджер модулей? Или, может быть, это все модули знают про такую структуру, и через неё обращаются к другим модулям? 2. Необходимо разработать новую подсистему (Simulation), которой требуется добавить к системному вектору пару переменных.
В связи с первым вопросом, непонятно также, для чего Simulation такая структура, требующая доступ к экземплярам всех модулей. Simulation как-то расширяет имеющуюся функциональность? Делает что-то специфическое с каждым модулем? б) есть очень красивое понятие инкапсуляция, и не хотелсь бы от него отказываться. Поэтому мне показалось естественным использовать наследование
Где здесь инкапсуляция? struct - это, прошу прощения за тавтологию, открытая структура, здесь ничто никуда не прячется.
|
|
« Последнее редактирование: 18-05-2010 08:10 от Вад »
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #3 : 18-05-2010 10:05 » |
|
Главной идеей системного вектора - локализация описания среды исполнения кода. Ну пусть - компромисс между локальными и глобальными переменными. Польза: унификация инициализации модулей, сокращение числа передоваемых параметров, сокращение числа интерфейсных прозрачных функций в головных модулях, нет необходимости в многочисленных мелких изменениях при изменении среды. Вот это уже не вполне соответствует действительности. Для унификации должна быть унифицированная сущность. Например, некая структура Module, и единый код, работающий с разными экземплярами этой сущности. В противном случае вся эта "унификация" на самом деле не унификация, а набор системных функций - по отдельной функции на каждую возможную конфигурацию. Раньше я просто бы добавил эти переменные в системный вектор, но: а) в больших системах системный вектор разрастается до неприятных размеров б) есть очень красивое понятие инкапсуляция, и не хотелсь бы от него отказываться. Поэтому мне показалось естественным использовать наследование Про инкапсуляцию Вад уже сказал. Остальное понятно в том смысле, что такие решения именно с такими родовыми травмами встречаются и нередко; но непонятно, зачем каково конечное назначение этих решений. Я правильно понимаю, что разные части программы нуждаются в разных подмножествах модулей из этого вектора? Но пусть даже так. Теперь вот это: Естественно при инициализации новой структуры ZSysVectorSimulation и возникла проблемы, вынесенная в заголовок данной темы. Потому что наследование используется не по назначению. Именно вариант с агрегацией (вложенной структурой) в данном случае является самым адекватным. Поскольку позволяет как раз добиться инкапсуляции. Внутри твоего нового модуля может использоваться новая структура, а вне модуля должна использоваться только старая (которая вложенная) структура, поскольку все внешние части системы про твою новую структуру не знают и умеют работать только со старой структурой. И если всё аккуратно расписать, никогда не возникнет ошибки, про которую я написал выше - когда поля структуры-потомка при операциях копирования пропускаются, теряются, не инициализируются, и в них оказывается мусор.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #4 : 18-05-2010 10:25 » |
|
Вот пример аккуратно сделанной надстройки в C-стиле (функции и структуры, без классов), в которой старая часть системы полностью автономна и понятия не имеет о каких-либо надстройках и способах своего использования, но в то же время не спрятана внутрь обёртки. #include <cstdlib> #include <cstdio>
namespace OldSystem {
// C-style solution.
struct Session { int id; }; Session *openNewSession() { Session *session = (Session *)malloc(sizeof(Session)); if(session != NULL) { session->id = rand(); } return session; } void action(Session *session) { if(session != NULL) { printf("%i\n", session->id); } } void closeSession(Session *session) { if(session != NULL) { free(session); } } }
namespace NewSystem { // C-style solution. struct Session { OldSystem::Session *base; int userID; }; Session *openNewSession() { Session *session = NULL; OldSystem::Session *base = OldSystem::openNewSession(); if(base != NULL) { session = (Session *)malloc(sizeof(Session)); if(session != NULL) { session->base = base; session->userID = rand(); } } return session; } void action(Session *session) { if(session != NULL) { OldSystem::action(session->base); printf("%i\n", session->userID); } } void closeSession(Session *session) { if(session != NULL) { OldSystem::closeSession(session->base); free(session); } } }
int main() { NewSystem::Session *session = NewSystem::openNewSession(); if(session != NULL) { NewSystem::action(session); NewSystem::closeSession(session); } return 0; }
|
|
« Последнее редактирование: 18-05-2010 10:30 от Dimka »
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
ezus
Опытный
Offline
|
|
« Ответ #5 : 18-05-2010 13:02 » |
|
Все замечания очень похожи, и это говорить скорее о Вашей правоте, чем о моей. Но.... Я сторонник ИДЕЙ ООП, но не самого ООП, особенно в исполнении С++. Поэтому аргументы типа "так принято в ООП" для меня не аргументы. Еще несколько слов об идее системного вектора. В программе сущестует много различных сервисных модулей, например: разные системные журналы, прогрессбар окна, модуль вывода отчетов, классы данных общего пользования, доступ к базам данных. Эти модули используются в самых разных местах пограммы. Причем некоторые из них по свой природе(или даже реализации) суть синглтоны, некоторые привязаны к конкретным фреймам или классам данных. Некоторым модулям при их инициализации требуются специфические данные. Но для их использования достаточно знать только указатель на них. При этом в месте использования я не хочу ничего знать о конкретном модуле кроме этого указателя - ни как он создается, ни где находится, ни что требуется для инициализации. В этот момент они все равноправны с т.зр. использования. Теперь подробнее: инициализировать можно через список инициализации конструктора struct ZSysVectorSimulation : public ZSysVector { SRand* _rand; SMainTree* _tree;
ZSysVectorSimulation():ZSysVector(...параметры для передачи в конструктор ZSysVector...)//,_rand(0),_tree(0) { } }
На моменте создания ZSysVectorSimulation основной ZSysVector уже существует, и мне хотелось минимальными усилиями скопировать содержимое ZSysVector в новый ZSysVectorSimulation. При этом я не хочу знать, что в ZSysVector входит - все что там есть, давай сюда. В этом случае будующие пополнения ZSysVector меня не интересуют, они будут скопированы автоматически. Поэтому данное предложение мне не подходит. Слишком много существительных Можно поподробнее, для чего это делается? Если модули независимые - то зачем их держать в какой-то одной структуре? Если кто-то внешний управляет их временем жизни - почему бы этого кого-то не инкапсулировать в объект - менеджер модулей? Как я уже сказал, модули рождаются в разных местах и по разным поводам, и поэтому обединение процедур их создания в единую фабрику не кажется мне чем-то хорошим. Или, может быть, это все модули знают про такую структуру, и через неё обращаются к другим модулям? Да, именно в этом идея системного вектора. В связи с первым вопросом, непонятно также, для чего Simulation такая структура, требующая доступ к экземплярам всех модулей. Simulation как-то расширяет имеющуюся функциональность? Делает что-то специфическое с каждым модулем?
На это я вроде ответил выше. Где здесь инкапсуляция? struct - это, прошу прощения за тавтологию, открытая структура, здесь ничто никуда не прячется.
Почему же? В С++ struct - это тот же класс, только с открытыми членами. Хотя согласен, что термин "инкапсуляция" здесь не совсем уместен. Здесь это скорее не сокрытие от других, а удобное средство суперпозиции данных. Ну пусть - компромисс между локальными и глобальными переменными. Да, согласен. Вот это уже не вполне соответствует действительности. Для унификации должна быть унифицированная сущность. Например, некая структура Module, и единый код, работающий с разными экземплярами этой сущности. В противном случае вся эта "унификация" на самом деле не унификация, а набор системных функций - по отдельной функции на каждую возможную конфигурацию. А где сказано, что термин "унификация" применим только в интерпретации ООП? В моем случае это унификация использования сервисных модулей. При этом никакого "набора системных функций" я тут не вижу, или я чего-то не понял, а жаль. Остальное понятно в том смысле, что такие решения именно с такими родовыми травмами встречаются и нередко; но непонятно, зачем каково конечное назначение этих решений. Я все еще не ответил? Я правильно понимаю, что разные части программы нуждаются в разных подмножествах модулей из этого вектора? Да. Но я не вижу, чем могут помешать лишние данные. Кроме того. если в дальнейшем потребуется обратиться к ранее неиспользованному модулю, то ни каких дополнительных телодвижений не потребуется. Потому что наследование используется не по назначению. ПОЧЕМУ? Именно вариант с агрегацией (вложенной структурой) в данном случае является самым адекватным. Поскольку позволяет как раз добиться инкапсуляции. Про инкапсуляцию я уже извинился, а вариант с агрегацией меня не устраивает, потому что каждый конкретный раз я должен знать откуда модуль - из ZSysVectorSimulation или из ZSysVector. А именно этот излишек требование к знанию я и хочу избежать.
|
|
|
Записан
|
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #6 : 18-05-2010 14:13 » |
|
На моменте создания ZSysVectorSimulation основной ZSysVector уже существует, и мне хотелось минимальными усилиями скопировать содержимое ZSysVector в новый ZSysVectorSimulation. При этом я не хочу знать, что в ZSysVector входит - все что там есть, давай сюда. В этом случае будующие пополнения ZSysVector меня не интересуют, они будут скопированы автоматически. Поэтому данное предложение мне не подходит.
не понимаю. Если у тебя ZSysVectorSimulation паблик от ZSysVector (читай: содержит в начале себя структуру ZSysVector ), то всё равно тебе туда копировать извне. Какая разница как? Оператор ли "=", конструктор ли, memmove, чёрт побери )) Зациклился ты на мелочи, как её решить уже много способов сказано. А тебе, видишь ли, ООП нравится, но ты его не применяешь. Ты пишешь на С++ ? Ну так используй его на всю катушку!
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #7 : 18-05-2010 14:39 » |
|
Я сторонник ИДЕЙ ООП, но не самого ООП, особенно в исполнении С++. Поэтому аргументы типа "так принято в ООП" для меня не аргументы. И я сторонник идей ООП. Идея классов и логического отношения род-вид между ними описана в работе Аристотеля "Категории". Надеюсь, Аристотеля ты не подозреваешь в симпатиях к самому ООП, реализованному в C++? В этом случае будующие пополнения ZSysVector меня не интересуют, они будут скопированы автоматически. Поэтому данное предложение мне не подходит. Ты хочешь ослабить типизацию и добиться того, чтобы во всяком месте копировалась вся структура, как бы она ни была устроена. В пределе это задача копирования произвольного по размеру куска памяти, спрятанного за void *. Для этого тебе как минимум нужно знать размер структуры. В случае присваивания типизированных переменных размер известен благодаря типу. Ты же от типа отказываешься - тогда храни размер вручную (либо в самой структуре, либо заведи идентификаторы типов и создай хэш из пар тип-размер), полиморфизм объектов в ООП в подавляющем большинстве языков реализуется именно так, только для этого задействуется RTTI, и вся работа перекладывается на компилятор. Другое дело, что ты иерархию типов переворачиваешь с ног на голову. Это всё равно, что пытаться всякое дерево назвать берёзой (поместить в переменную) и потом требовать от него берёзовых свойств. Здесь это скорее не сокрытие от других, а удобное средство суперпозиции данных. Нет, в C++ - это не "средство суперпозиции данных". Упомянутые средства имеются в других языках программирования и называются примесями - отдалённым их аналогом является импорт пространств имён. Но наследование служит для специализации, перехода по дедукции от общего к частному, а не для "подцепления вагонов к поезду". И именно поэтому у тебя оно не работает так, как тебе хочется. Здесь поможет только, как говорится, изучение матчасти - того, как именно и для чего именно так работают механизмы ООП в языке. В моем случае это унификация использования сервисных модулей. В твоём случае это называется архитектурой. Соглашением о том, что модули будут организованы так-то, и все разработчики обязаны это учитывать. Да. Но я не вижу, чем могут помешать лишние данные. Кроме того. если в дальнейшем потребуется обратиться к ранее неиспользованному модулю, то ни каких дополнительных телодвижений не потребуется. Раз они не мешают, тогда добавь их в исходную структуру - и дело с концом. Ты же весь огород городить начал именно из-за того, что они чем-то мешали. потому что каждый конкретный раз я должен знать откуда модуль - из ZSysVectorSimulation или из ZSysVector. А именно этот излишек требование к знанию я и хочу избежать. НЕТ! Если ты внимательно посмотришь мой пример, то OldSystem знает только про свой вариант Session, и другие варианты просто не примет. Это значит, что когда NewSession обращается наружу к OldSession, она гарантирует при вызовах передачу только вложенной структуры. А вот внутри NewSession работает со своим новым вариантом структуры. Аккуратное соблюдение типизации (если заметил, у меня нет ни одной операции приведения типов, кроме вызова malloc) является достаточным элементом контроля. Соответствие типов аргументов при вызове функции является предусловием исполнения функции, и в случае несоответствия ошибка возникнет ещё на этапе компиляции.
|
|
« Последнее редактирование: 18-05-2010 14:53 от Dimka »
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Вад
|
|
« Ответ #8 : 18-05-2010 16:20 » |
|
Слишком много существительных Можно поподробнее, для чего это делается? Если модули независимые - то зачем их держать в какой-то одной структуре? Если кто-то внешний управляет их временем жизни - почему бы этого кого-то не инкапсулировать в объект - менеджер модулей? Как я уже сказал, модули рождаются в разных местах и по разным поводам, и поэтому обединение процедур их создания в единую фабрику не кажется мне чем-то хорошим. Если они рождаются в разных местах и по разным поводам - то к чему централизация вообще? Пытаясь централизовать данные о модулях, ты получаешь загрязнение пространства имён, потому что каждый модуль вынужден неумышленно знать про все-все другие, если ему понадобится хотя бы один из них. Я уже не говорю о том, что в случае структуры указателей ты должен откуда-то извне строго следить за правильным порядком инициализации модулей и разруливать зависимости. Я не говорю про фабрику. Коль уж модули - сингельтоны, то можно использовать фабричные методы у самих этих модулей для получения экземпляра. Тогда каждому модулю достаточно знать интерфейс нужных ему, чтобы получить указатель на существующий экземпляр и работать с ним спокойно (тут же можно реализовать и подсчёт ссылок, если потребуется). Если модуль то является сингельтоном (разделяем несколькими модулями), то нет, или просто неохота "портить шкуру" - добавлять статический фабричный метод в интерфейс - то можно сделать сингельтон-посредник, который де-факто будет иметь статический экземпляр модуля и отдавать его всем желающим.
|
|
« Последнее редактирование: 18-05-2010 16:23 от Вад »
|
Записан
|
|
|
|
sss
Специалист
Offline
|
|
« Ответ #9 : 19-05-2010 01:31 » |
|
Д'Артаньян не в силах был продолжать этот разговор, он чувствовал, что сходит с ума. Он уронил голову на руки и притворился, будто спит. ...
|
|
« Последнее редактирование: 19-05-2010 01:33 от sss »
|
Записан
|
while (8==8)
|
|
|
ezus
Опытный
Offline
|
|
« Ответ #10 : 19-05-2010 08:35 » |
|
Ты хочешь ослабить типизацию и добиться того, чтобы во всяком месте копировалась вся структура, как бы она ни была устроена. В пределе это задача копирования произвольного по размеру куска памяти, спрятанного за void *. Для этого тебе как минимум нужно знать размер структуры. В случае присваивания типизированных переменных размер известен благодаря типу. Ты же от типа отказываешься ... Где я "ослабляю типизацию"? Причем тут "void *" и "произвольный по размеру куска памяти"? От чего я отказался? У меня есть ПОЛНОСТЬЮ определенная базовая структура, у меня есть ПОЛНОСТЬЮ определенная дочерняя структура. Я всего лишь хочу перенести значение одной базовой структуры в БАЗОВУЮ же часть дочерней. В чем здесь криминал? Какие принципы ООП я нарушил? Другое дело, что ты иерархию типов переворачиваешь с ног на голову. Это всё равно, что пытаться всякое дерево назвать берёзой (поместить в переменную) и потом требовать от него берёзовых свойств.
Где я требую от дерева березовых свойств? Вы мне приписываете что-то непонятное. У меня есть береза. Эта береза, как ни странно, является еще и деревом. Я хочу при создании объекта "береза" переписать в нее ту часть, которая касается ее как дерева. И буду в дальнейшем требовать от этой части только свойств березы как ДЕРЕВА. Что\где я здесь перевернул? Пришел товар; я о нем знаю только, что он пришел, от кого и по какой цене. В этом месте я работаю с ним как с ТОВАРОМ. Затем я узнаю, что это жидкость - мне приходится перейти к объекту другого типа, который наследует все свойства товара плюс объем. И наконец, я узнаю, что это пиво - и полностью теряю интерес к программирования А если серьезно, то где ошибка в только-что изложенном? Но наследование служит для специализации, перехода по дедукции от общего к частному, а не для "подцепления вагонов к поезду". И именно поэтому у тебя оно не работает так, как тебе хочется.Здесь поможет только, как говорится, изучение матчасти - того, как именно и для чего именно так работают механизмы ООП в языке. Не буду оспаривать "подцепления вагонов к поезду", хотя для меня путь от общего к частному как раз и представлялся путем последовательного уточнения свойств общего, которое чаще всего связанно с расширением списка этих свойств. Так и моем случае: есть самая общая среда выполнения программа, а отдельные блоки могут иметь свою среду, которая обладает только ей присущими свойствами, при этом общая среда программы никуда не делась. Где я здесь пошел против "матчасти"? Какие "механизмы ООП" и не должны обслуживать мои потребности? В моем случае это унификация использования сервисных модулей. В твоём случае это называется архитектурой. Соглашением о том, что модули будут организованы так-то, и все разработчики обязаны это учитывать. И чем это плохо? А в Ваших системах это не так? Да. Но я не вижу, чем могут помешать лишние данные. Кроме того. если в дальнейшем потребуется обратиться к ранее неиспользованному модулю, то ни каких дополнительных телодвижений не потребуется. Раз они не мешают, тогда добавь их в исходную структуру - и дело с концом. Ты же весь огород городить начал именно из-за того, что они чем-то мешали. Но в других частях программы они просто не имеют смысла. Так можно договориться до: наследование нам нужно только ради полиморфизма и, если такого-го нет - вали все в кучу. Я надеюсь, Вы не это имели ввиду? потому что каждый конкретный раз я должен знать откуда модуль - из ZSysVectorSimulation или из ZSysVector. А именно этот излишек требование к знанию я и хочу избежать. НЕТ! Если ты внимательно посмотришь мой пример, то OldSystem знает ....... Прекрасно! Но часто ли Вы используете данный стиль в своих последних разработках? Мне же надо тоже самое только в С++.
|
|
|
Записан
|
|
|
|
Dale
|
|
« Ответ #11 : 19-05-2010 09:11 » |
|
Так можно договориться до: наследование нам нужно только ради полиморфизма и, если такого-го нет - вали все в кучу. Я надеюсь, Вы не это имели ввиду?
ezus, можно вопрос по ходу дискуссии? Мне по небольшому опыту программирования на C++ известны два основных применения наследования: - Наследование интерфейса (открытое наследование от класса-предка). Потомок берется выполнять все обязательства базового класса, так что клиент в идеале не должен обнаружить различия.
- Наследование реализации (закрытое наследование от класса-предка). Потомок представляется как некая новая сущность с собственным поведением, пользуясь для своих нужд средствами предка.
Какой из двух вариантов предполагалось использовать в данной задаче? Или, возможно, какой-то третий вариант, который я не учел? P.S. Прошу прощения, если об этом уже говорилось выше. Тема интересная, но диалог ведется параллельно в слишком многих плоскостях - от античной философии до хакерства на уровне "а что там на самом деле творится в стеке?". В условиях ограниченного времени трудно проследить основную нить, стек переполняется.
|
|
|
Записан
|
Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.
Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard
Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
|
|
|
ezus
Опытный
Offline
|
|
« Ответ #12 : 19-05-2010 09:17 » |
|
Если они рождаются в разных местах и по разным поводам - то к чему централизация вообще? А как мне в произвольном месте программы иметь возможность использовать произвольный сервисный модуль? Мне известны всего несколько способов(не по важности или глупости, а только как пришли в голову): 1. Глобальные переменные - в настоящий момент преданные жуткой анафеме, хотя я их и не считаю, таким уж ЖУТКИМ грехом. 2. Синглтоны - классная вещь, на мой взгляд один из лучших неочевидных паттернов. Но далеко не все модули могут быть так оформлены. 3. Передаваемые параметры - в функции или при инициализации. Во-первых их может быть много, что неудобно. Во- вторых: передавать все - глупо, а иначе любая новая потребность порождает изменения по все цепочке вызовов. 4. Цепочки сред. При создании объекта ему передается обратный указатель на создателя. Так двигаясь в обратном порядке можно добраться до любого модуля в программе (конечно, если весь путь public). Требует знание структуры всей программы. 5. Локальный. Каждый модуль сам себе порождает сервисные модули. К сожалению не все модули можно так породить. 6. Сервисный центр. Некий модуль, который предоставляет доступ к сервисным модулям. Хорош для на общесистемном уровне. Но трудно покрывает локальные среды. 7. Системный вектор - чем-то схож с Сервисным центром, но более прост и мобилен. О недостатках не говорю, т.к. они подробно уже разобраны не мной. Наверняка есть еще что-нибудь. Буду рад пополнить свою коллекцию. Пытаясь централизовать данные о модулях, ты получаешь загрязнение пространства имён, потому что каждый модуль вынужден неумышленно знать про все-все другие, если ему понадобится хотя бы один из них. Я уже не говорю о том, что в случае структуры указателей ты должен откуда-то извне строго следить за правильным порядком инициализации модулей и разруливать зависимости. Согласен. Но нет идеальных решений. Хотя в С++, к счастью - слава создателям ) в данном случае "ЗНАТЬ" слишком сильно сказано - я знаю только указатель на какой-то там модуль и больше ничего. И если он мне не интересен, то описание не связывает их даже на этапе компиляции. Коль уж модули - сингельтоны, Далеко не все. Тогда каждому модулю достаточно знать интерфейс нужных ему, чтобы получить указатель на существующий экземпляр и работать с ним спокойно . А в моем случае и этого знать не надо.
|
|
|
Записан
|
|
|
|
ezus
Опытный
Offline
|
|
« Ответ #13 : 19-05-2010 09:18 » |
|
Д'Артаньян не в силах был продолжать этот разговор, он чувствовал, что сходит с ума. Он уронил голову на руки и притворился, будто спит. ...
Сочувствую. Я и сам себя так ощущаю.
|
|
|
Записан
|
|
|
|
ezus
Опытный
Offline
|
|
« Ответ #14 : 19-05-2010 09:42 » |
|
Мне по небольшому опыту программирования на C++ известны два основных применения наследования: - Наследование интерфейса (открытое наследование от класса-предка). Потомок берется выполнять все обязательства базового класса, так что клиент в идеале не должен обнаружить различия.
- Наследование реализации (закрытое наследование от класса-предка). Потомок представляется как некая новая сущность с собственным поведением, пользуясь для своих нужд средствами предка.
Мне кажется здесь 2 ортогональных измерения: одно - интерфейс \ реализация другой - функциональность. Во втором случае возможно как усечение возможностей - базовый класс используется только для своих нужд, так и расширение возможностей - предоставление клиенту кроме базовых еще и дополнительные возможности. Пример: цепочка наследования в окнах MFC Какой из двух вариантов предполагалось использовать в данной задаче? Или, возможно, какой-то третий вариант, который я не учел?
В данном случае предполагается второе уточнение второго варианта.
|
|
|
Записан
|
|
|
|
Вад
|
|
« Ответ #15 : 19-05-2010 09:42 » |
|
2. Синглтоны - классная вещь, на мой взгляд один из лучших неочевидных паттернов. Но далеко не все модули могут быть так оформлены.
Не все, но многие? Кроме того, я говорил о сингельтоне-посреднике как частном решении. Если же модуль используется в нескольких экземплярах - твой вариант со структурой тоже не подходит. Пытаясь централизовать данные о модулях, ты получаешь загрязнение пространства имён, потому что каждый модуль вынужден неумышленно знать про все-все другие, если ему понадобится хотя бы один из них. Я уже не говорю о том, что в случае структуры указателей ты должен откуда-то извне строго следить за правильным порядком инициализации модулей и разруливать зависимости. Согласен. Но нет идеальных решений. Хотя в С++, к счастью - слава создателям ) в данном случае "ЗНАТЬ" слишком сильно сказано - я знаю только указатель на какой-то там модуль и больше ничего. И если он мне не интересен, то описание не связывает их даже на этапе компиляции. Ну да. Под "знать" я подразумевал, что все модули знают о единой структуре ("векторе"), вынуждены её поддерживать и имеют в своём пространстве имён набор деклараций типов. Тогда каждому модулю достаточно знать интерфейс нужных ему, чтобы получить указатель на существующий экземпляр и работать с ним спокойно . А в моем случае и этого знать не надо. А чтобы работать с модулем - тоже не надо интерфейс?
|
|
|
Записан
|
|
|
|
ezus
Опытный
Offline
|
|
« Ответ #16 : 19-05-2010 09:55 » |
|
Если же модуль используется в нескольких экземплярах - твой вариант со структурой тоже не подходит. Почему не подходит? В среде каждого конкретного модуля должен быть только один экземпляр, но самих модулей может быть несколько. Пример: есть 2-3-4 окна, в каждом из которых мы работаем со своей базой данных. Ну да. Под "знать" я подразумевал, что все модули знают о единой структуре ("векторе"), вынуждены её поддерживать и имеют в своём пространстве имён набор деклараций типов. Да - о векторе и НЕТ о типах. Надо поддерживать только типы используемых модулей, а это надо делать в любом случае. А если модуль не используется, то необходим единственный стандартный тип void* Тогда каждому модулю достаточно знать интерфейс нужных ему, чтобы получить указатель на существующий экземпляр и работать с ним спокойно . А в моем случае и этого знать не надо. А чтобы работать с модулем - тоже не надо интерфейс? Надо, но только для использования, а не порождения. Хотя Вы правы - это не такое уж принципиальное отличие.
|
|
|
Записан
|
|
|
|
Dale
|
|
« Ответ #17 : 19-05-2010 11:03 » |
|
Мне кажется здесь 2 ортогональных измерения: одно - интерфейс \ реализация другой - функциональность. Во втором случае возможно как усечение возможностей - базовый класс используется только для своих нужд, так и расширение возможностей - предоставление клиенту кроме базовых еще и дополнительные возможности. ... В данном случае предполагается второе уточнение второго варианта.
Спасибо. Если я правильно понял конструкцию, потомок должен уметь все то же самое, что и предок, и плюс кое-что дополнительно? В таком случае структурой в стиле старого доброго C мы вряд ли обойдемся. Придется писать на C++, а значит, определять копирующий конструктор и/или операторы присваивания/преобразования типов и т.п. Пример: цепочка наследования в окнах MFC Если мы говорим о хорошем стиле ООП (а я надеюсь, что это так и есть), нам лучше все же не брать MFC в качестве образца для подражания.
|
|
|
Записан
|
Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.
Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard
Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #18 : 19-05-2010 11:37 » |
|
Где я "ослабляю типизацию"? Причем тут "void *" и "произвольный по размеру куска памяти"? От чего я отказался? У меня есть ПОЛНОСТЬЮ определенная базовая структура, у меня есть ПОЛНОСТЬЮ определенная дочерняя структура. Я всего лишь хочу перенести значение одной базовой структуры в БАЗОВУЮ же часть дочерней. В чем здесь криминал? Какие принципы ООП я нарушил? У тебя нет "базовой части" переменной - вот в этом и криминал. "Базовая часть" бывает только у значения. Поэтому я и говорил, что введение вложенной структуры может решить твои затруднения. А ослабление типизации происходит в том случае, когда из значения производного типа извлекают "базовую часть". К операции записи в переменную это не относится. Но ты это попытался скомбинировать, что показывает непонимание "матчасти". Теперь же я поясню, как твои действия оцениваются со стороны матчасти. Ты берёшь переменную дочернего типа и пробуешь записать туда значение базового типа. Комплиятор не знает, как правильно расширить значение базового типа до размера переменной. С его точки зрения под видом берёзы ему пытаются всучить чёрт знает что, может тополь какой. Где я требую от дерева березовых свойств? Вы мне приписываете что-то непонятное. У меня есть береза. Эта береза, как ни странно, является еще и деревом. Я хочу при создании объекта "береза" переписать в нее ту часть, которая касается ее как дерева. И буду в дальнейшем требовать от этой части только свойств березы как ДЕРЕВА. Что\где я здесь перевернул? Пришел товар; я о нем знаю только, что он пришел, от кого и по какой цене. В этом месте я работаю с ним как с ТОВАРОМ. Затем я узнаю, что это жидкость - мне приходится перейти к объекту другого типа, который наследует все свойства товара плюс объем. И наконец, я узнаю, что это пиво - и полностью теряю интерес к программирования А если серьезно, то где ошибка в только-что изложенном? Здесь ошибки нет, но это не соответствует тому, что у тебя написано в коде. Выражение (A)b = a; означает, что у тебя есть дерево (а не берёза) - значение в переменной a. И ты хочешь это дерево засунуть в берёзу - переменную b. То, что ты берёзу собираешься использовать лишь как дерево, компилятор не знает - не телепат, поэтому он старается обеспечить наличие в переменной b значения типа B (именно берёзы, а не чего-то там другого). Приведение к A - это уже из области "ловкости рук" при впаривании просроченной тухлятины под видом свежего товара. Если тебе нужны только свойства дерева, тогда и объявляй переменную типа A. Наверно ты бы тоже обиделся и не согласился, если тебе под видом пива пытались бы всучить квас. И чем это плохо? А в Ваших системах это не так? Ничем. Я просто уточнил, что речь идёт не об унификации, а об архитектуре. Но в других частях программы они просто не имеют смысла. Так можно договориться до: наследование нам нужно только ради полиморфизма и, если такого-го нет - вали все в кучу. Я надеюсь, Вы не это имели ввиду? Самое главное - это инкапсуляция и достигаемая с её помощью возможность абстрагироваться от несущественных деталей. Без неё обсуждение наследования и полиморфизма смысла не имеет. По крайней мере у тебя в наследовании никакого смысла нет. Поскольку на самом деле наследование (inheritance) наряду с делегированием с прототипами - это лишь языковые реализации чисто логического отношения обобщения (generalization), только первое используется в типизированных языках, а второе в нетипизированных, лично я могу добиться полиморфизма и без наследования, если мне так приспичит Прекрасно! Но часто ли Вы используете данный стиль в своих последних разработках? Мне же надо тоже самое только в С++. В C++ нужно структуру "растянуть" на всё пространство имён и включить в неё функции, убрав их параметр типа структуры. Это всё. Принципы ООП не зависят от языка реализации. Я могу и на чистом C реализовать и объекты, и их инкапсуляцию, и наследование с полиморфизмом, и без всяких издевательств над компилятором. Когда хорошо понимаешь принципы и возможные способы их "физической" реализации, язык программирования значения не имеет.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #19 : 19-05-2010 11:40 » |
|
Придется писать на C++, а значит, определять копирующий конструктор и/или операторы присваивания/преобразования типов и т.п. Эта здравая мысль звучала и раньше, но почему-то не нравится зачинщику обсуждения. Если мы говорим о хорошем стиле ООП (а я надеюсь, что это так и есть), нам лучше все же не брать MFC в качестве образца для подражания. Категорически согласен
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Антон (LogRus)
|
|
« Ответ #20 : 19-05-2010 11:46 » |
|
Здесь ошибки нет, но это не соответствует тому, что у тебя написано в коде. Выражение (A)b = a; означает, что у тебя есть дерево (а не берёза) - значение в переменной a. И ты хочешь это дерево засунуть в берёзу - переменную b. То, что ты берёзу собираешься использовать лишь как дерево, компилятор не знает - не телепат, поэтому он старается обеспечить наличие в переменной b значения типа B (именно берёзы, а не чего-то там другого). Приведение к A - это уже из области "ловкости рук" при впаривании просроченной тухлятины под видом свежего товара. Если тебе нужны только свойства дерева, тогда и объявляй переменную типа A. я бы рассматривал эту строку иначе (если убрать из неё изначальную ошибку) void DataCopy(A * dst, A * src) { *dst = *src; } ............. B b; // B унаследован от A A a; DataCopy(&b, &a);
Думаю тут все согласятся, что подобное приведение является верным и вполне себе классическим примером использования наследования, т.е. я не пытаюсь базовый тип привести к наследнику, а наследника привожу к базовому. единственное, что автор темы совершил небольшую опечатку, которая привела к созданию временного объекта, но идея от этого не изменилась.
|
|
|
Записан
|
Странно всё это....
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #21 : 19-05-2010 12:11 » |
|
LogRus, вот поэтому ему и говорили написать оператор присваивания или копирующий конструктор. Но не хочет.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Антон (LogRus)
|
|
« Ответ #22 : 19-05-2010 12:14 » |
|
Dimka, копирующий конструктор это конечно круто, НО это некоим образом не объясняет почему приведённая конструкция не работает
|
|
|
Записан
|
Странно всё это....
|
|
|
Dale
|
|
« Ответ #23 : 19-05-2010 12:28 » |
|
(A)b = a; ... Приведение к A - это уже из области "ловкости рук" при впаривании просроченной тухлятины под видом свежего товара. И в довершение картины - ловкий продавец при этом еще и старается говорить не на языке покупателя, чтобы еще больше запутать. Поскольку процитированный фрагмент написан не на C++.
|
|
|
Записан
|
Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.
Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard
Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #24 : 19-05-2010 13:11 » |
|
LogRus, твоя конструкция тоже не объясняет, почему не работает его выражение. Скорее наоборот, твой работающий пример способен навести на мысль, что и его конструкция должна работать.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
ezus
Опытный
Offline
|
|
« Ответ #25 : 20-05-2010 06:23 » |
|
Прошу прощения у LogRus. Конструкция прекрасно работает. Огромное спасибо. Ты вернул мне веру в себя, в ООП, и даже в С++. Конструкция (А)b=a; не работает по чисто компиляторским причинам. Например обратные a=(A)b; и даже a=b; прекрасно хаваются и исполняются. Правда, побочные последствия я не исследовал, возможно они и существуют, а будет жаль.
|
|
|
Записан
|
|
|
|
ezus
Опытный
Offline
|
|
« Ответ #26 : 20-05-2010 06:26 » |
|
LogRus, вот поэтому ему и говорили написать оператор присваивания или копирующий конструктор. Но не хочет.
Вы можете привести мне примеры предложенных приемов без использования поэлементного присвоения?
|
|
|
Записан
|
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #27 : 20-05-2010 06:34 » |
|
ezus, Конструкция (А)b=a; не работает по чисто компиляторским причинам. Например обратные a=(A)b; и даже a=b; прекрасно хаваются и исполняются. Правда, побочные последствия я не исследовал, возможно они и существуют, а будет жаль.
1)(А)b=a; - можно расписать так: A& ra=A(); ra=b; ra=a; //присвоения b=... нету.
2)a=(A)b; - можно расписать так: 3) a=b; - вызывается копирование по умолчанию , то есть так:
|
|
|
Записан
|
|
|
|
ezus
Опытный
Offline
|
|
« Ответ #28 : 20-05-2010 07:04 » |
|
(A)b = a; ... Приведение к A - это уже из области "ловкости рук" при впаривании просроченной тухлятины под видом свежего товара. И в довершение картины - ловкий продавец при этом еще и старается говорить не на языке покупателя, чтобы еще больше запутать. Поскольку процитированный фрагмент написан не на C++. На "ловкость рук" и "просроченную тухлятину" я не ответил в свое время во-первых у меня не было работающего примера, а во-вторых на грубость, тем более необоснованную, вообще не отвечаю. Но сейчас я убедился в своей правоте и попробую спокойно отреагировать на приведенную цитату. Чем не C++? Только тем, что Вы его не используете?(Пардон не удержался ) С точки зрения языка:Моя ошибка заключалась в незнании особенностей КОНКРЕТНОГО компилятора и в наследии пресловутой Java. Компилятор получил всю необходимую информацию для исполнения того, что я хочу. Я просто не знал о временной переменной. В Java конструкция (A) не оператор как здесь, а только указание интерпретатору на приведении типов. Поэтому работает (A&)b = a;. Хотя и конструкция (A)b тоже имеет смысл. Например: ((A)b).ff(); для доступа к функции ff класса А. С точки зрения ООП или "тухлятины":Разве береза перестала быть деревом только потому что она береза? Разве ее нельзя рассматривать как дерево? Что в конструкции "(A&)b = a" я впариваю как тухлятиню? Что я здесь продаю на другом языке?
|
|
|
Записан
|
|
|
|
ezus
Опытный
Offline
|
|
« Ответ #29 : 20-05-2010 07:09 » |
|
Алексей1153++ Спасибо. Полезно. А о своей ошибке я уже сказал в предыдущем посте.
|
|
|
Записан
|
|
|
|
|