祖传shi山代码重构实战(01)-Extract Class提炼类

简介: 某个类做了应该由两个类做的事。建立一个新类,将相关的字段和函数从旧类移到新类。

某个类做了应该由两个类做的事。


建立一个新类,将相关的字段和函数从旧类移到新类。

15.png



动机


一个类应该是一个清楚的抽象,处理一些明确的责任。但实际工作中,类会不断扩展。你会在这儿加入一些功能,在那儿加入一些数据。给某个类添加一项新责任时,你会觉得不值得为这项责任分离出一个单独的类。于是,随着责任不断增加,这个类会变得过分复杂。很快,你的类变成一团乱麻。这样的类往往含有大量函数和数据,太大而不易理解。


此时你需要考虑哪些部分可以分离出去,并将它们分离到一个单独的类。如果某些数据和某些函数总是一起出现,某些数据经常同时变化甚至彼此相依,这就表示你应该将它们分离出去。


一个有用的测试,问你自己,若你搬移了某些字段和函数,会发生啥事?其他字段和函数是否因此变得无意义?


另一个往往在开发后期出现的信号是类的子类化方式。若发现:


子类化只影响类的部分特性

或发现某些特性需要以一种方式来子类化,某些特性则需以另一种方式子类化

这就意味你需要分解原来的类。


做法


决定如何分解类所负的责任。


建立一个新类,用以表现从旧类中分离出来的责任。若旧类剩下的责任与旧类名称不符,为旧类改名


构造旧类时,创建一个新类的实例,建立“从旧类访问新类”的连接关系


对你想搬移的每个字段,运用【搬移字段】搬移之。每次更改后运行测试。


使用【搬移函数】将必要函数搬移到新类。先搬移较低层函数(也就是“被其他函数调用”多于“调用其他


函数”者)。每次更改后运行测试。


检查两个类的接口,去掉不再需要的函数,必要时为函数重新取一个适合新环境的名字。


决定是否公开新的类。如果确实需要,考虑对新类应用【将引用对象改为值对象】 使其成为一个值对象


范例


Person类:


package com.javaedge.refactor.extract_class;

import lombok.Data;

/**

* @author JavaEdge

* @date 2022/3/30

*/

@Data

public class Person {

   private String name;

   private String officeAreaCode;

   private String officeNumber;

   public String getTelephoneNumber() {

       return this.officeAreaCode + this.officeNumber;

   }

}



可以将与电话号码相关的行为分离到一个独立的类。


首先,定义一个空的 TelephoneNumber 类来表示“电话号码”:


class TelephoneNumber {

}


然后,建立从Person到TelephoneNumber的连接:


class Person {

private TelephoneNumber _officeTe1ephone = new TelephoneNumber()

}


现在,运用MOVE Field 移动一个字段过来:


package com.javaedge.refactor.extract_class;

import lombok.Data;

/**

* @author JavaEdge

* @date 2022/3/30

*/

@Data

public class Person {

   private String name;

   private String officeNumber;

   private TelephoneNumber officeTelephone = new TelephoneNumber();

   public String getTelephoneNumber() {

       return getOfficeAreaCode() + this.officeNumber;

   }

   public String getOfficeAreaCode() {

       return officeTelephone.getAreaCode();

   }

   public void setOfficeAreaCode(String areaCode) {

       officeTelephone.setAreaCode(areaCode);

   }

}



package com.javaedge.refactor.extract_class;

import lombok.AllArgsConstructor;

import lombok.Data;

import lombok.NoArgsConstructor;

/**

* @author JavaEdge

* @date 2022/3/30

*/

@Data

@NoArgsConstructor

@AllArgsConstructor

public class TelephoneNumber {

   private String areaCode;

}


移动其它字段,并用 move method将相关方法移动到TelephoneNumber


package com.javaedge.refactor.extract_class;

import lombok.Data;

/**

* @author JavaEdge

* @date 2022/3/30

*/

@Data

public class Person {

   private String name;

   private TelephoneNumber officeTelephone = new TelephoneNumber();

   public String getTelephoneNumber() {

       return officeTelephone.getTelephoneNumber();

   }

 

}


package com.javaedge.refactor.extract_class;

import lombok.AllArgsConstructor;

import lombok.Data;

import lombok.NoArgsConstructor;

/**

* @author JavaEdge

* @date 2022/3/30

*/

@Data

@NoArgsConstructor

@AllArgsConstructor

public class TelephoneNumber {

   private String areaCode;

   private String number;

   public String getTelephoneNumber() {

       return this.areaCode + this.number;

   }

}



要不要对用户公开这个新类?我可以将Person中与电话号码相关的函数委托至TelephoneNumber,从而完全隠藏这个新类;也可以直接将它对用户公开。我还可以将它公开给部分用户(位于同一个包中的用户),而不公开给其他用户。


如果我选择公开新类,就需要考虑别名带来的危险。如果我公开了TelephoneNumber,而有个用户修改了对象中的areaCode字段值,我又怎么能知道呢?而且,做出修改的可能不是直接用户,而是用户的用户的用户。面对这个问题,我有下列几种选择。


