我的名字是Radoslaw Sadowski,我是Microsoft认证的软件开发人员。从我的职业生涯开始,我正在使用微软的技术。
经过几年的经验,我看到这么多严重的代码,我可以写一本书,显示所有这些脏的例子。这些经验使我成为一个干净的代码怪胎。
这篇文章的目的是展示如何编写一个干净,可扩展和可维护的代码,通过显示不良写的类的例子。我将解释它带来什么麻烦,并提出一种如何用更好的解决方案替换它的方法 - 使用良好的实践和设计模式。
第一部分是为每个开发人员谁知道C#语言基础 - 它会显示一些基本的错误和技术如何使代码可读如书。高级部分适用于对设计模式至少有基本了解的开发人员 - 它将显示完全清洁,可单元测试的代码。
要理解这篇文章,你需要至少有以下基本知识:
C#语言依赖注入,工厂方法和策略设计模式本文中描述的示例是一个具体的,真实的世界特征 - 我不会显示使用装饰器模式构建比萨饼的实例或使用策略模式实现计算器:)
由于这些理论的例子非常好的解释,我发现非常难以使用它在一个真正的生产应用程序。
我们听到很多时候不使用此,并使用该来代替。但为什么?我将尝试解释它,并证明所有的良好做法和设计模式真的拯救了我们的生活!
注意:
我不会解释C#的语言特性和设计模式(这将使这篇文章太长),在网络中有这么多好的理论例子。我将集中展示如何在我们的日常工作中使用它
示例极为简化,仅突出了所描述的问题 - 我发现在我从包含代码色调的示例中学习时,难以理解文章的一般概念。
我不是说我显示的解决方案描述下面的问题是唯一的解决方案,但肯定是工作和从你的代码高质量的解决方案。
我不在乎下面的代码关于错误处理,日志等。代码只写显示解决方案的常见编程问题。
让我们去看看
我们的现实世界的例子将在下面类:
隐藏 复制代码public class Class1{ public decimal Calculate(decimal amount, int type, int years) { decimal result = 0; decimal disc = (years > 5) ? (decimal)5/100 : (decimal)years/100; if (type == 1) { result = amount; } else if (type == 2) { result = (amount - (0.1m * amount)) - disc * (amount - (0.1m * amount)); } else if (type == 3) { result = (0.7m * amount) - disc * (0.7m * amount); } else if (type == 4) { result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount)); } return result; }}这是一个很坏的家伙。我们可以想象上述类的作用是什么?它正在做一些有线计算?这就是我们现在可以说的...
现在想象一下,这是一个DiscountManager类负责计算折扣的客户,而他是买一些产品的网上商城。
- 来吧?真?- 不幸的是!
它是完全不可读,不可维护,不可扩展,它使用许多不良做法和反模式。
我们在这里有什么确切的问题?
命名 -我们只能猜测什么呢这个方法计算并究竟是这些计算的投入。这真的很难从这个类中提取计算算法。风险:在这种情况下,最重要的是- 浪费时间
,如果我们将获得从业务咨询到目前他们的算法细节,否则我们将有一个需要修改这段代码,将带我们年龄的人了解我们的逻辑计算方法。如果我们不会记录它或重构代码,下一次我们/其他开发人员将花费相同的时间来弄清楚究竟发生了什么。我们可以很容易犯错,同时修改它。
魔术数字
在我们的例子中类型变量的手段-对客户账户的状态。你能猜到吗?如果,否则,如果语句做出选择如何优惠后计算产品的价格。现在,我们没有一个想法是什么样的考虑是1,2,3或4。现在,让我们说你必须改变给予折扣算法想象ValuableCustomer帐户,您可以尝试从代码的其余部分搞清楚了-究竟会花费你很长的时间,但即使我们可以很容易犯错误,并修改算法BasicCustomer账户-号码,如2或3都不是很描述。在我们的错误后,客户会很高兴,因为他们会得到有价值的客户的折扣:)
没有明显的bug 因为我们的代码是非常脏且无法读取,我们可以轻易错过非常重要的事情。试想一下,有加入到我们的系统中一个新的客户帐户的状态- GoldenCustomer。现在我们的方法将返回0作为每个产品的最终价格,将从一种新的帐户中购买。为什么?因为如果没有我们的if-else语句,如果条件得到满足(将有未处理的帐户状态)方法将始终返回0。我们的老板是不开心-他卖了许多免费的产品之前有人意识到,什么是错的。

