Сообщество программистов Autodesk в СНГ

ADN Club => AutoCAD .NET API => Тема начата: Stalso от 24-02-2016, 16:28:46

Название: Dependency Injection и создание классов команд
Отправлено: Stalso от 24-02-2016, 16:28:46
Здравствуйте. Интересует вот какой вопрос. Хотелось бы по правильному организовать написание крупных плагинов. Для этого хочется иметь модульную структуру приложения. То есть, разбить функции плагина на набор сервисов (или подмодулей), которые я регистрирую с помощью какого-нибудь IoC контейнера  в методе Initialize() сборки. Предположим, что у нас в плагине есть три сервиса. Один отвечает за какую-нибудь хитрую подсветку и зуммирование на чертеже и реализует интерфейс IHighlighter, остальные два (Module1, Module2) выполняют какую то работу в командах (то есть, у них есть методы, помеченные атрибутом  [CommandMethod("")]), выбирая элементы чертежа и подвечивая их с помощью объекта класса, реализующего IHighlighter. Хотелось бы иметь возможность регистрировать их и проводить необходимую инициализацию:

Код - C# [Выбрать]
  1.         public class MISAEApp : IExtensionApplication
  2.     {
  3.         public void Initialize()
  4.         {
  5.              var container = new SomeServiceContainer();                
  6.              container.Register<IHighlighter,MyHighlighter>();
  7.              container.Register<Module1>();
  8.         }
  9.  
  10.  
  11.         public void Terminate()
  12.         {
  13.            
  14.         }
  15.     }
  16.  

Первый вопрос заключается в том, что хотелось бы иметь возможность указать автокаду не регистрировать команды Module2,не смотря на то, что у него есть методы, помеченные атрибутом  [CommandMethod("")] , если он не подключен мною в методе Initialize(). На сегодняшний день я знаю только один способ сделать это. Пометить нужные мне классы команд атрибутом CommandClass. Но мне кажется , что это костыль. Ведь если я хочу по какой то причине выключить модуль, мне надо лезть в код класса команд и убирать этот аттрибут. А потом, если я хочу включить модуль, то, несмотря на мои настройки инициализации мне надо слазить в этот класс и пометить его этим аттрибутом. 

Второй вопрос сложнее . Хотелось бы привести пример желаемого и не желаемого кода Module1 :

Код - C# [Выбрать]
  1.         // Круто
  2.         public class Module1
  3.         {
  4.                 IHighlighter Highlighter;
  5.                
  6.                 public ModuleCommands(IHighlighter highlighter)
  7.                 {
  8.                         Highlighter  = highlighter;
  9.                 }
  10.                
  11.                 [CommandMethod("cmd")]
  12.                 public void MyCmd()
  13.                 {
  14.                         var res = doSomeWork();
  15.                         Highlighter.Show(res);
  16.                 }
  17.                 private object doSomeWork()
  18.                 {
  19.                         // dosomework;
  20.                 }
  21.         }
  22.  
  23.  
  24.         // Не круто
  25.         public class Module1
  26.         {
  27.                 IHighlighter Highlighter
  28.                 {
  29.                         get
  30.                         {
  31.                                 return SomeServiceContainer.GetInstance<IHighlighter>();
  32.                         }
  33.                 }      
  34.                
  35.                 [CommandMethod("cmd")]
  36.                 public void MyCmd()
  37.                 {
  38.                         var res = doSomeWork();
  39.                         Highlighter.Show(res);
  40.                 }
  41.                 private object doSomeWork()
  42.                 {
  43.                         // dosomework;
  44.                 }
  45.         }
  46.  

Второй вариант меня не очень устраивает, поскольку это прямой пример антипаттерна ServiceLocator. Хочу инжектировать зависимости в конструкторе. Тобишь, иметь какую нибудь фабрику, которая при вызове команды создает нужный класс команд. Но у меня с этим две проблемы:


