首页 > Python基础教程 >
-
C#教程之C# BAD PRACTICES: Learn how to make a good code by(2)
数字5让我们的代码变得神秘了起来。
我们必须做些什么让这个变得更具表现性。
我会用另外一种方法来避免魔法数的表述的出现-也就是C#中的常量(关键词是const),我强烈建议在我们的应用程序中专门定义一个静态类来存储这些常量。
在我们的例子中,我是创建了下面的类:
1 public static class Constants 2 { 3 public const int MAXIMUM_DISCOUNT_FOR_LOYALTY = 5; 4 public const decimal DISCOUNT_FOR_SIMPLE_CUSTOMERS = 0.1m; 5 public const decimal DISCOUNT_FOR_VALUABLE_CUSTOMERS = 0.3m; 6 public const decimal DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS = 0.5m; 7 }
经过一定的修改,我们的DiscountManager类就变成了这样了:
1 public class DiscountManager 2 { 3 public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears) 4 { 5 decimal priceAfterDiscount = 0; 6 decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY/100 : (decimal)timeOfHavingAccountInYears/100; 7 switch (accountStatus) 8 { 9 case AccountStatus.NotRegistered: 10 priceAfterDiscount = price; 11 break; 12 case AccountStatus.SimpleCustomer: 13 priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS * price)); 14 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); 15 break; 16 case AccountStatus.ValuableCustomer: 17 priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS * price)); 18 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); 19 break; 20 case AccountStatus.MostValuableCustomer: 21 priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS * price)); 22 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); 23 break; 24 default: 25 throw new NotImplementedException(); 26 } 27 return priceAfterDiscount; 28 } 29 }
我希望你也认同我这个方法会更加使代码自身变得更具有说明性:)
VII:不要再重复啦!
我们可以通过分拆算法的方式来移动我们的计算方法,而不是仅仅简单的复制代码。
我们会通过扩展方法。
首先我们会创建两个扩展方法。
1 public static class PriceExtensions 2 { 3 public static decimal ApplyDiscountForAccountStatus(this decimal price, decimal discountSize) 4 { 5 return price - (discountSize * price); 6 } 7 8 public static decimal ApplyDiscountForTimeOfHavingAccount(this decimal price, int timeOfHavingAccountInYears) 9 { 10 decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY/100 : (decimal)timeOfHavingAccountInYears/100; 11 return price - (discountForLoyaltyInPercentage * price); 12 } 13 }
正如方法的名字一般,我不再需要单独解释一次他们的功能是什么。现在就开始在我们的例子中使用这些代码吧:
1 public class DiscountManager 2 { 3 public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears) 4 { 5 decimal priceAfterDiscount = 0; 6 switch (accountStatus) 7 { 8 case AccountStatus.NotRegistered: 9 priceAfterDiscount = price; 10 break; 11 case AccountStatus.SimpleCustomer: 12 priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS) 13 .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears); 14 break; 15 case AccountStatus.ValuableCustomer: 16 priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS) 17 .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears); 18 break; 19 case AccountStatus.MostValuableCustomer: 20 priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS) 21 .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears); 22 break; 23 default: 24 throw new NotImplementedException(); 25 } 26 return priceAfterDiscount; 27 } 28 }
扩展方法让代码看起来更加友善了,但是这个代码还是静态的类,所以会让你单元测试的时候遇到困难,甚至不可能。那么出于摆脱这个问题的打算我们在最后一步来解决这个问题。我将展示这些是如何简化我们的工作生活的。但是对于我个人而言,我喜欢,但是并不算是热衷粉。
不管怎样,你现在同意我们的代码看起来友善多了这一点么?
那我们就继续下去吧!
VIII:移除那些多余的代码
在写代码的时候原则上是我们的代码越是精简越好。精简的代码的意味着,越少的错误的可能性,在阅读理解代码逻辑的时候花费的时间越少。
所以现在开始精简我们的代码吧。
我们可以轻易发现我们三种客户账户下有着相同的方法:
.ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
我们可不可以只写一次呢?我们之前将未注册的用户放在了抛出异常中,因为我们的折扣率只会计算注册用户的年限,并没有给未注册用户留有功能设定。所以,我们应该给未注册用户设定的时间为多少呢? -0年
那么对应的折扣率也将变成0了,这样我们就可以安全的将折扣率交付给未注册用户使用了,那就开始吧!
1 public class DiscountManager 2 { 3 public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears) 4 { 5 decimal priceAfterDiscount = 0; 6 switch (accountStatus) 7 { 8 case AccountStatus.NotRegistered: 9 priceAfterDiscount = price; 10 break; 11 case AccountStatus.SimpleCustomer: 12 priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS); 13 break; 14 case AccountStatus.ValuableCustomer: 15 priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS); 16 break; 17 case AccountStatus.MostValuableCustomer: 18 priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS); 19 break; 20 default: 21 throw new NotImplementedException(); 22 } 23 priceAfterDiscount = priceAfterDiscount.ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears); 24 return priceAfterDiscount; 25 } 26 }
我们还可以将这一行移除到switch-case语句外面。好处就是:更少的代码量!
IX:提高-最后的得到干净整洁的代码
好了,现在我们可以像阅读一本书一样方便来审视我们的代码了,但是这就够了么?我们可以将代码变得超级精简的!
好的,那就开始做一些改变来实现这个目标吧。我们可以使用依赖注入和使用策略模式这两种方式。
这就是我们今天最后整理出来的代码了:
1 public class DiscountManager 2 { 3 private readonly IAccountDiscountCalculatorFactory _factory; 4 private readonly ILoyaltyDiscountCalculator _loyaltyDiscountCalculator; 5 6 public DiscountManager(IAccountDiscountCalculatorFactory factory, ILoyaltyDiscountCalculator loyaltyDiscountCalculator) 7 { 8 _factory = factory; 9 _loyaltyDiscountCalculator = loyaltyDiscountCalculator; 10 } 11 12 public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears) 13 { 14 decimal priceAfterDiscount = 0; 15 priceAfterDiscount = _factory.GetAccountDiscountCalculator(accountStatus).ApplyDiscount(price); 16 priceAfterDiscount = _loyaltyDiscountCalculator.ApplyDiscount(priceAfterDiscount, timeOfHavingAccountInYears); 17 return priceAfterDiscount; 18 } 19 }
1 public interface ILoyaltyDiscountCalculator 2 { 3 decimal ApplyDiscount(decimal price, int timeOfHavingAccountInYears); 4 } 5 6 public class DefaultLoyaltyDiscountCalculator : ILoyaltyDiscountCalculator 7 { 8 public decimal ApplyDiscount(decimal price, int timeOfHavingAccountInYears) 9 { 10 decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY/100 : (decimal)timeOfHavingAccountInYears/100; 11 return price - (discountForLoyaltyInPercentage * price); 12 } 13 }
1 public interface IAccountDiscountCalculatorFactory 2 { 3 IAccountDiscountCalculator GetAccountDiscountCalculator(AccountStatus accountStatus); 4 } 5 6 public class DefaultAccountDiscountCalculatorFactory : IAccountDiscountCalculatorFactory 7 { 8 public IAccountDiscountCalculator GetAccountDiscountCalculator(AccountStatus accountStatus) 9 { 10 IAccountDiscountCalculator calculator; 11 switch (accountStatus) 12 { 13 case AccountStatus.NotRegistered: 14 calculator = new NotRegisteredDiscountCalculator(); 15 break; 16 case AccountStatus.SimpleCustomer: 17 calculator = new SimpleCustomerDiscountCalculator(); 18 break; 19 case AccountStatus.ValuableCustomer: 20 calculator = new ValuableCustomerDiscountCalculator(); 21 break; 22 case AccountStatus.MostValuableCustomer: 23 calculator = new MostValuableCustomerDiscountCalculator(); 24 break; 25 default: 26 throw new NotImplementedException(); 27 } 28 29 return calculator; 30 } 31 }
1 public interface IAccountDiscountCalculator 2 { 3 decimal ApplyDiscount(decimal price); 4 } 5 6 public class NotRegisteredDiscountCalculator : IAccountDiscountCalculator 7 { 8 public decimal ApplyDiscount(decimal price) 9 { 10 return price; 11 } 12 } 13 14 public class SimpleCustomerDiscountCalculator : IAccountDiscountCalculator 15 { 16 public decimal ApplyDiscount(decimal price) 17 { 18 return price - (Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS * price); 19 } 20 } 21 22 public class ValuableCustomerDiscountCalculator : IAccountDiscountCalculator 23 { 24 public decimal ApplyDiscount(decimal price) 25 { 26 return price - (Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS * price); 27 } 28 } 29 30 public class MostValuableCustomerDiscountCalculator : IAccountDiscountCalculator 31 { 32 public decimal ApplyDiscount(decimal price) 33 { 34 return price - (Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS * price); 35 } 36 }
首先我们摆脱了扩展方法(也就是静态类),之所以要摆脱这种是因为扩展方法与折扣计算方法之间存在了紧耦合的关系。如果我们想要单元测试我们的方法ApplyDiscount的时候将变得不太容易,因为我们必须统一测试与之紧密关联的类PriceExtensions。
为了避免这个,我创建了DefaultLoyaltyDiscountCalculator 类,这里面包含了ApplyDiscountForTimeOfHavingAccount扩展方法,同事我通过抽象接口ILoyaltyDiscountCalculator隐藏了她的具体实现。现在,当我想测试我们的类DiscountManager的时候,我就可以通过 ILoyaltyDiscountCalculator模拟注入虚构对象到DiscountManager类中通过构造函数显示测试功能。这里我们运用的就叫依赖注入模式。
在做这个的同时,我们也将计算折扣率这个功能安全的移交到另一个不同的类中,如果我们想要修改这一段的逻辑,那我们就只需要修改DefaultLoyaltyDiscountCalculator 类就好了,而不需要改动其他的地方,这样减少了在改动他的时候产生破坏其他地方的风险,同时也不需要再增加单独测试的时间了。
下面是我们在DiscountManager类中使用分开的逻辑类:
priceAfterDiscount = _loyaltyDiscountCalculator.ApplyDiscount(priceAfterDiscount, timeOfHavingAccountInYears);
为了针对账户状态的逻辑来计算折扣率,我创建了一些比较复杂的东西。我们在DiscountManager类中有两个责任需要分解出去。
- 根据账户状态如何选择对应的计算方法。
- 特殊计算方法的细节
为了将第一个责任移交出去,我创建了工厂类(DefaultAccountDiscountCalculatorFactory),为了实现工厂模式,然后再把这个隐藏到抽象IAccountDiscountCalculatorFactory里面去。
我们的工厂会决定选择哪种计算方法。最后我们通过依赖注册模式构造函数将工厂模式注射到DiscountManager类中
下面就是运用了工厂的DiscountManager类:
priceAfterDiscount = _factory.GetAccountDiscountCalculator(accountStatus).ApplyDiscount(price);
以上会针对不同的账户状态返回何时的策略,然后调用ApplyDiscount 方法。
第一个责任已经被交接出去了,接下来就是第二个了。
接下来我们就开始讨论策略了…..
因为不同的账户状态会有不用的折扣计算方法,所以我们需要不同的实现策略。座椅非常适用于策略模式。
在我们的例子中,我们有三种策略:
NotRegisteredDiscountCalculator
SimpleCustomerDiscountCalculator
MostValuableCustomerDiscountCalculator
他们包含了具体的折扣计算方法的实现并被藏在了抽象IAccountDiscountCalculator里。
这就允许我们的类DiscountManager使用合适的策略,而不需要知道具体的实现。我们的类只需要知道与ApplyDiscount方法相关的IAccountDiscountCalculator 接口返回的对象的类型。
NotRegisteredDiscountCalculator, SimpleCustomerDiscountCalculator, MostValuableCustomerDiscountCalculator这些类包含了具体的通过账户状态选择适合计算的计算方法的实现。因为我们的这三个策略看起来相似,我们唯一能做的基本上就只有针对这三种计算策略创建一个方法然后每个策略类通过一个不用的参数来调用她。因为这会让我们的代码变得越来越多,所以我现在决定不这么做了。
好了,到目前为止我们的代码变得可读了,而且每个类都只有一个责任了-这样修改他的时候会单独一一对应了:
- DiscountManager-管理代码流
- DefaultLoyaltyDiscountCalculator-可靠的计算折扣率的方法
- DefaultAccountDiscountCalculatorFactory-决定根据账户状态选择哪个策略来计算。
- NotRegisteredDiscountCalculator, SimpleCustomerDiscountCalculator, MostValuableCustomerDiscountCalculator – 根据账户状态计算折扣率
现在开始比较现在与之前的方法:
1 public class Class1 2 { 3 public decimal Calculate(decimal amount, int type, int years) 4 { 5 decimal result = 0; 6 decimal disc = (years > 5) ? (decimal)5 / 100 : (decimal)years / 100; 7 if (type == 1) 8 { 9 result = amount; 10 } 11 else if (type == 2) 12 { 13 result = (amount - (0.1m * amount)) - disc * (amount - (0.1m * amount)); 14 } 15 else if (type == 3) 16 { 17 result = (0.7m * amount) - disc * (0.7m * amount); 18 } 19 else if (type == 4) 20 { 21 result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount)); 22 } 23 return result; 24 }