簡介
反射提供了描述程序集、模塊和類型的對象(Type 類型)。可以使用反射動態創建類型的實例,將類型綁定到現有對象,或從現有對象獲取類型並調用其方法或訪問其字段和屬性。如果代碼中使用了特性,可以利用反射來訪問它們。
這裏的類型信息包括類型的方法,變量名稱,類型等信息。
基於反射機制的工廠模式
如下圖所示,遊戲中常用的掉落物模型,Item是基類,定義了一些基礎屬性,也定義了一些abstract方法。
Food和Weapon繼承自Item,表示一類Item,再下一層的類就定義了具體的Item。代碼如下:
Item.cs
using UnityEngine;
using System.Collections;
public abstract class Item {
protected string name;
protected int level;
protected int durability;
protected int maxDurability = 10;
protected bool isStackable;
protected string describe;
public string Name
{
get { return name; }
set { name = value; }
}
public string Level
{
get { return name; }
set { name = value; }
}
public int Durability
{
get { return durability; }
set { durability = value; }
}
public abstract void Execute();
public void Upgrade()
{
level++;
}
public void Fix()
{
durability = maxDurability;
}
public virtual bool IsEquipped()
{
return false;
}
}
Food.cs
using UnityEngine;
using System.Collections;
using System;
public class Food : Item {
public Food()
{
isStackable = true;
name = "Food";
}
public override void Execute()
{
Debug.Log("Eat " + name);
}
}
Weapon.cs
using UnityEngine;
using System.Collections;
using System;
public class Weapon : Item
{
public override void Execute()
{
Debug.Log("Use Weapon " + name);
}
}
FrozenCarpaccio.cs
public class FrozenCarpaccio : Food {
public FrozenCarpaccio()
{
name = "=FrozenCarpaccio";
}
}
Sword.cs
public class Sword : Weapon {
public Sword()
{
name = "Sword";
}
}
代碼簡單意思一下,具體到遊戲肯定有更多的屬性和方法。
現在出現的要求是根據類名來動態創建對象。
常見的方法就是一堆的switchcase....
下面用反射的方式來處理。
工廠類如下
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
public class ItemFactory {
public static Dictionary<string, Type> foodClassesDict = new Dictionary<string, Type>();
public static Dictionary<string, Type> weaponClassesict = new Dictionary<string, Type>();
public static void CollectAllEntityClasses()
{
foodClassesDict.Clear();
weaponClassesict.Clear();
System.Reflection.Assembly[] AS = System.AppDomain.CurrentDomain.GetAssemblies();
for (int i = 0; i < AS.Length; i++)
{
Type[] types = AS[i].GetTypes();
for (int j = 0; j < types.Length; j++)
{
string className = types[j].Name;
if (types[j].IsSubclassOf(typeof(Food)))
{
Debug.Log("Food" + className);
foodClassesDict.Add(className, types[j]);
} else if (types[j].IsSubclassOf(typeof(Weapon)))
{
Debug.Log("Weapon" + className);
weaponClassesict.Add(className, types[j]);
}
}
}
}
public static Food CreateFoodByClassName(string name)
{
Type foodType = null;
if (foodClassesDict.TryGetValue(name, out foodType))
{
return Activator.CreateInstance(foodType) as Food;
}
else
{
return null;
}
}
public static Weapon CreateWeaponByClassName(string name)
{
Type weaponType = null;
if (weaponClassesict.TryGetValue(name, out weaponType))
{
return Activator.CreateInstance(weaponType) as Weapon;
}
else
{
return null;
}
}
}
代碼非常簡單,在使用工廠之前,首先要通過反射,將類型的信息都記錄到對應的Dictionary裏面, 創建對象的時候只要調用對應的靜態方法就可以了。
測試代碼
using UnityEngine;
using System.Collections;
public class ItemGenerator : MonoBehaviour {
ItemFactory itemFactory;
// Use this for initialization
void Start () {
ItemFactory.CollectAllEntityClasses();
itemFactory = new ItemFactory();
}
// Update is called once per frame
void Update () {
if(Input.GetKeyDown(KeyCode.F5))
{
MysteryMeat mysteryMeat = ItemFactory.CreateFoodByClassName("MysteryMeat") as MysteryMeat;
mysteryMeat.Execute();
}
if (Input.GetKeyDown(KeyCode.F6))
{
Dagger dagger = ItemFactory.CreateWeaponByClassName("Dagger") as Dagger;
dagger.Execute();
}
}
}
運行結果
增加可配置腳本
現在的需求是,需要用json腳本來配置一些item的屬性,這樣做的好處是顯而易見的 - 靈活!
json的內容如下:
[
{
"class": "FrozenCarpaccio",
"name":"Frozen Carpaccio",
"level":"1",
"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."
},
{
"class": "MysteryMeat",
"name":"the MysteryMeat",
"level":"1",
"describe":"Eat at your own risk!"
},
]
具體的思路是在工廠中添加一個靜態函數,用於加載所有class的配置屬性,用json data的方式存起來。在創建對應類的時候用存好的jsondata來給對應的變量賦值。
在Factory中添加
public static Dictionary<string, JsonData> classInfoDict = new Dictionary<string, JsonData>();
對應的方法
public static void CollectAllItemInfo()
{
classInfoDict.Clear();
TextAsset[] foodTables = Resources.LoadAll<TextAsset>("Data/Food");
foreach (TextAsset table in foodTables)
{
string jsonStr = table.text;
JsonData content = JsonMapper.ToObject(jsonStr);
if (content != null)
{
foreach (JsonData subclass in content)
{
if (LitJsonUtil.JsonDataContainsKey(subclass, "class"))
{
string classname = subclass["class"].ToString();
if (!classInfoDict.ContainsKey(classname))
{
classInfoDict.Add(classname, subclass);
}
}
}
}
}
}
在Item類中添加虛方法,用於初始化
public abstract void InitializeByJsonData(JsonData data);
Food類中添加實現
public override void InitializeByJsonData(JsonData data)
{
if (LitJsonUtil.JsonDataContainsKey(data, "name"))
{
name = data["name"].ToString();
}
if (LitJsonUtil.JsonDataContainsKey(data, "level"))
{
level = Int32.Parse(data["level"].ToString());
}
if (LitJsonUtil.JsonDataContainsKey(data, "describe"))
{
describe = data["describe"].ToString();
}
}
如果子類中有特殊的屬性藥初始化,可以通過override這個方法來處理。
這裏還可以通過反射獲取類型的變量名來自動匹配json中的key和類型的成員,然後通過SetValue方法來進行賦值。
工廠裏面創建FoodItem對應的方法也要稍微改一下
public static Food CreateFoodByClassName(string className)
{
Type foodType = null;
if (foodClassesDict.TryGetValue(className, out foodType))
{
Food tmp = Activator.CreateInstance(foodType) as Food;
tmp.InitializeByJsonData(classInfoDict[className]);
return tmp;
}
else
{
return null;
}
}
測試代碼不變,可以選擇把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/