基于C#反射机制的工厂模式

发表于2017-09-07
评论0 2.3k浏览

简介

反射提供了描述程序集、模块和类型的对象(Type 类型)。可以使用反射动态创建类型的实例,将类型绑定到现有对象,或从现有对象获取类型并调用其方法或访问其字段和属性。如果代码中使用了特性,可以利用反射来访问它们。

这里的类型信息包括类型的方法,变量名称,类型等信息。


基于反射机制的工厂模式


如下图所示,游戏中常用的掉落物模型,Item是基类,定义了一些基础属性,也定义了一些abstract方法。



Food和Weapon继承自Item,表示一类Item,再下一层的类就定义了具体的Item。代码如下:

Item.cs

  1. using UnityEngine;  
  2. using System.Collections;  
  3.   
  4. public abstract class Item {  
  5.     protected string name;  
  6.     protected int level;  
  7.     protected int durability;  
  8.     protected int maxDurability = 10;  
  9.     protected bool isStackable;  
  10.     protected string describe;  
  11.   
  12.     public string Name  
  13.     {  
  14.         get { return name; }  
  15.         set { name = value; }  
  16.     }  
  17.   
  18.     public string Level  
  19.     {  
  20.         get { return name; }  
  21.         set { name = value; }  
  22.     }  
  23.   
  24.     public int Durability  
  25.     {  
  26.         get { return durability; }  
  27.         set { durability = value; }  
  28.     }  
  29.   
  30.   
  31.     public abstract void Execute();  
  32.   
  33.     public void Upgrade()  
  34.     {  
  35.         level ;  
  36.     }  
  37.   
  38.     public void Fix()  
  39.     {  
  40.         durability = maxDurability;  
  41.     }  
  42.   
  43.     public virtual bool IsEquipped()  
  44.     {  
  45.         return false;  
  46.     }  
  47.   
  48.   
  49. }  

Food.csview plain copy
  1. using UnityEngine;  
  2. using System.Collections;  
  3. using System;  
  4.   
  5. public class Food : Item {  
  6.   
  7.     public Food()  
  8.     {  
  9.         isStackable = true;  
  10.         name = "Food";  
  11.     }  
  12.   
  13.     public override void Execute()  
  14.     {  
  15.         Debug.Log("Eat "   name);  
  16.     }  
  17. }  


Weapon.cs copy

  1. using UnityEngine;  
  2. using System.Collections;  
  3. using System;  
  4.   
  5. public class Weapon : Item  
  6. {  
  7.     public override void Execute()  
  8.     {  
  9.         Debug.Log("Use Weapon "   name);  
  10.     }  
  11. }  


FrozenCarpaccio.cs

  1. public class FrozenCarpaccio : Food {  
  2.   
  3.     public FrozenCarpaccio()  
  4.     {  
  5.         name = "=FrozenCarpaccio";  
  6.     }  
  7. }  

Sword.cs copy
  1. public class Sword : Weapon {  
  2.   
  3.     public Sword()  
  4.     {  
  5.         name = "Sword";  
  6.     }  
  7. }  

代码简单意思一下,具体到游戏肯定有更多的属性和方法。


现在出现的要求是根据类名来动态创建对象。

常见的方法就是一堆的switchcase....

下面用反射的方式来处理。

工厂类如下

  1. using UnityEngine;  
  2. using System.Collections;  
  3. using System.Collections.Generic;  
  4. using System;  
  5.   
  6. public class ItemFactory  {  
  7.     public static Dictionary<string, Type> foodClassesDict = new Dictionary<string, Type>();  
  8.     public static Dictionary<string, Type> weaponClassesict = new Dictionary<string, Type>();  
  9.   
  10.     public static void CollectAllEntityClasses()  
  11.     {  
  12.         foodClassesDict.Clear();  
  13.         weaponClassesict.Clear();  
  14.         System.Reflection.Assembly[] AS = System.AppDomain.CurrentDomain.GetAssemblies();  
  15.   
  16.         for (int i = 0; i < AS.Length; i )  
  17.         {  
  18.             Type[] types = AS[i].GetTypes();  
  19.             for (int j = 0; j < types.Length; j )  
  20.             {  
  21.                 string className = types[j].Name;  
  22.                 if (types[j].IsSubclassOf(typeof(Food)))  
  23.                 {  
  24.                     Debug.Log("Food"   className);  
  25.                     foodClassesDict.Add(className, types[j]);  
  26.                 } else if (types[j].IsSubclassOf(typeof(Weapon)))  
  27.                 {  
  28.                     Debug.Log("Weapon"   className);  
  29.                     weaponClassesict.Add(className, types[j]);  
  30.                 }  
  31.             }  
  32.         }  
  33.     }  
  34.   
  35.     public static Food CreateFoodByClassName(string name)  
  36.     {  
  37.         Type foodType = null;  
  38.         if (foodClassesDict.TryGetValue(name, out foodType))  
  39.         {  
  40.             return Activator.CreateInstance(foodType) as Food;  
  41.         }  
  42.         else  
  43.         {  
  44.             return null;  
  45.         }  
  46.     }  
  47.   
  48.     public static Weapon CreateWeaponByClassName(string name)  
  49.     {  
  50.         Type weaponType = null;  
  51.         if (weaponClassesict.TryGetValue(name, out weaponType))  
  52.         {  
  53.             return Activator.CreateInstance(weaponType) as Weapon;  
  54.         }  
  55.         else  
  56.         {  
  57.             return null;  
  58.         }  
  59.     }  
  60. }  


