链接:
逻辑内聚是指在一个模块中,各个功能虽然执行不同的操作,但它们基于某种逻辑条件或参数来决定具体执行哪个操作。也就是说,模块中的不同功能通过某些逻辑判断(如传入的参数或控制标志)来选择执行哪一部分代码。
尽管逻辑内聚比偶然内聚(Coincidental Cohesion)要好一些,但它仍然不是理想的内聚形式。这是因为模块内部的功能虽然有一定的逻辑联系,但这些功能并不紧密相关,通常只是因为它们共享了某些输入或控制条件而被组合在一起。
逻辑内聚的特点:
- 基于逻辑条件:模块根据传入的参数或控制标志来决定执行哪个操作。
- 部分相关性:模块中的功能具有一定的逻辑关系,但不一定紧密相关。
- 较低的内聚度:由于模块内的功能并不是完全相关的,导致其内聚度相对较低。
逻辑内聚的问题
- 低可读性:由于模块内部的功能并不是完全相关的,代码的可读性和可理解性较差,开发人员需要仔细阅读和理解模块中的逻辑判断部分。
- 高维护成本:当需要修改某个功能时,由于模块内部的功能不完全相关,开发人员可能需要检查整个模块以确保不会引入不必要的副作用。
- 脆弱性:由于模块内部的功能不完全相关,任何对模块的修改都可能导致其他不相关的部分出现问题,增加了系统的脆弱性。
- 难以重用:由于模块内部的功能不完全相关,很难将模块中的某个功能单独提取出来进行重用。
举例说明逻辑内聚
假设我们正在开发一个简单的文件处理系统,其中有一个模块 FileProcessor 被设计用来处理各种文件操作,例如创建文件、删除文件和重命名文件。这个模块根据传入的操作类型参数来决定执行哪种文件操作。
public class FileProcessor
{
public void processFile(String operation, String filePath, String newFilePath)
{
if ("create".equals(operation))
{
createFile(filePath);
}
else if ("delete".equals(operation))
{
deleteFile(filePath);
}
else if ("rename".equals(operation))
{
renameFile(filePath, newFilePath);
}
else
{
System.out.println("Unknown operation: " + operation);
}
}
private void createFile(String filePath)
{
// 假设这里有一些创建文件的逻辑
System.out.println("Creating file at: " + filePath);
}
private void deleteFile(String filePath)
{
// 假设这里有一些删除文件的逻辑
System.out.println("Deleting file at: " + filePath);
}
private void renameFile(String oldFilePath, String newFilePath)
{
// 假设这里有一些重命名文件的逻辑
System.out.println("Renaming file from: " + oldFilePath + " to: " + newFilePath);
}
}
在这个例子中,FileProcessor 类包含了一个 processFile 方法,该方法根据传入的 operation 参数来决定执行哪种文件操作。尽管这些文件操作之间有一定的逻辑联系(都是文件操作),但它们并不是紧密相关的,因为它们各自执行不同的任务。
这就是典型的逻辑内聚,因为模块内部的功能是基于传入的参数来选择执行哪个操作的。
改进方案:提高模块的内聚性
为了提高模块的内聚性,可以采用以下几种策略:
- 分离功能:将不相关的功能拆分到不同的模块中,使每个模块专注于特定的任务。
- 封装相关功能:将相关的功能封装在一起,形成高内聚的模块。
- 使用多态性:利用面向对象编程中的多态性,通过接口或抽象类来实现不同的操作。
改进后的示例代码:
我们可以将上述不相关的文件操作拆分成独立的模块,并利用接口和多态性来实现不同的文件操作。
public interface FileOperation
{
void execute();
}
// 创建文件操作实现类
public class CreateFileOperation implements FileOperation
{
private final String filePath;
public CreateFileOperation(String filePath)
{
this.filePath = filePath;
}
@Override
public void execute()
{
// 假设这里有一些创建文件的逻辑
System.out.println("Creating file at: " + filePath);
}
}
// 删除文件操作实现类
public class DeleteFileOperation implements FileOperation
{
private final String filePath;
public DeleteFileOperation(String filePath)
{
this.filePath = filePath;
}
@Override
public void execute()
{
// 假设这里有一些删除文件的逻辑
System.out.println("Deleting file at: " + filePath);
}
}
public class RenameFileOperation implements FileOperation
{
private final String filePath;
private final String newFilePath;
public RenameFileOperation(String filePath, String newFilePath)
{
this.filePath = filePath;
this.newFilePath = newFilePath;
}
@Override
public void execute()
{
// 假设这里有一些重命名文件的逻辑
System.out.println("Renaming file from: " + filePath + " to: " + newFilePath);
}
}
// 文件处理器类,负责根据操作类型执行相应的文件操作
public class FileProcessor
{
public void executeOperation(FileOperation operation)
{
operation.execute();
}
}
改进后的分析:
分离功能:
- 将不相关的文件操作(创建文件、删除文件、重命名文件)拆分到不同的类中,每个类只负责一个明确的任务。这符合单一职责原则,使得每个类更加专注和清晰。
封装相关功能:
- 使用接口 FileOperation 来封装所有文件操作的共同行为(即 execute 方法),并通过具体的实现类(如 CreateFileOperation、DeleteFileOperation 和 RenameFileOperation)来实现不同的文件操作。这种方式提高了模块的内聚性,使得每个类的功能更加紧密相关。
提高可读性和可维护性:
- 通过将不相关的功能拆分到不同的类中,代码的可读性和可维护性得到了显著提升。开发人员可以更容易地理解和修改每个类的功能。
降低脆弱性:
- 由于每个类只负责一个明确的任务,修改其中一个类不会影响到其他类,降低了系统的脆弱性。
便于重用:
- 现在每个类都可以独立使用,便于在其他地方重用。例如,CreateFileOperation 可以在其他需要创建文件的地方直接使用,而不需要依赖于其他不相关的功能。