基於C#反射機制的工廠模式

簡介

反射提供了描述程序集、模塊和類型的對象(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/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章