深入浅出设计守则2
小明收到需求, 需求如下:
麦当鸡系统需要统计消费次数的报表, 报表内容需要列出每一种消费种类的消费次数.
麦当鸡系统中有一个消费种类Enum 叫做ChargeType
public enum ChargeType { DuckNuggets, ChickenNuggets, FrenchFries, FriedChickenChops, GreenBeanSoup}
小明写了统计方法
public ChargeStatistics Compute(ChargeType[] chargeHistory){ var data = new ChargeStatistics(); foreach (var r in chargeHistory) { if (r == ChargeType.DuckNuggets) data.DuckNuggets++; else if (r == ChargeType.ChickenNuggets) data.ChickenNuggets++; else if (r == ChargeType.FrenchFries) data.FrenchFries++; else if (r == ChargeType.FriedChickenChops) data.FriedChickenChops++; else if (r == ChargeType.GreenBeanSoup) data.GreenBeanSoup++; } return data;}
可以改进的地方
观察上述的程式码, 可以发现到
有重複的程式码如果要新增ChargeType 的种类, 要修改统计的程式码设计守则 如果你有大量的资料型别的资料, 就考虑可能将资料组织起来,形成一个物件类.
集合资料的方法有很多种, 资料结构可以用阵列也可以利用集合(List/Dictionary/...).
故统计的结果物件可以修改为
public class ChargeStatistics{ public Dictionary<ChargeType, int> Data { get; set; }}
然后在统计方法内改写为
public ChargeStatistics Compute(ChargeType[] chargeHistory){ var result = new ChargeStatistics(); foreach (var r in chargeHistory) { IncreaseCounter(result.Data, r); } return result;}private void IncreaseCounter(Dictionary<ChargeType, int> data, ChargeType chargeType){ if (!data.TryGetValue(r, out var counter)) { data[r] = 1; } else { data[r] = counter + 1; }}
我们可以直接使用Dictionary 字典的特性, 来统计计算每一种消费种类的次数.
在IncreaseCounter 方法中, 检查data 是否出现了某个ChargeType ,
如果有, 则对应的ChargeType 计数自增如果没有, 则对应的ChargeType 计数 = 1LINQ 查询语法 (LINQ Query Syntax)
在整理资料的时候常常都需要给资料做分组, 以便更进一步的分析及处理,
而C# 有提供分组统计的方法, 我们可以拿来利用.
我们可以利用LINQ Group 语法将chargeHistory 资料做分组, 然后再用Count() 方法统计次数
故Compute() 方法可以改写为
public ChargeStatistics Compute(ChargeType[] chargeHistory){ var result = new ChargeStatistics(); var q1 = from tb1 in chargeHistory group tb1 by tb1 into g1 select new { Key = g1.Key, Count = g1.Count() }; result.Data = q1.ToDictionary(x => x.Key, x => x.Count); return result;}
LINQ 方法语法 (LINQ Method Syntax)
当然假如你不喜欢这种长得有点像SQL 语法的运算式, 你也能改写为Linq Method
public ChargeStatistics Compute(ChargeType[] chargeHistory){ var result = new ChargeStatistics(); var q1 = chargeHistory.GroupBy(x => x) .Select(new { Key = g1.Key, Count = g1.Count() }); result.Data = q1.ToDictionary(x => x.Key, x => x.Count); return result;}
让程式码会说话
最后在这个 "统计结果物件" 这个地方, Data 属性的宣告有点模糊,
public class ChargeStatistics{ public Dictionary<ChargeType, int> Data { get; set; }}
对于不熟悉麦当鸡系统的维护人员可能会不知道这个 Dictionary<ChargeType, int> 里面的 int 代表的是甚么意思.
故我们可以新增一个物件来明确宣告 "一个 ChargeType 的统计资料"
public class ChargeTypeStatistic{ public ChargeType ChargeType { get; set; } public int Count { get; set; }}
然后宣告Data 属性为List ,
这样就可以明确让维护人员知道这个Data 的定义
public class ChargeStatistics{ public List<ChargeTypeStatistic> Data { get; set; }}
当然统计方法也要修改为下面方式
public ChargeStatistics Compute(ChargeType[] chargeHistory){ var result = new ChargeStatistics(); result.Data = chargeHistory.GroupBy(x => x) .Select(new ChargeTypeStatistic { ChargeType = g1.Key, Count = g1.Count() }) .List(); return result;}
如此一来维护人员也比较清楚知道统计结果物件里面的代表意义,
对程式码的理解将不必再依靠繁杂的注解和厚厚的文件.