您的位置:首页 > 编程语言 > C#

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

2017-08-06 21:06 549 查看

简单介绍

反射提供了描写叙述程序集、模块和类型的对象(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;
}
}
}


代码很easy。在使用工厂之前,首先要通过反射。将类型的信息都记录到相应的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/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: