尝试从键值对列表构建对象时,什么是好的设计?
因此,如果我有一种解析文本文件并返回键值对列表的列表的方法,并且想根据返回的kvps创建对象(每个kvps列表代表一个不同的对象),那么最好的方法是什么?
想到的第一种方法非常简单,只需保留关键字列表即可:
private const string NAME = "name"; private const string PREFIX = "prefix";
并对照我获得的键来获取上面定义的所需常量。不过,这是我正在研究的项目的核心部分,因此我想做得很好。有没有人有更强有力的建议(不是说我在问的上述方法在本质上是不稳健的)?
已要求提供更多详细信息。我在业余时间正在开发一款小游戏,并且正在使用配置文件来构建游戏世界。有四个定义所有生物,另一个定义所有区域(及其在地图中的位置),另一个定义所有对象,最后一个定义各种配置选项以及不适合其他地方的事物。在前三个配置文件中,我将根据文件的内容创建对象,这将使文本非常繁琐,因此会有很多字符串,名称,复数和前缀之类的东西。配置值都像这样:
- key: value key: value - key: value key: value -
其中"-"线表示新的节/对象。
解决方案
回答
我们可以创建一个与列名匹配的接口,然后使用Reflection.Emit API在运行时创建一个类型,该类型可以访问字段中的数据。
回答
深入了解XmlSerializer。即使我们被约束为不在磁盘上使用XML,我们也可能希望复制其某些功能。然后可能如下所示:
public class DataObject { [Column("name")] public string Name { get; set; } [Column("prefix")] public string Prefix { get; set; } }
尽管要在文件中包含某种格式版本,但要小心,否则下一个格式更改将在地狱厨房中。
回答
我们需要什么对象?按照我们描述它的方式,无论如何,我们都将它们用作某种(按键)受限的地图。如果我们不需要某种继承,则只需将类似于地图的结构包装到这样的对象中:
[java-inspired pseudo-code:] class RestrictedKVDataStore { const ALLOWED_KEYS = new Collection('name', 'prefix'); Map data = new Map(); void put(String key, Object value) { if (ALLOWED_KEYS.contains(key)) data.put(key, value) } Object get(String key) { return data.get(key); } }
回答
@大卫:
我已经有了解析器(并且大多数解析器都是手写的,因此我决定使用XML)。但这看起来是我做事的好方法。我必须检查一下。关于版本控制也很不错。
@Argelbargel:
看起来也不错。 :')
回答
做出许多不必要的假设,我认为最好的方法是创建一个工厂,该工厂将接收键值对的列表,并返回适当的对象,或者如果无效则抛出异常(或者创建一个虚拟对象,或者其他任何方法)在特定情况下更好)。
private class Factory { public static IConfigurationObject Factory(List<string> keyValuePair) { switch (keyValuePair[0]) { case "x": return new x(keyValuePair[1]); break; /* etc. */ default: throw new ArgumentException("Wrong parameter in the file"); } } }
这里最强有力的假设是,所有对象都可以部分被视为相同(即,它们实现相同的接口(在示例中为IConfigurationObject)或者属于同一继承树)。
如果没有,则取决于程序流程以及我们对它们的处理方式。但是,尽管如此,他们应该:)
编辑:给出解释,每个文件类型可以有一个Factory,在其中的切换将是每个文件类型允许的类型的权威来源,并且它们可能共享一些共同点。反思是可能的,但它的风险更大,因为它不如此明显且具有自我记录能力。
回答
...This is a fairly core piece of the project I'm working on though...
真的吗?
试图将其抽象并提供一个基本的实现,以在以后进行重构是很诱人的。
然后,我们可以继续进行重要的事情:游戏。
只是一个想法
<bb />
回答
Is it really?
是的;我已经考虑过了。我要做的工作比必要的还多。 :')
回答
编辑:
从头开始,这仍然适用,但是我认为我们正在做的是读取配置文件并将其解析为以下内容:
List<List<KeyValuePair<String,String>>> itemConfig = new List<List<KeyValuePair<String,String>>>();
在这种情况下,我们仍然可以使用反射工厂来实例化对象,我只是将嵌套的内部列表传递给它,而不是传递每个单独的键/值对。
旧帖子:
这是使用反射的一种巧妙的小方法:
基本思路:
- 为每个Object类使用一个通用的基类。
- 将所有这些类放在自己的程序集中。
- 将该工厂也放置在该组件中。
- 传递我们从配置中读取的KeyValuePair,作为回报,它会找到与KV.Key匹配的类,并使用KV.Value对其进行实例化
public class KeyValueToObjectFactory { private Dictionary _kvTypes = new Dictionary(); public KeyValueToObjectFactory() { // Preload the Types into a dictionary so we can look them up later // Obviously, you want to reuse the factory to minimize overhead, so don't // do something stupid like instantiate a new factory in a loop. foreach (Type type in typeof(KeyValueToObjectFactory).Assembly.GetTypes()) { if (type.IsSubclassOf(typeof(KVObjectBase))) { _kvTypes[type.Name.ToLower()] = type; } } } public KVObjectBase CreateObjectFromKV(KeyValuePair kv) { if (kv != null) { string kvName = kv.Key; // If the Type information is in our Dictionary, instantiate a new instance of that class. Type kvType; if (_kvTypes.TryGetValue(kvName, out kvType)) { return (KVObjectBase)Activator.CreateInstance(kvType, kv.Value); } else { throw new ArgumentException("Unrecognized KV Pair"); } } else { return null; } } }