【问题标题】:Abstracting objects between description and implementation在描述和实现之间抽象对象
【发布时间】:2011-10-10 23:17:22
【问题描述】:

我觉得我知道设计模式,但这让我无法理解。我有两个独立的项目,一个作为另一个库。该库读取 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 相比)。

有什么好的模式可以解决这个问题?

【问题讨论】:

  • 它只有 Save 方法,但我认为它并不真正相关。如果您需要修改它以获得答案,请这样做。

标签: c# design-patterns interface


【解决方案1】:

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

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

【讨论】:

  • 好的,所以你的确实使用了某种切换,但它被包装到一个映射中。但我想没关系,我已经在引擎的其他地方使用了 Activator.CreateInstance。
  • @Tesserex 我认为必须在某处进行某种映射才能摆脱类型切换。我还没有想出任何其他方法来实现这一点。
【解决方案2】:

您可以为工厂创建一个接口,为给定的命令类型创建一个命令执行器。还要添加一个函数来将工厂注册到您的库中。当需要执行某个命令时,可以给注册的工厂该命令,并获取一个执行器对象。如果我正确理解了您的问题,则此代码位于库端。

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


有很多不同的方法可以做到这一点。我将假设您不想将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);
}

并添加工厂接口,以及向runner添加注册机制。由于应用程序端可能不想处理这种情况,让我们将两者都设为静态:

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&lt;Type, IPrototype&gt; mappings;作为你的映射,mappings[infoType].Clone()得到一个新的命令实例。

现在,剩下两件事:将信息类与命令类相关联,以及注册您的工厂。两者都应该很明显。

对于关联,您可以将RegisterCommand(Type infoType, IPrototype command) 添加到您的工厂,并将关联添加到您的程序入口点,或者将它们关联到静态方法中,然后从程序入口点调用该方法。您甚至可以设计一个属性来指定命令类的信息类,解析您的程序集并自动添加关联。

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

【讨论】:

  • 你能扩展一些示例代码吗?我不是很了解所有内容。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多