07.04.2011, 13:22 | #1 |
Участник
|
Как правильно хранить статичный набор начальных данных в классах?
Вопрос: Как правильно хранить статичный набор начальных данные в классах?
========================= Дисклаймер 1: хотелось бы обсудить не технические детали, а подходы к программированию в Аксапте. Обсудить плюсы и минусы. Дисклаймер 2: Специально возьму пример не из области учета, а из области программирования низкого уровня (чтобы не начинать обсуждение в стиле "сделай по-другому", "используй другой функционал"). Дисклаймер 3: я специально спрашиваю про классы, а не таблицы. с таблицами - никаких вопросов. Кроме того, что таблицы надо создавать и инициализировать отдельно. а вот классы: 3.1. могут "жить" и на клиенте, и на сервере. 3.2. могут наследовать - первоначальные данные могут переопределяться/добавляться в потомках 3.3. живут в памяти ========================= пусть я создаю какой-то системный класс, который обновляет Query. в классе статически прописан Query, в потомках Query может меняться. и есть начальные данные, которые прописывает программист (НЕ ПОЛЬЗОВАТЕЛЬ): набор пар <таблица, поле> именно на эти поля и накладывается фильтр классом. Дисклаймер 4: таблиц может быть несколько, полей может быть несколько, поля могут иметь расширенный код (например, Dimension[2]) Дисклаймер 5: хотелось бы обсудить хранение не только пар, но и троек/четверок/пятерок значений. Главное, что эти пары/тройки/четверки/и.т.д. - однородны по своей структуре. ============================ В С++ я бы написал что-то вроде struct { int64 refTable; int refField } myStruct; set<myStruct> myStructSet = ( {Table1, Field1}, {Table2, Field2} ); и получил бы одновременно и контроль типов, и легкость инициализации. ======================== Вопрос: а как лучше хранить такие статические наборы значений? Что можно использовать в Аксапте: Вариант 1.1 = контейнер контейнеров: = пример [ [table1, field1], [table2, field2] ] === плюсы: ======= легко инициализировать ======= легко добавлять/изменять значения в наборе в классах потомках === минусы: ======= никакой типизации и никакой проверки типов на этапе компиляции Вариант 1.2 = контейнер структур (struct): = пример new struct1 = new Struct(Types::Int64, Types::Integer); new struct2 = new Struct(Types::Int64, Types::Integer); ... [ struct1, struct2 ] === плюсы: ======= хоть какая то типизация === минусы: ======= инициализировать уже неудобно ======= нет проверки заполненности полей в структуре Вариант 1.3 = контейнер собственных классов (или подходящих. В данном случе можно использовать DictField) = пример [ new myPairClass(table1, field1), new myPairClass(table2, field2) ] === плюсы: ======= полный контроль над целостностью и валидностью данных ======= в потомках легко добавлять/изменять наборы === минусы: ======= нужно программировать свой класс - трудоемко Вариант 2.1 set of контейнеров (множество контейнеров) Вариант 2.2 set of struct (множество структур) Вариант 2.3 set of myPairClass (множество собственных классов) Вариант 3.1 list of контейнеров (множество контейнеров) Вариант 3.2 list of struct (множество структур) Вариант 3.3 list of myPairClass (множество собственных классов) другие способы? ======================== Какой способ вы предпочитаете? Почему? Какие плюсы и минусы у разных способов? Какие примеры хранения наборов данных есть в стандартной Аксапте? ======================== Например, в Аксапте есть FieldList. Этот класс хранит список полей в одной (!) таблице. Этот класс хранит список полей в контейнере. (со всеми вытекающими по контролю типов) Этот класс пытается сам предоставить интерфейс доступа через find. Этот класс не предоставляет enumerator'а для своего содержимого В общем, как-то не айс. Вопрос: Как правильно хранить статичный набор начальных данные в классах? Последний раз редактировалось mazzy; 07.04.2011 в 13:29. Причина: добавил оговорку про DictField. Да, можно использовать и его. Но хотелось бы обсудить более общий вариант. |
|
07.04.2011, 13:34 | #2 |
Участник
|
а может вообще не парится с такими начальными данными, а просто делать специализированные методы, в которых статически написано что и с какими полями нужно делать?
|
|
07.04.2011, 13:41 | #3 |
Moderator
|
Я бы еще добавил - создать временную таблицу правильной структуры (с добавлением ключа или просто индекса как в массиве) и засунуть ее в RecordSortedList.
Мне кажется что это будет самым компактным (в плане занимаемой памяти) и самым быстрым (сопоставимым с Map-ами по времени доступа) вариантом. Хотя обосновать не смогу... |
|
07.04.2011, 14:02 | #4 |
Модератор
|
|
|
07.04.2011, 14:09 | #5 |
Участник
|
Как развёрното замечено в первом посте, у каждого подхода есть и плюсы и минусы. А следовательно выбор того или иного способа реализации должен производится с учётом специфики конкретной задачи. Что важнее, память, скорость, надёжность, простота, масштабируемость?
Если сравнивать аксапту с другими системами. То я бы отметил, что наличие в аксапте встроенных средств работы с табличными курсорами является ценным преимуществом, от которого не нужно отказываться. |
|
07.04.2011, 14:18 | #6 |
MCTS
|
Контейнер не может содержать в себе классы, т.к. сам не является классом.
Контейнер относится к value-типам (как str, int, real), а не к reference-типам (класс, запись). Следовательно структура контейнера создается в памяти только один раз при его инициализации. Последующие изменения содержимого контейнера реализованы, как создание нового контейнера и копирование значений из старого в новый. Отсюда крайне не рекомендуется использовать решения вроде заполнения контейнера в цикле. Но полезно использовать, например, для передачи параметров между клиентом и сервером.
__________________
Dynamics AX Experience |
|
|
За это сообщение автора поблагодарили: mazzy (2), S.Kuskov (1). |
07.04.2011, 14:45 | #7 |
Участник
|
Цитата:
К тому же, ее в АОТ создавать нужно... На каждый чих не насоздаешься. Но согласен, что можно. Особенно для набора нескольких разнородных параметров. Параметров может быть несколько. В данном случае может быть несколько пар <таблица/поле>. Что должна возвращать функция? Куда? Как работать с этим Цитата:
значит способы 1.2, 1.3 отпадают. из способов с удобным для программиста написанием кода остается только контейнер контейнеров. остальные способы приводят к появлению кучи кода по инициализации начальных данных... ============================ как удобнее, как правильнее хранить статичный набор начальных данных? |
|
07.04.2011, 14:46 | #8 |
Участник
|
Напомню, что я мечтал бы о чем-то подобном - кратко, понятно и легко для восприятия программистом.
|
|
07.04.2011, 14:54 | #9 |
MCTS
|
А макросы не вариант?
__________________
Dynamics AX Experience |
|
07.04.2011, 14:56 | #10 |
Участник
|
имхо тут компромисс между легкостью инициализации и статическим контролем. То что хочет Маззи можно получить комбинацией вот этого тула:
http://www.axaptapedia.com/Generate_...hods_extension И паттерна expression builder. В простых случаях я предпочитаю контейнер контейнеров, расставиив ассерты и документировав. Сложных случаев я пока не делал. |
|
|
За это сообщение автора поблагодарили: mazzy (2). |
07.04.2011, 15:00 | #11 |
Участник
|
Цитата:
Цитата:
Сообщение от belugin
То что хочет Маззи можно получить комбинацией вот этого тула:
http://www.axaptapedia.com/Generate_...hods_extension И паттерна expression builder. Цитата:
Вдруг чего не знаю? |
|
08.04.2011, 01:59 | #12 |
Участник
|
Цитата:
а может вообще не парится с такими начальными данными, а просто делать специализированные методы, в которых статически написано что и с какими полями нужно делать?
Вариант 3.3 Стратегически рано или позно мы будем в С# мире, а тут понятие контейнер чуждо и не особо понятно что с ним делать. Учитывая что ваш код может использоваться сторонними приложениями через разные прокси и обвертки – классы предпочтительней. С другой стороны есть проблемы с производительностью при создании классов в аксапте. В 6-ке лутше – но все равно, не бесплатно. Кроме того классы проще тестировать, проще создать мок обьекты чем с контейнерами, проще читать код (ИМХО) Также проще его поддерживать людям которые приходят с другий областей.
__________________
Thx, Ievgenii Korovin| Dynamics Ax SCM| Microsoft Corp| http://blogs.msdn.com/DynamicsAxSCM/ Последний раз редактировалось Ievgenii; 08.04.2011 в 02:03. |
|
|
За это сообщение автора поблагодарили: mazzy (2). |
08.04.2011, 07:32 | #13 |
Участник
|
Хм...
так то оно так... но ведь есть еще вопросы сериализации/десериализации, передачи клиент/сервер. сейчас pack/unpack работают через контейнер. а как со списками/мапами/сетами? или забить? ============== получается, что в варианте 3.3 надо: 1. создать свой класс-примитив для хранения значений 2. писать что-то многословное вида { storage = new list(Types::Classes); storage.add(new MyPair(...)); ... } 3. поизвращаться с pack/unpack хотя я полностью согласен, что enumerat'ором потом удобно обрабатывать. |
|
08.04.2011, 11:02 | #14 |
Участник
|
выделил вопрос в новую тему а точно ЛЮБОЕ изменение приводит к пересозданию контейнера? даже conpoke?
|
|
08.04.2011, 14:42 | #15 |
Участник
|
Ну, лично я начинал бы инициализацию с контейнера контейнеров. А вот дальше все зависит от того, как предполагается использовать этот статический набор.
Можно ведь следующим шагом перегнать контейнер-контейнеров в нужный тип хранилища. В ту же временную таблицу, например. Имею в виду, нечто вроде такого кода инициализации X++: Container conValue; int nextI; TmpTable tmpTable; ; // Сами данные conValue = [ [Table1, Field1] ,[Table2, Field2] ,[Table3, Field3] ]; // Конвертация данных в необходимое представление for (nextI = 1; nextI <= conLen(conValue); nextI++) { tmpTable.refTableId = conPeek(conPeek(conValue,nextI), 1); tmpTable.refFieldId = conPeek(conPeek(conValue,nextI), 2); tmpTable.doInsert(); } Другими словами просто физически разделяем процесс инициализации данных от их конвертации в удобный для дальнейшего использования вид. |
|
|
За это сообщение автора поблагодарили: mazzy (2). |
10.04.2011, 11:46 | #16 |
Administrator
|
Примеры в коде:
1. \Classes\smmSalesManagementQueries, метод defaultQuery и метод, из которого он вызывается createDefaultQueries В интерфейсе: \CRM\Периодические операции\Управление продажами\Статистика продаж, кнопка Функции - Импорт запроса по умолчанию. В методе defaultQuery прописан весь набор начальных данных, который делится между собой макросами, прописанными в ClassDeclaration этого класса. В результате - каждая итерация цикла вставки в таблицу по сути прописана в коде и каждая запись промаркирована своим макросом. В конечном счете заполняется таблица. Данный пример тянется с версии 3.0 точно. Раньше не знаю. 2. \Data Dictionary\Tables\BITimePeriodsMDX\Methods\importFromAOT и методы importFromXml и fromXml. В интерфейсе: \Администрирование\Настройка\OLAP\Периоды времени, кнопка Импорт-Импорт из АОТ. Здесь начальные данные вытаскиваются из ресурсов (\Resources\BITimePeriodsMDX), в которых был сохранен XML-файл (и кстати, обратно могут быть сохранены туда же). В общем-то идея хранения данных в XML-файле сродни контейнеру контейнеров, просто с использованием другой технологии (в памяти торчит объект xmlDocument - как аналог контейнера контейнеров). Плюс от использования ресурсов - возможность переноса данных (которые прописываются в коде) вместе с приложением. При этом храня сами данные не в коде. Результат импорта из ресурсов записывается в табличку BITimePeriodsMDX Данный пример присутствует начиная с DAX 2009 Пока навскидку еще не приходят примеры.
__________________
Возможно сделать все. Вопрос времени Последний раз редактировалось sukhanchik; 10.04.2011 в 11:52. |
|
|
За это сообщение автора поблагодарили: mazzy (2). |
10.04.2011, 12:27 | #17 |
Administrator
|
Еще примеры вспомнил.
Классы NumberSeqReference* (автоматическое заполнение таблицы на закладке Номерные серии). Классы ReleaseUpdate* (автоматическое формирование списка пакетных заданий для скриптов - обновления версии при прохождении контрольного списка обновления). Суть: есть некий набор (небольшой) параметров, каждый из которых умещается в своем поле таблички (для номерных серий - см метод load, в котором каждый из нас по сути прописывает заполнение этих полей). Дальше выполняется метод create(), который уже "по-умному" дополняет запись и вставляет/обновляет ее по мере необходимости (допустим, мы изменили HelpText в методе load - так несмотря на то, что запись в табличке NumberSequenceReference уже существует - то HelpText будет обновлен). При этом важно - что каждый наследник (в обоих примерах) по сути по-своему заполняет нужную табличку. И проблема типизации отсутствует. А табличка всегда существует в БД и заполняется по мере обращения к ней (открытия формы параметров в каждом модуле или инициализация всех наследников сразу для скриптов обновлений)
__________________
Возможно сделать все. Вопрос времени Последний раз редактировалось sukhanchik; 10.04.2011 в 12:30. |
|
10.04.2011, 13:59 | #18 |
Moderator
|
Я обычно выбираю разновидность варианта 1.3 - класс, содержащий Set/Map/List других классов. То есть, работаю с двумя сущностями - Entity и EntityCollection.
Да, этот подход требует создания двух классов, но он оправдывает себя в долгосрочной перспективе, особенно, если я вижу, что в класс Entity в будущем можно будет добавить методы, работающие с данными класса. Как минимум в классе Entity я добавлю аналог метода toString(), для удобного отображения его содержимого в infolog-е, и методы set()/get(), поставив точку останова на которых другие разработчики смогут контролировать работу с этими классами. Кроме того, в данном варианте, код работающий с этими классами выглядит гораздо понятнее и проще, за счет того, что все низкоуровневые манипуляции с данными (conpeek/conpoke/работу с индексами/проверки) я прячу в методы этих классов, имеющие говорящие имена и сразу понятные разработчику. В будущем иногда бывает полезно создать класс EntityIterator для того, чтобы перебирать элементы коллекции. Кроме того, интерфейс для работы с моей коллекцией в этом случае будет выглядеть схожим образом с интерфейсом стандартных коллекций и их итераторов. |
|
10.04.2011, 14:47 | #19 |
Участник
|
Цитата:
или полностью свое? |
|
10.04.2011, 14:50 | #20 |
Moderator
|
В зависимости от обстоятельств. Но как правило это обычный класс аксапты в ClassDeclaration которого объявлена переменная типа Set, Map или List. В общем то, можно и наследоваться, но как по мне, так такой вариант приводит к слишком большой зависимости между объектами.
|
|