Сразу сорри за такое абстрактное название топика. Ничего конкретного не придумал ))
Есть интерфейс IAction
Этот интерфейс будут реализовывать несколько классов.
Action — это некоторый процесс, который следует запустить в определенное время работы программы.
Action:
+Name // имя
+Description // описание
+TimeStart // когда начинать процесс
+Settings // и здесь начинауются вопросы
Settings — у каждого будут разные настройки выполнения.
Например есть процесс "Измерить дыхание"
Settings:
Измерять на протяжении Х секунд или
Измерять Y вдохов выдохов
Другой процесс "Проиграть аудио файл"
Settings:
Путь к файлу
т.е. как видно, все, кроме Settings у этих объектов одного типа.
Как Settings красиво подвести под одну черту?
Чтобы в интерфейсе красиво описать...
AH>т.е. как видно, все, кроме Settings у этих объектов одного типа. AH>Как Settings красиво подвести под одну черту?
для начала объясните для чего вам это надо?
AH>Чтобы в интерфейсе красиво описать...
object. не?
Здравствуйте, Muxa, Вы писали:
AH>>т.е. как видно, все, кроме Settings у этих объектов одного типа. AH>>Как Settings красиво подвести под одну черту? M>для начала объясните для чего вам это надо?
чтобы в интерфейсе описать поле Settings какого-то общего типа
а в классе уже реализовать тот тип, который мне нужен.
AH>>Чтобы в интерфейсе красиво описать... M>object. не?
Просто написать тип Object?
А потом при вызове этого поля из класса проверять на соответствие типов?
типа
if (Settings.GetType() = TypeOf(ActionPlayAudioSettings)) // как-то так
M>>для начала объясните для чего вам это надо? AH>чтобы в интерфейсе описать поле Settings какого-то общего типа AH>а в классе уже реализовать тот тип, который мне нужен.
это-то понятно. зачем этот Settints наружу будет торчать?
AH>Просто написать тип Object? AH>А потом при вызове этого поля из класса проверять на соответствие типов? AH>типа if (Settings.GetType() = TypeOf(ActionPlayAudioSettings))
типа так:
class ИзмерительДыхания : IAction {
private ИзмерительДыханияSettings _settings = new ИзмерительДыханияSettings();
public string Name { get; set; }
public string Description { get; set; }
public DateTime TimeStart { get; set; }
public object Settings { get { return _settings; } }
public class ИзмерительДыханияSettings {
public int X { get; set; }
public int Y { get; set; }
}
}
class АудиоПлэйер : IAction{
private АудиоПлэйерSettings _settings = new АудиоПлэйерSettings();
public string Name { get; set; }
public string Description { get; set; }
public DateTime TimeStart { get; set; }
public object Settings { get { return _settings; } }
public class АудиоПлэйерSettings {
public FileInfo PathToFile { get; set;}
}
}
А еще это делается для того, чтобы когда нужно будет добавить новый Action — достаточно будет только реализовать интерфейс еще одному классу.
И все.
Все остальное уже будет делаться автоматически..
Здравствуйте, AHgpeu, Вы писали:
AH>Сразу сорри за такое абстрактное название топика. Ничего конкретного не придумал ))
AH>Есть интерфейс IAction AH>Этот интерфейс будут реализовывать несколько классов. AH>Action — это некоторый процесс, который следует запустить в определенное время работы программы.
AH>
AH>Action:
AH> +Name // имя
AH> +Description // описание
AH> +TimeStart // когда начинать процесс
AH> +Settings // и здесь начинауются вопросы
AH>
AH>Settings — у каждого будут разные настройки выполнения.
Например так :
public interface IAction<TSetting>
{
TSetting Settings { get; set; }
}
public class MeasureBreathAction : IAction<MeasureBreathSettings>
{
...
}
public class PlayFileAction : IAction<PlayFileSettings>
{
...
}
Здравствуйте, AHgpeu, Вы писали:
AH>А еще это делается для того, чтобы когда нужно будет добавить новый Action — достаточно будет только реализовать интерфейс еще одному классу. AH>И все. AH>Все остальное уже будет делаться автоматически..
Я присоединюсь к вопросу, который уже два раза задал Muxa. Кто будет иметь этот торчащий наружу интерфейс? Что именно он с ним будет делать? Какие методы и зачем вызывать? Вероятно, эти данные будут показываться пользователю. Или еще как-то использоваться. Вот это от вас и ждут. А вы пока говорите о конкретных "наследниках" и их эволюции.
Теперь по отквоченному. Цель хорошая. Рассмотрим ситуацию: создали мы новую реализацию WorldDominationTask с новым "классом" настроек WorldDominationSettings (которые вовсю используем внтури, нам так удобно). Что и как в этом случае будет делаться автоматически? Где-то нужно будет регистрировать редактор для этого типа настроек (тогда автоматизм не полный)? Или через рефлексию определяется структура этих настроек и с ними производится работа? Или это параметры, сведенные в некоторую "метаструктуру" (набор пар key/value, XML DOM и т.п.)?
Вот после ответа на эти вопросы станет ясно, как будет использоваться этот интерфейс, как будут использоваться настройки и какого контракта ждет пользователь. Вот этот то контракт и нужно будет добавлять в тот интерфейс.
P.S. А нужен там интерфейс? У вас интерфейс не согласуется с определением "некоторый процесс, который следует запустить в определенное время работы программы". По определению можно сказать, что у этого процесса есть время и есть "то, что запускать". Имя, описание и свойства — это уже какие-то подробности, которые для задачи "запустить" не нужны. Они есть в реализации, но скрыты в "то, что запускать" (например, метод run() в интерфейсе). Ваши данные без настроек (возможно, и с настройками) пока прекрасно отрезаются от действия, которое нужно выполнить. Композиция будет. Что-то вроде JobSpecification {Name, Description, TimeStart} + Action (с Settings и методом run). Связь между ними — в зависимости от конкретной задачи. Либо снаружи (объект со ссылками на обе вещи), либо ссылка из задачи на Action, либо ссылка из Action на JobSpecification. Т.е. сущности "процесс", "то, что нужно пользователю" и "все это вместе" — разные сущности и моделируются отдельно. Нет, правда, какое дело действию до того, какое у него имя и описание (а также и время запуска)?
Можно посмотреть на интерфейс java.swing.Action, и абстрактный класс
java.swing.AbstractAction.
А потом еще на jdesktop.swingworker.SwingWorker и на его потомка
org.jdesktop.application.Task.
Задачи которые решены с помощью вышеперечисленных классов довольно
сильно похожи на перечисленные вами. Но только в SwingWorker
реализованно выполнение задачи в фоновом процессе, а в интерфейсе Action
есть методы для разрешения/запрещения действия (для того, чтобы
управлять GUI приложением).
В общих чертах идея такая.
public class BreezeMeasure extends AbstractAction {
public BreezeMeasure(int secondsToMeasure, int breezeCount) {
// здесь определяете как создавать эти объекты (и все что
// нужно устанавливаете
}
public void actionPerformed() {
// здесь собственно работа
}
}
public class PlayAudioFile extends AbstractAction {
public PlayAudioFile(String fileName) {
}
public void actionPerformed() {
}
}
Т.е. каждый объект реализует один метод actionPerformed а все остальное
одинаковое для всех — реализуется в AbstractAction.
Posted via RSDN NNTP Server 2.1 beta
Re[3]: Подскажите плиз
От:
Аноним
Дата:
27.03.10 10:31
Оценка:
Здравствуйте, Ziaw, Вы писали:
Z>Вопрос топик-стартеру: как планируется использовать эти настройки снаружи? От использования и пишем тип этих самых настроек.
Эти настройки будет отображаться в GUI, чтобы пользователь мог сконфигурировать каждый процесс.
Например, для
PlayAudio — будет строка для ввода пути к файлу
для
MeasureBreath — будет возможность у пользователя поставить "галочку" либо на запись в теч. Х сек (Х — тоже user конфигурирует), либо на запись Y вдохов (Y — тоже устанавливает пользователь в форме)
Re[4]: Подскажите плиз
От:
Аноним
Дата:
27.03.10 10:40
Оценка:
Да... начинаю склоняться к тому что Settings и не нужно наружу выставлять...
Но надо еще обдумать...
Ваша идея позволить любому экшену работать с любыми настройками это серьёзный overhead, который кроме громоздкого, трудночитаемого кода и кучи проблем ничего не принесёт.
Да и реализация невнятна. Как по вашему будут выглядеть PlayAudioSetting или MeasureBreathSettings ?
Конкретному Action'у нужен конкретный тип Settings. PlayAudioAction знает в его settings лежит путь к файлу, а не количество вдохов в секунду. Поэтому он смело может юзать PlayAudioSetting
и не париться =)
А>Эти настройки будет отображаться в GUI, чтобы пользователь мог сконфигурировать каждый процесс. А>Например, для А>PlayAudio — будет строка для ввода пути к файлу А>для А>MeasureBreath — будет возможность у пользователя поставить "галочку" либо на запись в теч. Х сек (Х — тоже user конфигурирует), либо на запись Y вдохов (Y — тоже устанавливает пользователь в форме)
Насколько я понял система позволяет созадавать Actions с определёнными настройками, сохранять его, а затем в нужный момент запускать. Абстрагироваться от действия система должна на момент сохранения/запуска. При создании думаю известен конкретный тип действия.
На данный момент IAction представляет собой просто набор параметров действия. Чтобы это всё заработало, нужно добавить в IAction метод Invoke(), или actionPerformed() как писал Other Sam.
Если действия могут запускатся в разном окружении, то его(окружение) нужно передавать в Invoke.
public interface IAction<TSetting>
//where TSetting : ISettings если у settings найдётся что - нибудь общее. Из примера этого не видно.
{
Name // имя
Description // описание
TimeStart // когда начинать процесс
...
TSetting Settings;
IActionResult Invoke(IActionContext context);
}
Если одно и то же действие может совершаться по разному — например PlayAudio используя различные проигрыватели, можно использовать паттерн strategy.
Реализация зависит, от того известна стратегия на момент создания действия или в момент выполнения.
Теперь с гуя можно создавать объекты Action(напрямую или через Factory) и сохранять если надо. Вызывающий поток берёт экшн и через интерфейс запускает его, дёргая Invoke.
Т.е. по сути нужно реализовать паттерн Command, если я правильно понял. =)
Здравствуйте, Аноним, Вы писали:
А>Да... начинаю склоняться к тому что Settings и не нужно наружу выставлять...
Очень верятно что вызывающемк коду рано или поздно понадобится отображать/логировать инфо о выполняемых действиях.
Иначе для чего тогда Name, Description нужны ?
Здравствуйте, alexkh, Вы писали:
Z>>>А теперь расскажите зачем нужен этот интерфейс?
A>Вопрос был как можно красиво описать setting в интерфейсе. Всё остальное для краткости я опустил =)
Ответ на вопрос будет? Какую унификацию экшенов он даст внешнему коду?
A>Ваша идея позволить любому экшену работать с любыми настройками это серьёзный overhead, который кроме громоздкого, трудночитаемого кода и кучи проблем ничего не принесёт.
Не понял. Каким образом?
A>Да и реализация невнятна. Как по вашему будут выглядеть PlayAudioSetting или MeasureBreathSettings ?
Это личное дело PlayAudio и MeasureBreath. Интерфейсу (и как следствие коду с ним работающему) это побоку.
A>Конкретному Action'у нужен конкретный тип Settings. PlayAudioAction знает в его settings лежит путь к файлу, а не количество вдохов в секунду. Поэтому он смело может юзать PlayAudioSetting A>и не париться =)
Я ему мешаю? Пусть внутри себя юзает что хочет, для внешнего кода только откроет их в обобщенном интерфейсе
A>Теперь с гуя можно создавать объекты Action(напрямую или через Factory) и сохранять если надо. Вызывающий поток берёт экшн и через интерфейс запускает его, дёргая Invoke. A>Т.е. по сути нужно реализовать паттерн Command, если я правильно понял. =)
Напишите код который сортирует массив экшенов по имени. Может тогда поймете свою ошибку. Дженерик интерфейс тут как палка в колесе.
Доработал решение с generic'ами. Проблемы : -Вызывать через generic interface очень неудобно/невозможно.
-Нельзя получить список settings
-В каждой реализации IAction предполагается заново реализовывать свойства Name,Decription,TimeStart ...
В общем виде выглядит так :
public interface IAction // Этот интерфейс публикуется и используется Invoker'ом.
{
IActionResult Invoke(IContext context);
string Name { get;}
string Description { get;}
DateTime TimeStart { get;}
...
Dictionary<string, string> GetSettings();
}
internal abstract class BaseAction<TSetting> : IAction
where TSetting : BaseSettings
{
protected BaseAction(TSetting setting) // Добавить все required поля.
{
this.Setting = setting;
...
}
protected TSetting Setting {get; set;};
public abstract IActionResult Invoke(IContext context);
//......
//Реализация полей Name, Description , TimeStart ...
//......
Dictionary<string, string> IAction.GetSettings()
{
return this.Setting.GetSettings();
}
}
public abstract class BaseSettings
{
public Dictionary<string, string> GetSettings()
{
//Берём рефлекией все поля, маркнутые SettingsFieldAttribute и кладём в dictionary.
//SettingsFieldAttribute содержит User Friendly Name поля. Оно же ключ в dictionary.
}
}
public class ConcreateActionSettings : BaseSettings
{
[SettingsField("Settings name")]
public string Name {get; set;};
}
public class ConcreteAction : BaseAction<ConcreateActionSettings>
{
public ConcreteAction(ConcreateActionSettings settings) : base(settings)
{
}
public override IActionResult Invoke(IContext context)
{
Console.WriteLine(string.Format("Settings name is : {0}", Setting.Name)); // Можно работать с settings как с конкретным объектом.return ActionResult.Success;
}
}
Здравствуйте, Ziaw, Вы писали:
A>>Ваша идея позволить любому экшену работать с любыми настройками это серьёзный overhead, который кроме громоздкого, трудночитаемого кода и кучи проблем ничего не принесёт. Z>Не понял. Каким образом?
Не сразу увидел за вашим ISetting что — то вроде своего BaseSettings. А так вобщем — то мы говорим об одних и тех же вещах.
Z>Напишите код который сортирует массив экшенов по имени. Может тогда поймете свою ошибку. Дженерик интерфейс тут как палка в колесе.
Здравствуйте, alexkh, Вы писали:
A>Доработал решение с generic'ами.
Давайте еще поработаем над решением с generic'ами. В качестве результата получим, что и без generic'ов там совсем не сильно хуже.
Сразу замечу, что проблему "в каждой реализации IAction предполагается заново реализовывать свойства Name, Description, TimeStart" вы не решили а просто обошли (путем наследования). Т.е. все равно каким-то образом реализация должна иметь указанные методы. При этом вы в качестве решения "требуете" наследования от базового класса. Я уже упоминал, что при желании эти параметры выносятся в отдельный класс и используется композиция. Т.е. в некоторых случаях будет необходимо реализовать метод получения этой структуры. Это уже зависит от направления связей в композиции, может быть, даже ничего лишнего не понадобится. Далее по тексту эту проблему я решать не буду (пусть наследование останется).
Читал решение. Возник вопрос — а что такое BaseSettings? У вас это часть ритуала по использованию (наследованию от) BaseAction. А что-нибудь еще можно сказать? Ну, например, в документацию записать? И очень желательно — уложить в одно предложение, отвечающее на вопрос "что это такое?". После такого предложения может быть еще что-то (расшифровка, подробности и т.п.). Но само предложение очень полезно — оно заставляет задуматься о роли класса, о том, чем являются экземпляры класса (или сам класса).
По коду (или комментарию) видно, что же такое BaseSettings — некоторый способ получить из полей "наследника" то, что требуется интерфейсом. Адаптер. Одно не понятно — почему же вы требуете для использования этого адаптера обязательного наследования от него? Рефлексия, что ли, не заработает? Более того, в данном конкретном случае наследование не даст ничего хорошего — у нас нарушится отношение "is a". Поясняю подробнее. Как мы только что увидели, базовый класс — это адаптер, но ни коим образом не настройки (ну нет в нем ни одной настроки и ответственность — именно адаптация наследников). В то же время его наследники — это конкретные настройки, ответственности "адаптация" в них как бы и нет. Решать надо эту проблему. При этом решение очень простое — композиция. Вместо того, чтобы ходить по своим полям, мы получаем объект и ходим по его полям. Вся остальная логика остается та же самая. Т.е. в коде везде, где было (явное или неявное) this мы используем явное that (ну и получаем его в конструктор).
Почистили BaseSettings (и заодно переименовали в какой-нибудь Adapter). Что теперь мы имеем? Для настроек наследования не требуется, мы можем удалить все ограничения типов (и создавать явно адаптер) в BaseAction. Настройки — это именно настройки, которые базовым классом адаптируются до интерфейса. Наверное, очевидно, что прибивать гвоздями способ задания настроек для возможности наследования (использования общих Name, Description, TimeStart) — не очень хорошо. Поэтому можно эти три поля вытащить "наверх" в BaseAction (без параметров типа). А текущий BaseAction переименовать в какой-нибудь AnnotatedSettingsAction. Т.е. можно будет делать и другие механизмы настроек (с адаптерами и т.п.) там, где это будет целесообразно. И вот здесь встает интересный вопрос — а нужен ли вообще такой AnnotatedSettingsAction? Он содержит в себе поле для настроек и метод их конвертации. В то же время можно не делать этот класс, самому заводить поле под настройки (во всех наследниках) и вручную вызывать конвертацию. Однозначного ответа пока не вижу (очень зависит от количества классов, где наследование было бы полезно). Наверное, я все же предпочел бы поле и конвертацию вручную — нет лишних наследований. Да и фокусы вроде конвертации this можно делать, а не заводить отдельный класс настроек ради одного-двух полей (тоже нужно осторожно делать). В последнем случае еще и generic'ов нет. Совсем. Потому что не нужны, информация о типах находится ровно там, где используется (ваш текущий BaseAction не пользуется информацией о конкретном типе).
Еще немного офтопика (детали реализации, к наследованию отношения не имеют).
Механизм Dictionary в данном случае не очень подходит. Вам же нужен конкретный Dictionary, который изменения транслирует в изменения объекта. Более простой (самописный) интерфейс в данном случае будет гораздо проще реализовывать, чем интерфейс "общего назначения" Dictionary. Лишней возможности "добавить что-нибудь" не будет. Да и "User friendly name" поля через аннотации в данном случае не очень... С локализацией оно плохо дружит (имена меняться будут в зависимости от локали).
Сразу отвечу и на A> Не сразу увидел за вашим ISetting что — то вроде своего BaseSettings. А так вобщем — то мы говорим об одних и тех же вещах.
Но ведь там за ISetting не BaseSettings. ISetting это аналог вашего Dictionary<String, String>. Роль ISetting — предоставлять пользователю интерфейс в стандартном виде. За ним нет никакой реализации, реализации будут уже потом. А реализации — да, либо BaseSettings, либо адаптеры, а может и еще что-то.
С учетом всего вышесказанного, получается следующее (исправлено не все, на что обратил внимание, там больше конкретики задачи нужно):
//Общие части поскипаны для уменьшения объема, я про них помню :)public abstract class BaseAction : IAction // public он, зачем internal?
{
protected BaseAction() // Добавить все required поля.
{
...
}
public abstract IActionResult Invoke(IContext context);
}
// extends того, что нужно по интерфейсу.internal class AnnotatedSettingsConverter extends Dictionary<string, string>
{
private final Object obj; // ну или не final, а const какой-нибудьinternal AnnotatedSettingsConverter(Object obj) {
this.obj = obj;
//По рефлексии вычисляем список полей, заполняем структуру и т.п.
}
//Переменные для кэширования информации из рефлексии и т.п.
//Переопределяем методы (редактирования, возможно, еще какие-нибудь).
}
public class SettingsConverter {
private SettingsConverter() {
throw new UnsupportedOperationError("Not supported");
}
public static Dictionary<String, String> convertAnnotatedObject(Object obj) {
return new AnnotatedSettingsConverter(obj);
}
}
public class ConcreteActionSettings
{
[SettingsField("Settings name")]
public string Name {get; set;};
}
public class ConcreteAction : BaseAction
{
private ConcreteActionSettings settings;
public ConcreteAction(ConcreateActionSettings settings)
{
this.settings = settings;
}
public override IActionResult Invoke(IContext context)
{
Console.WriteLine(string.Format("Settings name is : {0}", Setting.Name)); // Можно работать с settings как с конкретным объектом.return ActionResult.Success;
}
Dictionary<string, string> IAction.GetSettings()
{
return SettingsConverter.convertAnnotatedObject(settings);
}
}
В коде есть неочевидный момент — невидимый пользователю AnnotatedSettingsConverter. Не видим он потому, что для пользователя наличие "этого конкретного класса" — лишная информация. Да, ему нужно отконвертировать объект, но как — не важно. Все это скрыто в методе SettingsConverter.convertAnnotatedObject. Сам по себе класс SettingsConverter — набор методов различной (стандартной) конвертации настроек. Что хорошо — в конвертации он получает огромную свободу. Например, можно получать метаданные не из аннотаций а из какой-либо структуры (ну например, список полей). При этом AnnotatedSettingsConverter преобразуется в ReflectiveConverter (который извне получает список полей но через рефлексию получает значения) и пару методов в SettingsConverter, которые готовят метаданные. В этом случае мы избежали создания лишних классов, так как разница в их "ответственности" прекрасно заменяется разницей в вызываемых методах.