45

Attribute特性小案例-俄罗斯世界杯球迷入境-腾讯游戏学院

 5 years ago
source link: http://gad.qq.com/article/detail/288040
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
这是一篇之前写的老文章,当时在复习Attribute的知识,又赶上在世界杯期间,就用世界杯来做一个Attribute的小案例来加深应用和理解

功能简述:

为了方便球迷“出入”俄罗斯观看世界杯比赛,在球迷购买了正式比赛任意场次的门票以后,可以通过球票相关的凭证信息在FIFA的官网申请FAN ID(一个球迷的“身份证”)

FAN ID是何物?

这是一个球迷的证件 ,上面有你护照的信息,姓名等等,FAN ID有如下几个作用:
1.凭借FAN ID 球迷可以在世界杯期间无限次的“进出”俄罗斯,这张FAN ID就相当于俄罗斯的签证Visa。
2.比赛日当天,凭FAN ID可以免费乘坐公共交通,如地铁,公交,甚至是往返城市的铁路,均是免费的。

下面就简单以这个小例子来演示一下特性Attribute的应用。实现两个功能:
1.外国公民入境--持有俄罗斯的VISA或者球迷FAN ID均可入境(VISA和FAN ID有其一即可)
2.比赛日当天--凭FAN ID可以免费乘坐公共交通。


我们先定义特性类:

[AttributeUsage(AttributeTargets.Field)]
public sealed class IDENTITY_AUTHAttribute:System.Attribute
{
	public string FAN_ID{ get; set;}
	public string FAN_NAME{ get; set;}
	public string VISA_ID{ get; set;}
}
[AttributeUsage(AttributeTargets.Field)]

我们指定,IDENTITY_AUTHA特性只能应用在字段Field上。

解释说明:
定义IDENTITY_AUTHAttribute特性类,定义三个属性properties:

FAN_ID
FAN_NAME
VISA_ID
球迷只需要持有FAN ID就可以了,但我们在入境的途中,发生了一件小插曲,我的名字叫WANG HUAN,我们在FIFA注册的时候,用的英文名字的习惯,姓和名是反着的,填写的HUAN WANG,但入境时,俄罗斯那边说你的名字和你护照的的名字不符,因为是颠倒的,要求你改掉......必须打电话进行更正.....险些没有赶上飞机。
VISA_ID 即是你申请的俄罗斯签证,并假定它在有效期内。


将该特性应用在下面的公民上面,标识身份。


定义公民类:

public class Citizen{
	public string passportNumber;
	public string name;
	public int age;
	public int gender;//1-male 2-female
	public Citizen(string passportNumber,string name,int age,int gender)
	{
		this.passportNumber = passportNumber;
		this.name = name;
		this.age = age;
		this.gender = gender;
	}
}

解释说明:
定义Citizen类,添加了一些常用的字段Field,主要用到pasportNumber和name。


下面的代码有点点儿长,但都很简单,我还是分段来说,先通过Attribute为几个公民配置相关的信息,并将他们加入到容器中,方便我们遍历。

[IDENTITY_AUTH(FAN_ID="132993994",FAN_NAME="huanwang")]
	public Citizen wanghuan;
	[IDENTITY_AUTH(FAN_ID="663938249",FAN_NAME="feipeng")]
	public Citizen pengfei;
	[IDENTITY_AUTH(FAN_ID="991240982",FAN_NAME="gumenghua")]
	public Citizen gumenghua;
	[IDENTITY_AUTH(VISA_ID="9865326014343")]
	public Citizen bajia;
	[IDENTITY_AUTH]
	public Citizen hulei;

解释说明:
我们定义了5个公民,每个公民均使用IDENTITY_AUTH特性指定了不同的信息,有的只有VISA ID,也有的连护照都没有。

下面,初始化这些公民的信息,并加入到容器中。