Я считаю, что если решить этот вопрос (если возможно) и сделать из классов команд подобие контроллеров в ASP.Net MVC (если кто знаком с технологией), то можно сделать разработку плагинов понятнее и улучшить тестируемость самих плагинов.
Название: Re: Dependency Injection и создание классов команд
Отправлено: Александр Ривилис от 24-02-2016, 18:35:45
Из всего, что ты написал, я понял, что тебе нужно:
1) Иметь возможность динамически регистрировать команды.
2) Иметь возможность динамически удалять зарегистрированные команды.
В описанном и документированном AutoCAD .NET API этой возможности нет. Всё построено на атрибутах CommandClassAttribute, и команды регистрируются при загрузке сборки в AutoCAD.
Однако, есть недокументированные методы:
Код - C# [Выбрать]
  1. // Autodesk.AutoCAD.Internal.Utils
  2. public unsafe static void AddCommand(string cmdGroupName, string cmdGlobalName, string cmdLocalName, CommandFlags cmdFlags, CommandCallback func);
  3. public unsafe static void RemoveCommand(string cmdGroupName, string cmdGlobalName);
С их помощью (аналогично тому, как это делается в ObjectARX) можно динамически регистрировать и удалять команды.
Название: Re: Dependency Injection и создание классов команд
Отправлено: Александр Ривилис от 24-02-2016, 18:58:25
Код - C# [Выбрать]
  1. using Autodesk.AutoCAD.Runtime;
  2. using Autodesk.AutoCAD.ApplicationServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Internal;
  5.  
  6. // This line is not mandatory, but improves loading performances
  7. [assembly: ExtensionApplication(typeof(CommandUtils.MyPlugin))]
  8.  
  9. namespace CommandUtils
  10. {
  11.   public class MyPlugin : IExtensionApplication
  12.   {
  13.     void IExtensionApplication.Initialize()
  14.     {
  15.       Utils.AddCommand("RIVILIS", "TEST1", "ТЕСТ1", CommandFlags.Modal, CommandHandler1);
  16.       Utils.AddCommand("RIVILIS", "TEST2", "ТЕСТ2", CommandFlags.Modal, CommandHandler2);
  17.     }
  18.  
  19.     void IExtensionApplication.Terminate()
  20.     {
  21.       Utils.RemoveCommand("RIVILIS", "TEST1");
  22.       Utils.RemoveCommand("RIVILIS", "TEST2");
  23.     }
  24.     public void CommandHandler1()
  25.     {
  26.       Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  27.       ed.WriteMessage("\nЗапущена команда ТЕСТ1");
  28.     }
  29.     public void CommandHandler2()
  30.     {
  31.       Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  32.       ed.WriteMessage("\nЗапущена команда ТЕСТ2");
  33.     }
  34.   }
  35. }


Название: Re: Dependency Injection и создание классов команд
Отправлено: Дима_ от 24-02-2016, 19:04:33
У меня dll'ки поделены на "функциональные" группы, все они загружаются через "сборку-загрузчик" при старте автокада, которая сверяет версию в "репризитории" БД и др. настройки "доступа" и уже в зависимости от полученных параметров загружает необходимое просто через Assembly.LoadFrom(...), сделано это правда больше для автоматического обновления пользователей.
Название: Re: Dependency Injection и создание классов команд
Отправлено: Андрей Бушман от 24-02-2016, 20:54:28
У меня такое ощущение, что автор темы применяя слова "сервисы", "контейнеры", "модули" и "подмодули", делает это интуитивно, вкладывая в них смысл, понятный только ему.

Цитировать
Первый вопрос заключается в том, что хотелось бы иметь возможность указать автокаду не регистрировать команды Module2,не смотря на то, что у него есть методы, помеченные атрибутом  [CommandMethod("")] , если он не подключен мною в методе Initialize(). На сегодняшний день я знаю только один способ сделать это. Пометить нужные мне классы команд атрибутом CommandClass.
Какая-то муть... Обозначенный атрибут как раз указывает на класс, в котором определены команды. Отсутствие атрибута на конечный результат не влияет - акад всё равно найдёт команды. Коим образом он указывает НЕ регистрировать команду?

