Dependency Injection и создание классов команд

Автор Тема: Dependency Injection и создание классов команд  (Прочитано 15070 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Оффлайн Андрей Бушман

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Представьте, у меня в плагине есть класс команд, который выполняет определенные функции. В какой то момент времени разработки я хочу , чтобы эти команды не определялись автокадом. Например, если я хочу определить другой класс команд, который содержит такие же команды, но делает все немножко по другому.
А для чего это всё? Если подразумевается два разных поведения сборки: trial версия и полноценная, то почему просто не создавать два варианта сборки на основе одного и того же набора исходников, получая разный результат за счёт использования в коде директив препроцессора?

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

Оффлайн Андрей Бушман

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Если уж всё же очень нужно динамически подменять код, вызываемый командой, то можно просто создать словарь, ключами которого являются имена команд, а значениями - делегаты, указывающие на код методов, которые должны вызываться при запуске команды.

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

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

Однако я пока не вижу особой практической пользы всего этого.

Оффлайн StalsoАвтор темы

  • ADN OPEN
  • Сообщений: 30
  • Карма: 0
А для чего это всё? Если подразумевается два разных поведения сборки: 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 небольшенькую обертку написать, чтобы не использовать его напрямую.

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




Оффлайн Андрей Бушман

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Как я уже сказал , я нашел решение проблемы. Оформлю это в статью.
Чем не устроил вариант решения обозначенный мною выше?

Оффлайн Привалов Дмитрий

  • ADN Club
  • *****
  • Сообщений: 546
  • Карма: 119
И я хочу применить в нем хотя бы некоторые общепринятые техники . Эта конкретная техника позволяет мне иметь систему из слабосвязанных, взаимозаменямых, тестируемых элементов, которые сильно завязаны только на само API, а не на друг друга.
Суть понятна. Непонятно зачем регистрировать для каждого модуля команду в автокаде, когда можно напрямую вызвать класс команды.

Оффлайн Андрей Бушман

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Всякая оптимизация полезна, когда она применяется к месту и в меру. Оптимизировать что-либо можно до бесконечности - тут главное вовремя остановиться. ИМХО.

Оффлайн StalsoАвтор темы

  • ADN OPEN
  • Сообщений: 30
  • Карма: 0
Чем не устроил вариант решения обозначенный мною выше?

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

Оффлайн Андрей Бушман

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Может великий и ужасны Андрей Бушман заюзает мой подход
"Великий и ужасны" здесь А.Н. Ривилис. Я белый и пушистый. Насчёт "заюзает" - это вряд ли, т.к. пока не вижу пользы от этого.

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

Код - 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" сойдёт и обозначенный выше вариант.

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