Scenario
最近在重构一个mirai-api-http的C#包装器,好尝试一些以前没有使用过的技术(比如Rx.Net的IObservable接口什么的)。所以我对一些旧有的实现方式进行了一些思考。这篇文章要探讨的场景就是下面这张图片,我们该如何以更简单和优雅的方式来实现同样的需求呢?
首先我们来分析这个场景的逻辑。这个方法有一个string
参数传进来,它是一个json文本,我们要做的就是根据这个json文本把它转换成对应的实体类再返回去(当然,返回的类型是这些实体类的基类)。我们知道有这样的一个json文件,它对应某某一个实体类,可是编译器不知道。所以,在这个方法里,我直接使用了暴力枚举的方案来实现这个需求。也就是说,我们知道且确保这些json文件可以反序列化成一个共同的基类,但是我们不知道这个json对应哪一个具体的子类。所以,我们要根据基类的Type
属性来判断哪个基类是最后要返回的。
这样写的问题就在于,每次添加新的事件类都需要到这个冗长的switch语句里再添加一个分支,这一点也不面向对象!而且这样写太原始,太暴力,酷哥不应该这样做。
Elegant
酷哥应该如何做? 答案是:反射。
首先,我们要确保我们的每个实体类都有一个能判断它是谁的要素,比如在它的名字里加上前缀或者后缀,或者给它一个有默认值的属性。在这里,我们的每个实体类都有一个Type
属性,这是一个枚举,并且包含默认值,所以知道这个Type
属性的值是什么,就知道这个实体类是谁了。所以我们使用Activator.CreateInstance
来创建这个实体类的默认实例。现在我们有了一个集合,里面是我们所有实体类的默认实例。
有了这个属性,我们再根据JsonConvert.DeserializeObject
来创建一个基类的实例,这个基类实例的数据是丢失的,我们会用到这个基类实例的Type
属性。
接下来的事情就简单了,判断我们的实例集合里有没有和这个基类的类型相同的实体类,如果没有,就抛出异常,如果有,就进行下一步工作:
首先找出唯一的和这个基类的Type
相同的实体类实例,然后再遍历储存实体类的Type(typeof)的集合,如果这个唯一的实力类实例和Type(typeof)的集合中的某个元素的Type(typeof)相等的话,就可以直接使用JsonConvert.DeserializeObject(json, type)
这个重载方法获取到object然后再强转成基类对象就完事了。