C# 重构if-else if-else
我有下面的代码示例C# 重构if-else if-else,c#,design-patterns,if-statement,refactoring,solid-principles,C#,Design Patterns,If Statement,Refactoring,Solid Principles,我有下面的代码示例 if(object.Time > 0 && <= 499) { rate = .75m } else if(object.Time >= 500 && <= 999) { rate = .85m } else if(object.Time >= 1000) { rate = 1.00m } else { rate = 0m; } if(object.Time>0&&=500
if(object.Time > 0 && <= 499)
{
rate = .75m
}
else if(object.Time >= 500 && <= 999)
{
rate = .85m
}
else if(object.Time >= 1000)
{
rate = 1.00m
}
else
{
rate = 0m;
}
if(object.Time>0&&=500&&=1000)
{
速率=1.00m
}
其他的
{
速率=0米;
}
我的问题是,我可以使用什么样的设计模式来改进它
编辑:为了更清楚一点,您在这里看到的代码是目前存在于策略模式实现中的代码。我们有3种类型的计算,其中2种有3种不同的“速率”,可以根据您在下面看到的时间使用。我曾想过为每种利率创建一个策略实现,但随后我会移动确定策略使用的逻辑,并将其弄得一团糟
谢谢 在
if-else
条件下,您可以选择一种格式而不是设计模式
一般来说,如果你有很多条件,我更喜欢if
而不是很多嵌套的if-else
,你可以选择这样的条件
if(condition1){
return x; // or some operation
}
if(condition 2){
return y; // or some operation
}
return default; // if none of the case is satisfied.
我不认为这是一个反模式的问题,对于代码度量也是oki。If不是嵌套的,也不是很复杂!。 但您可能会做得更好,例如使用开关,或者创建自己的类,该类包含IsAgeBiggerThanMax()等属性
交换机更新:
var range = (time - 1) / 499;
switch (range)
{
case 0: // 1..499
rate = 0.75;
break;
case 1: // 500.. 999
rate = 0.85;
break;
default:
rate = 0;
if (time == 1000)
{
rate = 1.0;
}
break;
}
测试是一个哲学问题,我们不知道这个函数是什么,它在做什么。也许它可以从外部100%测试 有一种不太出名的模式叫做“规则模式” 其思想是,所有内容都被提取到一个对象中,让它处理自己的工作。您将为每个规则定义每个类,这些规则是您定义的条件语句,例如(object.Time>0&&0&&date.Something=500&&date.Something 0&&500&&1000) { 速率=1.00m } 其他的 { 速率=0米; } 现在是,
private List<IRules>_rules = new List<IRules>();
public SomeConstructor()
{
_rules.Add(new RuleNumberOne());
_rules.Add(new RuleNumberTwo());
}
public void DoSomething()
{
Oobject date = new Oobject();
foreach(var rule in this._rules)
{
Decimal rate = rule.Execute(date);
}
}
private List_rules=new List();
公共构造函数()
{
_添加(新的RuleNumberOne());
_添加(新规则编号wo());
}
公共无效剂量测定法()
{
Oobject日期=新Oobject();
foreach(本规则中的var规则)
{
十进制率=规则。执行(日期);
}
}
这里的想法是,一旦得到嵌套的if条件,就很难读取条件语句,开发人员也很难进行任何更改。因此,它将每个单独规则的逻辑及其效果分离到自己的类中,该类遵循规则单一责任模式
一些注意事项是
1.)只读
2.)明确的顺序
3.)依赖关系
4)优先权
5)持久性
再次,考虑当条件复杂度越来越大,应用程序的要求保证时,使用规则模式。
如果您不想让它返回十进制或其他内容,您可以对其进行自定义,但想法就在这里。如果您有大量的“如果”,或者如果您想将此信息放在设置文件中,那么我建议您创建一个类来存储此信息
Class
FromTime
ToTime
Value
values.Add(New Class(0, 499, .75));
values.Add(New Class(500, 999, .85));
values.Add(New Class(1000, 9999, 1));
然后循环集合中的每个项目
if(object.Time >= curItem.FromTime && object.Time <= curItem.ToTime)
rate = curItem.Value;
if(object.Time>=curItem.FromTime&&object.Time=curItem.FromTime | | curItem.FromTime=-1)&&(object.Time只需在每个if中进行一次比较,并使用值从上到下进行比较:
if (Object.Time >= 1000)
rate = 1.0;
else
if (Object.Time >= 500)
rate = 0.85;
else
if (Object.Time > 0)
rate = 0.75;
else
rate = 0;
您只需要检查范围的一个端点,另一个端点由您在代码中的实际位置暗示,因为前面的条件为false
if (obj.Time <= 0) {
rate = 0.00m;
}
// At this point, obj.Time already must be >= 0, because the test
// to see if it was <= 0 returned false.
else if (obj.Time < 500) {
rate = 0.75m;
}
// And at this point, obj.Time already must be >= 500.
else if (obj.Time < 1000) {
rate = 0.85m;
}
else {
rate = 1.00m;
}
if(obj.Time=0,因为测试
//看看它是否等于500。
如果(对象时间<1000){
速率=0.85米;
}
否则{
速率=1.00m;
}
出于可读性和性能方面的原因,最好是将量表中更常见的一端设置为您首先检查的一端。但这两种方式都适用。如果您真的在寻找设计模式,我会选择责任链模式
基本上,你的“链接”试图处理输入。如果它无法处理它,它将沿着链传递,直到其他链接可以处理它。如果你有一些接口,你也可以定义一个接口,以便在单元测试中轻松模拟
因此,每个链接都将继承这个抽象类:
public abstract class Link
{
private Link nextLink;
public void SetSuccessor(Link next)
{
nextLink = next;
}
public virtual decimal Execute(int time)
{
if (nextLink != null)
{
return nextLink.Execute(time);
}
return 0;
}
}
然后使用规则创建每个链接:
public class FirstLink : Link
{
public override decimal Execute(int time)
{
if (time > 0 && time <= 499)
{
return .75m;
}
return base.Execute(time);
}
}
public class SecondLink : Link
{
public override decimal Execute(int time)
{
if (time > 500 && time <= 999)
{
return .85m;
}
return base.Execute(time);
}
}
public class ThirdLink : Link
{
public override decimal Execute(int time)
{
if (time >= 1000)
{
return 1.00m;
}
return base.Execute(time);
}
}
你所要做的就是用一个干净的电话来呼叫连锁店:
var result = chain.Execute(object.Time);
我真的很喜欢Leo Lorenzo Luis的解决方案。
但我不会返回利率,而是让规则来处理它。
这将从根本上尊重美国
还有
另外,当一个类“询问”包含在另一个类中的值时,您可以将其标识为名为的气味
也就是说:我会做两件事来完善Leo Lorenzo的解决方案:
调用右规则
,而不调用for循环
在其关联的规则
类中执行请求的行为
为了做到这一点,我们必须将规则
类与其时间范围进行映射,这样就可以直接访问它们,而不必通过循环进行迭代。您需要实现自己的映射对象(或列表对象,或集合),重载[]操作符
及其添加
函数
因此,您可以在地图中添加规则,例如:
ranges.Add(0,500).AddRule(rule1);
ranges.Add(500,1000).AddRule(rule2);
etc..
您可以在上面看到,存在一个对象范围
,该对象可以关联一个对象规则
。因此,您最终可以为同一范围
添加多个规则
然后,你这样称呼它:
ranges[Object.time].Execute(Object);
使用地图:
var map = new[]
{
new { Rule = (Func<Oobject, bool>) ( x => x.Time > 0 && x.Something <= 499 ),
Value = .75m },
new { Rule = (Func<Oobject, bool>) ( x => x.Time >= 500 && x.Something <= 999 ),
Value = .85m },
new { Rule = (Func<Oobject, bool>) ( x => true ),
Value = 0m }
};
var date = new Oobject { Time = 1, Something = 1 };
var rate = map.First(x => x.Rule(date) ).Value;
Assert.That( rate, Is.EqualTo(.75m));
测试失败。尽管调用了RuleNumberOne
并返回了一个非零值,但随后调用了RuleNumberWO
并返回了零以覆盖正确的值
为了复制if..else..else逻辑,它需要能够短路
这里有一个快速修复方法:更改接口的Execute
方法以返回bool
以指示是否
var result = chain.Execute(object.Time);
ranges.Add(0,500).AddRule(rule1);
ranges.Add(500,1000).AddRule(rule2);
etc..
ranges[Object.time].Execute(Object);
var map = new[]
{
new { Rule = (Func<Oobject, bool>) ( x => x.Time > 0 && x.Something <= 499 ),
Value = .75m },
new { Rule = (Func<Oobject, bool>) ( x => x.Time >= 500 && x.Something <= 999 ),
Value = .85m },
new { Rule = (Func<Oobject, bool>) ( x => true ),
Value = 0m }
};
var date = new Oobject { Time = 1, Something = 1 };
var rate = map.First(x => x.Rule(date) ).Value;
Assert.That( rate, Is.EqualTo(.75m));
[Test]
public void TestRulesUsingList()
var rules = new IRules[]{ new RuleNumberOne(), new RuleNumberTwo() };
var date = new Oobject { Time = 1, Something = 1 };
var rate = 0m;
foreach(var rule in rules)
rate = rule.Execute(date);
Assert.That( rate, Is.EqualTo(.75m));
}
[Test]
public void TestRulesUsingList2()
{
var rules = new IRules[]{ new RuleNumberOne(), new RuleNumberTwo(),
new DefaultRule() };
var date = new Oobject { Time = 1, Something = 1 };
var rate = rules.First(x => x.Execute(date)).Value;
Assert.That( rate, Is.EqualTo(.75m));
}
public class Oobject
{
public int Time { get; set; }
public int Something { get; set; }
}
public interface IRules
{
bool Execute(Oobject date);
decimal Value { get; }
}
public class RuleNumberOne : IRules
{
public bool Execute(Oobject date)
{
return date.Time > 0 && date.Something <= 499;
}
public decimal Value
{
get { return .75m; }
}
}
public class RuleNumberTwo : IRules
{
public bool Execute(Oobject date)
{
return date.Time >= 500 && date.Something <= 999;
}
public decimal Value
{
get { return .85m; }
}
}
public class DefaultRule : IRules
{
public bool Execute(Oobject date)
{
return true;
}
public decimal Value
{
get { return 0; }
}
}