在上一篇文章《代码气味–第一部分》中 ,我谈到了膨胀器:它们是代码气味,可以识别为长方法,大型类,基元痴迷,长参数列表和数据块。 在这一篇中,我想深入研究面向对象的滥用者和变更阻止者 。
面向对象的滥用者
当面向对象的原则不完整或应用不正确时,通常会发生这种类型的代码异味。
切换语句
这种情况很容易识别:我们有一个开关箱。 但是,如果找到一系列ifs,您也应该考虑一下它的气味。 (这是变相的开关盒)。 为什么switch语句不好? 因为添加新条件后,您必须查找每次出现这种情况的情况。 因此,在与David交谈时,他问我:如果将开关封装到一个方法中会发生什么情况呢? 这确实是一个好问题……如果您的开关盒仅用于“照顾”一种行为,仅此而已,那就可以了。 记住,识别出代码的气味并不意味着您必须经常使用它:这是一个权衡。 如果您发现您的switch语句已复制,并且每个复制都有不同的行为,那么您不能简单地将switch语句隔离在一个方法中。 您需要找到一个合适的“家”才能进入。根据经验,当您处于这种情况时,应该考虑多态性。 我们可以在此处应用两种重构技术:
那么什么时候使用其中一个? 如果类型代码没有改变类的行为,则可以使用子类技术。 将每个行为分为适当的子类将强制执行“ 单一职责原则”,并通常使代码更具可读性。 如果需要添加其他情况,则只需在代码中添加一个新类,而无需修改任何其他代码。 因此,您可以应用“ 打开/关闭原则” 。 当类型代码影响类的行为时,应使用策略方法。 如果要更改类,字段和许多其他操作的状态,则应使用State Pattern 。 如果它仅影响您选择班级行为的方式,则“ 策略模式”是一个更好的选择。
嗯...有点混乱,不是吗? 因此,让我们尝试一个例子。
您有一个枚举EmployeeType:
public enum EmployeeType
{
Worker,
Supervisor,
Manager
}
和一个班级的雇员:
public class Employee
{
private float salary;
private float bonusPercentage;
private EmployeeType employeeType;
public Employee(float salary, float bonusPercentage, EmployeeType employeeType)
{
this.salary = salary;
this.bonusPercentage = bonusPercentage;
this.employeeType = employeeType;
}
public float CalculateSalary()
{
switch (employeeType)
{
case EmployeeType.Worker:
return salary;
case EmployeeType.Supervisor:
return salary + (bonusPercentage * 0.5F);
case EmployeeType.Manager:
return salary + (bonusPercentage * 0.7F);
}
return 0.0F;
}
}
一切都很好。 但是,如果您需要计算年度奖金怎么办? 您将添加另一个这样的方法:
public float CalculateYearBonus()
{
switch (employeeType)
{
case EmployeeType.Worker:
return 0;
case EmployeeType.Supervisor:
return salary + salary * 0.7F;
case EmployeeType.Manager:
return salary + salary * 1.0F;
}
return 0.0F;
}
看到重复的开关吗? 因此,让我们首先尝试子类方法:这是超类:
abstract public class Employee
{
protected float salary;
protected float bonusPercentage;
public EmployeeFinal(float salary, float bonusPercentage)
{
this.salary = salary;
this.bonusPercentage = bonusPercentage;
}
abstract public float CalculateSalary();
virtual public float CalculateYearBonus()
{
return 0.0F;
}
}
这里有子类:
public class Worker: Employee
{
two
public Worker(float salary, float bonusPercentage)
: base(salary, bonusPercentage)
{}
override public float CalculateSalary()
{
return salary;
}
}
public class Supervisor : Employee
{
public Supervisor(float salary, float bonusPercentage)
: base(salary, bonusPercentage)
{}
override public float CalculateSalary()
{
return salary + (bonusPercentage * 0.5F);
}
public override float CalculateYearBonus()
{
return salary + salary * 0.7F;
}
}
使用策略方法,我们将创建一个用于计算报酬的接口:
public interface IRetributionCalculator
{
float CalculateSalary(float salary);
float CalculateYearBonus(float salary);
}
有了适当的接口,我们现在可以将符合该协议的任何类别传递给员工,并计算正确的薪水/奖金。
public class Employee
{
private float salary;
private IRetributionCalculator retributionCalculator;
public Employee(float salary, IRetributionCalculator retributionCalculator)
{
this.salary = salary;
this.retributionCalculator = retributionCalculator;
}
public float CalculateSalary()
{
return retributionCalculator.CalculateSalary(salary);
}
public float CalculateYearBonus()
{
return retributionCalculator.CalculateYearBonus(salary);
}
}
临时场
当我们正在计算一些需要多个输入变量的大型算法时,就会发生这种情况。 通常,在类中创建这些字段没有任何价值,因为它们仅用于特定的计算。 这也很危险,因为您必须确保在开始下一次计算之前重新初始化它们。 这里最好的重构技术是将Replace Method与Method Object一起使用,这会将方法提取到单独的类中。 然后,您可以将方法拆分为同一类中的多个方法。
拒绝遗赠
这段代码的气味很难检测,因为当子类没有使用其父类的所有行为时就会发生这种情况。 因此,好像子类“拒绝”了其父类的某些行为(“ bequest”)。
在这种情况下,如果继续使用继承没有意义,那么最好的重构技术就是更改为委派 :我们可以通过在子类中创建父类类型的字段来摆脱继承。 这样,每次您需要父类中的方法时,就将它们委托给这个新对象。
如果继承是正确的事情,则从子类中移走所有不必要的字段和方法。 从子类和父类中提取所有方法和字段,并将它们放在新类中。 使这个新类成为超类,子类和父类应该从该超类继承。 此技术称为提取超类 。
具有不同接口的替代类
嗯,这种情况让我想到了同一团队成员之间的“缺乏沟通”,因为当我们有两个类做相同的事情但其方法的名称不同时,就会发生这种情况。 从重命名方法或移动方法开始 ,因此您可以使两个类都实现相同的接口。 在某些情况下,两个类中仅部分行为重复。 如果是这样,请尝试提取超类并使原始类成为子类。
变革预防者
好家伙! 这种代码的味道是您真正要避免的。 这些就是在一个地方进行更改时,基本上也必须遍历整个代码库在其他地方进行更改的过程。 因此,这是我们所有人都想避免的噩梦!
发散变化
当您发现自己出于多种不同原因而更改同一班级时,就是这种情况。 这意味着您违反了“ 单一责任原则” (与关注点分离有关)。 此处应用的重构技术是“ 提取类”,因为您要将不同的行为提取到不同的类中。
弹枪手术
这意味着当您在一个班级中进行少量更改时,您必须同时更改几个班级。 尽管它看起来与Divergent Change的气味相同,但实际上它们是相反的: Divergent Change是对一个类进行多次更改的时间。 gun弹枪外科手术是指同时对多个类别进行一次更改时。
这里要应用的重构技术是Move Method和/或Move Field 。 这将允许您将重复的方法或字段移到一个通用类。 如果该类不存在,请创建一个新类。 在原始类几乎保持为空的情况下,也许您应该考虑一下该类是否是冗余的,如果是,请使用Inline Class消除它:将其余的方法/字段移至创建的新类之一。 这完全取决于原始类是否不再承担任何责任。
并行继承层次结构
在这种情况下,您发现自己为类B创建了一个新的子类,因为您向类A添加了一个子类。在这里,您可以:首先,使一个层次结构引用另一个层次结构的实例。 在第一步之后,您可以使用“ 移动方法”和“ 移动字段”删除引用类中的层次结构。 您也可以在此处应用“ 访客”模式 。
结论
对于面向对象的滥用者和变更阻止者 ,我认为,如果您知道如何将良好的设计应用于代码,则可以更轻松地避免使用它们。 而这伴随着很多实践。 今天,我讨论了一些重构技术,但还有很多。 您可以在Refactoring.com中找到有关所有内容的良好参考。 就像我在本系列的第一部分中所说的那样,代码的气味不能总是被消除。 研究每种情况并做出决定:请记住,这始终是一个权衡。
翻译自: https://www.javacodegeeks.com/2016/05/code-smells-part-ii.html