Цитировать
Ведь если я хочу по какой то причине выключить модуль, мне надо лезть в код класса команд и убирать этот аттрибут. А потом, если я хочу включить модуль, то, несмотря на мои настройки инициализации мне надо слазить в этот класс и пометить его этим аттрибутом. 
Очередной фрагмент, который сложно уличить в ясности. Что подразумевается под "включить\выключить модуль"? Что вообще подразумевается под словом "модуль"? Сборка что ли?

Цитировать
Второй вопрос сложнее .
И снова без стакана не разобраться в сути вопроса... Ты не знаешь как работает паттерн Factory? Google забанили? :) См. здесь (http://adn-cis.org/forum/index.php?topic=400.0) п.17.
Название: Re: Dependency Injection и создание классов команд
Отправлено: Привалов Дмитрий от 25-02-2016, 09:49:41
Ты не знаешь как работает паттерн Factory?
Возможно и знает, но применяет не там где нужно.

То есть, разбить функции плагина на набор сервисов (или подмодулей), которые я регистрирую с помощью какого-нибудь IoC контейнера  в методе Initialize() сборки.

Непонятно зачем такая "гибкость" загрузки сервисов/подмодулей, регистрации команд.?
В теме смешаны вопросы архитектуры приложения и вопросы низкоуровневой реализации.
Стоит начать с архитектуры приложения.

Автору надо разобраться с вопросами:
один загрузчик в виде dll, который управляет загрузкой других модулей - dll?
Количество dll заранее не известно?
Как загрузчик должен узнавать о новых модулях? Каждый раз перекомпилировать загрузчик, изменяя список dll или сканировать папку и подгружать все dll, или информация о модулях хранится во внешнем файле?  Должен ли пользователь выбирать какие плагины загружать?

Действительно ли нужно загружать по выбору? Может проще загрузить все разом? (В NET обычно все подгружается и не выгружается до закрытия автокада.)

После общих вопросов уже можно приступать к реализации, коду и т.д.
Название: Re: Dependency Injection и создание классов команд
Отправлено: Александр Пекшев aka Modis от 25-02-2016, 10:05:12
Ага! Мою идею реализовывают )))) Уже реализованную. Конечно не так "заумно" (я имею ввиду все эти страшные и незнакомые слова)
Лично я делал примерно так:
1. Есть dll, которая грузится в автокад и которая управляет загрузкой плагинов. Не забываем, что выгружать их нельзя. Достаточно Assembly.LoadFrom()
2. Есть программа (вне автокада), которая является неким сервисом по настройке - там пользователь включает/отключает плагины
3. Ну и, соответственно, есть некий файл конфигурации (достаточно обычного xml) с которым работают оба товарища из п.1,2 и в котором прописаны все настройки и предпочтения пользователя

И нечего мудрить - все должно быть просто, если нет иной необходимости ;)
Название: Re: Dependency Injection и создание классов команд
Отправлено: Дима_ от 25-02-2016, 10:30:13
Ну и, соответственно, есть некий файл конфигурации (достаточно обычного xml) с которым работают оба товарища из п.1,2
Вот от XML я в итоге полностью ушел (не считая экспорта для управляющих ЧПУ которые используют его) - в рамках предприятия ИХМО все удобней и практичней завязать на СУБД - минусов я не нашел (ну если только сеть упадет - но все равно все остальное без сети и БД так-же работать не будет).
Название: Re: Dependency Injection и создание классов команд
Отправлено: Александр Пекшев aka Modis от 25-02-2016, 10:36:52
в рамках предприятия ИХМО все удобней и практичней завязать на СУБД
Ну я же говорю - нечего мудрить, если нет иной необходимости. В моем случае СУБД ну вот просто ни к чему) Достаточно обычного xml
Все зависит от функциональности - где-то достаточно xml, где-то лучше привязать базу данных...
Название: Re: Dependency Injection и создание классов команд
Отправлено: Stalso от 25-02-2016, 10:52:05
Какая-то муть... Обозначенный атрибут как раз указывает на класс, в котором определены команды. Отсутствие атрибута на конечный результат не влияет - акад всё равно найдёт команды. Коим образом он указывает НЕ регистрировать команду?