代码非常简单,在使用工厂之前,首先要通过反射,将类型的信息都记录到对应的Dictionary里面, 创建对象的时候只要调用对应的静态方法就可以了。


测试代码

  1. using UnityEngine;  
  2. using System.Collections;  
  3.   
  4. public class ItemGenerator : MonoBehaviour {  
  5.     ItemFactory itemFactory;  
  6.     // Use this for initialization  
  7.     void Start () {  
  8.         ItemFactory.CollectAllEntityClasses();  
  9.         itemFactory = new ItemFactory();  
  10.     }  
  11.       
  12.     // Update is called once per frame  
  13.     void Update () {  
  14.         if(Input.GetKeyDown(KeyCode.F5))  
  15.         {  
  16.             MysteryMeat mysteryMeat = ItemFactory.CreateFoodByClassName("MysteryMeat"as MysteryMeat;  
  17.             mysteryMeat.Execute();  
  18.         }  
  19.   
  20.         if (Input.GetKeyDown(KeyCode.F6))  
  21.         {  
  22.             Dagger dagger = ItemFactory.CreateWeaponByClassName("Dagger"as Dagger;  
  23.             dagger.Execute();  
  24.         }  
  25.     }  
  26. }  


运行结果





增加可配置脚本

现在的需求是,需要用json脚本来配置一些item的属性,这样做的好处是显而易见的 - 灵活!

json的内容如下:

copy

  1. [  
  2.    {  
  3.         "class": "FrozenCarpaccio",  
  4.         "name":"Frozen Carpaccio",  
  5.         "level":"1",  
  6.         "describe":"It's a piece of frozen raw meat. The only way to eat it is by cutting thin slices of it. And this way it's suprisingly good."  
  7.     },  
  8.     {  
  9.         "class": "MysteryMeat",  
  10.         "name":"the MysteryMeat",  
  11.         "level":"1",  
  12.         "describe":"Eat at your own risk!"  
  13.     },  
  14. ]  
  15.      


具体的思路是在工厂中添加一个静态函数,用于加载所有class的配置属性,用json data的方式存起来。在创建对应类的时候用存好的jsondata来给对应的变量赋值。

在Factory中添加

  1. public static Dictionary<string, JsonData> classInfoDict = new Dictionary<string, JsonData>();  

对应的方法
  1. public static void CollectAllItemInfo()  
  2.    {  
  3.        classInfoDict.Clear();  
  4.        TextAsset[] foodTables = Resources.LoadAll("Data/Food");  
  5.        foreach (TextAsset table in foodTables)  
  6.        {  
  7.            string jsonStr = table.text;  
  8.            JsonData content = JsonMapper.ToObject(jsonStr);  
  9.            if (content != null)  
  10.            {  
  11.                foreach (JsonData subclass in content)  
  12.                {  
  13.                    if (LitJsonUtil.JsonDataContainsKey(subclass, "class"))  
  14.                    {  
  15.                        string classname = subclass["class"].ToString();  
  16.                        if (!classInfoDict.ContainsKey(classname))  
  17.                        {  
  18.                            classInfoDict.Add(classname, subclass);  
  19.                        }  
  20.                    }  
  21.                }  
  22.   
  23.            }  
  24.        }  
  25.    }  


在Item类中添加虚方法,用于初始化 copy

  1. public abstract void InitializeByJsonData(JsonData data);  

Food类中添加实现 copy
  1. public override void InitializeByJsonData(JsonData data)  
  2.   {  
  3.       if (LitJsonUtil.JsonDataContainsKey(data, "name"))  
  4.       {  
  5.           name = data["name"].ToString();  
  6.       }  
  7.       if (LitJsonUtil.JsonDataContainsKey(data, "level"))  
  8.       {  
  9.           level = Int32.Parse(data["level"].ToString());  
  10.       }  
  11.       if (LitJsonUtil.JsonDataContainsKey(data, "describe"))  
  12.       {  
  13.           describe = data["describe"].ToString();  
  14.       }  
  15.   }  


如果子类中有特殊的属性药初始化,可以通过override这个方法来处理。

这里还可以通过反射获取类型的变量名来自动匹配json中的key和类型的成员,然后通过SetValue方法来进行赋值。


工厂里面创建FoodItem对应的方法也要稍微改一下 copy

  1. public static Food CreateFoodByClassName(string className)  
  2. {  
  3.     Type foodType = null;  
  4.     if (foodClassesDict.TryGetValue(className, out foodType))  
  5.     {  
  6.         Food tmp = Activator.CreateInstance(foodType) as Food;  
  7.         tmp.InitializeByJsonData(classInfoDict[className]);  
  8.         return tmp;  
  9.     }  
  10.     else  
  11.     {  
  12.   
  13.         return null;  
  14.     }  
  15. }  


测试代码不变,可以选择把describe打印出来看看。





参考

反射(C# 和 Visual Basic) - https://msdn.microsoft.com/zh-cn/library/ms173183.aspx

动态加载和使用类型 - https://msdn.microsoft.com/zh-cn/library/k3a58006.aspx

C# 反射(Reflection)- http://www.runoob.com/csharp/csharp-reflection.html

详解C#编程中的反射机制与方法 - http://developer.51cto.com/art/200904/118971_all.htm

LitJson - https://lbv.github.io/litjson/

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引