public List<Citizen> CitizenList = new List<Citizen> ();
	// Use this for initialization
	void Start () {
		wanghuan = new Citizen ("10001", "wanghuan", 29, 1);
		pengfei = new Citizen ("10002", "pengfei", 30, 1);
		gumenghua = new Citizen ("10003", "gumenghua", 32, 2);
		bajia = new Citizen("10004","bajia",29,2);
		hulei = new Citizen ("10005", "hulei", 33, 1);
		CitizenList.Add (wanghuan);
		CitizenList.Add (pengfei);
		CitizenList.Add (gumenghua);
		CitizenList.Add (bajia);
		CitizenList.Add (hulei);
	}

10001,10002......这些是passportNumber中国公民的护照号码。后面的参数分别是name名字,age年龄,gender性别。

我们先实现第一个功能:

外国公民入境--持有俄罗斯的VISA或者球迷FAN ID均可入境(VISA和FAN ID有其一即可)

实现方式是通过反射获取公民身上的IDENTITY_AUTH特性,判断VISA_ID,如果没有则判断FAN_ID&&FAN_NAME。

(这里FAN_ID和FAN_NAME要同时进行校验,FAN_NAME正确的应该是你护照上的全拼,可以看到,上面公民wanghuan的身份信息是有误的,FAN_NAME="huangwang",而中国护照上的名字则是"wanghuan",这样无法通过校验)

//check in
		foreach (var citizen in CitizenList) {
			CheckIn (citizen);
		}

外国公民入境方法实现:

/// <summary>
	/// Checks in
	/// </summary>
	/// <returns><c>true</c>, if in was checked, <c>false</c> otherwise.</returns>
	/// <param name="val">Value.</param>
	public bool CheckIn(Citizen val)
	{
		Type t = typeof(AirportCheckIn);//.GetType ();
		FieldInfo info = t.GetField (val.name);
		if (info.IsDefined (typeof(IDENTITY_AUTHAttribute),false)) {
			IDENTITY_AUTHAttribute attribute = (IDENTITY_AUTHAttribute)Attribute.GetCustomAttribute (info, typeof(IDENTITY_AUTHAttribute));
			if (attribute != null) {
				string str = "";
				if (!string.IsNullOrEmpty(attribute.VISA_ID)) {
					//通过VISA入境
					Debug.Log(val.name+" has the VISA of Russia.But do not have permission for free Public Transportation.");
				} else {
					str = val.name+" do NOT have the VISA of Russia.";
					if (!string.IsNullOrEmpty (attribute.FAN_ID)) {
						str += " But " + val.name + " has the FAN ID,we need to check it!";
						if (attribute.FAN_NAME.Equals (val.name)) {//compare with name
							str += val.name + " FAN ID was approved,Public Transportation are free for you.";
							Debug.Log (str);
						} else {
							str += " Unfortunately," + val.name + " FAN ID is not same as passport name,please call FIFA FAN ID CENTER to change it!";
							Debug.Log (str);
							return false;
						}
					} else {
						Debug.Log(val.name+" do not have VISA and FAN ID! REJECTED!");
						return false;
					}
				}
			}
		}
		return true;
	}

这里的AirportCheckIn类是我定义公民信息的类,上面代码中我隐去了。

代码的排版上有点儿问题,可以copy到notepad++上查看,代码很简单,先判断VISA ID,如果有VISA ID(这里我们假定VISA ID只要配置均是正确的),则可以直接入境,如果VISA ID不存在,则继续判断FAN ID和FAN NAME做校验,如果也有效,则验证通过,可以入境。

那么,第二个功能也就更加的简单了。

比赛日当天--凭FAN ID可以免费乘坐公共交通。

//free public transportation
		foreach (var citizen in CitizenList) {
			CheckPublicTransportationFree (citizen);
		}