Гм. Так я вроде пояснил. Извините, если непонятно, ни разу не писатель. Речь идет об одной сборке. Я бы хотел иметь возможность самостоятельно управлять процессом регистрации команд ОДНОЙ сборки (Александр Ривилис показал, как это делать). Представьте, у меня в плагине есть класс команд, который выполняет определенные функции. В какой то момент времени разработки я хочу , чтобы эти команды не определялись автокадом. Например, если я хочу определить другой класс команд, который содержит такие же команды, но делает все немножко по другому. Для того, чтобы не загружались команды первого класса мне надо - либо помечать нужные мне классы команд атрибутом CommandClass (а из ненужных, соотвественно, его поубирать, тогда команды ненужных классов не будут загружены), либо выпилить первый класс из сборки, либо у первого класса закомментить все CommandMethod атрибуты. Мне кажется это неудобным. Хотелось бы иметь возможность где-то в одном месте плагина указывать, какие конкретно классы команд мне нужны.

И снова без стакана не разобраться в сути вопроса... Ты не знаешь как работает паттерн Factory? Google забанили?  См. здесь п.17.

А вот тут я вас уже не понял. Интересно, и как это я его должен применить, если я не имею возможности создавать классы команд? Я четко обрисовал требования. Хочу внедрять зависимости в классы команд. Как я могу написать фабрику и инжектировать в класс команд нужные зависимости, если автокад сам классы команд создает, причем один раз? Вот вам пример в MVC. Система принимает запрос, с помощью маршрутизации определяет, какой контроллер нужно выбрать (контроллер MVC вполне можно представить как аналог класса команд а автокаде). Далее вступает в дело переопределяемая  фабрика, которая управляет процессом создания экземпляра контроллера, в конструктор которого я могу инжектировать все, что ему надо, после выполнения запроса и возврата результата контроллер уничтожается. В автокаде я такого не знаю. Есть мысль сделать небольшой костыль. Навесить CommandStartReactor. В нем ловить нужные мне команды и пересоздавать с помощью фабрики класс команд, внедряя в него все, что мне нужно. Но я пока не знаю, прокатит ли это. Тут несколько узких мест которые мне нужно проверить.


Собственно для того, чтобы сделать то, что я хочу мне нужно:


С огромным уважением , Андрей, если Вы (или кто-либо еще) поможете с двумя последними вопросами, я реализую мою идею и пришлю Вам лично, чтобы вы ее оценили. Суть идеи в том, чтобы реализовать инжектирование зависимостей в классы команд (которые я обозвал модулями, что не совсем корректно, согласен) и некое подобие автокадовского OWIN (это второй этап), в котором вместо url выступают команды автокада.

Название: Re: Dependency Injection и создание классов команд
Отправлено: Привалов Дмитрий от 25-02-2016, 11:52:22
Иметь возможность сопоставить где-нибудь в реакторе команду и класс, в котором она определена.
После сопоставления, иметь возможность создать САМОМУ экземпляр класса команд передавая в конструктор все  необходимые зависимости, которые я извлекаю из IoC контейнера.

Не слишком ли сложно? В автокаде обычно немного другие подходы, чем в ASP.Net.
Дело в том, что не стоит рассматривать команду автокада как класс или объект, который стоит генерировать автоматически. Автокад хранит список команд  "имя команды"-"метод". И расчитан этот механизм больше на расширение списка команд, которые знает автокад и может вызывать пользователь. К примеру создаешь кнопку и вешаешь вызов команды автокада. Для расширения функционала не обязательно создавать команды на лету.

Возможные варианты:
список из 10 команд и вызов их с параметрами.
Или как вариант вызов 10 команд, но функционал их зависит от настроек в другом месте и это тоже возможно.
Если создаешь свой интерфейс WPF/WinForms то можешь привязываться к своему функционалу без регистрации команд. Команды понадобяться только для вызова формы.
Можно подписаться на события автокада и опять же не прибегать к созданию команд.
Возможны другие сценарии без создания/переопределения команд.

