反射的妙用_项目中的时间配置问题

内容摘要

1:阐述问题

2:分析问题,解决问题

3:演示解决方案

1:阐述问题

      有时候,我们会遇上这样一个问题:有很多条件 condition1 、condition2 、condition3、condition4 、condition5......这些条件各不相同,可能同时配置其中几个,这几个条件有一个交集,交集内部就是我们需要的。

  给一个实例吧。用户在系统中配置了一个时间条件集合,用户可以按照年、月、周或者日来配置,按照其中一种来配置,下面有很多条件可以选择,其中开始日期和时间是必须配置的,最后会形成一个xml信息存储在数据库里面,我们会用当前时间判断每个用户的配置条件,如何符合,我们把他的邮箱拿出放到一个字符串尾部,不符合则不管,最后这个字符串就是所有符合用户配置条件的邮箱集合,我们可以把我们的信息推送给这些用户。其中xml按照月配置的如下:

 1 <ScheduleDefinition xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 2   <StartDateTime xmlns="http://schemas.microsoft.com/sqlserver/reporting/2010/03/01/ReportServer">2012-07-21T22:00:00.000+08:00</StartDateTime>
 3   <MonthlyDOWRecurrence xmlns="http://schemas.microsoft.com/sqlserver/reporting/2010/03/01/ReportServer">
 4     <WhichWeek>FirstWeek</WhichWeek>
 5     <DaysOfWeek>
 6       <Monday>true</Monday>
 7     </DaysOfWeek>
 8     <MonthsOfYear>
 9       <January>true</January>
10       <February>true</February>
11       <March>true</March>
12       <April>true</April>
13       <May>true</May>
14       <June>true</June>
15       <July>true</July>
16       <August>true</August>
17       <September>true</September>
18       <October>true</October>
19       <November>true</November>
20       <December>true</December>
21     </MonthsOfYear>
22   </MonthlyDOWRecurrence>
23 </ScheduleDefinition>
View Code

StartDateTime是开始时间,它是一个基本条件,每个配置信息里面都必须存在。MonthlyDOWRecurrence是代表是按照月来配置的(也可能是WeeklyRecurrence周、DailyRecurrence日等)这个xml节点下面存在很多条件,例如WhichWeek是代表哪一周,DaysOfWeek表示每周的哪一天,又或者MonthsOfYear是每年的哪几个月。

     问题就是这样,我从数据库MatchData得到所有用户的配置的数据集 Id、Email、MatchData(sql :SELECT [Id],[Email],[MatchData]FROM [MatchData].[dbo].[EmailInTime]),MatchData就是我们的配置xml信息,从集合中找出所有符合条件的用户Email。

2:分析问题,解决问题

     如果你遇上了啦,该如何处理呢?下面是我的处理方法,如果你有更好的办法,请给我留意,不吝赐教,谢谢!

     有人可以会总结一下有多少个条件,例如10个condition,然后写成是个if语句从上到下去判断是否存在这个条件,如果存在,判断是否当前时间符合这个条件。

 1 private bool IsMatchWithMatchData(string matchData, DateTime nowDt)
 2         {
 3             bool result=true;
 4             if(condition1 in matchData)
 5             {
 6                 if(nowDt is match matchData by condition1)
 7                 {
 8                     result+=true
 9                 }
10                 else
11                 {
12                     result+=false;
13                 }
14             }
15 
16             if(condition2 in matchData)
17             {
18                 if(nowDt is match matchData by condition2)
19                 {
20                     result+=true
21                 }
22                 else
23                 {
24                     result+=false;
25                 }
26             }
27             //.
28             //.条件3到9
29             //.
30             if(condition10 in matchData)
31             {
32                 if(nowDt is match matchData by condition10)
33                 {
34                     result+=true
35                 }
36                 else
37                 {
38                     result+=false;
39                 }
40             }
41             return result;
42         }
View Code

这是一个伪代码,简单模拟了一下,大家都会发现很多问题,

