您现在的位置是:首页 >技术杂谈 >Java代码重构学习笔记-简化函数调用网站首页技术杂谈
Java代码重构学习笔记-简化函数调用
Rename Method(函数改名)
其目的是更改方法的名称,以便更好地反映其功能或用途,提高代码的可读性和可维护性。
下面举一个例子:
原来的代码如下:
public class UserService {
public User getById(int id) {
// ...
}
}
现在我们希望将 getById
方法改名为 getUserById
,可以按照以下步骤进行:
-
打开 IDE 的重构工具,选择 “Rename” 或者 “Refactor -> Rename”。
-
输入新的方法名,比如
getUserById
,然后点击 “Refactor” 按钮。 -
IDE 会自动扫描项目中使用了该方法的所有地方,并将其更新为新的方法名。
修改后的代码如下:
public class UserService {
public User getUserById(int id) {
// ...
}
}
通过这样的修改,我们可以更加清晰地了解方法的功能和作用,增强了程序的可读性和可维护性。同时,由于 IDE 可以自动更新调用方法的位置,可以避免手动修改方法名带来的错误和漏洞。
Add Parameter(添加参数)
其目的是向方法中添加一个或多个参数,以便更好地满足需求变化,增加方法的灵活性。
下面举一个例子:
原来的代码如下:
public class UserService {
public User getById(int id) {
// ...
}
}
现在我们希望将 getById
方法改为 getByIdAndType
,并增加一个参数 type
来获取指定类型的用户信息。可以按照以下步骤进行:
- 在
getById
方法中添加一个新的参数String type
。
public User getByIdAndType(int id, String type) {
// ...
}
- 将原来的
getById
方法调用替换为getByIdAndType
方法调用,并传入新的参数。
User user = userService.getByIdAndType(123, "admin");
通过这样的修改,我们实现了方法的改进,可以根据不同的需求获取不同类型的用户信息,提高了程序的灵活性和可扩展性。
需要注意的是,添加参数时需要确保参数的默认值符合业务需求并对代码的其他部分没有影响,同时也要注意对调用该方法的地方进行相应的修改。
Remove Parameter(移除参数)
其目的是移除方法中不需要或者无用的参数,精简方法的输入参数,提高方法的可读性和可维护性。
下面举一个例子:
原来的代码如下:
public class UserService {
public User getByUsernameAndPassword(String username, String password, boolean isEncrypt) {
// ...
}
}
现在我们发现 isEncrypt
参数没有被使用过,可以将其移除以精简方法的输入参数。可以按照以下步骤进行:
- 将方法中对
isEncrypt
参数的使用全部删除。
public User getByUsernameAndPassword(String username, String password) {
// ...
}
- 更新调用该方法的地方,删除对应的参数。
User user = userService.getByUsernameAndPassword("test", "123456");
通过这样的修改,我们实现了方法的改进,简化了方法的输入参数,提高了程序的可读性和可维护性。
需要注意的是,在移除参数时需要确保不会影响方法的功能和其他部分的调用,同时也要考虑是否需要对该方法的其他部分进行相应的修改。
Separate Query fom Modifier (将查询函数和修改函数分离)
其目的是将方法的查询功能和修改功能拆分为两个独立的方法,从而提高程序的可读性、可维护性和可重用性。
下面举一个例子:
原来的代码如下:
public class UserService {
public void updatePassword(String username, String oldPassword, String newPassword) {
User user = userDao.getByUsernameAndPassword(username, oldPassword);
if (user != null) {
user.setPassword(newPassword);
userDao.save(user);
}
}
}
其中 updatePassword
方法既包含了查询用户信息的功能,又包含了修改用户密码的功能。
现在我们希望将查询用户信息的功能拆分出来,可以按照以下步骤进行:
- 创建一个新的方法
getByUsernameAndPassword
,用于查询用户信息。
public User getByUsernameAndPassword(String username, String password) {
return userDao.getByUsernameAndPassword(username, password);
}
- 在
updatePassword
方法中调用getByUsernameAndPassword
方法获取用户信息。
public void updatePassword(String username, String oldPassword, String newPassword) {
User user = getByUsernameAndPassword(username, oldPassword);
if (user != null) {
user.setPassword(newPassword);
userDao.save(user);
}
}
通过这样的修改,我们实现了方法的改进,将查询用户信息和修改用户密码的功能拆分为两个独立的方法,提高了程序的可读性、可维护性和可重用性。
需要注意的是,在拆分方法时需要确保不会影响原有的功能和调用顺序,同时也要考虑到修改后代码的可维护性和适应性。
ParameterizeMethod(令函数携带参数)
其目的是将方法中的固定参数拆分成变量参数或者参数对象,以提高方法的通用性、可扩展性和可重用性。
下面举一个例子:
原来的代码如下:
public class UserService {
public void createUser(String username, String password, String email) {
// ...
}
}
其中 createUser
方法的参数包括用户名、密码和邮箱等三个固定参数。
现在我们希望将这些固定参数拆分为一个参数对象 User
,可以按照以下步骤进行:
- 创建一个新的
User
类,用于存储用户信息。
public class User {
private String username;
private String password;
private String email;
// 构造函数、getter 和 setter 方法省略
}
- 修改
createUser
方法,将固定参数拆分为一个参数对象User
。
public void createUser(User user) {
// ...
}
- 更新调用该方法的地方,将固定参数封装到一个
User
对象中。
User user = new User();
user.setUsername("test");
user.setPassword("123456");
user.setEmail("test@example.com");
userService.createUser(user);
通过这样的修改,我们实现了方法的改进,将固定参数拆分为一个参数对象,提高了程序的通用性、可扩展性和可重用性。
需要注意的是,在参数化方法时需要确保参数对象的属性合理、精确地表示了方法的输入,同时也要考虑到其他部分对该方法的调用需要进行相应的修改。
Replace Parameter with Explicit Methods(以明确函数取代参数)
其目的是将方法中的参数替换为多个独立的方法,以提高程序的可读性、可维护性和可扩展性。
下面举一个例子:
原来的代码如下:
public class PaymentService {
public void processPayment(String paymentType, double amount) {
if ("credit_card".equals(paymentType)) {
processCreditCardPayment(amount);
} else if ("paypal".equals(paymentType)) {
processPaypalPayment(amount);
} else if ("alipay".equals(paymentType)) {
processAlipayPayment(amount);
}
}
private void processCreditCardPayment(double amount) {
// ...
}
private void processPaypalPayment(double amount) {
// ...
}
private void processAlipayPayment(double amount) {
// ...
}
}
其中 processPayment
方法接收两个参数:支付类型和支付金额。根据不同的支付类型调用相应的处理方法进行支付。
现在我们希望将参数替换为明确的方法,可以按照以下步骤进行:
- 创建三个新的方法,分别处理信用卡支付、PayPal 支付和支付宝支付。
public class PaymentService {
public void processCreditCardPayment(double amount) {
// ...
}
public void processPaypalPayment(double amount) {
// ...
}
public void processAlipayPayment(double amount) {
// ...
}
}
- 修改
processPayment
方法,删除支付类型参数,直接调用相应的方法进行支付。
public class PaymentService {
public void processPayment(double amount) {
processCreditCardPayment(amount);
}
}
通过这样的修改,我们实现了方法的改进,将参数替换为明确的方法,提高了程序的可读性、可维护性和可扩展性。
需要注意的是,在以明确函数取代参数时需要确保方法名称准确、清晰地反映了方法的功能,并且选择合适的方法名称可以有效地提高代码的可读性和可维护性。同时也要考虑到其他部分对该方法的调用需要进行相应的修改。
Preserve Whole Object(保持对象完整)
其目的是将方法中的多个参数封装为一个对象,以提高程序的可读性和可维护性。
下面举一个例子:
原来的代码如下:
public class WeatherService {
public double calculateAverageTemperature(double temperature1, double temperature2, double temperature3) {
return (temperature1 + temperature2 + temperature3) / 3;
}
}
其中 calculateAverageTemperature
方法接收三个参数:温度1、温度2 和温度3,计算它们的平均值并返回。
现在我们希望将这些参数封装为一个对象,可以按照以下步骤进行:
- 创建一个温度列表对象
TemperatureList
,用于存储多个温度值。
public class TemperatureList {
private List<Double> temperatures = new ArrayList<>();
public void addTemperature(double temperature) {
temperatures.add(temperature);
}
public int getSize() {
return temperatures.size();
}
public double getTemperature(int index) {
return temperatures.get(index);
}
}
- 修改
calculateAverageTemperature
方法,接收一个温度列表对象temperatures
。
public class WeatherService {
public double calculateAverageTemperature(TemperatureList temperatures) {
double sum = 0;
for (int i = 0; i < temperatures.getSize(); i++) {
sum += temperatures.getTemperature(i);
}
return sum / temperatures.getSize();
}
}
- 更新调用该方法的地方,创建一个
TemperatureList
对象,并添加需要计算平均值的温度值。
TemperatureList temperatures = new TemperatureList();
temperatures.addTemperature(20);
temperatures.addTemperature(25);
temperatures.addTemperature(30);
double averageTemperature = weatherService.calculateAverageTemperature(temperatures);
通过这样的修改,我们实现了方法的改进,将多个参数封装为一个对象,提高了程序的可读性和可维护性。同时还可以通过 TemperatureList
对象提供一些额外的功能,例如:获取温度列表大小、获取指定位置的温度值等。
需要注意的是,在保持对象完整时需要确保对象能够准确地表示方法的输入,同时也要考虑到其他部分对该方法的调用需要进行相应的修改。
SReplace Parameter with Methods(以函数取代参数)
它的目的是将一个函数的一个或多个参数替换为一个方法调用,这个方法返回的值就是原参数的值。这样做可以使函数更加灵活和简洁,也可以减少代码冗余。
以下是一个示例代码:
public class MyClass {
private int mX;
private int mY;
public MyClass(int x, int y) {
mX = x;
mY = y;
}
public double calculateDistance(int x, int y) {
int deltaX = mX - x;
int deltaY = mY - y;
return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
}
}
在上面的代码中,calculateDistance()
函数接受两个参数 x
和 y
,然后计算 (x,y)
和 (mX, mY)
这两个点之间的距离。如果我们将 mX
和 mY
转换为方法,代码会变成如下所示:
public class MyClass {
private int mX;
private int mY;
public MyClass(int x, int y) {
mX = x;
mY = y;
}
public double calculateDistance() {
return calculateDistance(mX, mY);
}
public double calculateDistance(int x, int y) {
int deltaX = mX - x;
int deltaY = mY - y;
return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
}
}
通过上面的修改,我们将 calculateDistance()
函数的参数去掉了,使函数更加简洁易懂,同时也减少了代码冗余。在调用 calculateDistance()
函数时,它会自动计算 (mX, mY)
和 (mX, mY)
之间的距离,并返回结果。
总之,"以函数取代参数"技术可以使代码更加灵活、简洁,并消除潜在的错误来源,是一项很有用的重构技术。
Introduce Parameter Object (引入参数对象)
它的目的是将一组函数参数打包成一个对象,从而简化函数的参数列表,使代码更加清晰易读。
以下是一个示例代码:
public class Customer {
private String mName;
private int mAge;
private String mAddress;
public Customer(String name, int age, String address) {
mName = name;
mAge = age;
mAddress = address;
}
public String getDetails() {
return mName + ", " + mAge + ", " + mAddress;
}
}
在上面的代码中,Customer
类表示一个顾客信息,包括名字、年龄和地址。getDetails()
函数返回这个顾客的详情,将三个属性拼接成一个字符串。如果我们想要搜索所有住在某个区域的顾客,需要传递一个区域名称作为参数,可以创建一个新的类来表示这个参数:
public class SearchCriteria {
private String mArea;
public SearchCriteria(String area) {
mArea = area;
}
public String getArea() {
return mArea;
}
}
然后可以修改 Customer
类,将 getDetails()
函数的参数改为 SearchCriteria
类型:
public String getDetails(SearchCriteria criteria) {
if (!mAddress.contains(criteria.getArea())) {
return null;
}
return mName + ", " + mAge + ", " + mAddress;
}
上面的代码中,参数列表变得更简单了,同时函数的使用方式也变得更加清晰:我们可以使用一个 SearchCriteria
对象来查询所有住在某个区域的顾客。
通过引入参数对象,我们可以将一组相关的参数打包成一个对象,从而简化函数的参数列表,使代码更加清晰易读。它还有助于将逻辑相关的代码放在一起,提高代码的可维护性。
Remove Setting Method (移除设值函数)
它的目的是消除类中的不必要的设值方法,其中“不必要”指的是在类的生命周期中不需要修改该属性的值。
以下是一个示例代码:
public class Person {
private String mName;
private int mAge;
public void setName(String name) {
mName = name;
}
public void setAge(int age) {
mAge = age;
}
public String getName() {
return mName;
}
public int getAge() {
return mAge;
}
}
在上面的代码中,Person
类包含两个属性 name
和 age
,以及相应的设值方法和取值方法。假设在该应用程序中创建一个 Person
对象后,我们不希望修改该对象的 name
属性。这时,我们可以将 setName()
方法移除:
public class Person {
private String mName;
private final int mAge;
public Person(String name, int age) {
mName = name;
mAge = age;
}
public String getName() {
return mName;
}
public int getAge() {
return mAge;
}
}
上面的代码中,我们将 setName()
方法移除了,并添加了一个构造函数,在构造函数中设置了 name
和 age
属性的值。由于 mName
是一个非 final 类型的成员变量,我们可以在构造函数中对其设值,但是在后续的代码中就无法修改它的值了。
通过移除设值函数,我们可以消除类中不必要的方法,从而提高类的可维护性和可读性。但是需要注意的是,在某些情况下,设值方法是必要的,因此需要谨慎考虑是否应该将其移除。
Hide Method(隐藏函数)
它的目的是将某些方法或函数从类的公共接口中移除,以达到简化类接口和提高封装性的目的。
以下是一个示例代码:
public class Calculator {
public int add(int num1, int num2) {
return num1 + num2;
}
public int subtract(int num1, int num2) {
return num1 - num2;
}
public int multiply(int num1, int num2) {
return num1 * num2;
}
public int divide(int num1, int num2) {
if (num2 == 0) {
throw new IllegalArgumentException("Cannot divide by zero");
}
return num1 / num2;
}
}
在上面的代码中,Calculator
类包含四个公共方法用于执行简单的数学运算。但是,这些方法都是公共的,因此所有的客户端代码都可以随意调用它们。
如果我们只想在 Calculator
内部使用这些方法,那么我们可以将它们设置为私有方法:
public class Calculator {
private int add(int num1, int num2) {
return num1 + num2;
}
private int subtract(int num1, int num2) {
return num1 - num2;
}
private int multiply(int num1, int num2) {
return num1 * num2;
}
private int divide(int num1, int num2) {
if (num2 == 0) {
throw new IllegalArgumentException("Cannot divide by zero");
}
return num1 / num2;
}
public int addNumbers(int num1, int num2) {
return add(num1, num2);
}
public int subtractNumbers(int num1, int num2) {
return subtract(num1, num2);
}
public int multiplyNumbers(int num1, int num2) {
return multiply(num1, num2);
}
public int divideNumbers(int num1, int num2) {
return divide(num1, num2);
}
}
在上面的代码中,我们将所有的数学运算方法设置为私有的,并添加了一个公共方法来对外暴露它们。这样,客户端代码就无法直接调用数学运算方法,只能通过公共方法来实现相应的功能。
通过隐藏函数,我们可以减少类对外暴露的方法数量,从而简化类接口并提高封装性。但是需要注意的是,在某些情况下,公共方法是必要的,因此需要谨慎考虑是否应该将其隐藏。
Replace Constructor with Factory Method(以工厂函数取代构造函数)
它的目的是通过提供一个工厂方法来替换构造函数,从而实现更灵活、可控制的对象创建方式。
以下是一个示例代码:
public class Product {
private String mName;
private String mDescription;
public Product(String name, String description) {
mName = name;
mDescription = description;
}
public String getName() {
return mName;
}
public String getDescription() {
return mDescription;
}
}
在上面的代码中,Product
类包含一个构造函数,用于创建产品对象。如果我们想要为产品对象创建一个默认的实例,那么需要使用 new
操作符并调用该构造函数:
Product product = new Product("Product A", "This is a product");
如果我们希望通过工厂方法来创建产品对象,那么可以像下面这样修改代码:
public class Product {
private String mName;
private String mDescription;
private Product(String name, String description) {
mName = name;
mDescription = description;
}
public static Product createProduct(String name, String description) {
return new Product(name, description);
}
public String getName() {
return mName;
}
public String getDescription() {
return mDescription;
}
}
在上面的代码中,我们将构造函数设置为私有的,并添加了一个公共静态方法 createProduct()
,用于创建产品对象。由于该方法是公共的,客户端代码可以直接调用它来创建产品对象:
Product product = Product.createProduct("Product A", "This is a product");
通过使用工厂方法,我们可以实现更灵活、可控制的对象创建方式。例如,我们可以在工厂方法中添加额外的逻辑来处理产品对象的创建过程,或者返回不同的产品子类对象。但是需要注意的是,在某些情况下,构造函数是必要的,因此需要谨慎考虑是否应该使用工厂方法替换它们。
Encapsulate Downcast(封装向下转型)
它的目的是将向下转型操作封装在类内部,以增强代码的安全性和可维护性。
在对象引用向下转型时,需要使用强制类型转换符 (Type) variable
。例如:
Animal animal = new Dog();
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.bark();
}
在上面的代码中,我们首先创建了一个 Dog
对象,并将其赋值给 Animal
引用。然后,我们通过检查 animal
是否为 Dog
对象,确定其是否可以被转换为 Dog
类型。如果可以,我们使用强制类型转换将其转换为 Dog
类型,并调用 bark()
方法。
虽然这段代码看起来正确无误,但实际上存在潜在的风险。如果某个代码修改了 Animal
类型的对象,使其不再是 Dog
类型,那么向下转型就会失败,并抛出一个 ClassCastException
异常。这可能会导致程序崩溃或产生错误的结果。
为了避免这种情况,我们可以将向下转型操作封装在 Dog
类的方法中:
public class Dog extends Animal {
public void bark() {
System.out.println("Woof!");
}
public void doBark() {
if (this instanceof Dog) {
bark();
}
}
}
在上面的代码中,我们添加了一个 doBark()
方法,并在其中进行向下转型操作。由于该方法在 Dog
类中被定义,因此在调用时不需要进行强制类型转换,也不需要使用 instanceof
进行检查。只有当 this
引用指向 Dog
对象时,才会执行 bark()
方法。
通过封装向下转型,我们可以避免对客户端代码进行强制类型转换,从而增强代码的安全性和可维护性。但是需要注意的是,在某些情况下,向下转型是必要的,因此需要谨慎考虑是否应该封装它们。
Replace Error Code with Exception(以异常取代错误码)
它的目的是将错误处理逻辑从返回码转换为抛出异常,以提高代码的可读性、可维护性和健壮性。
在传统的错误处理模式中,返回码被用于指示方法执行的结果,通常采用数字编码的方式。例如:
public int doSomething() {
// ...
if (someCondition) {
return 0; // 成功
} else {
return -1; // 失败
}
}
在这种情况下,客户端代码需要检查返回值,以确定是否发生了错误。这种处理方式存在一些缺点:
- 返回值可能不够明确,难以描述错误的具体类型。
- 客户端代码需要进行额外的处理来处理错误情况,增加了代码的复杂度。
- 可能会忘记检查返回值或者对返回码进行误解,导致错误未被及时处理。
相比之下,使用异常则可以更好地处理错误情况。当发生错误时,方法可以抛出一个异常,并将其交给上层方法或者客户端代码来处理。例如:
public void doSomething() throws SomeException {
// ...
if (!someCondition) {
throw new SomeException("Error message"); // 抛出异常
}
}
在上面的代码中,我们定义了一个 SomeException
异常,并在 doSomething()
方法中抛出它。客户端代码可以使用 try-catch
语句来捕获异常并进行错误处理。
通过使用异常,我们可以更清晰地描述错误,减少客户端代码的复杂度,并提高代码的健壮性和可维护性。但是需要注意的是,在某些情况下,返回码是必要的,例如在性能或者资源限制的环境下,因此需要谨慎考虑是否应该以异常取代错误码。
Replace Exception with Test (以测试取代异常)
它的目的是使用条件语句或者其他方式替换抛出异常的逻辑,以提高代码的性能和可维护性。
在传统的错误处理模式中,异常被用于指示程序运行时的错误。当发生错误时,程序会抛出一个异常,并使得上层调用栈中的方法来处理它。这种处理方式存在一些缺点:
- 异常会导致程序的执行流程不稳定,降低程序的性能。
- 异常没有结构化的信息,难以对其进行分类和管理。
- 异常需要额外的处理逻辑,增加代码的复杂度。
相比之下,使用条件语句或者其他方式可以更好地处理错误情况。例如:
public void doSomething() {
// ...
if (!someCondition) {
// 抛出异常
throw new SomeException("Error message");
}
// ...
}
可以改写为:
public void doSomething() {
// ...
if (!someCondition) {
// 使用条件语句
return; // 方法结束
}
// ...
}
在上面的代码中,我们使用条件语句替换了抛出异常的逻辑。如果 someCondition
等于 false,那么方法直接返回,否则继续执行。这种处理方式可以减少异常的使用,从而提高程序的性能和可维护性。
通过使用条件语句或者其他方式来代替异常,我们可以提高代码的可维护性、性能和可读性。但是需要注意的是,在某些情况下,异常是必要的,例如处理文件系统、网络通信等操作时,因此需要谨慎考虑是否应该以测试取代异常。