Т.е. не стоит создавать/переопределять много команд автокада, и привязываться к ним, врятли это правильное решение.
Это может вызвать нестабильность или замедление работы, т.к. механизм регистрации команд в автокаде не факт что расчитан на такой "спам регистраций".
Название: Re: Dependency Injection и создание классов команд
Отправлено: Stalso от 25-02-2016, 12:14:18
Не слишком ли сложно? В автокаде обычно немного другие подходы, чем в ASP.Net.
Дело в том, что не стоит рассматривать команду автокада как класс или объект, который стоит генерировать автоматически. Автокад хранит список команд  "имя команды"-"метод". И расчитан этот механизм больше на расширение списка команд, которые знает автокад и может вызывать пользователь. К примеру создаешь кнопку и вешаешь вызов команды автокада. Для расширения функционала не обязательно создавать команды на лету.

Но что делать, если у меня в коде команды используются другие компоненты моего плагина. В стартовом посте я привел пример IHighlighter .  Я  хочу уйти от того, чтобы обращаться с статическому ServiceLocator для получения экземпляра IHighlighter
Название: Re: Dependency Injection и создание классов команд
Отправлено: Привалов Дмитрий от 25-02-2016, 13:15:20
Но что делать, если у меня в коде команды используются другие компоненты моего плагина. В стартовом посте я привел пример IHighlighter .  Я  хочу уйти от того, чтобы обращаться с статическому ServiceLocator для получения экземпляра IHighlighter

..так мысли вслух
Ну ок есть статический репозиторий и каковы его функции?
Подгрузили в него список команд. ....дальше оповестили автокад?
И по ходу программы этот список должен меняться и автокад получать другой список команд или переопределенных команд.
А как такие команды должны вообще вызываться, интерфейс должен также меняться по ходу программы?
Из командной строки вызывать изменяемый список команд как-то сомнительно.
Нужен интерфейс, подстраивающийся под изменение команд?
Интерфейс автокада или собственный?
Если собственный, то не нужно регистрировать команды автокада.
Если интерфейс автокада то возможно, но зачем менять список команд? Можно делать неизменный список команд при загрузке и менять интерфейс отображая нужный в данный момент функционал.
Если нужно другое поведение команды, то можно использовать параметры передаваемые команде, либо связь с статическим классом настроек.

Может вообще отказаться от переопределения команд автокада и ввести к примеру промежуточный класс Action, Modul или какнибудь еще и в нем хранить экземпляр класса, который выполняет определенные действия.

..это все к чему, просто сомнения, что нужно именно оперировать списком команд автокада, меняя его по ходу программы.
Название: Re: Dependency Injection и создание классов команд
Отправлено: Stalso от 25-02-2016, 14:08:23
Решил вопрос посоветовавшись с ребятами из команды aspnet в гиттере. Оформлю это как статью и опубликую здесь, может кому будет полезно. Практически полностью содрал их подход. Восхищают меня такие ребята. Ни строчки кода в автокаде не написали, а поди ж ты, рабочие штуки советуют.)) Спасибо большое Александру Ривилису за подсказку с командами.
Название: Re: Dependency Injection и создание классов команд
Отправлено: Александр Ривилис от 25-02-2016, 14:34:40
Как по имени команды узнать какому классу она принадлежит?
Если это касается команд из твоей сборки, то сам и храни эту информацию. В ObjectARX эту информацию можно получить при помощи AcEdCommandStack::lookupCmd и AcEdCommandStack::lookupCmd2. В AutoCAD .NET API аналогов нет (ни документированных, ни недокументированных).
Куда денется старый экземпляр класса команд?
Никуда не денется, но если одноименная команда зарегистрирована позже, то будет работать именно её обработчик.
Можно воспользоваться из того же класса методами:
Код - C# [Выбрать]
  1. // Команда из ядра AutoCAD
  2. // Autodesk.AutoCAD.Internal.Utils
  3. [return: MarshalAs(UnmanagedType.U1)]
  4. public unsafe static bool IsCoreCommand(string name);
  5. // Команда уже зарегистрирована
  6. // Autodesk.AutoCAD.Internal.Utils
  7. [return: MarshalAs(UnmanagedType.U1)]
  8. public unsafe static bool IsCommandDefined(string cmdName);
  9. // Имя команды уже используется
  10. // Autodesk.AutoCAD.Internal.Utils
  11. public unsafe static CommandTypeFlags IsCommandNameInUse(string name);