不可读我们都不得不承认,我们的代码是完全不可读。不可读=更多的时间来理解代码+增加的错误风险。
幻数-再次我们是否知道是什么号码,如0.1,0.7,0.5是什么意思?不,我们不,但我们应该如果我们是代码的所有者。让我们想象一下,你必须改变这一行:结果=(金额- (0.5M *金额)) -盘*(金额- (0.5M *量)); 因为方法是完全不可读的,你只改变第一个0.5到0.4,留下第二个0.5。它可以是一个错误,但它也可以是一个完全正确的修改。这是因为0.5不告诉我们什么。我们在转换的情况下,同样的故事多年变量光盘变量:小数盘=(年> 5)?(十进制)5/100:(十进制)年/ 100; 它计算在我们的系统中有帐户的时间的折扣百分比。好,但是什么是5?它是客户可以获得忠诚度的最大折扣百分比。你能猜到吗?
干-不要重复自己,是不是先看看可见,但也有在我们的方法很多地方是重复的代码。例如:盘*(金额- (0.1M *量)); 是同样的逻辑:盘*(金额- (0.5M *量)) ,只有这是制造差异静态变量-我们可以很容易地参数化这个变量。如果我们不会去掉重复的代码,我们会遇到一些情况,我们只做一部分任务,因为我们不会看到我们必须以相同的方式改变代码中的5个地方。以上逻辑是计算作为我们系统中的客户多年的折扣。因此,如果我们将在3个地方中的2个地方改变这个逻辑,我们的系统将变得不一致。
每班多的责任我们的方法至少有3职责:1.选择计算算法2.计算帐户状态打折,3.计算折扣的岁月是我们的客户。它违反了单一职责原则。它带来了什么风险?如果我们需要修改这3个功能之一,它也会影响2其他。这意味着它可以打破某些功能,我们不想触摸。因此,我们将不得不再次测试所有类- 浪费时间。
重构...
在下面的9步中,我将向您展示如何避免上述风险和不良做法,实现一个清洁,可维护和单元可测试的代码,它将像一本书一样可读。
I STEP - 命名,命名,命名
它是IMHO的一个好代码的最重要的方面之一。我们只改变了方法,参数和变量的名称,现在我们完全知道下面的类是什么。
隐藏 缩小
复制代码public class DiscountManager{ public decimal ApplyDiscount(decimal PRice, int accountStatus, int timeOfHavingAccountInYears) { decimal priceAfterDiscount = 0; decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100; if (accountStatus == 1) { priceAfterDiscount = price; } else if (accountStatus == 2) { priceAfterDiscount = (price - (0.1m * price)) - (discountForLoyaltyInPercentage * (price - (0.1m * price))); } else if (accountStatus == 3) { priceAfterDiscount = (0.7m * price) - (discountForLoyaltyInPercentage * (0.7m * price)); } else if (accountStatus == 4) { priceAfterDiscount = (price - (0.5m * price)) - (discountForLoyaltyInPercentage * (price - (0.5m * price))); } return priceAfterDiscount; }}但是我们仍然不知道什么1,2,3,4意思,让我们做点什么!
II步 - 魔法数字
一个避免在C#神奇数字技术被取代它枚举秒。我准备AccountStatus枚举在更换我们的幻数的if-else如果语句:
隐藏 复制代码public enum AccountStatus{ NotRegistered = 1, SimpleCustomer = 2, ValuableCustomer = 3, MostValuableCustomer = 4}现在看看我们重构的类,我们可以很容易地说,计算折扣的哪个算法用于哪个帐户状态。混淆帐户状态的风险迅速下降。
隐藏 缩小
复制代码public class DiscountManager{ public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears) { decimal priceAfterDiscount = 0; decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100; if (accountStatus == AccountStatus.NotRegistered) { priceAfterDiscount = price; } else if (accountStatus == AccountStatus.SimpleCustomer) { priceAfterDiscount = (price - (0.1m * price)) - (discountForLoyaltyInPercentage * (price - (0.1m * price))); } else if (accountStatus == AccountStatus.ValuableCustomer) { priceAfterDiscount = (0.7m * price) - (discountForLoyaltyInPercentage * (0.7m * price)); } else if (accountStatus == AccountStatus.MostValuableCustomer) { priceAfterDiscount = (price - (0.5m * price)) - (discountForLoyaltyInPercentage * (price - (0.5m * price))); } return priceAfterDiscount; }}III STEP - 更易读
在这一步中,我们将通过更换改善我们班的可读性的if-else如果 with语句的switch-case语句。
我也把长的一行算法分成两行。我们现在将“计算帐户状态的折扣”从“计算有客户帐户年份的折扣”中分离出来。
比如,行:priceAfterDiscount =(售价- (0.5M *价格)) - (discountForLoyaltyInPercentage *(价格- (0.5M *价格)));
改为:priceAfterDiscount =(售价- (0.5M *价格)); priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
下面代码介绍的描述变化:
隐藏 缩小
复制代码public class DiscountManager{ public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears) { decimal priceAfterDiscount = 0; decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100; switch (accountStatus) { case AccountStatus.NotRegistered: priceAfterDiscount = price; break; case AccountStatus.SimpleCustomer: priceAfterDiscount = (price - (0.1m * price)); priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); break; case AccountStatus.ValuableCustomer: priceAfterDiscount = (0.7m * price); priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); break; case AccountStatus.MostValuableCustomer: priceAfterDiscount = (price - (0.5m * price)); priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); break; } return priceAfterDiscount; }}IV步骤 - 不明显的错误
我们终于得到了我们隐藏的bug!正如我我们的方法前面提到ApplyDiscount将返回0作为最终价格为每将从新的帐户购买产品。悲伤但真实
我们如何解决它?通过抛出NotImplementedException!

