AXForum  
Вернуться   AXForum > Блоги > dech
DAX
Забыли пароль?
Зарегистрироваться Правила Справка Пользователи Сообщения за день Поиск

Оценить эту запись

Как можно обойтись без методов pack/unpack?

Запись от dech размещена 13.12.2016 в 17:05

Если честно, то меня достало каждый раз писать одно и то же. Сидишь перекрываешь методы, которые выучил наизусть... И в каждом новом наследнике RunBase делаешь дублирование кода... Каждый раз. Что говорят по этому поводу Банда Четырех, Кент Бек, ну или Мартин Фаулер? Дублирование - это плохо.
А что делать, если из-за макросов #CurrentVersion/#CurrentList попросту по-другому никак? У нас есть ООП, но макрос со списком полей существует только в конкретном классе.
Я решил попытаться обойти эту проблему, создав промежуточный класс, наследник RunBase.
Пусть он будет тоже абстрактным, как и его родитель.
X++:
public abstract class DC_RunBase extends RunBase
{
}
Для начала попробуем избавиться от #CurrentVersion. Пусть это будет статический метод, возвращающий актуальный номер версии. Нужен именно статический, из-за возможности перекрытия метода. И тогда всё поедет. Модификатор final в наследнике будет не к месту, т.к. в этом случае вся ответственность возлагается на автора класса-наследника.
X++:
public static Version version()
{
    return 0;
}
Если же мы сделаем статический метод, то он в принципе будет работать как description(), извлекая версию из конкретного класса. Тогда нам нужен аналог метода getDescription():
X++:
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*. Его мы реализуем в последнюю очередь. А пока займемся парой занудных методов. :-)
X++:
public final container pack()
{
    return [DC_RunBase::getCurrentVersion(classidget(this))] + this.currentList();
}
Метод pack() выглядит достаточно просто. Можно было бы еще немного упростить, если сделать currentList() контейнером в контейнере. Но я решил оставить так для совместимости.
Метод unpack() в принципе тоже несложный. Выглядит проще, чем оригинальный RunBase-овский:
X++:
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:
X++:
public abstract container currentList(container _currentList = connull())
{
    #if.never
    if (!prmisdefault(_currentList))
    {
        [#CurrentList] = _currentList;
    }

    return [#CurrentList];
    #endif
}
Вот и всё. Продукт готов к употреблению. ;-) Замечу, что все-таки не удалось полностью избавиться от макросов. #CurrentList у нас все-таки будет присутствовать. Но цель была не в этом, а в упразднении пары методов pack/unpack.
Давайте сделаем пример класса-наследника, который будет использовать все новые преимущества.
Определим в classDeclaration() простой чекбокс:
X++:
class DC_RunBaseChild extends DC_RunBase
{
    NoYesId     checked;

    DialogField dfChecked;
}
Перекроем dialog() и getFromDialog(). Эти методы делаем как обычно.
X++:
protected Object dialog(DialogRunbase dialog, boolean forceOnClient)
{
    ;

    dialog = super(dialog, forceOnClient);

    dfChecked = dialog.addFieldValue(typeId(NoYesId), checked, "Почистить зубы");

    return dialog;
}
Второй метод. Все стандартно.
X++:
public boolean getFromDialog()
{
    boolean ret = super();
    ;

    checked = dfChecked.value();

    return ret;
}
Перекроем метод run(), чтобы условно выполнить задание
X++:
public void run()
{
    ;

    if (checked)
        info("Зубы блестят как новые");
    else
        warning("Изо рта попахивает");
}
Таким же обычным способом пишем точку входа:
X++:
public static void main(Args _args)
{
    DC_RunBaseChild    runbase = new DC_RunBaseChild();
    ;

    if (runbase.prompt() && runbase.init())
    {
        runbase.run();
    }
}
Ну а теперь самое интересное. Пишем два метода, которые делают всю "черную" работу. Первый возвращает номер текущей версии:
X++:
public static Version version()
{
    return 1;
}
Второй строит список полей, который можно как читать, так и записывать.
X++:
public container currentList(container _currentList = connull())
{
    #localmacro.CurrentList
        checked
    #endmacro

    if (!prmisdefault(_currentList))
    {
        [#CurrentList] = _currentList;
    }

    return [#CurrentList];
}
Компилируем, запускаем, все работает.
Размещено в Без категории
Просмотров 53873 Комментарии 1
Всего комментариев 1

Комментарии

  1. Старый комментарий
    Аватар для Ruff
    Смущает прямолинейная проверка на несовпадение версий в финальном методе unpack(). Значит если я хочу расширить класс с переносом сохраненных в предыдущей версии значений, то ничего не получится - получу false, так как даже unpack() не смогу перекрыть. Если с минимальными правками, предлагаю:

    - добавить в Ваш currentList() параметр с номером версии:
    X++:
    public abstract container currentList(container _currentList = connull(), int _version = 0)
    - заменить жесткую проверку на передачу параметра:
    X++:
    public final boolean unpack(container _packedClass)
    {
        /*
        if (RunBase::getVersion(_packedClass) != DC_RunBase::getCurrentVersion(classidget(this)))
            return false;
        */
        
        this.currentList(condel(_packedClass, 1, 1), RunBase::getVersion(_packedClass));
    
        return true;
    }
    И теперь в наследнике можно более гибко управлять версиями:
    X++:
    public container currentList(container _currentList = connull(), int _version = 0)
    {
        #localmacro.List_v1
            checked        
        #endmacro
        
        #localmacro.CurrentList
            checked,
            qty
        #endmacro
    
        if (!prmisdefault(_currentList))
        {
            switch(_version)
            {
                case 1:
                    [#List_v1]      = _currentList;
                    break;
                    
                case 2:
                    [#CurrentList]  = _currentList;
                    break;
            }                
        }
    
        return [#CurrentList];
    }
    Запись от Ruff размещена 13.12.2016 в 20:55 Ruff is offline
 


Рейтинг@Mail.ru
Часовой пояс GMT +3, время: 21:01.