對(duì)設(shè)計(jì)及重構(gòu)的一點(diǎn)反思
本文是我對(duì)一個(gè)項(xiàng)目中一個(gè)小功能點(diǎn)的演進(jìn)及重構(gòu)過(guò)程的一點(diǎn)反思與心得。
背景:
本項(xiàng)目是一個(gè)電子商務(wù)類(lèi)的網(wǎng)站,其中有個(gè)功能是在訂單狀態(tài)改變到某種狀態(tài)后向客戶(hù)發(fā)送通知短信的功能,短信及網(wǎng)關(guān)功能均已封裝為組建的方式,我們直接調(diào)用即可。
為更清晰明白地說(shuō)明與本主題相關(guān)的功能,在此我以一個(gè)控制臺(tái)的程序方式說(shuō)明代碼的演進(jìn)過(guò)程。
重構(gòu)的演進(jìn)過(guò)程:
最初我們是如大多數(shù)項(xiàng)目一樣,為在規(guī)定的時(shí)間內(nèi)完成相關(guān)功能點(diǎn)而努力奮斗著,這個(gè)功能點(diǎn)的主要代碼如下:
- v1
- static void SendSMS_V1(DataTable dt)
- {
- if (null == dt) return;
- for (int i = 0; i < dt.Rows.Count; i++)
- {
- var row = dt.Rows[i];
- OrderStateEnum state = (OrderStateEnum)((int)row["OrderState"]);
- string template = string.Empty;
- switch (state)
- {
- case OrderStateEnum.UnConfirmed:
- template = "尊敬的{0},你好!你的訂單已成功下達(dá),請(qǐng)盡快付款以便配送。";
- break;
- case OrderStateEnum.Confirmed:
- template = "尊敬的{0},你好!你的訂單(訂單號(hào):{1})已被確認(rèn),請(qǐng)耐心等待。";
- break;
- case OrderStateEnum.Cancel:
- template = "尊敬的{0},你好!你的訂單(訂單號(hào):{1})已被取消,具體原因請(qǐng)上網(wǎng)查看。";
- break;
- case OrderStateEnum.Finish:
- template = "尊敬的{0},你好!你的訂單(訂單號(hào):{1})已完成,網(wǎng)站感謝您的支持與配合,歡迎再次光臨。";
- break;
- default:
- break;
- }
- string content = string.Format(template, row["CustomerName"], row["OrderID"]);
- SendSMS.Send(row["phone"].ToString(), content);
- }
- }
在項(xiàng)目上線初期,這段代碼工作良好。
在運(yùn)營(yíng)一段時(shí)間后運(yùn)營(yíng)部門(mén)同事陸續(xù)提出要在某些地方將產(chǎn)品名稱(chēng)給加上去,在這一改動(dòng)過(guò)程中,我發(fā)現(xiàn)代碼沒(méi)有和數(shù)據(jù)相分離,再者如要增加一個(gè)訂單狀態(tài)后增加相應(yīng)的短信提示或者取消某一個(gè)狀態(tài)的短信提示,這個(gè)改動(dòng)過(guò)程有點(diǎn)麻煩。于是初步想到將短信的內(nèi)容放到配置文件中,在調(diào)用的時(shí)候讀取訂單狀態(tài)對(duì)應(yīng)的配置文件然后格式化即可。如讀取的內(nèi)容為空或者該文件不存在則跳過(guò)不發(fā)送。主要代碼如下:
- v2 內(nèi)容和數(shù)據(jù)分離
- #region v2 內(nèi)容和數(shù)據(jù)分離
- static void SendSMS_V2(DataTable dt)
- {
- if (null == dt) return;
- for (int i = 0; i < dt.Rows.Count; i++)
- {
- var row = dt.Rows[i];
- string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SMSTemplates",
- "V2", row["OrderState"].ToString() + ".txt");
- var template = FileHelper.Read(path);
- if (!string.IsNullOrEmpty(template)) {
- string content = string.Format(template, row["CustomerName"], row["OrderID"], row["ProductName"]);
- SendSMS.Send(row["phone"].ToString(), content);
- }
- }
- }
- #endregion
模版
- 尊敬的{0},你好!你的訂單(訂單號(hào):{1})已被確認(rèn),請(qǐng)耐心等待。
隨著運(yùn)營(yíng)進(jìn)行,運(yùn)營(yíng)部門(mén)不斷地提出將某些字段在某些地方顯示, string.Format 后的參數(shù)不斷地增加,每次改后都要將整個(gè)過(guò)程重新測(cè)試一通,很是讓人頭疼!我的想法是開(kāi)發(fā)這邊提供一個(gè)數(shù)據(jù)標(biāo)簽列表,同時(shí)在后臺(tái)提供操作界面將短信管理模板讓運(yùn)營(yíng)同事自己去修改,不用每次都找我們?有了以上想法,使用一個(gè)自定義的 formatter :
- 自定義參數(shù)格式化
- public class IndexerNamedFormatter : IFormatProvider, ICustomFormatter
- {
- public IndexerNamedFormatter() { }
- public object GetFormat(Type formatType)
- {
- if (formatType == typeof(ICustomFormatter))
- return this;
- throw new TypeAccessException("不匹配的類(lèi)型。");
- }
- public string Format(string format, object arg, IFormatProvider formatProvider)
- {
- if (null == arg)
- throw new ArgumentNullException("參數(shù) arg 不能為 null");
- int indexer = 0;
- bool isIndexed = int.TryParse(format, out indexer);
- //如是 datarow
- if (arg is System.Data.DataRow)
- {
- return GetStringFromDataRow(format, arg, indexer, isIndexed);
- }
- //如是 datareader 之類(lèi)的
- if (arg is System.Data.IDataRecord)
- {
- GetStringFromIDataRecord(format, arg, indexer, isIndexed);
- }
- return string.Empty; ;
- }
- private static void GetStringFromIDataRecord(string format, object arg, int indexer, bool isIndexed)
- {
- var dr = (System.Data.IDataRecord)arg;
- string.Format("{0}", isIndexed ? dr[indexer] : dr[format]);
- }
- string GetStringFromDataRow(string format, object arg, int indexer, bool isIndexed)
- {
- var row = (System.Data.DataRow)arg;
- return string.Format("{0}", isIndexed ? row[indexer] : row[format]);
- }
- v3 使用自定義標(biāo)簽
- #region v3 使用自定義標(biāo)簽
- static void SendSMS_V3(DataTable dt)
- {
- if (null == dt) return;
- IndexerNamedFormatter formatter = new IndexerNamedFormatter();
- for (int i = 0; i < dt.Rows.Count; i++)
- {
- var row = dt.Rows[i];
- string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SMSTemplates",
- "V3", row["OrderState"].ToString() + ".txt");
- var template = FileHelper.Read(path);
- if (!string.IsNullOrEmpty(template))
- {
- string content = string.Format(formatter,
- template, row);
- SendSMS.Send(row["phone"].ToString(), content);
- }
- }
- }
- #endregion
模版
- 尊敬的{0:CustomerName},你好!你的訂單(訂單號(hào):{0:OrderID},產(chǎn)品:產(chǎn)品{0:productname})已被確認(rèn),請(qǐng)耐心等待。
將數(shù)據(jù)字段取出來(lái)后寫(xiě)成一個(gè)標(biāo)簽列表文檔提供給運(yùn)營(yíng)人員后,從此關(guān)于這一塊的修改要求安靜了。
以上只是一個(gè)小小設(shè)計(jì)技巧,這也讓我明白需求的準(zhǔn)確把握與挖掘是何等地重要!往往客戶(hù)今天說(shuō)要這樣,明天要那樣,大概很多人都在抱怨:你們真麻煩!但在需求不斷地出現(xiàn)時(shí)我們是不是在修改的時(shí)候也反思下是不是我們未準(zhǔn)確把握他們所想要的功能而讓功能設(shè)計(jì)出了點(diǎn)問(wèn)題?當(dāng)然我也不崇尚一開(kāi)始就大談設(shè)計(jì),過(guò)度設(shè)計(jì)要付出大量的時(shí)間成本和可能導(dǎo)致實(shí)現(xiàn)的復(fù)雜度的增加。
一點(diǎn)疑問(wèn):
通過(guò)查閱文檔我一直感覺(jué) arg is System.Data.DataRow 這種實(shí)現(xiàn)方式很是別扭,為什么索引器不定義為一個(gè)接口 ?有誰(shuí)有更好的實(shí)現(xiàn)方法麻煩告知,謝謝!
感謝您的閱讀!文中所涉及到的代碼可從此下載。
原文鏈接:http://www.cnblogs.com/infozero/archive/2013/03/05/2944106.html