你会想 - 它不是异常驱动的开发吗?不,不是!
当我们的方法将得到尽可能的参数值AccountStatus我们不支持,我们希望被这个事实马上注意到并停止程序流程没有把我们的系统中的任何不可预知的操作。
这种情况不应该发生NEVER所以我们必须要抛出异常,如果会发生的。
下面的代码被修改为抛出一个NotImplementedException如果没有条件得到满足-在默认的部分的switch-case语句:
隐藏 缩小
复制代码public class DiscountManager{ public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears) { decimal priceAfterDiscount = 0; decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100; switch (accountStatus) { case AccountStatus.NotRegistered: priceAfterDiscount = price; break; case AccountStatus.SimpleCustomer: priceAfterDiscount = (price - (0.1m * price)); priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); break; case AccountStatus.ValuableCustomer: priceAfterDiscount = (0.7m * price); priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); break; case AccountStatus.MostValuableCustomer: priceAfterDiscount = (price - (0.5m * price)); priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); break; default: throw new NotImplementedException(); } return priceAfterDiscount; }}V STEP - 用于分析计算
在我们的示例中,我们有两个标准为我们的客户提供折扣:
帐户状态在我们的系统中拥有帐户的时间。所有算法类似于打折的情况下被客户时间:(discountForLoyaltyInPercentage * priceAfterDiscount)
,但在计算帐户状态不断打折的情况下,只有一个例外:0.7米*价格
所以让我们改变它看起来一样在其他情况下:价格- (0.3M *价格)
隐藏 缩小
复制代码public class DiscountManager{ public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears) { decimal priceAfterDiscount = 0; decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100; switch (accountStatus) { case AccountStatus.NotRegistered: priceAfterDiscount = price; break; case AccountStatus.SimpleCustomer: priceAfterDiscount = (price - (0.1m * price)); priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); break; case AccountStatus.ValuableCustomer: priceAfterDiscount = (price - (0.3m * price)); priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); break; case AccountStatus.MostValuableCustomer: priceAfterDiscount = (price - (0.5m * price)); priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); break; default: throw new NotImplementedException(); } return priceAfterDiscount; }}现在我们有所有根据帐户状态计算折扣一种格式的规则:价格- ((static_discount_in_percentages / 100)*价格)
VI步骤 - 摆脱魔法数字 - 另一种技术
让我们来看看静态变量是折扣算法的帐户状态的一部分:(static_discount_in_percentages / 100)
和它的具体实例:0.1米0.3米0.5米
这些数字也是非常魔术 - 他们没有告诉我们任何关于自己。我们在“在几年有一个帐户的时间”来折现忠诚“折扣转换的情况下,同样的情况:小数discountForLoyaltyInPercentage =(timeOfHavingAccountInYears> 5)?(十进制)5/100:(十进制)timeOfHavingAccountInYears / 100;
数字5使我们的代码非常神秘。我们必须做一些事情,使它更具描述性!
我会用魔法避免串的另一种技术-这是常量(常量在C#中的关键字)。我强烈建议为常量创建一个静态类,以便在我们的应用程序的一个地方。
对于我们的例子,我创建了下面的类:
隐藏 复制代码public static class Constants{ public const int MAXIMUM_DISCOUNT_FOR_LOYALTY = 5; public const decimal DISCOUNT_FOR_SIMPLE_CUSTOMERS = 0.1m; public const decimal DISCOUNT_FOR_VALUABLE_CUSTOMERS = 0.3m; public const decimal DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS = 0.5m;}和修改后我们DiscountManager类看起来如下:
隐藏 缩小
复制代码public class DiscountManager{ public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears) { decimal priceAfterDiscount = 0; decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY/100 : (decimal)timeOfHavingAccountInYears/100; switch (accountStatus) { case AccountStatus.NotRegistered: priceAfterDiscount = price; break; case AccountStatus.SimpleCustomer: priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS * price)); priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); break; case AccountStatus.ValuableCustomer: priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS * price)); priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); break; case AccountStatus.MostValuableCustomer: priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS * price)); priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); break; default: throw new NotImplementedException(); } return priceAfterDiscount; }}我希望你会同意我们的方法现在更自我说明:)
第七步 - 不要重复你自己!

