IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

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

    qp120291570发表于 2016-04-09 19:11:22
    love 0

    简介

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



沪ICP备19023445号-2号
友情链接