代码气味–第二部分

在上一篇文章《代码气味–第一部分》中 ,我谈到了膨胀器:它们是代码气味,可以识别为长方法,大型类,基元痴迷,长参数列表和数据块。 在这一篇中,我想深入研究面向对象的滥用者变更阻止者

面向对象的滥用者

当面向对象的原则不完整或应用不正确时,通常会发生这种类型的代码异味。

切换语句

这种情况很容易识别:我们有一个开关箱。 但是,如果找到一系列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
 
{
   twopublic 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/353555.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

solr 启动、停止

启动命令: solr start 停止命令 solr stop -all 转载于:https://www.cnblogs.com/yby120/p/9139791.html

第一篇博客测试

第一次发博客测试,看看都能进行什么操作。 再编辑一下,发表的时间就又改了? 没想到这个测试也有这么多人访问,那我把我的测试结果也贴出来供大家参考: 1. 无论怎么编辑,发表时间不会更改; 2. 编…

hadoop 提交程序并监控运行

程序编写及打包 使用maven导入第三方jar pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation&qu…

digester_Apache Digester示例–轻松配置

digester解决问题–硬编码&#xff0c;需要为您的应用程序创建自定义配置&#xff0c;例如struts配置文件&#xff0c;仅通过更改文件即可改变应用程序的行为。 Apache Digester可以轻松为您完成此任务。 使用Apache Digester相当容易将XML文档转换为相应的Java bean对象层次结…

MFC状态栏编程(显示系统时间和进度条)

显示系统时间 1、 在状态栏中设置两个新的栏位Timer和Progress。首先到ResourceView中编辑String Table&#xff0c;增加IDS_TIMER(时间),PROGRESS(进度)。然后在MainFrame中修改indicators数组&#xff0c;插入IDS_TIMER和IDS_PROGRESS。插入的位置&#xff0c;即为显示的位置…

极光推送服务端API(定时推送任务,推送到指定设备,推送到所有设备)

极光推送常用的几个api方法总结&#xff0c;抽取出了utils类&#xff0c;利用MsgType进行业务类型区别&#xff0c;方便app端收到推送后进行不同处理&#xff1a; 首先引入依赖&#xff1a; <!-- 极光推送 --><dependency><groupId>cn.jpush.api</groupId…

Java 9附加流

Java 9即将发布&#xff01; 它不仅仅是Jigsaw项目 。 &#xff08;我也很惊讶。&#xff09;它给平台带来了很多小的变化&#xff0c;我想一一看一下。 我将标记所有这些帖子&#xff0c;您可以在这里找到它们。 让我们从…开始 流 Streams学习了两个新技巧。 第一个处理前缀…

MFC注册快捷键

1. 使用RegisterHotKey()注册快捷键&#xff1b;2. OnHotKey()函数中响应快捷键&#xff1b;3. 程序退出时&#xff0c;使用UnregisterHotKey(hWnd, m_HotKeyId)取消快捷键注册。

Hibernate---对象的三种状态

Hibernate---对象的三种状态 简而言之&#xff0c;hibernate本就是面向对象的基于ORM的框架&#xff0c;位于dao层&#xff0c;对数据进行操作的框架。我就谈谈hibernate的对象的三种状态。他们分别为&#xff1a;游离&#xff0c;持久和瞬时。通过代码来详解一下吧。 hibernat…

VS2008 C++ 项目添加“依赖”、“库目录”和“包含目录”

1. 添加编译所需要&#xff08;依赖&#xff09;的 lib 文件[解决方案资源管理器]“项目->属性->配置属性->连接器->输入->附加依赖项”里填写“winsock.lib”&#xff0c;多个 lib 以空格隔开。 &#xff08;等同于“#pragma comment(lib, "winsock.lib&q…

IDEA项目搭建四——使用Mybatis实现Dao层

一、引入mybatis及mysql的jar包 可以从阿里云上面查找版本&#xff0c;db操作放在dao层所以打开该层的pom.xml文件&#xff0c;找到<dependencies>节点增加两个引入 <dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifac…

连词我们…讨厌

最近&#xff0c;我写了与实现相关的名称&#xff0c;并提供了一些示例&#xff0c;这些示例由于方法名称与主体之间的紧密关系而导致方法名称不正确。 有一会儿&#xff0c;我们有以下代码&#xff1a; boolean isComplexOrUnreadableWithTests() { return (complex || unre…

C#常见编译错误

CSharp类型初始值设定项引发异常&#xff1a;类的静态变量初始化遇到异常&#xff0c;或者构造函数中遇到异常

python函数的 全局变量与局部变量

一、函数的全局变量 1、什么是全局变量 顶着头开始写&#xff0c;没有任何缩进&#xff0c;在py文件的任何位置都能调用 #!/usr/bin/env python # _*_ coding:utf8 _*_ name"gouguoqi"name"gouguoqi" def change_name():print ("111",(name)) …

C#程序将DLL包进EXE方法

有时候我们在发布程序的时候只想发布一个EXE&#xff0c;而编写程序的时候往往会有多个DLL&#xff0c;这个时候如果能把这些DLL装进EXE将是一个很令人振奋的事情&#xff0c;事实上对于C#程序有很多方法如下&#xff1a;1. 使用微软的ILMerge&#xff08;缺点&#xff1a;不支…

org.springframework.web.servlet.PageNotFound - No mapping found for HTTP request with URI

org.springframework.web.servlet.PageNotFound - No mapping found for HTTP request with URI 在重启项目中会遇到[org.springframework.web.servlet.PageNotFound] - No mapping found for HTTP request with URI [*********] in DispatcherServlet with name SpringMVC 这个…

spring本地化默认英文_Spring3国际化和本地化

spring本地化默认英文我最近想将Spring 3提供的国际化和本地化功能添加到我当前的项目之一中。 我浏览了Spring文档&#xff0c;然后在Internet上搜索以找到一些资源。 但是我找不到能够满足客户要求的资源。 大多数教程都像hello world应用程序&#xff0c;它提供了基本的理解…

我所知道的Ribbon库

QT&#xff1a; http://www.devmachines.com/qtitanribbon-overview.html http://qribbon.sourceforge.net MFC、c#可以到微软官方下载 System.Windows.Forms.Ribbon35.DLL - Type: Managed DLL - An Open Source Ribbon Control for .NET WinForm - Read more: http://…

JUnit 5 –动态测试

在定义测试时&#xff0c;JUnit 4有一个很大的弱点&#xff1a;它必须在编译时发生。 现在&#xff0c;JUnit 5将解决此问题&#xff01; Milestone 1 刚刚发布 &#xff0c;并带有全新的动态测试&#xff0c;该动态测试允许在运行时创建测试。 总览 本系列中有关JUnit 5的其他…

win8.1自带metro应用不工作解决办法

输入如下命令 powershell -ExecutionPolicy Unrestricted Add-AppxPackage -DisableDevelopmentMode -Register $Env:SystemRoot\WinStore\AppxManifest.XML powershell -ExecutionPolicy Unrestricted Add-AppxPackage -DisableDevelopmentMode -Register $Env:SystemRoot\Im…