只是为了不复制我们的代码,我们将移动部分的算法,以单独的方法。
我们将使用扩展方法来做到这一点。
首先我们要创建2个扩展方法:
隐藏 复制代码public static class PriceExtensions{ public static decimal ApplyDiscountForAccountStatus(this decimal price, decimal discountSize) { return price - (discountSize * price); } public static decimal ApplyDiscountForTimeOfHavingAccount(this decimal price, int timeOfHavingAccountInYears) { decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY/100 : (decimal)timeOfHavingAccountInYears/100; return price - (discountForLoyaltyInPercentage * price); }}由于我们的方法的名称是非常描述性的,我不必解释他们的责任,现在让我们使用新的代码在我们的例子:
隐藏 缩小
复制代码public class DiscountManager{ public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears) { decimal priceAfterDiscount = 0; switch (accountStatus) { case AccountStatus.NotRegistered: priceAfterDiscount = price; break; case AccountStatus.SimpleCustomer: priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS) .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears); break; case AccountStatus.ValuableCustomer: priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS) .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears); break; case AccountStatus.MostValuableCustomer: priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS) .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears); break; default: throw new NotImplementedException(); } return priceAfterDiscount; }}扩展方法非常好,可以使你的代码更简单,但在一天的结尾仍然是静态类,可以使你的单元测试非常困难,甚至不可能。因为这样,我们将在最后一步中摆脱它。我使用它只是为了向您介绍他们如何使我们的生活更轻松,但我不是他们的大粉丝。
无论如何,你会同意我们的代码看起来好多了吗?
所以,让我们跳到下一步!
第八步 - 删除一些不必要的行...
我们应该写尽可能简短和简单的代码。更短的代码=更少可能的错误,更短的时间了解业务逻辑。让我们更简化我们的例子。
我们可以很容易发现,我们有相同的马托呼吁3种客户账户:.ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
我们不能做一次吗?不,我们有例外NotRegistered用户,因为折扣多年身为注册客户不会使未注册客户任何意义。是的,但什么时候有帐户有未注册的用户?
- 0年
在这种情况下的折扣将永远是0,所以我们可以安全地添加这个折扣也为未注册的用户,让我们做!
隐藏 缩小
复制代码public class DiscountManager{ public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears) { decimal priceAfterDiscount = 0; switch (accountStatus) { case AccountStatus.NotRegistered: priceAfterDiscount = price; break; case AccountStatus.SimpleCustomer: priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS); break; case AccountStatus.ValuableCustomer: priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS); break; case AccountStatus.MostValuableCustomer: priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS); break; default: throw new NotImplementedException(); } priceAfterDiscount = priceAfterDiscount.ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears); return priceAfterDiscount; }}我们能够在switch-case语句之外移动这一行。好处少代码!
第九步 - 高级 - 最后得到干净的代码
好吧!现在我们可以像读一本书一样阅读我们的课,但这对我们还不够!我们想要超级干净的代码!