判断是否可以乘坐免费公共交通的方法:(假定我们当前就是比赛日,因为只有比赛日才可以有免费乘坐公共交通的机会)
/// <summary>
	/// Checks the public transportation free.
	/// </summary>
	/// <returns><c>true</c>, if public transportation free was checked, <c>false</c> otherwise.</returns>
	/// <param name="val">Value.</param>
	public bool CheckPublicTransportationFree(Citizen val)
	{
		Type t = typeof(AirportCheckIn);//.GetType ();
		FieldInfo info = t.GetField (val.name);
		if (info.IsDefined (typeof(IDENTITY_AUTHAttribute),false)) {
			IDENTITY_AUTHAttribute attribute = (IDENTITY_AUTHAttribute)Attribute.GetCustomAttribute (info, typeof(IDENTITY_AUTHAttribute));
			if (attribute != null) {
				string str = "";
				if (string.IsNullOrEmpty(attribute.FAN_ID)) {
					Debug.Log(val.name+" do not have FAN ID,you should buy tickets.");
				} else {
					str += val.name + " has the FAN ID.";
					if (attribute.FAN_NAME.Equals (val.name)) {//compare with name
						str += "and FAN ID was approved,Public Transportation are free for you! Enjoy the FIFA World Cup Show!";
						Debug.Log (str);
					} else {
						str += " Unfortunately," + val.name + " FAN ID is not same as passport name,please call FIFA FAN ID CENTER to change it!";
						Debug.Log (str);
						return false;
					}
					}
				}
			}
		return true;
	}

解释说明:
只需要通过反射获取IDENTITY_AUTH特性,判断FAN_ID&&FAN_NAME即可。

控制台输出:

5bd7ac957a873.png


上面的例子仅仅是为了演示Attribute的应用,但在实际上的开发中,尤其是在移动端,还是不要频繁的使用反射,上面的列子并没有调用传递参数相关的API,反射需要将参数方向在一个object[],这个过程可能会产生boxing/unboxing,会在拖管堆上分配内存空间等等消耗,而且反射是依赖字符串的,依赖字符串便会有遍历搜索的情况,效率比较低。

比如上面GetField的源码在C#中的实现如下:

public override FieldInfo GetField(String name, BindingFlags bindingAttr) 
        {
            if (name == null) throw new ArgumentNullException();
            Contract.EndContractBlock();
            bool ignoreCase;
            MemberListType listType;
            RuntimeType.FilterHelper(bindingAttr, ref name, out ignoreCase, out listType);
            RuntimeFieldInfo[] cache = Cache.GetFieldList(listType, name);
            FieldInfo match = null;
            bindingAttr ^= BindingFlags.DeclaredOnly;
            bool multipleStaticFieldMatches = false;
            for (int i = 0; i < cache.Length; i++)
            {
                RuntimeFieldInfo fieldInfo = cache[i];
                if ((bindingAttr & fieldInfo.BindingFlags) == fieldInfo.BindingFlags)
                {
                    if (match != null)
                    {
                        if (Object.ReferenceEquals(fieldInfo.DeclaringType, match.DeclaringType))
                            throw new AmbiguousMatchException(Environment.GetResourceString("Arg_AmbiguousMatchException"));
                        if ((match.DeclaringType.IsInterface == true) && (fieldInfo.DeclaringType.IsInterface == true))
                            multipleStaticFieldMatches = true;
                    }
                    if (match == null || fieldInfo.DeclaringType.IsSubclassOf(match.DeclaringType) || match.DeclaringType.IsInterface)
                        match = fieldInfo;
                }
            }
            if (multipleStaticFieldMatches && match.DeclaringType.IsInterface)
                throw new AmbiguousMatchException(Environment.GetResourceString("Arg_AmbiguousMatchException"));
            return match;
        }
需要进行很多层的调用,移动端,尤其考虑性能角度,不是不能用,是尽量少用,但频繁使用是不可取的。

感谢您的阅读,如文中有误,欢迎指正,大家共同提高。




About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK