尝试从键值对列表构建对象时,什么是好的设计?

时间:2020-03-05 18:49:22  来源:igfitidea点击:

因此,如果我有一种解析文本文件并返回键值对列表的列表的方法,并且想根据返回的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;
            }
        }
    }