我觉得我知道设计模式,但这是在逃避我。我有两个独立的项目,一个作为另一个的库。该库读取XML文件并将其解析为数据结构。它只是负责与XML的转换。我的另一个项目是一个引擎,它对库组装的数据起作用。它可能包含镜像库中的类,但使用行为方法。如果你想知道,引擎和库分离的原因是有第三个项目,一个编辑器,修改XML数据。这是一场比赛。
我现在正在库中创建一个类,它表示可以在引擎中执行的一组命令,以及包含许多不同命令的对象。我的问题是我无法找到定义这些命令的好模式和抽象它们的容器,这样引擎就不必打开类型。如果这里没有两个项目,只有一个项目,那将很容易。不同的命令将实现包含Execute()
方法或类似内容的接口。但它在这里不起作用。该库无法实现命令的行为,只能实现从XML中提取的属性。
我可以在库中创建容器可以使用的接口,其中包含加载和保存XML数据的方法。但是引擎仍然需要switch
命令才能执行以下操作:
有问题的容器是我的游戏及其中的关键帧的过场动画。命令将控制其行为,例如音乐,图像,文本等。
以下是图书馆的一些示例代码:
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
比较)。
有什么好的模式可以解决这个问题吗?
答案 0 :(得分:0)
您可以为工厂创建一个接口,为给定的命令类型创建一个命令执行程序。还添加一个函数以将工厂注册到库中。当您需要执行命令时,可以为已注册的工厂提供命令,并获取执行程序对象。如果我正确地理解了您的问题,那么这段代码就存在于库中。
现在,从应用程序端,您可以创建工厂实现,使用可以执行它们的类注册所有已知的命令类型,最后将此工厂注册到库中。
有很多不同的方法可以做到这一点。我将添加假设您不希望将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;
}
到目前为止,这么好。现在,工厂代码的时间(在库客户端)。这与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实例作为参数传递给命令的新实例。