Абстрагирование объектов между описанием и реализацией - PullRequest
1 голос
/ 11 октября 2011

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

Сейчас я создаю в библиотеке класс, представляющий набор команд, которые могут быть выполнены в движке, а также объект, который содержит много разных команд.Моя проблема в том, что я не могу найти хороший шаблон для определения этих команд и контейнера, который их абстрагирует, так что движку не нужно переключать типы.Если бы здесь не было двух проектов, только один, это было бы легко.Различные команды реализуют интерфейс, содержащий метод Execute() или что-то в этом роде.Но это не сработает здесь.Библиотека не имеет возможности реализовать поведение команд, только их атрибуты, извлеченные из XML.

Я могу в библиотеке создать интерфейс, который может использовать контейнер, который содержит методы длязагрузить и сохранить данные XML.Но движку все равно придется switch в командах, чтобы либо:

  1. запустить метод в классе контейнера движка для соответствующего изменения его собственного состояния, либо
  2. создатьправильный тип объекта движка, который управляет поведением команды, а затем выполняет ее через ее интерфейс.

Рассматриваемый контейнер является кат-сценой для моей игры и ключевых кадров в ней.Команды будут управлять его поведением, таким как музыка, изображения, текст и т. Д.

Вот пример кода из библиотеки:

public class SceneInfo
{
    /* Other stuff... */
    public List<KeyFrameInfo> KeyFrames { get; private set; }
}

public class KeyFrameInfo
{
    public List<IKeyFrameCommandInfo> Commands { get; private set; }
}

public class KeyFramePlayCommandInfo : IKeyFrameCommandInfo
{
    public int Track { get; set; }

    public static KeyFramePlayCommandInfo FromXml(XElement node)
    {
        var info = new KeyFramePlayCommandInfo();
        info.Track = node.GetInteger("track");
        return info;
    }

    public void Save(XmlTextWriter writer)
    {
        writer.WriteStartElement("PlayMusic");
        writer.WriteAttributeString("track", Track.ToString());
        writer.WriteEndElement();
    }
}

Я еще не написал половину движка, но он получит доступ к keyframe.Commands, перебирает его и делает ... что-то.Я пытаюсь избежать переключения типа, без чрезмерного проектирования этой проблемы.У него могут быть классы типа KeyFramePlayCommand (сравните с KeyFramePlayCommandInfo).

Какие-нибудь хорошие шаблоны, которые решают эту проблему?

Ответы [ 2 ]

0 голосов
/ 11 октября 2011

Для задач, связанных с созданием экземпляров (в частности, абстрагирование экземпляров от логики), обычно в игру вступает Factory.

public void KeyCommandFactory{
   public static GetKeyCommand(IKeyCommandInfo info){
      /* ? */
   }
}

Поскольку вы представляете только один интерфейс, у нас есть другая проблема: какой класс создавать?

Лучший способ, которым я могу думать в настоящее время, - это отображение клавиш / типов.

public void KeyCommandFactory{
   private static Map<string,Type> Mappings;

   private static KeyCommandFactory(){
      /* Example */
      Mappings.Add("PlayMusic",typeof(PlayMusicCommand));
      Mappings.Add("AnimateFrame",typeof(AnimateFrameCommand));
      Mappings.Add("StopFrame",typeof(StopFrameCommand));
   }

   public static GetKeyCommand(IKeyCommandInfo info){
      return (IKeyCommandInfo)Activator.CreateInstance(Mappings[info.CommandName]); // Add this property
   }
}

Если вы хотите придерживаться соглашения, вы можете даже попытаться использовать рефлексию, чтобы имена команд совпадали с именами объектов Command, но это, конечно, менее гибко.

public void KeyCommandFactory{
   public static GetKeyCommand(IKeyCommandInfo info){
      Type type = Type.GetType("Namespace.To." + info.CommandName + "Command");
      return (IKeyCommandInfo)Activator.CreateInstance(type, info); // Add this property
   }
}

Я бы положил это за пределы вашей библиотеки (в движке). Выше я передаю экземпляр info в качестве параметра новому экземпляру вашей команды.

0 голосов
/ 11 октября 2011

Вы можете создать интерфейс для фабрики, который создает исполнителя команд для данного типа команд. Также добавьте функцию для регистрации фабрики в вашей библиотеке. Когда вам нужно выполнить команду, вы можете дать зарегистрированной фабрике команду и получить объект исполнителя. Если я правильно понял вашу проблему, этот код живет на стороне библиотеки.

Теперь со стороны вашего приложения вы можете создать реализацию фабрики, зарегистрировать все известные типы команд в классах, которые могут их выполнять, и наконец зарегистрировать эту фабрику в библиотеке.


Есть довольно много разных способов сделать это. Я добавлю, что вы не захотите добавлять void Execute() к SceneInfo, KeyFrameInfo или IKeyFrameCommandInfo - в конце концов, это классы информации. Итак, давайте создадим класс SceneRunner:

public class SceneRunner
{
    public ExecuteScene(SceneInfo scene) {
        // loop over scene.KeyFrames and keyFrames.Commands, and execute
    }
}

Поскольку мы не знаем, как выполнять эти команды, давайте создадим фабрику, которая позволит нам получить класс, который знает, как это сделать:

public interface IKeyFrameCommandFactory
{
    IKeyFrameCommand GetCommand(IKeyFrameCommandInfo info);
}

И добавьте заводской интерфейс и механизм регистрации для бегуна. Поскольку сторона приложения, вероятно, не захочет иметь дело с экземплярами этого, давайте сделаем оба статических:

public class SceneRunner
{
    static public RegisterFactory(IKeyFrameCommandFactory factory)
    {
      this.factory = factory;
    }

    static private IKeyFrameCommandFactory factory = null;
}

Пока все хорошо. Теперь время для заводского кода (на стороне клиента библиотеки). Это очень похоже на то, что Дэрил предложил (вы можете использовать его дословно, просто добавив спецификацию интерфейса):

public void KeyCommandFactory: IKeyFrameCommandFactory
{
    private static Map<Type, Type> mappings;

    public IKeyCommand GetKeyCommand(IKeyCommandInfo info)
    {
        Type infoType = info.GetType();
        return Activator.CreateInstance(mappings[infoType], info);
    }
}

Если вам не нравится размышлять, вы можете реализовать Prototype Pattern в своих командах и использовать IDictionary<Type, IPrototype> mappings; в качестве карты и mappings[infoType].Clone(), чтобы получить новый экземпляр команда.

Теперь осталось две вещи: связать информационные классы с классами команд и зарегистрировать вашу фабрику. Оба должны быть довольно очевидными.

Для ассоциаций вы можете либо добавить RegisterCommand(Type infoType, IPrototype command) к своей фабрике и добавить ассоциации к точке входа вашей программы, либо преобразовать их в статический метод и вызвать этот метод из точки входа программы. Вы даже можете создать атрибут для указания информационного класса командного класса, анализа вашей сборки и автоматического добавления ассоциаций.

И, наконец, зарегистрируйте свою фабрику в SceneRunner, позвонив по номеру SceneRunner.RegisterFactory(new KeyCommandFactory()).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...