Оформлю это как статью и опубликую здесь, может кому будет полезно.
Для этого у нас есть раздел: http://adn-cis.org/forum/index.php?board=19.0
А ссылку на тему со статьёй можешь положить сюда.
Название: Re: Dependency Injection и создание классов команд
Отправлено: Андрей Бушман от 25-02-2016, 15:17:20
Представьте, у меня в плагине есть класс команд, который выполняет определенные функции. В какой то момент времени разработки я хочу , чтобы эти команды не определялись автокадом. Например, если я хочу определить другой класс команд, который содержит такие же команды, но делает все немножко по другому.
А для чего это всё? Если подразумевается два разных поведения сборки: trial версия и полноценная, то почему просто не создавать два варианта сборки на основе одного и того же набора исходников, получая разный результат за счёт использования в коде директив препроцессора?

Или это нужно для чего-то другого? Если так, то хотелось бы пример.
Название: Re: Dependency Injection и создание классов команд
Отправлено: Андрей Бушман от 25-02-2016, 15:27:32
Если уж всё же очень нужно динамически подменять код, вызываемый командой, то можно просто создать словарь, ключами которого являются имена команд, а значениями - делегаты, указывающие на код методов, которые должны вызываться при запуске команды.

В коде самой команды всего одна строка - вызов метода, на который указывает делегат, соответствующий имени текущей команды (извлекается из словаря).

В любой момент времени делегату, хранящемуся в записи словаря, можно назначить новое значение (т.е. указать на др. метод). В результате следующий вызов команды выполнит код нового метода. Т.о. функционал команды меняется динамически.

Однако я пока не вижу особой практической пользы всего этого.
Название: Re: Dependency Injection и создание классов команд
Отправлено: Stalso от 26-02-2016, 10:48:15
А для чего это всё? Если подразумевается два разных поведения сборки: trial версия и полноценная, то почему просто не создавать два варианта сборки на основе одного и того же набора исходников, получая разный результат за счёт использования в коде директив препроцессора?

Или это нужно для чего-то другого? Если так, то хотелось бы пример.

Так затем, чтоб можно было использовать паттерн DependencyInjection. А хороший тон в его использование - инжектирование зависимостей в конструкторе.  http://ru.stackoverflow.com/questions/461814/%D0%97%D0%B0%D1%87%D0%B5%D0%BC-%D0%BD%D1%83%D0%B6%D0%B5%D0%BD-dependency-injection , http://stackoverflow.com/questions/14301389/why-does-one-use-dependency-injection . Вот еще хорошая статья http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/ .  По своему роду деятельности я программирую не только под Autodesk но и под web (мы собственно и занимаемся тем, что активно интегрируем веб сервисы в продукцию Autodesk, при этом привнося автоматизацию некоторых сфер деятельности). Там (да и не только там) этот паттерн используется повсеместно (AngularJs построен не нем полностью, asp net MVC, да много чего).  Так вот, без использования нормального паттерна проектирования легко валиться любой крупный проект. AutoCAD .Net API, в силу своей специфики, имеет ряд антипаттернов. На них не обращаешь внимание, если пишешь мелкие, независимые друг от друга плагины. Но вот, я столкнулся с необходимостью проектирования и написания достаточно крупного плагина. И я хочу применить в нем хотя бы некоторые общепринятые техники . Эта конкретная техника позволяет мне иметь систему из слабосвязанных, взаимозаменямых, тестируемых элементов, которые сильно завязаны только на само API, а не на друг друга.  Причем если потратить немного времени, что можно и для самого API небольшенькую обертку написать, чтобы не использовать его напрямую.

Как я уже сказал , я нашел решение проблемы. Оформлю это в статью.



