22种坏味道
对一个类的修改涉及到多个不相关的功能或行为时(类里有不相关的功能,违背了单一职责原则)
public class User {
private String username;
private String email;
private String password;
private Date registrationDate;
// getters and setters
public void sendWelcomeEmail() {
// 发送欢迎邮件的逻辑
}
public void grantPermission(String permission) {
// 授予用户特定权限的逻辑
}
}
解决办法提取类(Extract Class)
将不相关的功能提取到独立的类中,让每个类专注于单一的职责。在这个例子中,可以将发送邮件的逻辑提取到一个EmailSender
类中,将权限管理的逻辑提取到一个UserPermissionManager
类中,从而减少User
类的职责,使其更符合单一职责原则。
描述:当需要修改一个功能时,需要在代码的多个不相关部分进行多次小的修改,而不是集中在一个地方进行修改。
假设有一个名为Order
的类,用于表示系统中的订单信息。该类包含了订单的基本属性和一些与订单相关的方法。
随着系统的演化,需求变更需要在订单发生变化时记录日志。于是开发人员决定在Order
类的各个方法中添加记录日志的逻辑。
public class Order {
private String orderId;
private Date orderDate;
private double totalAmount;
// ...
public void calculateTotalAmount() {
// 计算订单总金额的逻辑
}
public void generateInvoice() {
// 生成订单发票的逻辑
}
public void notifyCustomer() {
// 通知顾客订单状态的逻辑
}
// ...
}
public class Order {
// ...
public void calculateTotalAmount() {
// 计算订单总金额的逻辑
// 记录日志的逻辑
}
public void generateInvoice() {
// 生成订单发票的逻辑
// 记录日志的逻辑
}
public void notifyCustomer() {
// 通知顾客订单状态的逻辑
// 记录日志的逻辑
}
// ...
}
成为坏味道的原因:修改时容易遗漏,同时,这种修改方式也增加了代码的重复性和维护的复杂性。
解决方案:提取方法,在需要的地方调用
当一个方法过度依赖于另一个类的特定特性或数据,而忽略了自身所在类的特性和数据时,就存在依恋情结。
假设有一个名为Customer
的类,表示系统中的顾客信息。另外还有一个名为Order
的类,表示顾客的订单信息。Order
类中的某个方法过度依赖于Customer
类的特定特性或数据。
职责划分不清,一个应该放在Customer类中的方法放在了Order类中(放在Customer中,其他类用的时候可以调用A类,放在Order类中,其他类用的时候需要依赖Customer,重写一遍计算逻辑或者调用Order,Order类在依赖与Customer类)
public class Order {
private Customer customer;
private List<String> shopList;
// ...
// 就算折后总金额
public double calculateDiscountedTotal() {
// 假设calculateDiscountedTotal方法依赖于Customer类的discount属性
double discount = customer.getDiscount();
// 根据折扣计算订单的折扣后总金额的逻辑
// ...
return discount * 商品总金额
}
// ...
}
在这个例子中,Order
类的calculateDiscountedTotal
方法过度依赖于Customer
类的discount
属性。这种情况违反了封装的原则,因为Order
类过于了解Customer
类的内部细节,导致了依恋情结。
依恋情结的一种方法是将方法移动到更适合的类中,以减少对其他类的依赖。在这个例子中,可以考虑将calculateDiscountedTotal
方法移动到Customer
类中,使得折扣计算的逻辑与Customer
类关联。
public class Customer {
// ...
public double calculateDiscountedTotal(Order order) {
double discount = getDiscount();
// 根据折扣计算订单的折扣后总金额的逻辑
// ...
}
// ...
}
这样一来,calculateDiscountedTotal
方法与Customer
类关联,避免了Order
类对Customer
类的依恋情结。Order
类不再直接依赖于Customer
类的具体属性,而是通过传递Order
对象给Customer
类的方法来实现折扣计算。
Feature Envy(依恋情结)的问题在于方法过度依赖于另一个类的特定特性或数据,而忽略了自身所在类的特性和数据。这导致了以下问题:
解决办法:将方法移到更合适的类中
当多个数据项经常一起出现,并且它们总是以相同的方式组合在一起使用时,就存在数据泥团.
违反了数据库的第三范式:非主属性对主属性存在传递函数依赖关系(非主属性与非主属性之间存在函数依赖关系,例如f(用户名)->用户地址)
假设有一个名为Order
的类,表示系统中的订单信息。该类包含了订单的基本属性,如订单号、客户信息、商品列表等。
public class Order {
private String orderId;
private String customerName;
private String customerAddress;
private List<String> itemList;
// ...
}
在这个例子中,customerName
和customerAddress
两个属性经常一起使用,而且它们总是以相同的方式组合在一起。这种情况下,就存在数据泥团,因为这两个数据项可以组合成一个独立的类来表示客户信息,而不是分散在订单类中。
public class Order {
private String orderId;
private Customer customer;
private List<String> itemList;
// ...
}
public class Customer {
private String name;
private String address;
// ...
}
为了解决数据泥团问题,可以使用重构手法如提炼类(Extract Class)将相关的数据项提取到一个新的类中,形成一个独立的客户类。
过度使用基本类型(如整数、字符串)来表示领域内的概念,而不是使用更具表达力和语义的自定义类型。
public class Product {
private String name;
private double price;
private int quantity;
// ...
}
如果有必要提取类的话,也就是刚开始基本类型可以实现,后来基本类型满足不了了,就没必要偏执了
public class Product {
private String name;
private Money price;
private Quantity quantity;
// ...
}
public class Money {
private double value;
// ...
}
public class Quantity {
private int value;
// ...
}
解决方案:提取类
假设有一个名为PaymentProcessor
的类,负责处理不同类型的支付方式。在该类中,根据支付方式的类型来执行相应的操作。
public class PaymentProcessor {
public void processPayment(Payment payment) {
switch (payment.getType()) {
case CREDIT_CARD:
// 执行信用卡支付逻辑
break;
case PAYPAL:
// 执行 PayPal 支付逻辑
break;
case BANK_TRANSFER:
// 执行银行转账支付逻辑
break;
// 更多支付方式的分支
}
}
}
public interface Payment {
void processPayment();
}
public class CreditCardPayment implements Payment {
@Override
public void processPayment() {
// 执行信用卡支付逻辑
}
}
public class PaypalPayment implements Payment {
@Override
public void processPayment() {
// 执行 PayPal 支付逻辑
}
}
public class BankTransferPayment implements Payment {
@Override
public void processPayment() {
// 执行银行转账支付逻辑
}
}
public class PaymentProcessor {
public void processPayment(Payment payment) {
payment.processPayment();
}
}
解决方案:提取类
在一个系统中存在两个或多个相互关联的继承体系,这些继承体系中的类之间存在强耦合关系。以下是一个示例来说明平行继承体系的情况:
假设有一个图形编辑器的系统,其中有两个主要的继承体系:Shape
(形状)和Color
(颜色)。在Shape
继承体系中有具体的形状类如Circle
(圆形)和Rectangle
(矩形),而在Color
继承体系中有具体的颜色类如Red
(红色)和Blue
(蓝色)。这两个继承体系之间存在强耦合关系,需要相互配对使用。
// 形状继承体系
public abstract class Shape {
protected Color color;
// ...
}
public class Circle extends Shape {
// ...
}
public class Rectangle extends Shape {
// ...
}
// 颜色继承体系
public abstract class Color {
// ...
}
public class Red extends Color {
// ...
}
public class Blue extends Color {
// ...
}
在这个例子中,Shape
继承体系和Color
继承体系是平行的,即每个具体的形状类需要与一个具体的颜色类配对使用。例如,Circle
类需要与Red
类配对使用,Rectangle
类需要与Blue
类配对使用。这种设计使得新增或修改形状或颜色时,需要同时修改两个继承体系中的类,导致代码的脆弱性和复杂性增加。
为了解决平行继承体系的问题,可以使用桥接模式(Bridge Pattern)来分离形状和颜色的继承关系,使它们可以独立变化。桥接模式通过将一个继承体系抽象为一个独立的继承层次结构,并通过组合关系将它与另一个继承体系关联起来。
// 形状继承体系
public abstract class Shape {
protected Color color;
public Shape(Color color) {
this.color = color;
}
public abstract void draw();
// ...
}
public class Circle extends Shape {
public Circle(Color color) {
super(color);
}
@Override
public void draw() {
// 绘制圆形
color.fill();
}
// ...
}
public class Rectangle extends Shape {
public Rectangle(Color color) {
super(color);
}
@Override
public void draw() {
// 绘制矩形
color.fill();
}
// ...
}
// 颜色继承体系
public interface Color {
void fill();
// ...
}
public class Red implements Color {
@Override
public void fill() {
// 填充红色
}
// ...
}
public class Blue implements Color {
@Override
public void fill() {
// 填充蓝色
}
// ...
}
通过桥接模式,Shape
继承体系和Color
继承体系被解耦,形状类和颜色类可以独立变化。每个具体的形状类通过组合关系持有一个颜色对象,并通过调用颜色对象的方法来实现绘制操作。这种设计减少了继承体系之间的耦合性,使系统更灵活、可扩展和易于维护。当新增或修改形状或颜色时,只需修改相应的类,不会对其他类造成影响。
在代码中存在过长的消息链式调用,即通过多个对象之间的连续方法调用来获取所需的数据或执行操作,导致代码的耦合度增加,可读性降低。以下是一个示例来说明过度耦合的消息链的情况:
假设有一个名为 Customer
的类,用于表示顾客信息。该类具有一个关联的 Order
对象,而 Order
类又关联了 Address
对象,以表示顾客的订单和地址信息。
javaCopy codepublic class Customer {
private Order order;
public Order getOrder() {
return order;
}
}
public class Order {
private Address address;
public Address getAddress() {
return address;
}
}
public class Address {
private String street;
private String city;
private String country;
public String getStreet() {
return street;
}
public String getCity() {
return city;
}
public String getCountry() {
return country;
}
}
在这个例子中,通过 Customer
对象获取顾客的国家信息可能需要经过多次连续的方法调用:
javaCopy codeCustomer customer = new Customer();
// ...
String country = customer.getOrder().getAddress().getCountry();
这样的代码存在过度耦合的消息链,使得代码的可读性和维护性降低。如果需要修改消息链中的任何一个类或方法,都会对调用链上的多个对象产生影响。
为了解决过度耦合的消息链的问题,可以考虑以下重构方式:
javaCopy codepublic class Customer {
private Order order;
public Order getOrder() {
return order;
}
public Address getAddress() {
return order.getAddress();
}
}
现在,获取顾客的国家信息可以通过中间对象 Customer
的方法来获取:
javaCopy codeCustomer customer = new Customer();
// ...
String country = customer.getAddress().getCountry();
javaCopy codepublic class Customer {
private Order order;
public Order getOrder() {
return order;
}
public String getCountry() {
return order.getAddress().getCountry();
}
}
现在,可以直接通过 Customer
对象获取顾客的国家信息:
javaCopy codeCustomer customer = new Customer();
// ...
String country = customer.getCountry();
通过以上重构方式,可以减少过度耦合的消息链的使用,提高代码的可读性和维护性。避免了对多个对象的连续方法调用,使代码更加清晰和易于理解。
存在一些中间类或中间方法,它们仅仅是转发调用到其他类或方法,对代码逻辑没有实质性的贡献。以下是一个示例来说明中间人的情况:
假设有一个名为 Employee
的类,表示公司的员工信息。而在公司的员工管理系统中,有一个 EmployeeService
类负责处理员工相关的业务逻辑。
javaCopy codepublic class Employee {
private String name;
private String department;
public String getName() {
return name;
}
public String getDepartment() {
return department;
}
}
public class EmployeeService {
private List<Employee> employees;
public List<Employee> getAllEmployees() {
// 调用中间人方法获取员工列表
return EmployeeRepository.getAllEmployees();
}
}
public class EmployeeRepository {
public static List<Employee> getAllEmployees() {
// 实际的员工数据获取逻辑
// ...
}
}
在这个例子中,EmployeeService
类充当了中间人的角色,它的 getAllEmployees()
方法仅仅是转发调用到 EmployeeRepository
类的 getAllEmployees()
方法,没有添加任何额外的业务逻辑。
中间人的存在可能会导致代码的冗余和复杂性增加,不利于代码的维护和扩展。对于这种情况,可以考虑以下重构方式:
javaCopy codepublic class EmployeeService {
private List<Employee> employees;
public List<Employee> getAllEmployees() {
// 直接调用被调用对象的方法
return EmployeeRepository.getAllEmployees();
}
}
javaCopy codepublic class EmployeeService {
private List<Employee> employees;
public List<Employee> getAllEmployees() {
// 直接调用被调用对象的方法
// 移除中间人类
return EmployeeRepository.getAllEmployees();
}
}
通过以上重构方式,可以消除不必要的中间人,简化代码结构,提高代码的可读性和可维护性。避免了仅仅转发调用的冗余代码,使代码更加清晰和易于理解。
两个类之间的关系过于密切,一个类过度依赖或访问另一个类的内部细节,违反了良好的封装原则。以下是一个示例来说明狎昵关系的情况:
假设有两个类 Customer
和 Order
,表示顾客和订单信息。现在,Customer
类需要获取 Order
类的一些信息,但是它过度地依赖和访问了 Order
类的内部细节。
javaCopy codepublic class Customer {
private String name;
private Order order;
public String getName() {
return name;
}
public String getOrderStatus() {
// 过度依赖和访问Order类的内部细节
if (order != null && order.getStatus() != null) {
return order.getStatus();
}
return "Unknown";
}
}
public class Order {
private String status;
public String getStatus() {
return status;
}
}
在这个例子中,Customer
类需要获取 Order
类的状态信息。但是,Customer
类直接访问了 Order
类的状态属性,违反了封装的原则。这种依赖关系过于密切,当 Order
类的内部结构发生变化时,需要修改 Customer
类的代码。
为了解决狎昵关系的问题,可以考虑以下重构方式:
Order
类中,通过提供公共的访问方法来封装内部细节。javaCopy codepublic class Customer {
private String name;
private Order order;
public String getName() {
return name;
}
public String getOrderStatus() {
if (order != null) {
return order.getStatus();
}
return "Unknown";
}
}
public class Order {
private String status;
public String getStatus() {
return status;
}
// 其他Order类的操作和属性
}
现在,Customer
类只需要调用 Order
类的公共方法来获取订单状态,而不直接依赖和访问内部细节。
Customer
类需要更多的订单操作,可以通过委托的方式将操作交给 Order
类来处理。javaCopy codepublic class Customer {
private String name;
private Order order;
public String getName() {
return name;
}
public String getOrderStatus() {
if (order != null) {
return order.getStatus();
}
return "Unknown";
}
public void cancelOrder() {
if (order != null) {
order.cancel();
}
}
}
public class Order {
private String status;
public String getStatus() {
return status;
}
public void cancel() {
// 取消订单的操作
}
// 其他Order类的操作和属性
}
通过委托的方式,Customer
类将订单操作委托给 Order
类进行处理,减少了类之间的耦合性,提高了代码的可维护性和灵活性。
通过以上重构方式,可以解决狎昵关系带来的问题,减少类之间的依赖,增强代码的封装性和可扩展性。
存在两个或多个类,它们实现了相似的功能,但使用了不同的接口或方法命名,导致代码的可读性和维护性降低。以下是一个示例来说明异曲同工的类的情况:
假设有两个类 Rectangle
和 Square
,表示矩形和正方形。它们都有计算面积和周长的方法,但使用了不同的接口或方法命名。
javaCopy codepublic class Rectangle {
private int width;
private int height;
public int calculateArea() {
return width * height;
}
public int calculatePerimeter() {
return 2 * (width + height);
}
}
public class Square {
private int sideLength;
public int calculateSquareArea() {
return sideLength * sideLength;
}
public int calculateSquarePerimeter() {
return 4 * sideLength;
}
}
在这个例子中,Rectangle
类和 Square
类都表示不同的几何形状,但是它们的功能是相似的:计算面积和周长。然而,它们使用了不同的方法命名和接口,使得代码阅读和维护变得困难。
为了解决异曲同工的类的问题,可以考虑以下重构方式:
javaCopy codepublic interface Shape {
int calculateArea();
int calculatePerimeter();
}
public class Rectangle implements Shape {
private int width;
private int height;
@Override
public int calculateArea() {
return width * height;
}
@Override
public int calculatePerimeter() {
return 2 * (width + height);
}
}
public class Square implements Shape {
private int sideLength;
@Override
public int calculateArea() {
return sideLength * sideLength;
}
@Override
public int calculatePerimeter() {
return 4 * sideLength;
}
}
通过定义一个统一的接口 Shape
,将功能相似的类统一到同一个接口下,并使用相同的方法命名,提高了代码的可读性和维护性。
javaCopy codepublic abstract class Shape {
// 公共属性和方法
public abstract int calculateArea();
public abstract int calculatePerimeter();
}
public class Rectangle extends Shape {
private int width;
private int height;
@Override
public int calculateArea() {
return width * height;
}
@Override
public int calculatePerimeter() {
return 2 * (width + height);
}
}
public class Square extends Shape {
private int sideLength;
@Override
public int calculateArea()
库或框架中的某个类功能不完整,缺少某些常用的方法或功能,导致开发者需要自行补充或扩展该类的功能。以下是一个示例来说明不完整的库类的情况:
假设我们使用一个第三方库中的 StringUtils
类来处理字符串相关操作。然而,该类缺少一些常用的方法,比如截取子串、替换字符串等。
javaCopy codepublic class StringUtils {
// 该类缺少一些常用的方法
public static boolean isEmpty(String str) {
return str == null || str.length() == 0;
}
public static boolean isNotEmpty(String str) {
return !isEmpty(str);
}
}
在这个例子中,StringUtils
类是一个常用的字符串处理类,但是它缺少一些常用的方法,限制了开发者在处理字符串时的灵活性。为了补充缺失的功能,开发者可能需要自行编写或扩展该类的功能,增加了额外的工作量和复杂度。
为了解决不完整的库类的问题,可以考虑以下重构方式:
javaCopy codepublic class EnhancedStringUtils extends StringUtils {
public static String substring(String str, int startIndex, int endIndex) {
// 添加截取子串的功能
if (str == null || startIndex < 0 || endIndex < 0 || startIndex >= str.length() || endIndex > str.length()) {
throw new IllegalArgumentException("Invalid arguments");
}
return str.substring(startIndex, endIndex);
}
public static String replace(String str, String oldSubstring, String newSubstring) {
// 添加替换字符串的功能
if (str == null || oldSubstring == null || newSubstring == null) {
throw new IllegalArgumentException("Invalid arguments");
}
return str.replace(oldSubstring, newSubstring);
}
}
通过创建一个新的类 EnhancedStringUtils
,继承原始的 StringUtils
类并添加缺失的方法或功能,可以补充库类的不完整功能,提供更全面的字符串处理功能。
javaCopy codepublic class CustomStringUtils {
public static String substring(String str, int startIndex, int endIndex) {
// 添加截取子串的功能
if (str == null || startIndex < 0 || endIndex < 0 || startIndex >= str.length() || endIndex > str.length()) {
throw new IllegalArgumentException("Invalid arguments");
}
return str.substring(startIndex, endIndex);
}
public static String replace(String str, String oldSubstring, String newSubstring) {
// 添加替换字符串的功能
if (str == null || oldSubstring == null || newSubstring == null) {