在描述和实现之间抽象对象

时间:2011-10-10 23:17:22

标签: c# design-patterns interface

我觉得我知道设计模式,但这是在逃避我。我有两个独立的项目,一个作为另一个的库。该库读取XML文件并将其解析为数据结构。它只是负责与XML的转换。我的另一个项目是一个引擎,它对库组装的数据起作用。它可能包含镜像库中的类,但使用行为方法。如果你想知道,引擎和库分离的原因是有第三个项目,一个编辑器,修改XML数据。这是一场比赛。

我现在正在库中创建一个类,它表示可以在引擎中执行的一组命令,以及包含许多不同命令的对象。我的问题是我无法找到定义这些命令的好模式和抽象它们的容器,这样引擎就不必打开类型。如果这里没有两个项目,只有一个项目,那将很容易。不同的命令将实现包含Execute()方法或类似内容的接口。但它在这里不起作用。该库无法实现命令的行为,只能实现从XML中提取的属性。

我可以在库中创建容器可以使用的接口,其中包含加载和保存XML数据的方法。但是引擎仍然需要switch命令才能执行以下操作:

  1. 在引擎的容器类中运行一个方法来相应地修改自己的状态,或者
  2. 创建控制命令行为的正确类型的引擎对象,然后通过其接口执行该操作。
  3. 有问题的容器是我的游戏及其中的关键帧的过场动画。命令将控制其行为,例如音乐,图像,文本等。

    以下是图书馆的一些示例代码:

    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 :(得分:0)

您可以为工厂创建一个接口,为给定的命令类型创建一个命令执行程序。还添加一个函数以将工厂注册到库中。当您需要执行命令时,可以为已注册的工厂提供命令,并获取执行程序对象。如果我正确地理解了您的问题,那么这段代码就存在于库中。

现在,从应用程序端,您可以创建工厂实现,使用可以执行它们的类注册所有已知的命令类型,最后将此工厂注册到库中。


有很多不同的方法可以做到这一点。我将添加假设您不希望将void Execute()添加到SceneInfoKeyFrameInfoIKeyFrameCommandInfo - 毕竟它们是信息类。所以,让我们创建一个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;
}

到目前为止,这么好。现在,工厂代码的时间(在库客户端)。这与Daryl suggested非常类似(你可以通过添加接口规范逐字地使用他的代码):

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),并将关联添加到程序入口点,或者以静态方法查找关联,并从程序入口点调用该方法。您甚至可以设计一个属性来指定命令类的info类,解析程序集,并自动添加关联。

最后,通过调用SceneRunner.RegisterFactory(new KeyCommandFactory())将您的工厂注册到SceneRunner。

答案 1 :(得分:0)

对于处理实例化的问题(特别是抽象实例化远离逻辑)工厂通常会发挥作用。

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实例作为参数传递给命令的新实例。