Название: Re: Dependency Injection и создание классов команд
Отправлено: Андрей Бушман от 26-02-2016, 11:29:01
Как я уже сказал , я нашел решение проблемы. Оформлю это в статью.
Чем не устроил вариант решения обозначенный мною выше?
Название: Re: Dependency Injection и создание классов команд
Отправлено: Привалов Дмитрий от 26-02-2016, 11:51:38
И я хочу применить в нем хотя бы некоторые общепринятые техники . Эта конкретная техника позволяет мне иметь систему из слабосвязанных, взаимозаменямых, тестируемых элементов, которые сильно завязаны только на само API, а не на друг друга.
Суть понятна. Непонятно зачем регистрировать для каждого модуля команду в автокаде, когда можно напрямую вызвать класс команды.
Название: Re: Dependency Injection и создание классов команд
Отправлено: Андрей Бушман от 26-02-2016, 11:54:17
Всякая оптимизация полезна, когда она применяется к месту и в меру. Оптимизировать что-либо можно до бесконечности - тут главное вовремя остановиться. ИМХО.
Название: Re: Dependency Injection и создание классов команд
Отправлено: Stalso от 26-02-2016, 12:23:59
Чем не устроил вариант решения обозначенный мною выше?

Если вы про ваш вариант со словарем, то если я правильно понял,  вы предлагаете в классе плагина держать словарь, у которого ключ - имя команды, а значение делегат, который является методом нужного мне сервиса, который я вызываю в коде команды. Так это тогда мне надо делать в сервисе статический метод, либо заранее один раз создать экземпляр сервиса, содержащего метод. А если мне такие синглтоны не надо? Если мне нужно чтоб экземпляр какой либо зависимости создавался для каждой команды по новой? Вобщем, как оформлю свой вариант, тогда его и оцените и сами для себя решите. Может великий и ужасны Андрей Бушман заюзает мой подход)) Это прямо честь)
Название: Re: Dependency Injection и создание классов команд
Отправлено: Андрей Бушман от 26-02-2016, 13:16:07
Может великий и ужасны Андрей Бушман заюзает мой подход
"Великий и ужасны" здесь А.Н. Ривилис. Я белый и пушистый. Насчёт "заюзает" - это вряд ли, т.к. пока не вижу пользы от этого.

Вот пример кода, о котором я писал выше:

