第10章 类
一、导读
1.1 本章学习目标
第10章讲解了类设计的艺术。Uncle Bob 介绍了类的组织原则:类应该短小、单一职责、高内聚。本章的核心学习目标包括:
- 掌握类应该短小:比函数更短。
- 理解单一职责原则(SRP):一个类只有一个变更理由。
- 学会内聚性:类的字段和方法应该紧密相关。
- 掌握封装:隐藏实现细节。
- 理解依赖关系:最小化类的依赖。
- 学会类的组织:字段、公共方法、私有方法。
1.2 为什么本章如此重要
类是面向对象编程的基本构建块。Uncle Bob 指出:类的设计质量直接决定了系统的可维护性。一个好的类应该职责清晰、接口简洁、实现封装。
本章的重要性在于它提供了类设计的系统化方法。你会理解如何设计高质量的类,如何避免"上帝类",如何实现良好的封装。
1.3 核心问题引导
你的类有多"大"?有没有超过500行的?
你的类是否遵循单一职责原则?
类的内聚性如何?字段和方法是否紧密相关?
封装做得如何?内部细节是否暴露了?
类的依赖关系是否合理?
二、核心概念详解
2.1 类应该短小
2.1.1 短小的标准
Uncle Bob 建议:类比函数更应该短小。函数的理想长度是20行,类的理想长度是100-200行。
// 差:过大的类
public class UserService {
// 500行代码
// 用户验证
// 用户创建
// 用户修改
// 用户删除
// 用户查询
// 用户通知
// 用户日志
// 用户统计
// ...
}
// 好:短小的类
public class UserService {
private final UserValidator validator;
private final UserRepository repository;
private final UserNotifier notifier;
public UserResult createUser(CreateUserRequest request) {
validator.validate(request);
User user = User.create(request);
repository.save(user);
notifier.sendWelcome(user);
return UserResult.success(user.getId());
}
}
public class UserValidator {
public void validate(CreateUserRequest request) {
// 只负责验证
}
}
public class UserRepository {
public void save(User user) {
// 只负责持久化
}
}
public class UserNotifier {
public void sendWelcome(User user) {
// 只负责通知
}
}2.2 单一职责原则
2.2.1 一个类只有一个变更理由
单一职责原则(SRP):一个类应该只有一个变更的理由。
// 差:多个变更理由
public class User {
// 变更理由1:用户数据模型变化
private String name;
private String email;
// 变更理由2:验证规则变化
public boolean isValid() {
return name != null && email != null && email.contains("@");
}
// 变更理由3:持久化方式变化
public void save() {
// SQL 语句
// 直接操作数据库
}
// 变更理由4:通知方式变化
public void sendNotification(String message) {
// 发送邮件
}
}
// 好:每个类只有一个变更理由
public class User {
// 只有数据模型变化时才修改
private final String name;
private final String email;
public String getName() { return name; }
public String getEmail() { return email; }
}
public class UserValidator {
// 只有验证规则变化时才修改
public boolean isValid(User user) {
return user.getName() != null
&& user.getEmail() != null
&& user.getEmail().contains("@");
}
}
public class UserRepository {
// 只有持久化方式变化时才修改
public void save(User user) {
// SQL 语句
}
}
public class UserNotifier {
// 只有通知方式变化时才修改
public void sendNotification(User user, String message) {
// 发送邮件
}
}2.3 内聚性
2.3.1 高内聚的类
高内聚意味着类的字段和方法紧密相关,共同服务于一个目的。
// 差:低内聚
public class Employee {
// 员工数据
private String name;
private double salary;
// 税务计算(不紧密相关)
public double calculateTax() {
return salary * 0.2;
}
// 报告生成(不紧密相关)
public String generateReport() {
return "Employee: " + name + ", Salary: " + salary;
}
// 数据库操作(不紧密相关)
public void saveToDatabase() {
// SQL
}
}
// 好:高内聚
public class Employee {
private String name;
private Money salary;
public String getName() { return name; }
public Money getSalary() { return salary; }
public void giveRaise(Money amount) {
salary = salary.add(amount);
}
// 所有方法都围绕"员工数据"这个核心
}
public class TaxCalculator {
public Money calculateTax(Employee employee) {
return employee.getSalary().multiply(0.2);
}
}
public class EmployeeReportGenerator {
public String generate(Employee employee) {
return "Employee: " + employee.getName() +
", Salary: " + employee.getSalary();
}
}2.4 封装
2.4.1 隐藏实现细节
// 差:暴露内部细节
public class Stack {
public List<Object> elements; // 暴露内部数据结构
public void push(Object element) {
elements.add(element);
}
}
// 外部可以直接修改内部数据
Stack stack = new Stack();
stack.elements.clear(); // 绕过 push 方法
// 好:良好封装
public class Stack {
private final List<Object> elements = new ArrayList<>();
public void push(Object element) {
elements.add(element);
}
public Object pop() {
if (elements.isEmpty()) {
throw new EmptyStackException();
}
return elements.remove(elements.size() - 1);
}
public boolean isEmpty() {
return elements.isEmpty();
}
// 内部实现可以随意修改
// 比如改为数组,不影响外部代码
}2.5 依赖关系
2.5.1 最小化依赖
// 差:过多依赖
public class OrderProcessor {
private final DatabaseConnection dbConn;
private final SmtpClient smtpClient;
private final FileSystem fileSystem;
private final Logger logger;
private final ConfigManager config;
private final CacheManager cache;
// ... 20个依赖
public OrderProcessor() {
// 直接创建依赖
this.dbConn = new DatabaseConnection("jdbc:mysql://...");
this.smtpClient = new SmtpClient("smtp.example.com");
// ...
}
}
// 好:最小化依赖
public class OrderProcessor {
private final OrderRepository repository;
private final NotificationService notifier;
public OrderProcessor(OrderRepository repository,
NotificationService notifier) {
this.repository = repository;
this.notifier = notifier;
}
public void process(Order order) {
repository.save(order);
notifier.sendConfirmation(order);
}
}2.6 类的组织
2.6.1 标准的类结构
public class OrderService {
// 1. 常量
private static final int MAX_RETRY_ATTEMPTS = 3;
private static final Money MINIMUM_ORDER_AMOUNT = Money.of(10);
// 2. 静态字段
private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
// 3. 实例字段
private final OrderRepository orderRepository;
private final PaymentGateway paymentGateway;
private final NotificationService notificationService;
// 4. 构造函数
public OrderService(OrderRepository orderRepository,
PaymentGateway paymentGateway,
NotificationService notificationService) {
this.orderRepository = orderRepository;
this.paymentGateway = paymentGateway;
this.notificationService = notificationService;
}
// 5. 公共方法
public OrderResult placeOrder(CreateOrderRequest request) {
validateRequest(request);
Order order = createOrder(request);
orderRepository.save(order);
PaymentResult payment = paymentGateway.charge(
order.getCustomer(), order.getTotalAmount());
if (payment.isSuccess()) {
order.confirm();
orderRepository.save(order);
notificationService.sendConfirmation(order);
return OrderResult.success(order.getId());
} else {
order.cancel();
orderRepository.save(order);
return OrderResult.failure(payment.getErrorMessage());
}
}
// 6. 私有方法
private void validateRequest(CreateOrderRequest request) {
if (request == null) {
throw new IllegalArgumentException("Request must not be null");
}
if (request.getItems().isEmpty()) {
throw new IllegalArgumentException("Order must have items");
}
}
private Order createOrder(CreateOrderRequest request) {
return new Order(
OrderId.generate(),
request.getCustomerId(),
request.getItems()
);
}
}三、实战案例
3.1 案例一:拆分上帝类
// 改善前:上帝类
public class UserManager {
// 用户数据
private String name;
private String email;
private String password;
// 用户验证
public boolean validate() { ... }
public boolean isEmailValid() { ... }
public boolean isPasswordStrong() { ... }
// 用户持久化
public void save() { ... }
public void delete() { ... }
public User findById(int id) { ... }
// 用户通知
public void sendWelcomeEmail() { ... }
public void sendPasswordReset() { ... }
// 用户统计
public int countActiveUsers() { ... }
public List<User> findRecentUsers() { ... }
}
// 改善后:拆分
public class User {
private final String name;
private final String email;
private final EncryptedPassword password;
public String getName() { return name; }
public String getEmail() { return email; }
}
public class UserValidator {
public ValidationResult validate(User user) { ... }
public boolean isEmailValid(String email) { ... }
public boolean isPasswordStrong(String password) { ... }
}
public class UserRepository {
public void save(User user) { ... }
public void delete(UserId id) { ... }
public Optional<User> findById(UserId id) { ... }
}
public class UserNotifier {
public void sendWelcomeEmail(User user) { ... }
public void sendPasswordReset(User user) { ... }
}
public class UserStatistics {
public int countActiveUsers() { ... }
public List<User> findRecentUsers() { ... }
}四、本章小结
第10章讲解了类设计的艺术,核心要点包括:
类应该短小:ideally 100-200行。
单一职责原则:一个类只有一个变更理由。
高内聚:字段和方法紧密相关。
封装:隐藏实现细节,暴露稳定接口。
最小化依赖:减少类的依赖数量。
类的组织:常量→静态字段→实例字段→构造函数→公共方法→私有方法。
Uncle Bob 的核心观点是:好的类设计是系统可维护性的基础。
五、思考与练习
思考题
你的类有多"大"?有没有超过500行的?
你的类是否遵循单一职责原则?
类的内聚性如何?
编程练习
找到一个"上帝类",将其拆分为多个单一职责的类。
为一个"图书馆管理系统"设计类结构。
改善一个类的封装,隐藏内部细节。