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