1:判断是否存在这个条件在xml里面,耗性能;

2:有很多条件是不用去判断的,最好是xml配置文件里面有什么条件,我们就去判断什么条件;

3:扩展性差,当我们需要条件更多的条件的时候,需要修改已有的代码。

如何解决这三个问题呢?我们可以去遍历xml配置信息,我们遇到什么条件的时候,我们反射到封装好的的条件方法里面。遍历xml,我们就可以解决第1个问题;遇上什么条件我们判断什么条件,这个可以解决第2个问题;利用每个条件都封装成方法,遇到条件的时候反射到对应的方法,就解决了第3个问题。

3:演示解决方案

      下面我把解决方案的数据库和代码展现给大家,希望能看到你的宝贵意见!

数据库结构如下:

数据库在代码的App_Data文件夹下,下载代码可以得到。

定义好数据库后,我们看一下解决方案:

      工程下面包含三个类SqlHelper、AnalyseMatchData和MatchWithMatchData:SqlHelper类是网上下载的帮助工具类,用于操作数据库;AnalyseMatchData类只包含一个公共的方法GetMailAddressByMatchData,作为对外提供的API接口;MatchWithMatchData类是程序集内部类internal class ,因为我们一个会分层,把这下代码放在一个类库里面,这里面包含的是每个条件反射的方法。然后就是一个web.config配置文件和一个Index页面,Index页面用于测试效果,web.config配置文件里面当然就是连接字符串了啦,如果要用本人的代码就需要附加数据库和修改配置的连接字符串了,相信对大家都是小KS了。

 1         private readonly string connectionStr;
 2         private readonly string matchDataSqlStr;
 3         MatchWithMatchData matchFunction;//use to reflect the xml node to the corresponding function
 4         Type matchType;//the MatchWithMatchData class 's type
 5         XmlDocument xmldoc;
 6         DateTime nowDt;
 7 
 8         public AnalyseMatchData()
 9         {
10             connectionStr = System.Configuration.ConfigurationManager.ConnectionStrings["MatchData"].ConnectionString;
11             matchDataSqlStr = "SELECT [Id],[Email],[MatchData]FROM [MatchData].[dbo].[EmailInTime]";
12             matchFunction = new MatchWithMatchData();
13             matchType = matchFunction.GetType();
14             xmldoc = new XmlDocument();
15         }

首先是定义需要的使用的全局变量,然后再构造函数里面初始化变量,细心的朋友会看到当前时间nowDt变量没有初始化,因为应该放在后面的api接口里面,当前时间应该是用户调用接口的时间。

 1         /// <summary>
 2         /// the api to get the email address of  match all conditions 
 3         /// </summary>
 4         /// <returns></returns>
 5         public string GetMailAddressByMatchData()
 6         {
 7             StringBuilder emailAddresses = new StringBuilder();
 8             nowDt = Convert.ToDateTime("2013-06-26T22:15:00.000+08:00");//DateTime.Now;//
 9             using (SqlDataReader dr = SqlHelper.ExecuteReader(connectionStr, CommandType.Text, matchDataSqlStr))
10             {
11                 while (dr.Read())
12                 {
13                     string matchData = dr.GetString(dr.GetOrdinal("MatchData"));
14                     if (IsMatchWithMatchData(matchData, nowDt))
15                     {
16                         string emails = dr.GetString(dr.GetOrdinal("Email"));
17                         emailAddresses.Append(string.IsNullOrWhiteSpace(emails) ? string.Empty : ";" + emails);
18                     }
19                 }
20             }
21             return emailAddresses.Length > 0 ? emailAddresses.Remove(0, 1).ToString() : string.Empty;
22         }
23         /// <summary>
24         /// judge whether the current time match all conditions in the xml string 'matchData'
25         /// </summary>
26         /// <param name="matchData"></param>
27         /// <param name="nowDt"></param>
28         /// <returns></returns>
29         private bool IsMatchWithMatchData(string matchData, DateTime nowDt)
30         {
31             bool result = true;
32             xmldoc.LoadXml(matchData);
33             try
34             {
35                 XmlNodeList xmllist = xmldoc.SelectSingleNode("ScheduleDefinition").ChildNodes;
36                 DateTime startDt = Convert.ToDateTime(xmllist[0].InnerText).ToLocalTime();
37                 matchFunction.InitData(startDt, nowDt);
38                 foreach (XmlNode xmlnode in xmllist)
39                 {
40                     MethodInfo method = matchType.GetMethod(xmlnode.Name + "Function", BindingFlags.Instance | BindingFlags.Public);
41                     object oj = method.Invoke(matchFunction, new object[] { xmlnode.OuterXml });
42                     if (result)
43                         result = result && Convert.ToBoolean(oj);
44                     else
45                         break;
46                 }
47                 return result;
48             }
49             catch
50             {
51                 return false;
52             }
53         }
    GetMailAddressByMatchData方法是对应的 api接口他需要遍历从数据库里面得到的数据集,最后把符合条件的email地址返回给调用方。其中比较关键的是IsMatchWithMatchData方法用于判断xml配置条件和当前的时间nowDt的比较,如何前期时间在配置的时间条件内返回为真true否则为假false。遍历xml节点,反射到对应的方法,方法的名称是很有讲究的,是节点名称后面加一个后缀xmlnode.Name + "Function"。
