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

发布于 2024-07-04 12:27:00 字数 716 浏览 6 评论 0原文

因此,如果我有一种解析文本文件并返回列表 列表 的方法键值对,并且想要从返回的 kvps 创建对象(每个 kvps 列表代表一个不同的对象),最好的方法是什么?

想到的第一个方法非常简单,只需保留一个关键字列表:

private const string NAME   = "name";
private const string PREFIX = "prefix";

并检查我获得的键以获取上面定义的我想要的常量。 这是我正在从事的项目的一个相当核心的部分,所以我想把它做好; 有没有人有任何更可靠的建议(并不是说上述方法本质上不可靠 - 我只是四处询问)?

编辑:

已要求提供更多详细信息。 我在业余时间制作一个小游戏,我正在用配置文件构建游戏世界。 有四个 - 一个定义所有生物,另一个定义所有区域(及其在地图中的位置),另一个定义所有对象,最后一个定义各种配置选项和不适合其他地方的东西。 对于前三个配置文件,我将根据文件的内容创建对象 - 它将包含大量文本,因此会有很多字符串,例如名称、复数、前缀等。 配置值都如下所示:

-
key: value 
key: value
-
key: value
key: value
-

其中“-”行表示新的部分/对象。

So if I have a method of parsing a text file and returning a list of a list of key value pairs, and want to create objects from the kvps returned (each list of kvps represents a different object), what would be the best method?

The first method that pops into mind is pretty simple, just keep a list of keywords:

private const string NAME   = "name";
private const string PREFIX = "prefix";

and check against the keys I get for the constants I want, defined above. This is a fairly core piece of the project I'm working on though, so I want to do it well; does anyone have any more robust suggestions (not saying there's anything inherently un-robust about the above method - I'm just asking around)?

Edit:

More details have been asked for. I'm working on a little game in my spare time, and I am building up the game world with configuration files. There are four - one defines all creatures, another defines all areas (and their locations in a map), another all objects, and a final one defines various configuration options and things that don't fit else where. With the first three configuration files, I will be creating objects based on the content of the files - it will be quite text-heavy, so there will be a lot of strings, things like names, plurals, prefixes - that sort of thing. The configuration values are all like so:

-
key: value 
key: value
-
key: value
key: value
-

Where the '-' line denotes a new section/object.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

手心的温暖 2024-07-11 12:27:00

你需要对象做什么? 按照您描述的方式,您无论如何都会将它们用作某种(按键)限制的映射。 如果您不需要某种继承,我只需将类似映射的结构包装到像这样的对象中:

[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);
   }
}

What do you need object for? The way you describe it, you'll use them as some kind (of key-wise) restricted map anyway. If you do not need some kind of inheritance, I'd simply wrap a map-like structure into a object like this:

[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);
   }
}
梦断已成空 2024-07-11 12:27:00

深入了解 XmlSerializer。 即使您被迫不使用磁盘上的 XML,您也可能想要复制它的一些功能。 这可能看起来像这样:

public class DataObject {
  [Column("name")]
  public string Name { get; set; }

  [Column("prefix")]
  public string Prefix { get; set; }
}

但要小心在文件中包含某种格式版本,否则下一次格式更改时您将陷入地狱厨房。

Take a deep look at the XmlSerializer. Even if you are constrained to not use XML on-disk, you might want to copy some of its features. This could then look like this:

public class DataObject {
  [Column("name")]
  public string Name { get; set; }

  [Column("prefix")]
  public string Prefix { get; set; }
}

Be careful though to include some kind of format version in your files, or you will be in hell's kitchen come the next format change.

吾性傲以野 2024-07-11 12:27:00

做出很多没有根据的假设,我认为最好的方法是创建一个工厂,它将接收键值对列表并返回正确的对象,或者如果它无效则抛出异常(或者创建一个虚拟对象,或者其他任何东西)在特定情况下更好)。

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)或属于相同的继承树)。

如果他们不这样做,那么这取决于你的程序流程以及你用它们做什么。 但尽管如此,他们应该:)

编辑:根据您的解释,您可以为每种文件类型拥有一个工厂,其中的开关将是每种文件类型允许的类型的权威来源,并且它们可能共享一些共同点。 反思是可能的,但风险更大,因为它不像这个那么明显和自我记录。

Making a lot of unwarranted assumptions, I think that the best approach would be to create a Factory that will receive the list of key value pairs and return the proper object or throw an exception if it's invalid (or create a dummy object, or whatever is better in the particular case).

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");
       }

  }

}

The strongest assumption here is that all your objects can be treated partly like the same (ie, they implement the same interface (IConfigurationObject in the example) or belong to the same inheritance tree).

If they don't, then it depends on your program flow and what are you doing with them. But nonetheless, they should :)

EDIT: Given your explanation, you could have one Factory per file type, the switch in it would be the authoritative source on the allowed types per file type and they probably share something in common. Reflection is possible, but it's riskier because it's less obvious and self documenting than this one.

软的没边 2024-07-11 12:27:00

编辑:

从头开始,这仍然适用,但我认为您所做的是读取配置文件并将其解析为:

List<List<KeyValuePair<String,String>>> itemConfig = 
    new List<List<KeyValuePair<String,String>>>();

在这种情况下,我们仍然可以使用反射工厂来实例化对象,我只需将嵌套的内部列表传递给它,而不是传递每个单独的键/值对。

旧帖子

这是一个使用反射来执行此操作的巧妙小方法:

基本思想:

  • 为每个对象类使用公共基类。
  • 将所有这些类放在它们自己的程序集中。
  • 也将该工厂放入该组件中。
  • 传入您从配置中读取的 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;
            }
        }
    }

EDIT:

Scratch that, this still applies, but I think what your doing is reading a configuration file and parsing it into this:

List<List<KeyValuePair<String,String>>> itemConfig = 
    new List<List<KeyValuePair<String,String>>>();

In this case, we can still use a reflection factory to instantiate the objects, I'd just pass in the nested inner list to it, instead of passing each individual key/value pair.

OLD POST:

Here is a clever little way to do this using reflection:

The basic idea:

  • Use a common base class for each Object class.
  • Put all of these classes in their own assembly.
  • Put this factory in that assembly too.
  • Pass in the KeyValuePair that you read from your config, and in return it finds the class that matches KV.Key and instantiates it with 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;
            }
        }
    }
一瞬间的火花 2024-07-11 12:27:00

您可以创建一个与列名称匹配的接口,然后使用 Reflection.Emit API 在运行时创建一个类型,以允许访问字段中的数据。

You could create an interface that matched the column names, and then use the Reflection.Emit API to create a type at runtime that gave access to the data in the fields.

淡淡的优雅 2024-07-11 12:27:00

真的吗?

是的; 我已经想出来了。 我绝不会做不必要的工作。 :')

Is it really?

Yes; I have thought this out. Far be it from me to do more work than neccessary. :')

一桥轻雨一伞开 2024-07-11 12:27:00

...这是一个相当核心的部分
我正在做的项目...

真的吗?

人们很容易将其抽象出来并提供一个基本的实现,以便稍后进行重构。

然后你就可以继续做重要的事情了:游戏。

只是一个想法

>

...This is a fairly core piece of the
project I'm working on though...

Is it really?

It's tempting to just abstract it and provide a basic implementation with the intention of refactoring later on.

Then you can get on with what matters: the game.

Just a thought

<bb />

扛起拖把扫天下 2024-07-11 12:27:00

@大卫:
我已经有了解析器(其中大部分都是手写的,所以我决定不使用 XML)。 但这看起来我的做法确实很好; 我得去检查一下。 关于版本控制也很精彩。

@Argelbargel:
看起来也不错。 :')

@David:
I already have the parser (and most of these will be hand written, so I decided against XML). But that looks like I really nice way of doing it; I'll have to check it out. Excellent point about versioning too.

@Argelbargel:
That looks good too. :')

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文