允许任何对象修改TelephoneNumber对象的任何部分。这就使得TelephoneNumber对象成为引用对象,于是我应该考虑使用CZimigeValue to Reference(179)。这种情况下,Person应该是TelephoneNumber的访问点

不许任何人不通过Person对象就修改TelephoneNumber对象,为此,可以将TelephoneNumber设为不可修改或为它提供一个不可修改的接口

先复制一TelephoneNumber对象,然后将复制得到的新对象传递给用户。但这可能会造成一定程度的迷惑,因为人们会认为他们可以修改TelephoneNumber对象的值。此外,如果同个TelephoneNumber对象被传递给多个用户,也可能在用户之间造成别名问题

电话号码”对象一般还具有复用价值,因此我考虑将新提炼的类暴露给更多的客户端。需要访问TelephoneNumber对象时,只须把Person类中那些office开头的访问函数搬移过来并略作修改即可。但这样TelephoneNumber就更像一个值对象了,因此我会先对它使用【将引用对象改为值对象】。


Extract Class是改善并发程序的一种常用技术,因为它使你可以为提炼后的两个类分别加锁。如果你不需要同时锁定两个对象,就不必这样做。


这里也存在危险性。如果需要确保两个对象被同时锁,就面临事务问题,需要使用其他类型的共享锁。这是一个复杂领域,比起一般情况需要更繁重的机制。事务很有实用性,但是编写事务管理程序则超出了大多数程序员的职责范围。

目录
相关文章
|
6月前
|
C++
【C++练级之路】【Lv.8】【STL】list类的模拟实现
【C++练级之路】【Lv.8】【STL】list类的模拟实现
|
存储 C++
C++ Primer Plus 第十一章答案 使用类
只有聪明人才能看见的摘要~( ̄▽ ̄~)~
67 0
|
编译器 C++
C++ Primer Plus 第十四章答案 C++中的代码重用
只有聪明人才能看见的摘要~( ̄▽ ̄~)~
64 0
|
存储 C#
对于‘用C#编写一个员工工资计算’问题的代码编写风格和结构设计考虑的比较【发现自己还是太弱,大家可以在评论区中提出我代码中的不足】
对于‘用C#编写一个员工工资计算’问题的代码编写风格和结构设计考虑的比较【发现自己还是太弱,大家可以在评论区中提出我代码中的不足】
119 0
对于‘用C#编写一个员工工资计算’问题的代码编写风格和结构设计考虑的比较【发现自己还是太弱,大家可以在评论区中提出我代码中的不足】
|
设计模式 数据库
如何才能不写出面条代码--理论篇
本文是“面条代码系列”文章的第一篇,在这个系列里,我将从理论开始,再通过实践和大家一起来探讨如何才能写好代码。 [如何才能不写出面条代码--理论篇](???) 如何才能不写出面条代码--物流计费引擎实践 如何才能不写出面条代码--物流计费清洗管道实践 如何才能不写出面条代码--物流计费分摊实践 ## 面条代码系列文章目的 一直以来有一个问题困扰着我,大家好像都在不断的批判面条代码,但不知不觉中,每
8540 2
如何才能不写出面条代码--理论篇
|
前端开发
前端工作总结229-代码中漫花谷出现很多NBSP
前端工作总结229-代码中漫花谷出现很多NBSP
95 0
前端工作总结229-代码中漫花谷出现很多NBSP
|
弹性计算 前端开发 IDE
High&NewTech:新物种?这是一种不需要写代码的程序猿,这事,得从Ta们掌握了 iVX工具(首个无代码编程语言)说起……
High&NewTech:新物种?这是一种不需要写代码的程序猿,这事,得从Ta们掌握了 iVX工具(首个无代码编程语言)说起……
High&NewTech:新物种?这是一种不需要写代码的程序猿,这事,得从Ta们掌握了 iVX工具(首个无代码编程语言)说起……
|
设计模式 算法 程序员
你是否 diss 过别人的代码?| 怎样的代码才算优秀?
你一定在内心吐槽过他的代码太烂:没注释、逻辑混乱、到处都是 magic number、实现方案过时、耦合严重、一改就出 bug。 此时心中的怒火油然而生,仿佛自己是正义的化身。。。
124 0
|
弹性计算 前端开发 IDE
High&NewTech:新物种?这是一种不需要写代码的程序猿,这事,得从Ta们掌握了 iVX工具(首个无代码编程语言)说起(一)
High&NewTech:新物种?这是一种不需要写代码的程序猿,这事,得从Ta们掌握了 iVX工具(首个无代码编程语言)说起……
High&NewTech:新物种?这是一种不需要写代码的程序猿,这事,得从Ta们掌握了 iVX工具(首个无代码编程语言)说起(一)
|
SQL 前端开发 程序员
High&NewTech:新物种?这是一种不需要写代码的程序猿,这事,得从Ta们掌握了 iVX工具(首个无代码编程语言)说起(二)
High&NewTech:新物种?这是一种不需要写代码的程序猿,这事,得从Ta们掌握了 iVX工具(首个无代码编程语言)说起……
High&NewTech:新物种?这是一种不需要写代码的程序猿,这事,得从Ta们掌握了 iVX工具(首个无代码编程语言)说起(二)