好吧,让我们做一些改变,最终实现这个目标。我们将使用依赖注入和战略与工厂方法的设计模式!
这就是我们的代码在一天结束时的样子:
隐藏 复制代码public class DiscountManager{ private readonly IAccountDiscountCalculatorFactory _factory; private readonly ILoyaltyDiscountCalculator _loyaltyDiscountCalculator; public DiscountManager(IAccountDiscountCalculatorFactory factory, ILoyaltyDiscountCalculator loyaltyDiscountCalculator) { _factory = factory; _loyaltyDiscountCalculator = loyaltyDiscountCalculator; } public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears) { decimal priceAfterDiscount = 0; priceAfterDiscount = _factory.GetAccountDiscountCalculator(accountStatus).ApplyDiscount(price); priceAfterDiscount = _loyaltyDiscountCalculator.ApplyDiscount(priceAfterDiscount, timeOfHavingAccountInYears); return priceAfterDiscount; }}隐藏 复制代码public interface ILoyaltyDiscountCalculator{ decimal ApplyDiscount(decimal price, int timeOfHavingAccountInYears);} public class DefaultLoyaltyDiscountCalculator : ILoyaltyDiscountCalculator{ public decimal ApplyDiscount(decimal price, int timeOfHavingAccountInYears) { decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY/100 : (decimal)timeOfHavingAccountInYears/100; return price - (discountForLoyaltyInPercentage * price); }}隐藏 缩小
复制代码public interface IAccountDiscountCalculatorFactory{ IAccountDiscountCalculator GetAccountDiscountCalculator(AccountStatus accountStatus);} public class DefaultAccountDiscountCalculatorFactory : IAccountDiscountCalculatorFactory{ public IAccountDiscountCalculator GetAccountDiscountCalculator(AccountStatus accountStatus) { IAccountDiscountCalculator calculator; switch (accountStatus) { case AccountStatus.NotRegistered: calculator = new NotRegisteredDiscountCalculator(); break; case AccountStatus.SimpleCustomer: calculator = new SimpleCustomerDiscountCalculator(); break; case AccountStatus.ValuableCustomer: calculator = new ValuableCustomerDiscountCalculator(); break; case AccountStatus.MostValuableCustomer: calculator = new MostValuableCustomerDiscountCalculator(); break; default: throw new NotImplementedException(); } return calculator; }}隐藏 缩小
复制代码public interface IAccountDiscountCalculator{ decimal ApplyDiscount(decimal price);} public class NotRegisteredDiscountCalculator : IAccountDiscountCalculator{ public decimal ApplyDiscount(decimal price) { return price; }} public class SimpleCustomerDiscountCalculator : IAccountDiscountCalculator{ public decimal ApplyDiscount(decimal price) { return price - (Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS * price); }} public class ValuableCustomerDiscountCalculator : IAccountDiscountCalculator{ public decimal ApplyDiscount(decimal price) { return price - (Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS * price); }} public class MostValuableCustomerDiscountCalculator : IAccountDiscountCalculator{ public decimal ApplyDiscount(decimal price) { return price - (Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS * price); }}
首先我们摆脱了扩展方法(阅读:静态类),因为使用它们做主叫类(DiscountManager)紧密结合扩展方法,内部折扣算法。如果我们想进行单元测试我们ApplyDiscount方法,这是不可能的,因为我们会还在测试PriceExtensions类。
为了避免这个问题我已经创建DefaultLoyaltyDiscountCalculator其中包含的逻辑类ApplyDiscountForTimeOfHavingAccount扩展方法,并隐藏它背后的抽象实现(读:接口)ILoyaltyDiscountCalculator。现在,当我们要测试我们的DiscountManager类,我们将能够注入它实现模拟/假的对象ILoyaltyDiscountCalculator到我们DiscountManager通过构造类来测试只DiscountManager实现。我们正在使用此依赖注入设计模式。
通过这样做,我们也搬到计算忠诚折扣为不同类别的责任,所以,如果我们需要修改这个逻辑,我们将不得不改变只DefaultLoyaltyDiscountCalculator类和所有其他代码将保持不变-打破东西的风险较低,减少测试时间。
下面利用我们分为单独的类逻辑DiscountManager类:priceAfterDiscount = _loyaltyDiscountCalculator.ApplyDiscount(priceAfterDiscount,timeOfHavingAccountInYears);
在帐户状态逻辑的计算折扣的情况下,我不得不创建更复杂的东西。我们有责任2,我们想从搬出DiscountManager:
根据帐户状态使用哪种算法特定算法计算的详细信息搬出我创建了一个工厂类(第一责任DefaultAccountDiscountCalculatorFactory),这是一个实现工厂方法设计模式,躲在背后的抽象- IAccountDiscountCalculatorFactory。