Код - C# [Выбрать]
  1. /* Commands.cs
  2.  * © Andrey Bushman, 2016
  3.  * The example of the commands behaviour changing.
  4.  */
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Linq;
  8. using System.Text;
  9. using System.Reflection;
  10.  
  11. #if AUTOCAD
  12. using Autodesk.AutoCAD.Runtime;
  13. using Autodesk.AutoCAD.ApplicationServices;
  14. using cad = Autodesk.AutoCAD.ApplicationServices.Application;
  15. using Autodesk.AutoCAD.DatabaseServices;
  16. using Autodesk.AutoCAD.EditorInput;
  17. #endif
  18.  
  19. [assembly: CommandClass(typeof(Bushman.CAD.Sandbox.Commands))]
  20.  
  21. namespace Bushman.CAD.Sandbox {
  22.  
  23.     // ************************************************************************
  24.  
  25.     static class Tools {
  26.         public static void Do(MethodInfo mi, Dictionary<string, Action> dict) {
  27.             if (null == dict) {
  28.                 throw new ArgumentNullException("dict");
  29.             }
  30.  
  31.             string key = GetKey(mi);
  32.  
  33.             if (dict.ContainsKey(key) && null != dict[key]) {
  34.                 dict[key]();
  35.             }
  36.             else {
  37.                 String msg = String.Format("Either the dictionary hasn't '{0}' " +
  38.                     "key or the record's value is NULL.", key);
  39.                 throw new ArgumentException(msg);
  40.             }
  41.         }
  42.  
  43.         public static string GetKey(MethodInfo mi) {
  44.             if (null == mi) {
  45.                 throw new ArgumentNullException("mi");
  46.             }
  47.  
  48.             CommandMethodAttribute att = mi.GetCustomAttributes(
  49.                 typeof(CommandMethodAttribute), false).FirstOrDefault()
  50.                 as CommandMethodAttribute;
  51.  
  52.             if (null == att) {
  53.                 String msg = String.Format("Method '{0}' hasn't " +
  54.                     "'CommandMethod' attribute.", mi.Name);
  55.                 throw new ArgumentException(msg);
  56.             }
  57.  
  58.             string format = null == att.GroupName ||
  59.                 att.GroupName.Trim() == String.Empty ? "{1}" : "{0}.{1}";
  60.             string key = String.Format(format, att.GroupName.Trim().ToLower(),
  61.                 att.GlobalName.Trim().ToLower());
  62.             return key;
  63.         }
  64.     }
  65.  
  66.     // ************************************************************************
  67.     sealed class Operations {
  68.         public static void Operation_01() {
  69.             MethodInfo mi = (MethodInfo)MethodBase.GetCurrentMethod();
  70.             WriteHelloMessage(mi);
  71.         }
  72.  
  73.         public static void Operation_02() {
  74.             MethodInfo mi = (MethodInfo)MethodBase.GetCurrentMethod();
  75.             WriteHelloMessage(mi);
  76.         }
  77.  
  78.         public static void Operation_03() {
  79.             MethodInfo mi = (MethodInfo)MethodBase.GetCurrentMethod();
  80.             WriteHelloMessage(mi);
  81.         }
  82.  
  83.         public static void Operation_04() {
  84.             MethodInfo mi = (MethodInfo)MethodBase.GetCurrentMethod();
  85.             WriteHelloMessage(mi);
  86.         }
  87.  
  88.         private static void WriteHelloMessage(MethodInfo mi) {
  89.             if (null == mi) {
  90.                 throw new ArgumentNullException("mi");
  91.             }
  92.  
  93.             Document doc = cad.DocumentManager.MdiActiveDocument;
  94.             if (null == doc) {
  95.                 return;
  96.             }
  97.  
  98.             Editor ed = doc.Editor;
  99.             ed.WriteMessage("Hello from {0}!\n", mi.Name);
  100.         }
  101.     }
  102.  
  103.     // ************************************************************************
  104.     public sealed class Commands {
  105.  
  106.         const string groupName = "bushman";
  107.         static Dictionary<string, Action> dict = new Dictionary<string,
  108.             Action>();
  109.  
  110.         static Commands() {
  111.             if (dict.Count == 0) {
  112.                 dict.Add(groupName + ".test1", Operations.Operation_01);
  113.                 dict.Add(groupName + ".test2", Operations.Operation_02);
  114.             }
  115.         }
  116.  
  117.         [CommandMethod(groupName, "Test1", CommandFlags.Modal)]
  118.         public static void Test1() {
  119.             MethodInfo mi = (MethodInfo)MethodBase.GetCurrentMethod();
  120.             Tools.Do(mi, dict);
  121.         }
  122.  
  123.         [CommandMethod(groupName, "Test2", CommandFlags.Modal)]
  124.         public static void Test2() {
  125.             MethodInfo mi = (MethodInfo)MethodBase.GetCurrentMethod();
  126.             Tools.Do(mi, dict);
  127.         }
  128.  
  129.         [CommandMethod(groupName, "ChangeCmd", CommandFlags.Modal)]
  130.         public void ChangeCmd() {
  131.             string key = "bushman.test1";
  132.             dict[key] = dict[key] == Operations.Operation_01 ?
  133.                 (Action)Operations.Operation_03 : Operations.Operation_01;
  134.             key = "bushman.test2";
  135.             dict[key] = dict[key] == Operations.Operation_02 ?
  136.                 (Action)Operations.Operation_04 : Operations.Operation_02;
  137.         }
  138.     }
  139. }

В статическом конструкторе Commands можно написать автоматическое заполнение словаря на основе получения списка всех доступных команд с автоматическим назначением им соответствующих значений. Но я поленился это делать, т.к. для "Hello World" сойдёт и обозначенный выше вариант.

Вот результат работы кода: