<?xml version="1.0" encoding="UTF-8"?>

<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
	<channel>
		<title>AXForum - Блоги - dech</title>
		<link>//axforum.info/forums/blog.php?u=15255</link>
		<description>Microsoft Dynamics: Axapta, CRM, Navision. Форум, Вопросы и помощь специалистов.</description>
		<language>ru</language>
		<lastBuildDate>Fri, 24 Apr 2026 17:36:25 GMT</lastBuildDate>
		<generator>vBulletin</generator>
		<ttl>15</ttl>
		<image>
			<url>http://axforum.info//img.axforum.info/misc/rss.jpg</url>
			<title>AXForum - Блоги - dech</title>
			<link>//axforum.info/forums/blog.php?u=15255</link>
		</image>
		<item>
			<title>Паттерн Related Table</title>
			<link>//axforum.info/forums/blog.php?b=8266</link>
			<pubDate>Wed, 17 Oct 2018 12:29:50 GMT</pubDate>
			<description><![CDATA[Пожалуй уже всем надоело каждый раз выискивать подходящий метод на таблице для поиска подчиненных (ну или просто связанных) записей. Возьмем к примеру таблицу SalesLine, у которой есть такие методы как salesTable(), inventTable(), inventDim() и так далее...
Т.е. у нас есть некоторый внешний ключ, по которому мы хотим найти связанную запись в таблице, для которой данный ключ является первичным. Чтобы не плодить кучу методов, я решил добавить один новый метод, который будет "лучше старых двух (https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D0%B0%D1%80%D1%8B%D0%B9_%D0%B4%D1%80%D1%83%D0%B3_%D0%BB%D1%83%D1%87%D1%88%D0%B5_%D0%BD%D0%BE%D0%B2%D1%8B%D1%85_%D0%B4%D0%B2%D1%83%D1%85)".
Как лучше к нему обращаться? Нужно,чтобы метод был универсальным, т.е. мог вернуть любую таблицу, какую хотим. Ну и конечно же необходима совместимость с самими методами, которые мы хотим заместить новым. Т.е. нужно добавить возможность выборки для обновления.
Таким образом, напрашиваются следующие варианты:
salesTable  = salesLine.related(tablenum(SalesTable));
custTable   = salesLine.related(tablenum(CustTable), true);
Это, конечно, немного длиннее, чем вызывать конкретно salesLine.salesTable(), однако читабельности это совсем не вредит. Второй плюс, уже более очевидный - это стандартизация кода. Когда видишь такие строчки, тебе четко ясно, что хотел донести разработчик. Третий плюс - нам больше не нужны методы salesTable(), inventDim() и им подобные! У нас есть один метод, который будет работать за всех остальных. Дармоеды нам не нужны!)))
Ну вот и сам метод:
public Common related(TableId _tableId, Boolean _forUpdate = false)
{
    switch (_tableId)
    {
        case tablenum(SalesTable):
            return SalesTable::find(this.SalesId, _forUpdate);

        case tablenum(InventTable):
            return InventTable::find(this.ItemId, _forUpdate);

        case tablenum(InventDim):
            return InventDim::find(this.InventDimId, _forUpdate);
    }

    throw error(strfmt("Relation on %1 to %2 table not implemented yet", tableid2name(this.TableId), tableid2name(_tableId)));
}Проявляется и четвертый плюс: расширяемость. Всегда можно добавить пару строчек кода и использовать единый метод в кастомном коде.

P.S. Спешу заметить, что могут встречаться несколько полей, ссылающихся на одну и ту же таблицу. Например FromInventDimId, ToInventDimId и т.д. По каждому из них хотелось бы искать связанную запись точно таким же способом. Напрашивается добавление параметра по FieldId, однако... лушчее - враг хорошего. Я предлагаю для таких случаев использовать отдельные методы, иначе данный паттерн потеряет свою простоту, а вместе с тем и свою привлекательность.]]></description>
			<content:encoded><![CDATA[<div>Пожалуй уже всем надоело каждый раз выискивать подходящий метод на таблице для поиска подчиненных (ну или просто связанных) записей. Возьмем к примеру таблицу SalesLine, у которой есть такие методы как salesTable(), inventTable(), inventDim() и так далее...<br />
Т.е. у нас есть некоторый внешний ключ, по которому мы хотим найти связанную запись в таблице, для которой данный ключ является первичным. Чтобы не плодить кучу методов, я решил добавить один новый метод, который будет &quot;<a href="https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D0%B0%D1%80%D1%8B%D0%B9_%D0%B4%D1%80%D1%83%D0%B3_%D0%BB%D1%83%D1%87%D1%88%D0%B5_%D0%BD%D0%BE%D0%B2%D1%8B%D1%85_%D0%B4%D0%B2%D1%83%D1%85" target="_blank">лучше старых двух</a>&quot;.<br />
Как лучше к нему обращаться? Нужно,чтобы метод был универсальным, т.е. мог вернуть любую таблицу, какую хотим. Ну и конечно же необходима совместимость с самими методами, которые мы хотим заместить новым. Т.е. нужно добавить возможность выборки для обновления.<br />
Таким образом, напрашиваются следующие варианты:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">salesTable  = salesLine.related(<span style="color: blue">tablenum</span>(SalesTable));
custTable   = salesLine.related(<span style="color: blue">tablenum</span>(CustTable), <span style="color: blue">true</span>);</pre></div>Это, конечно, немного длиннее, чем вызывать конкретно salesLine.salesTable(), однако читабельности это совсем не вредит. Второй плюс, уже более очевидный - это стандартизация кода. Когда видишь такие строчки, тебе четко ясно, что хотел донести разработчик. Третий плюс - нам больше не нужны методы salesTable(), inventDim() и им подобные! У нас есть один метод, который будет работать за всех остальных. Дармоеды нам не нужны!)))<br />
Ну вот и сам метод:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: blue">public</span> Common related(TableId _tableId, Boolean _forUpdate = <span style="color: blue">false</span>)
{
    <span style="color: blue">switch</span> (_tableId)
    {
        <span style="color: blue">case</span> <span style="color: blue">tablenum</span>(SalesTable):
            <span style="color: blue">return</span> SalesTable::find(this.SalesId, _forUpdate);

        <span style="color: blue">case</span> <span style="color: blue">tablenum</span>(InventTable):
            <span style="color: blue">return</span> InventTable::find(this.ItemId, _forUpdate);

        <span style="color: blue">case</span> <span style="color: blue">tablenum</span>(InventDim):
            <span style="color: blue">return</span> InventDim::find(this.InventDimId, _forUpdate);
    }

    <span style="color: blue">throw</span> error(strfmt(<span style="color: red">&quot;Relation on %1 to %2 table not implemented yet&quot;</span>, tableid2name(this.TableId), tableid2name(_tableId)));
}</pre></div>Проявляется и четвертый плюс: расширяемость. Всегда можно добавить пару строчек кода и использовать единый метод в кастомном коде.<br />
<br />
P.S. Спешу заметить, что могут встречаться несколько полей, ссылающихся на одну и ту же таблицу. Например FromInventDimId, ToInventDimId и т.д. По каждому из них хотелось бы искать связанную запись точно таким же способом. Напрашивается добавление параметра по FieldId, однако... лушчее - враг хорошего. Я предлагаю для таких случаев использовать отдельные методы, иначе данный паттерн потеряет свою простоту, а вместе с тем и свою привлекательность.</div>

]]></content:encoded>
			<dc:creator>dech</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8266</guid>
		</item>
		<item>
			<title>Изменяем паттерн construct()</title>
			<link>//axforum.info/forums/blog.php?b=8260</link>
			<pubDate>Mon, 19 Mar 2018 12:45:48 GMT</pubDate>
			<description><![CDATA[Давайте посмотрим, что нам говорит Best Practices. Используйте метод construct() в каждом классе, чтобы можно было его правильно расширять классами-потомками и вызывать через menu items. Что касается таких классов как SalesLineType - говорить нечего. Метод construct() используется чисто как шаблон проектирования Строитель/Builder. Однако, разработчики решили, что этого мало, и стали использовать Enum для запуска конкретного подкласса из какого-либо семейства классов, например SalesFormLetter. Заполняем в menu item свойства EnumTypeParameter и EnumParameter, запускаем его и через Args наш класс получает нужное значение enum. Далее - дело техники, метод construct() создает нужный экземпляр на основе переданного enum-значения: static SalesFormLetter  construct(DocumentStatus    document,
                                  boolean           getParmId = true)
{
    switch(document)
    {
        case DocumentStatus::Confirmation       :   return new SalesFormLetter_Confirm           (getParmId);
        case DocumentStatus::PickingList        :   return SalesFormLetter_PickingList::construct(getParmId);
        case DocumentStatus::PackingSlip        :   return new SalesFormLetter_PackingSlip       (getParmId);
        case DocumentStatus::ProjectPackingSlip :   return new SalesFormLetter_PackingSlipProject(getParmId);
        case DocumentStatus::Invoice            :   return new SalesFormLetter_Invoice           (getParmId);
        case DocumentStatus::ProjectInvoice     :   return new SalesFormLetter_InvoiceProject    (getParmId);

        default : throw error(strfmt("@SYS19306",funcname()));
    }

    throw error(strfmt("@SYS19306",funcname()));
}Итак, каждый раз, когда мы через menu item хотим запустить конкретный подкласс, мы должны следовать букве BP и создавать для каждого нового семейства свой Enum. Я пошел немного другим путём, который немного попроще, да и менее затратный по времени. Всё, что я опишу далее, касается только запуска классов через menu items. При вызове класса в коде лучше создавать свой статический метод construct(), хотя можно извратиться и создавать программно menu item и args. Но это уже перебор. :eek:
ОК, что нам нужно, чтобы упростить создание объектов? Интересный факт, свойство Parameters типа строка используется реже, чем EnumParameter. Я решил через него передавать имя класса для создания нового объекта. Это даст возможность передавать еще и enum для каких-либо дополнительных целей. Я перенёс метод construct() в класс Global, сделав его по существу глобальной функцией. Чтобы он заработал как надо, я добавил следующий код:
public static Object construct(Args _args)
{
    DictClass       dictClass;
    IdentifierName  className;
    ;

    if (!_args)
        throw error(Error::missingParameter(null));

    className = _args.parm() ? _args.parm() : _args.menuItemName();

    dictClass = new DictClass(classname2id(className));

    if (!dictClass)
        throw error(strfmt("Unable to instantiate \"%1\" class", className));

    return dictClass.makeObject();
}Кто использует метки, создайте метку для последнего сообщения.
Далее, для проверки можно создать пару классов, пусть они называются Base и Derived:
class Base 
{
    public static void main(Args _args)
    {
        Base base = construct(_args);
        ;
    
        info(base.getType());
    }

    public ClassDescription getType()
    {
        return "Base";
    }
}class Derived extends Base
{
    public ClassDescription getType()
    {
        return "Derived";
    }
}После того, как классы сделаны, создаем 2 menu items для запуска суперкласса Base. Пусть один называется Base, а второй - Derived. Внимание, оба менюайтема вызывают один и тот же базовый класс. Для менюайтема Derived прописываем имя класса Derived в свойстве Parameters. для менюайтема Base можно ничего не прописывать, если имя класса совпадает с именем менюайтема.
А теперь тест! Запускаем Base, запускаем Derived. Инфолог выдаёт то, что мы и ожидаем. Поздравляю, мы с вами только что уделали Microsoft, который до сих пор указывает в BP использовать статический метод construct(). :D
Однако, для AX2012 и выше этот код уже не актуален. Для запуска батчей теперь используется SysOperation Framework и атрибуты. Так что, если вы всё еще работаете в старой версии аксапты, прошу обкатывать новый паттерн.]]></description>
			<content:encoded><![CDATA[<div>Давайте посмотрим, что нам говорит Best Practices. Используйте метод construct() в каждом классе, чтобы можно было его правильно расширять классами-потомками и вызывать через menu items. Что касается таких классов как SalesLineType - говорить нечего. Метод construct() используется чисто как шаблон проектирования Строитель/Builder. Однако, разработчики решили, что этого мало, и стали использовать Enum для запуска конкретного подкласса из какого-либо семейства классов, например SalesFormLetter. Заполняем в menu item свойства EnumTypeParameter и EnumParameter, запускаем его и через Args наш класс получает нужное значение enum. Далее - дело техники, метод construct() создает нужный экземпляр на основе переданного enum-значения: <div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: blue">static</span> SalesFormLetter  construct(DocumentStatus    document,
                                  boolean           getParmId = <span style="color: blue">true</span>)
{
    <span style="color: blue">switch</span>(document)
    {
        <span style="color: blue">case</span> DocumentStatus::Confirmation       :   <span style="color: blue">return</span> <span style="color: blue">new</span> SalesFormLetter_Confirm           (getParmId);
        <span style="color: blue">case</span> DocumentStatus::PickingList        :   <span style="color: blue">return</span> SalesFormLetter_PickingList::construct(getParmId);
        <span style="color: blue">case</span> DocumentStatus::PackingSlip        :   <span style="color: blue">return</span> <span style="color: blue">new</span> SalesFormLetter_PackingSlip       (getParmId);
        <span style="color: blue">case</span> DocumentStatus::ProjectPackingSlip :   <span style="color: blue">return</span> <span style="color: blue">new</span> SalesFormLetter_PackingSlipProject(getParmId);
        <span style="color: blue">case</span> DocumentStatus::Invoice            :   <span style="color: blue">return</span> <span style="color: blue">new</span> SalesFormLetter_Invoice           (getParmId);
        <span style="color: blue">case</span> DocumentStatus::ProjectInvoice     :   <span style="color: blue">return</span> <span style="color: blue">new</span> SalesFormLetter_InvoiceProject    (getParmId);

        <span style="color: blue">default</span> : <span style="color: blue">throw</span> error(strfmt(<span style="color: red">&quot;@SYS19306&quot;</span>,funcname()));
    }

    <span style="color: blue">throw</span> error(strfmt(<span style="color: red">&quot;@SYS19306&quot;</span>,funcname()));
}</pre></div>Итак, каждый раз, когда мы через menu item хотим запустить конкретный подкласс, мы должны следовать букве BP и создавать для каждого нового семейства свой Enum. Я пошел немного другим путём, который немного попроще, да и менее затратный по времени. Всё, что я опишу далее, касается только запуска классов через menu items. При вызове класса в коде лучше создавать свой статический метод construct(), хотя можно извратиться и создавать программно menu item и args. Но это уже перебор. :eek:<br />
ОК, что нам нужно, чтобы упростить создание объектов? Интересный факт, свойство Parameters типа строка используется реже, чем EnumParameter. Я решил через него передавать имя класса для создания нового объекта. Это даст возможность передавать еще и enum для каких-либо дополнительных целей. Я перенёс метод construct() в класс Global, сделав его по существу глобальной функцией. Чтобы он заработал как надо, я добавил следующий код:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: blue">public</span> <span style="color: blue">static</span> Object construct(Args _args)
{
    DictClass       dictClass;
    IdentifierName  className;
    ;

    <span style="color: blue">if</span> (!_args)
        <span style="color: blue">throw</span> error(Error::missingParameter(<span style="color: blue">null</span>));

    className = _args.parm() ? _args.parm() : _args.menuItemName();

    dictClass = <span style="color: blue">new</span> DictClass(classname2id(className));

    <span style="color: blue">if</span> (!dictClass)
        <span style="color: blue">throw</span> error(strfmt(<span style="color: red">&quot;Unable to instantiate \&quot;%1\&quot; class&quot;</span>, className));

    <span style="color: blue">return</span> dictClass.makeObject();
}</pre></div>Кто использует метки, создайте метку для последнего сообщения.<br />
Далее, для проверки можно создать пару классов, пусть они называются Base и Derived:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: blue">class</span> Base 
{
    <span style="color: blue">public</span> <span style="color: blue">static</span> <span style="color: blue">void</span> main(Args _args)
    {
        Base base = construct(_args);
        ;
    
        info(base.getType());
    }

    <span style="color: blue">public</span> ClassDescription getType()
    {
        <span style="color: blue">return</span> <span style="color: red">&quot;Base&quot;</span>;
    }
}</pre></div><div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: blue">class</span> Derived <span style="color: blue">extends</span> Base
{
    <span style="color: blue">public</span> ClassDescription getType()
    {
        <span style="color: blue">return</span> <span style="color: red">&quot;Derived&quot;</span>;
    }
}</pre></div>После того, как классы сделаны, создаем 2 menu items для запуска суперкласса Base. Пусть один называется Base, а второй - Derived. Внимание, оба менюайтема вызывают один и тот же базовый класс. Для менюайтема Derived прописываем имя класса Derived в свойстве Parameters. для менюайтема Base можно ничего не прописывать, если имя класса совпадает с именем менюайтема.<br />
А теперь тест! Запускаем Base, запускаем Derived. Инфолог выдаёт то, что мы и ожидаем. Поздравляю, мы с вами только что уделали Microsoft, который до сих пор указывает в BP использовать статический метод construct(). :D<br />
Однако, для AX2012 и выше этот код уже не актуален. Для запуска батчей теперь используется SysOperation Framework и атрибуты. Так что, если вы всё еще работаете в старой версии аксапты, прошу обкатывать новый паттерн.</div>

]]></content:encoded>
			<dc:creator>dech</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8260</guid>
		</item>
		<item>
			<title>Как можно обойтись без методов pack/unpack?</title>
			<link>//axforum.info/forums/blog.php?b=8234</link>
			<pubDate>Tue, 13 Dec 2016 14:05:34 GMT</pubDate>
			<description><![CDATA[Если честно, то меня достало каждый раз писать одно и то же. Сидишь перекрываешь методы, которые выучил наизусть... И в каждом новом наследнике RunBase делаешь дублирование кода... Каждый раз. Что говорят по этому поводу Банда Четырех, Кент Бек, ну или Мартин Фаулер? Дублирование - это плохо.
А что делать, если из-за макросов #CurrentVersion/#CurrentList попросту по-другому никак? У нас есть ООП, но макрос со списком полей существует только в конкретном классе.
Я решил попытаться обойти эту проблему, создав промежуточный класс, наследник RunBase.
Пусть он будет тоже абстрактным, как и его родитель.public abstract class DC_RunBase extends RunBase
{
}Для начала попробуем избавиться от *#CurrentVersion*. Пусть это будет статический метод, возвращающий актуальный номер версии. Нужен именно статический, из-за возможности перекрытия метода. И тогда всё поедет. Модификатор *final* в наследнике будет не к месту, т.к. в этом случае вся ответственность возлагается на автора класса-наследника.public static Version version()
{
    return 0;
}Если же мы сделаем статический метод, то он в принципе будет работать как *description()*, извлекая версию из конкретного класса. Тогда нам нужен аналог метода *getDescription()*:public client server static Version getCurrentVersion(ClassId _classNum)
{
    ExecutePermission   casPerm = new ExecutePermission();
    SysDictClass        classObj;
    IdentifierName      staticName = staticmethodstr(DC_RunBase, version);

    if (! _classNum)
        return 0;

    classObj = new SysDictClass(_classNum);

    if (classObj &&
        classObj.hasStaticMethod(staticName))
    {
        casPerm.assert();

        //BP Deviation Documented
        return classObj.callStatic(staticName);
    }

    return 0;
}
Отлично! С версией разобрались. Теперь можно браться за работу над *#CurrentList*. По сути его заменит метод, который будет работать как метод доступа parm*. Его мы реализуем в последнюю очередь. А пока займемся парой занудных методов. :-)
public final container pack()
{
    return [DC_RunBase::getCurrentVersion(classidget(this))] + this.currentList();
}Метод *pack()* выглядит достаточно просто. Можно было бы еще немного упростить, если сделать *currentList()* контейнером в контейнере. Но я решил оставить так для совместимости.
Метод *unpack()* в принципе тоже несложный. Выглядит проще, чем оригинальный RunBase-овский:public final boolean unpack(container _packedClass)
{
    if (RunBase::getVersion(_packedClass) != DC_RunBase::getCurrentVersion(classidget(this)))
        return false;

    this.currentList(condel(_packedClass, 1, 1));

    return true;
}Делаем запрет на перекрытие, чтобы эти методы больше не болтались. В принципе почти готово. Осталось сделать последний штрих - метод которого не хватает - *currentList()*. Именно из-за него класс является абстрактным. Внутрь я решил положить шаблон, как это сделано для pack/unpack:
public abstract container currentList(container _currentList = connull())
{
    #if.never
    if (!prmisdefault(_currentList))
    {
        [#CurrentList] = _currentList;
    }

    return [#CurrentList];
    #endif
}Вот и всё. Продукт готов к употреблению. ;-) Замечу, что все-таки не удалось полностью избавиться от макросов. *#CurrentList* у нас все-таки будет присутствовать. Но цель была не в этом, а в упразднении пары методов pack/unpack.
Давайте сделаем пример класса-наследника, который будет использовать все новые преимущества.
Определим в *classDeclaration()* простой чекбокс:class DC_RunBaseChild extends DC_RunBase
{
    NoYesId     checked;

    DialogField dfChecked;
}Перекроем *dialog()* и *getFromDialog()*. Эти методы делаем как обычно.protected Object dialog(DialogRunbase dialog, boolean forceOnClient)
{
    ;

    dialog = super(dialog, forceOnClient);

    dfChecked = dialog.addFieldValue(typeId(NoYesId), checked, "Почистить зубы");

    return dialog;
}Второй метод. Все стандартно.
public boolean getFromDialog()
{
    boolean ret = super();
    ;

    checked = dfChecked.value();

    return ret;
}Перекроем метод run(), чтобы условно выполнить заданиеpublic void run()
{
    ;

    if (checked)
        info("Зубы блестят как новые");
    else
        warning("Изо рта попахивает");
}Таким же обычным способом пишем точку входа:public static void main(Args _args)
{
    DC_RunBaseChild    runbase = new DC_RunBaseChild();
    ;

    if (runbase.prompt() && runbase.init())
    {
        runbase.run();
    }
}Ну а теперь самое интересное. Пишем два метода, которые делают всю "черную" работу. Первый возвращает номер текущей версии:public static Version version()
{
    return 1;
}Второй строит список полей, который можно как читать, так и записывать.
public container currentList(container _currentList = connull())
{
    #localmacro.CurrentList
        checked
    #endmacro

    if (!prmisdefault(_currentList))
    {
        [#CurrentList] = _currentList;
    }

    return [#CurrentList];
}Компилируем, запускаем, все работает.]]></description>
			<content:encoded><![CDATA[<div>Если честно, то меня достало каждый раз писать одно и то же. Сидишь перекрываешь методы, которые выучил наизусть... И в каждом новом наследнике RunBase делаешь дублирование кода... Каждый раз. Что говорят по этому поводу Банда Четырех, Кент Бек, ну или Мартин Фаулер? Дублирование - это плохо.<br />
А что делать, если из-за макросов #CurrentVersion/#CurrentList попросту по-другому никак? У нас есть ООП, но макрос со списком полей существует только в конкретном классе.<br />
Я решил попытаться обойти эту проблему, создав промежуточный класс, наследник RunBase.<br />
Пусть он будет тоже абстрактным, как и его родитель.<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: blue">public</span> <span style="color: blue">abstract</span> <span style="color: blue">class</span> DC_RunBase <span style="color: blue">extends</span> RunBase
{
}</pre></div>Для начала попробуем избавиться от <b>#CurrentVersion</b>. Пусть это будет статический метод, возвращающий актуальный номер версии. Нужен именно статический, из-за возможности перекрытия метода. И тогда всё поедет. Модификатор <b>final</b> в наследнике будет не к месту, т.к. в этом случае вся ответственность возлагается на автора класса-наследника.<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: blue">public</span> <span style="color: blue">static</span> Version version()
{
    <span style="color: blue">return</span> 0;
}</pre></div>Если же мы сделаем статический метод, то он в принципе будет работать как <b>description()</b>, извлекая версию из конкретного класса. Тогда нам нужен аналог метода <b>getDescription()</b>:<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: blue">public</span> <span style="color: blue">client</span> <span style="color: blue">server</span> <span style="color: blue">static</span> Version getCurrentVersion(ClassId _classNum)
{
    ExecutePermission   casPerm = <span style="color: blue">new</span> ExecutePermission();
    SysDictClass        classObj;
    IdentifierName      staticName = <span style="color: blue">staticmethodstr</span>(DC_RunBase, version);

    <span style="color: blue">if</span> (! _classNum)
        <span style="color: blue">return</span> 0;

    classObj = <span style="color: blue">new</span> SysDictClass(_classNum);

    <span style="color: blue">if</span> (classObj &amp;&amp;
        classObj.hasStaticMethod(staticName))
    {
        casPerm.assert();

        <span style="color: green">//BP Deviation Documented
</span>        <span style="color: blue">return</span> classObj.callStatic(staticName);
    }

    <span style="color: blue">return</span> 0;
}</pre></div>Отлично! С версией разобрались. Теперь можно браться за работу над <b>#CurrentList</b>. По сути его заменит метод, который будет работать как метод доступа parm*. Его мы реализуем в последнюю очередь. А пока займемся парой занудных методов. :-)<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: blue">public</span> <span style="color: blue">final</span> <span style="color: blue">container</span> pack()
{
    <span style="color: blue">return</span> [DC_RunBase::getCurrentVersion(classidget(this))] + this.currentList();
}</pre></div>Метод <b>pack()</b> выглядит достаточно просто. Можно было бы еще немного упростить, если сделать <b>currentList()</b> контейнером в контейнере. Но я решил оставить так для совместимости.<br />
Метод <b>unpack()</b> в принципе тоже несложный. Выглядит проще, чем оригинальный RunBase-овский:<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: blue">public</span> <span style="color: blue">final</span> boolean unpack(<span style="color: blue">container</span> _packedClass)
{
    <span style="color: blue">if</span> (RunBase::getVersion(_packedClass) != DC_RunBase::getCurrentVersion(classidget(this)))
        <span style="color: blue">return</span> <span style="color: blue">false</span>;

    this.currentList(condel(_packedClass, 1, 1));

    <span style="color: blue">return</span> <span style="color: blue">true</span>;
}</pre></div>Делаем запрет на перекрытие, чтобы эти методы больше не болтались. В принципе почти готово. Осталось сделать последний штрих - метод которого не хватает - <b>currentList()</b>. Именно из-за него класс является абстрактным. Внутрь я решил положить шаблон, как это сделано для pack/unpack:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: blue">public</span> <span style="color: blue">abstract</span> <span style="color: blue">container</span> currentList(<span style="color: blue">container</span> _currentList = connull())
{
    #<span style="color: blue">if</span>.never
    <span style="color: blue">if</span> (!prmisdefault(_currentList))
    {
        [#CurrentList] = _currentList;
    }

    <span style="color: blue">return</span> [#CurrentList];
    #endif
}</pre></div>Вот и всё. Продукт готов к употреблению. ;-) Замечу, что все-таки не удалось полностью избавиться от макросов. <b>#CurrentList</b> у нас все-таки будет присутствовать. Но цель была не в этом, а в упразднении пары методов pack/unpack.<br />
Давайте сделаем пример класса-наследника, который будет использовать все новые преимущества.<br />
Определим в <b>classDeclaration()</b> простой чекбокс:<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: blue">class</span> DC_RunBaseChild <span style="color: blue">extends</span> DC_RunBase
{
    NoYesId     checked;

    DialogField dfChecked;
}</pre></div>Перекроем <b>dialog()</b> и <b>getFromDialog()</b>. Эти методы делаем как обычно.<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: blue">protected</span> Object dialog(DialogRunbase dialog, boolean forceOnClient)
{
    ;

    dialog = <span style="color: blue">super</span>(dialog, forceOnClient);

    dfChecked = dialog.addFieldValue(<span style="color: blue">typeId</span>(NoYesId), checked, <span style="color: red">&quot;Почистить зубы&quot;</span>);

    <span style="color: blue">return</span> dialog;
}</pre></div>Второй метод. Все стандартно.<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: blue">public</span> boolean getFromDialog()
{
    boolean ret = <span style="color: blue">super</span>();
    ;

    checked = dfChecked.value();

    <span style="color: blue">return</span> ret;
}</pre></div>Перекроем метод run(), чтобы условно выполнить задание<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: blue">public</span> <span style="color: blue">void</span> run()
{
    ;

    <span style="color: blue">if</span> (checked)
        info(<span style="color: red">&quot;Зубы блестят как новые&quot;</span>);
    <span style="color: blue">else</span>
        warning(<span style="color: red">&quot;Изо рта попахивает&quot;</span>);
}</pre></div>Таким же обычным способом пишем точку входа:<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: blue">public</span> <span style="color: blue">static</span> <span style="color: blue">void</span> main(Args _args)
{
    DC_RunBaseChild    runbase = <span style="color: blue">new</span> DC_RunBaseChild();
    ;

    <span style="color: blue">if</span> (runbase.prompt() &amp;&amp; runbase.init())
    {
        runbase.run();
    }
}</pre></div>Ну а теперь самое интересное. Пишем два метода, которые делают всю &quot;черную&quot; работу. Первый возвращает номер текущей версии:<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: blue">public</span> <span style="color: blue">static</span> Version version()
{
    <span style="color: blue">return</span> 1;
}</pre></div>Второй строит список полей, который можно как читать, так и записывать.<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: blue">public</span> <span style="color: blue">container</span> currentList(<span style="color: blue">container</span> _currentList = connull())
{
    #localmacro.CurrentList
        checked
    #endmacro

    <span style="color: blue">if</span> (!prmisdefault(_currentList))
    {
        [#CurrentList] = _currentList;
    }

    <span style="color: blue">return</span> [#CurrentList];
}</pre></div>Компилируем, запускаем, все работает.</div>

]]></content:encoded>
			<dc:creator>dech</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8234</guid>
		</item>
	</channel>
</rss>