我们的工厂将决定选择哪种折扣算法。最后,我们正在我们的工厂注入到DiscountManager通过构造类使用依赖注入的设计模式。
下面利用工厂在我们的DiscountManager类:priceAfterDiscount = _factory.GetAccountDiscountCalculator(accountStatus).ApplyDiscount(价);
上面一行将返回特定帐户状态正确的战略,将调用ApplyDiscount方法就可以了。
第一责任分开让我们谈谈第二个。
让我们谈谈策略...

由于每个帐户状态的折扣算法可能不同,因此我们必须使用不同的策略来实现它。这是用巨大的机遇Strategy设计模式!
在我们的例子中,我们现在有3个战略:NotRegisteredDiscountCalculator SimpleCustomerDiscountCalculator MostValuableCustomerDiscountCalculator
它们含有实施特别折扣算法和背后都隐藏着抽象:IAccountDiscountCalculator。
它将使我们的DiscountManager类使用正确的策略,而不其实施的知识。DiscountManager只知道返回的对象实现IAccountDiscountCalculator接口,其中包含的方法ApplyDiscount。
NotRegisteredDiscountCalculator,SimpleCustomerDiscountCalculator,MostValuableCustomerDiscountCalculator根据帐户状态类包含实施适当的算法。因为我们的3个策略看起来类似,我们唯一能做的更多的是为所有3个算法创建一个方法,并从具有不同参数的每个策略类调用它。因为它会使我们的例子大,我没有决定这样做。
好吧,总结一下,现在我们有一个干净可读的代码和我们所有的类都只有一个责任- 只有一个理由去改变:1。DiscountManager -管理代码流2. DefaultLoyaltyDiscountCalculator -忠诚折扣计算3. DefaultAccountDiscountCalculatorFactory -决定其中计算帐户状态折扣策略选择4. NotRegisteredDiscountCalculator,SimpleCustomerDiscountCalculator,MostValuableCustomerDiscountCalculator -折扣计算帐户状态
现在从开始比较方法:
隐藏 复制代码public class Class1{ public decimal Calculate(decimal amount, int type, int years) { decimal result = 0; decimal disc = (years > 5) ? (decimal)5 / 100 : (decimal)years / 100; if (type == 1) { result = amount; } else if (type == 2) { result = (amount - (0.1m * amount)) - disc * (amount - (0.1m * amount)); } else if (type == 3) { result = (0.7m * amount) - disc * (0.7m * amount); } else if (type == 4) { result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount)); } return result; }}到我们的新的,重构的代码:
隐藏 复制代码public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears){ decimal priceAfterDiscount = 0; priceAfterDiscount = _factory.GetAccountDiscountCalculator(accountStatus).ApplyDiscount(price); priceAfterDiscount = _loyaltyDiscountCalculator.ApplyDiscount(priceAfterDiscount, timeOfHavingAccountInYears); return priceAfterDiscount;}结论
在本文中提出的代码极其简化,使使用的技术和模式的解释更容易。它显示了如何以肮脏的方式解决常见的编程问题,以及使用良好实践和设计模式以适当,干净的方式解决问题的好处。
在我的工作经验中,我看到很多次在本文中强调的坏习惯。它们显然存在于许多应用程序中,而不是在我的示例中的一个类中,这使得发现它更困难,因为它们隐藏在正确的代码之间。编写这种代码的人总是认为他们遵循Keep It Simple Stupid规则。不幸的是,几乎总是系统正在成长和变得非常复杂。然后,在这个简单的,不可扩展的代码中的每一个修改都是,并带来巨大的风险,打破的东西。
请记住,您的代码将长期存在于生产环境中,并且将在每次业务需求更改时进行修改。所以写太简单,不可扩展的代码将很快会有严重的后果。最后是对开发人员很好,谁会自己维护你的代码:)
如果你有一些问题根据文章不要犹豫与我联系!
新闻热点
疑难解答