这样我们达到了业务的分离,每一个方法表示一个条件业务,需要扩展的时候,新增一种xml节点类型,然后新添加一个方法,其他的一概不用管,是不是很方便!
有人说反射消耗性能,何不使用switch想简单工厂一样,遇到什么节点调用什么方法。其实我不太提倡这种方式:
1:扩张性没有反射好,扩展的时候需要修改代码,不符合开闭原则;
2:xml现在是两层条件,如果用switch,需要嵌套地使用switch了,如果以后变成三层的呢?所有灵活度也不如反射。
3:其实现在反射的性能愈来愈好啦,几乎和一般的方式调用很接近了。我曾使用一万六千多条在3秒内完成,相信我们的员工没有这么多吧!也可以做一些其他的优化,例如利用缓冲,还有及时条件中只要一个条件为false,就直接返回为假,不用检测所有的XML里面的条件,相信你有更多的方式优化它。
反射的方法类MatchWithMatchData里面:
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Reflection;
  5 using System.Web;
  6 using System.Xml;
  7 
  8 namespace MatchData
  9 {
 10     /// <summary>
 11     /// determine whether the current now match with the condition of the xml string 
 12     /// </summary>
 13     internal class MatchWithMatchData
 14     {
 15         DateTime startDt;
 16         DateTime nowDt;
 17         /// <summary>
 18         /// initialize data
 19         /// </summary>
 20         /// <param name="startDt">start datetime</param>
 21         /// <param name="nowDt">now datetime</param>
 22         public void InitData(DateTime startDt, DateTime nowDt)
 23         {
 24             this.startDt = startDt;
 25             this.nowDt = nowDt;
 26         }
 27 
 28         public bool StartDateTimeFunction(string xml)
 29         {
 30             XmlDocument xmldoc = new XmlDocument();
 31             xmldoc.LoadXml(xml);
 32             //whether the date and time is in the range of startDateTime
 33             if (nowDt.Date >= startDt.Date && Math.Abs((startDt.TimeOfDay - nowDt.TimeOfDay).TotalSeconds) < 20)
 34                 return true;
 35             else
 36                 return false;
 37         }
 38 
 39         public bool EndDateFunction(string xml)
 40         {
 41             XmlDocument xmldoc = new XmlDocument();
 42             xmldoc.LoadXml(xml);
 43             DateTime dt = DateTime.Parse(xmldoc["EndDate"].InnerText).ToLocalTime();
 44             if (nowDt.Date <= dt.Date)
 45                 return true;
 46             else
 47                 return false;
 48         }
 49         //-----------------------------------------------------------------------------
 50         public bool MonthlyDOWRecurrenceFunction(string xml)
 51         {
 52             bool result = true;
 53             XmlDocument xmldoc = new XmlDocument();
 54             xmldoc.LoadXml(xml);
 55             foreach (XmlNode xmlnode in xmldoc.ChildNodes[0].ChildNodes)
 56             {
 57                 MethodInfo method = this.GetType().GetMethod(xmlnode.Name + "Function", BindingFlags.Instance | BindingFlags.Public);
 58                 object oj = method.Invoke(this, new object[] { xmlnode.OuterXml });
 59                 if (result)
 60                     result = result && Convert.ToBoolean(oj);
 61                 else
 62                     break;
 63             }
 64             return result;
 65         }
 66 
 67         public bool MonthlyRecurrenceFunction(string xml)
 68         {
 69             return MonthlyDOWRecurrenceFunction(xml);
 70         }
 71 
 72         public bool WeeklyRecurrenceFunction(string xml)
 73         {
 74             return MonthlyDOWRecurrenceFunction(xml);
 75         }
 76 
 77         public bool DailyRecurrenceFunction(string xml)
 78         {
 79             return MonthlyDOWRecurrenceFunction(xml);
 80         }
 81         //-------------------------------------------------------------------------------
 82         // Sub methods to handle sub options
 83         //-------------------------------------------------------------------------------
 84         public bool WhichWeekFunction(string xml)
 85         {
 86             XmlDocument xmldoc = new XmlDocument();
 87             xmldoc.LoadXml(xml);
 88             string WhichWeek = xmldoc["WhichWeek"].InnerText;
 89             double r = (nowDt.Day - (double)nowDt.DayOfWeek);//the month's day of last week sunday 
 90             int nowWeekInt = Convert.ToInt32(Math.Ceiling((r < 0 ? 0 : r) / 7)) + 1;//the index of the current week of the current month
 91             return WhichWeek == WhichWeekToName(nowWeekInt);
 92         }
 93         /// <summary>
 94         /// whether current datetime accord with the condition of every month's days
 95         /// </summary>
 96         /// <param name="xml"></param>
 97         /// <returns></returns>
 98         public bool DaysFunction(string xml)
 99         {
100             XmlDocument xmldoc = new XmlDocument();
101             xmldoc.LoadXml(xml);
102             string day = xmldoc["Days"].InnerText;
103             string[] days = day.Split(',');
104             foreach (string dy in days)
105             {
106                 if (dy.Length >= 3)
107                 {
108                     string[] dys = dy.Split('-');
109                     if (nowDt.Day >= Convert.ToInt32(dys[0]) && nowDt.Day <= Convert.ToInt32(dys[1]))
110                         return true;
111                 }
112                 else
113                 {
114                     if (nowDt.Day == Convert.ToInt32(dy))
115                         return true;
116                 }
117             }
118             return false;
119         }
120 
121         public bool WeeksIntervalFunction(string xml)
122         {
123             XmlDocument xmldoc = new XmlDocument();
124             xmldoc.LoadXml(xml);
125             int weeksInterval = Convert.ToInt32(xmldoc["WeeksInterval"].InnerText) + 1;
126             double ts = (Math.Abs((nowDt - startDt).TotalDays) + Convert.ToInt32(startDt.DayOfWeek) - 7) / 7 % (weeksInterval);
127             return ts < 1;
128         }
129 
130         public bool DaysIntervalFunction(string xml)
131         {
132             XmlDocument xmldoc = new XmlDocument();
133             xmldoc.LoadXml(xml);
134             int daysInterval = Convert.ToInt32(xmldoc["DaysInterval"].InnerText);
135             double ts = Math.Abs((nowDt - startDt).TotalDays) % daysInterval;
136             return ts < 1;
137         }
138 
139         public bool MonthsOfYearFunction(string xml)
140         {
141             XmlDocument xmldoc = new XmlDocument();
142             xmldoc.LoadXml(xml);
143             string monthStr = MonthToName(nowDt.Month);
144             XmlElement nodeElement = xmldoc["MonthsOfYear"][monthStr];
145             if (nodeElement != null && Convert.ToBoolean(nodeElement.InnerText))
146                 return true;
147             else
148                 return false;
149         }
150 
151         public bool DaysOfWeekFunction(string xml)
152         {
153             XmlDocument xmldoc = new XmlDocument();
154             xmldoc.LoadXml(xml);
155             string weekStr = WeekToName(Convert.ToInt32(nowDt.DayOfWeek));
156             XmlElement nodeElement = xmldoc["DaysOfWeek"][weekStr];
157             if (nodeElement != null && Convert.ToBoolean(nodeElement.InnerText))
158                 return true;
159             else
160                 return false;
161         }
162         //-------------------------------------------------------------------------------
163         private string WhichWeekToName(int weekOfMonth)
164         {
165             string nowWeekStr = "LastWeek";
166             switch (weekOfMonth)
167             {
168                 case 1:
169                     nowWeekStr = "FirstWeek";
170                     break;
171                 case 2:
172                     nowWeekStr = "SecondWeek";
173                     break;
174                 case 3:
175                     nowWeekStr = "ThreeWeek";
176                     break;
177                 case 4:
178                     nowWeekStr = "FourtWeek";
179                     break;
180                 default:
181                     break;
182             }
183             return nowWeekStr;
184         }
185 
186         private string MonthToName(int month)
187         {
188             string monthStr = "December";
189             switch (month)
190             {
191                 case 1:
192                     monthStr = "January";
193                     break;
194                 case 2:
195                     monthStr = "February";
196                     break;
197                 case 3:
198                     monthStr = "March";
199                     break;
200                 case 4:
201                     monthStr = "April";
202                     break;
203                 case 5:
204                     monthStr = "May";
205                     break;
206                 case 6:
207                     monthStr = "June";
208                     break;
209                 case 7:
210                     monthStr = "July";
211                     break;
212                 case 8:
213                     monthStr = "August";
214                     break;
215                 case 9:
216                     monthStr = "September";
217                     break;
218                 case 10:
219                     monthStr = "October";
220                     break;
221                 case 11:
222                     monthStr = "November";
223                     break;
224                 default:
225                     break;
226 
227             }
228             return monthStr;
229         }
230 
231         private string WeekToName(int week)
232         {
233             string weekStr = "Sunday";
234             switch (week)
235             {
236                 case 1:
237                     weekStr = "Monday";
238                     break;
239                 case 2:
240                     weekStr = "Tuesday";
241                     break;
242                 case 3:
243                     weekStr = "Tuesday";
244                     break;
245                 case 4:
246                     weekStr = "Thursday";
247                     break;
248                 case 5:
249                     weekStr = "Friday";
250                     break;
251                 case 6:
252                     weekStr = "Saturday";
253                     break;
254                 default:
255                     break;
256             }
257             return weekStr;
258         }
259     }
260 }
View Code

 

   比较有意思的是WhichWeekFunction方法,判断当前日期是哪一周,MonthlyDOWRecurrenceFunction等方法下面,还有条件,又使用了反射的方式调用。整体是如此简单,如此容易理解。测试的时候,把DateTime.Now;//Convert.ToDateTime("2013-06-26T22:15:00.000+08:00");//改成Convert.ToDateTime("2013-06-26T22:15:00.000+08:00");//DateTime.Now;//,这是一个小技巧。这个Convert.ToDateTime("2013-06-26T22:15:00.000+08:00");测试用例下面有数据,总体跟踪一遍,就能理解这个代码
   最后祝大家好运,希望在能够帮助大家,也希望大家能多提意见,觉得有收获的帮助顶一下。
代码下载:https://files.cnblogs.com/zhangxl/MatchData.zip
文章出处:http://www.cnblogs.com/zhangxl/p/reflection.html 

 


posted @ 2013-09-21 21:12  劍聖  阅读(2284)  评论(2编辑  收藏  举报