介绍
我的名字是拉多斯拉夫·萨多斯基我就是一个微软认证软件开发人员。由于我的职业生涯开始时,我与微软的技术合作。
经过这几年的经验,我看到这么多糟糕的代码,我可以写一本书,显示所有这些肮脏的例子。
这些经历让我成为一个干净的代码怪胎。
这篇文章的目的是展示如何通过展示例如写的不好类写一个干净的,可扩展和维护的代码。我将解释它可能带来什么样的麻烦和呈现方式如何通过更好的解决方案取代它 - 用好的做法和设计模式。
第一部分是对谁知道C#语言的基础知识每一个开发商 - 它会显示一些基本的错误和技术如何使代码的可读性,就像一本书。先进的部分是谁拥有在设计模式至少有基本的了解,开发者 - 它会显示完全干净,单元测试代码。
要理解这篇文章,你需要有至少基本知识:
- C#语言
- 依赖注入,工厂方法和策略的设计模式
在这篇文章中描述的例子是一个具体的,现实世界中的功能 - 使用Decorator模式我不会让喜欢比萨构建例子或使用策略模式实现计算器
由于这些理论的例子是非常好的解释,我发现极难在实际生产应用中使用它。
我们听到很多时候不使用此,并使用该来代替。但为什么?我会尽量解释,并证明所有的良好做法和设计模式是真正拯救我们的生命!
注意:
-
我就不解释了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类负责计算折扣的客户,而他是买一些产品的网上商城。
- 来吧?真的吗?
-可惜是的!
它是完全不可读,不可维护,unextendable它是采用了许多不良做法和反模式。
什么确切的问题做我们在这里?
-
命名 -我们只能猜测什么呢这个方法计算并究竟是这些计算的投入。这实在是很难从这个类中提取计算算法。
风险:
在这种情况下,最重要的是- 浪费时间
,如果我们将获得从业务咨询到目前他们的算法细节,否则我们将有必要修改这一段代码它会带我们的年龄,了解我们的逻辑计算方法。如果我们不将其记录下来或重构代码,下一次我们/其他开发商将花同样的时间去弄清楚究竟是发生在那里。我们可以非常容易地在修改它,以及错误。 -
魔术数字
在我们的例子中类型变量的手段-对客户账户的状态。你能猜到吗?如果,否则,如果语句做出选择如何计算折扣后的产品价格。
现在我们没有一个想法是什么样的考虑是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地在我们的代码以同样的方式来改变。上述逻辑计算折扣岁月是在我们的系统中的客户。因此,如果我们将在2 3地改变这个逻辑,我们的系统会变得不一致。
-
多重责任每班
我们的方法至少有3职责:
1。选择计算算法,
2。计算帐户状态,折扣
3。计算年成为我们的客户一定的折扣。
它违反了单一职责原则。它带来什么样的风险?如果我们将需要修改这些3的功能的话,会影响到另外2。这意味着,它可能打破我们没有想触摸功能的东西。因此,我们将不得不再次测试所有类- 浪费时间。
重构...
在9以下步骤我会告诉你如何才能避免上述风险和不良行为都说明,实现清洁,维护和单元测试代码,这将是可读的像一本书。
我一步 - 命名,命名,命名
它是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的意思是,让我们用它做什么!
二STEP - 幻数
一个避免在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; } }
三步骤 - 更可读
在这一步中,我们将通过更换改善我们班的可读性的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;
}
}
四步骤 - 未明显的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进阶 - 让计算分析
在我们的例子中,我们必须给予我们的客户打折两个标准:
- 帐户状态
- 时间有几年在我们的系统中的账户。
所有算法类似于打折的情况下被客户时间:(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)*价格)
六步 - 摆脱幻数 - 另一种技术
让我们来看看静态变量是折扣算法的帐户状态的一部分:(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个strategies: NotRegisteredDiscountCalculatorSimpleCustomerDiscountCalculator MostValuableCustomerDiscountCalculator
它们含有实施特别折扣算法和背后都隐藏着抽象:IAccountDiscountCalculator。
它将使我们的DiscountManager类使用正确的策略,而不其实施的知识。DiscountManager只知道返回的对象实现IAccountDiscountCalculator接口,其中包含的方法ApplyDiscount。
NotRegisteredDiscountCalculator,SimpleCustomerDiscountCalculator,MostValuableCustomerDiscountCalculator根据帐户状态类包含实施适当的算法。由于我们的3个战略看起来很相似,我们可以做更多的唯一的事情是为所有3算法创建一个方法,并从每一个策略类不同的参数调用它。因为这将使我们的例子,我的大没有决定这样做。
好吧,总结一下,现在我们有一个干净可读的代码和我们所有的类都只有一个责任- 只有一个理由去改变:
1。DiscountManager -管理代码流
2. DefaultLoyaltyDiscountCalculator -忠诚折扣计算
3. DefaultAccountDiscountCalculatorFactory -决定该战略的计算帐户状态折扣 折扣计算帐户状态
从现在开始比较方法:
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;
}
结论
在这篇文章中提出的代码被极度简化,使所用技术和模式更容易解释。它显示了如何编程的常见问题可以在一个肮脏的方式来解决,什么是使用的良好做法和设计模式在一个适当的,干净的方式解决它的好处。
在我的工作经验,我看到这篇文章的不良做法中强调了很多次。它们在应用的许多地方存在明显未在一类如在我的例子,这使得更加难以找到它,因为它们是正确的代码之间的隐藏。谁是写这种代码的人总是认为他们是以下保持简单愚蠢的规则。不幸的是,几乎总是系统的成长以及变得非常复杂。然后,在这个简单,unextendable代码每次修改是非常,但并带来突破的东西巨大的风险。
裸记住,你的代码将生活在一个生产环境中很长一段时间,将在每一个业务需求变更修改。所以写得太简单,unextendable代码很快就会产生严重的后果。最后是开发商,自己以后谁将会保持你的代码不错