mcpField
S
solid-principles

solid-principles

github.com/decebals开发761357 次安装7 周前更新
代码仓库

关于 solid-principles

用于在 Java 代码中审查和应用 SOLID 原则的技能。它通过说明五项原则、违规迹象和常见修复方式,支持类设计审查与重构。

主要功能

  1. 01讲解全部五项 SOLID 原则
  2. 02列出设计违规迹象
  3. 03提供按原则分类的审查问题
  4. 04将常见违规对应到重构模式

适用场景

  1. 01审查 Java 类设计
  2. 02重构职责过多的类
  3. 03检查继承、接口和依赖关系

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/decebals/claude-code-java --skill solid-principles

安装后在 Claude Code 中输入/solid-principles确认已加载。

附带文件README.md

详细介绍

SOLID 原则技能

审查并在 Java 代码中应用 SOLID 原则。

适用场景

  • 用户说“检查 SOLID”/“SOLID 审查”/“这个类是不是做得太多了?”
  • 审查类设计
  • 重构大型类
  • 关注设计的代码审查

快速参考

字母原则一句话说明
S单一职责原则一个类 = 一个变更理由
O开闭原则对扩展开放,对修改关闭
L里氏替换原则子类型必须能够替换基类型
I接口隔离原则多个专用接口 > 一个通用接口
D依赖倒置原则依赖抽象,而非具体实现

S - 单一职责原则(SRP)

“一个类应该只有一个变更理由。”

违反示例
// ❌ BAD: UserService does too much
public class UserService {

    public User createUser(String name, String email) {
        // validation logic
        if (email == null || !email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }

        // persistence logic
        User user = new User(name, email);
        entityManager.persist(user);

        // notification logic
        String subject = "Welcome!";
        String body = "Hello " + name;
        emailClient.send(email, subject, body);

        // audit logic
        auditLog.log("User created: " + email);

        return user;
    }
}

问题:

  • 验证逻辑变更?修改 UserService
  • 邮件模板变更?修改 UserService
  • 审计格式变更?修改 UserService
  • 难以分别测试每项职责
重构后
// ✅ GOOD: Each class has one responsibility

public class UserValidator {
    public void validate(String name, String email) {
        if (email == null || !email.contains("@")) {
            throw new ValidationException("Invalid email");
        }
    }
}

public class UserRepository {
    public User save(User user) {
        entityManager.persist(user);
        return user;
    }
}

public class WelcomeEmailSender {
    public void sendWelcome(User user) {
        String subject = "Welcome!";
        String body = "Hello " + user.getName();
        emailClient.send(user.getEmail(), subject, body);
    }
}

public class UserAuditLogger {
    public void logCreation(User user) {
        auditLog.log("User created: " + user.getEmail());
    }
}

public class UserService {
    private final UserValidator validator;
    private final UserRepository repository;
    private final WelcomeEmailSender emailSender;
    private final UserAuditLogger auditLogger;

    public User createUser(String name, String email) {
        validator.validate(name, email);
        User user = repository.save(new User(name, email));
        emailSender.sendWelcome(user);
        auditLogger.logCreation(user);
        return user;
    }
}
如何检测违反 SRP 的情况
  • 类中有许多来自不同领域的 import 语句
  • 类名包含“And”“Manager”或“Handler”(通常如此)
  • 方法处理互不相关的数据
  • 某个领域的变更需要修改无关的方法
  • 难以为类取一个简洁的名称
快速检查问题
  1. 能否用一句不含“和”的话描述这个类的用途?
  2. 是否会有不同的利益相关者要求修改这个类?
  3. 是否有方法未使用类的大部分字段?

O - 开闭原则(OCP)

“软件实体应该对扩展开放,对修改关闭。”

违反示例
// ❌ BAD: Must modify class to add new discount type
public class DiscountCalculator {

    public double calculate(Order order, String discountType) {
        if (discountType.equals("PERCENTAGE")) {
            return order.getTotal() * 0.1;
        } else if (discountType.equals("FIXED")) {
            return 50.0;
        } else if (discountType.equals("LOYALTY")) {
            return order.getTotal() * order.getCustomer().getLoyaltyRate();
        }
        // Every new discount type = modify this class
        return 0;
    }
}
重构后
// ✅ GOOD: Add new discounts without modifying existing code

public interface DiscountStrategy {
    double calculate(Order order);
    boolean supports(String discountType);
}

public class PercentageDiscount implements DiscountStrategy {
    @Override
    public double calculate(Order order) {
        return order.getTotal() * 0.1;
    }

    @Override
    public boolean supports(String discountType) {
        return "PERCENTAGE".equals(discountType);
    }
}

public class FixedDiscount implements DiscountStrategy {
    @Override
    public double calculate(Order order) {
        return 50.0;
    }

    @Override
    public boolean supports(String discountType) {
        return "FIXED".equals(discountType);
    }
}

public class LoyaltyDiscount implements DiscountStrategy {
    @Override
    public double calculate(Order order) {
        return order.getTotal() * order.getCustomer().getLoyaltyRate();
    }

    @Override
    public boolean supports(String discountType) {
        return "LOYALTY".equals(discountType);
    }
}

// New discount? Just add new class, no modification needed
public class SeasonalDiscount implements DiscountStrategy {
    @Override
    public double calculate(Order order) {
        return order.getTotal() * 0.2;
    }

    @Override
    public boolean supports(String discountType) {
        return "SEASONAL".equals(discountType);
    }
}

public class DiscountCalculator {
    private final List<DiscountStrategy> strategies;

    public DiscountCalculator(List<DiscountStrategy> strategies) {
        this.strategies = strategies;
    }

    public double calculate(Order order, String discountType) {
        return strategies.stream()
            .filter(s -> s.supports(discountType))
            .findFirst()
            .map(s -> s.calculate(order))
            .orElse(0.0);
    }
}
如何检测违反 OCP 的情况
  • 按类型/状态进行的 if/else 或 switch 分支会不断增加
  • 基于枚举的分派逻辑经常需要添加新值
  • 变更需要修改核心类
常见 OCP 模式
模式适用场景
策略模式同一操作有多种算法
模板方法模式结构相同,步骤不同
装饰器模式动态添加行为
工厂模式无需指定类即可创建对象

L - 里氏替换原则(LSP)

“子类型必须能够替换基类型。”

违反示例
// ❌ BAD: Square violates Rectangle contract
public class Rectangle {
    protected int width;
    protected int height;

    public void setWidth(int width) {
        this.width = width;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getArea() {
        return width * height;
    }
}

public class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        this.width = width;
        this.height = width;  // Violates expected behavior!
    }

    @Override
    public void setHeight(int height) {
        this.width = height;  // Violates expected behavior!
        this.height = height;
    }
}

// This test fails for Square!
void testRectangle(Rectangle r) {
    r.setWidth(5);
    r.setHeight(4);
    assert r.getArea() == 20;  // Square returns 16!
}
重构后
// ✅ GOOD: Separate abstractions

public interface Shape {
    int getArea();
}

public class Rectangle implements Shape {
    private final int width;
    private final int height;

    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public int getArea() {
        return width * height;
    }
}

public class Square implements Shape {
    private final int side;

    public Square(int side) {
        this.side = side;
    }

    @Override
    public int getArea() {
        return side * side;
    }
}
LSP 规则
规则含义
前置条件子类不能加强前置条件(要求更多)
后置条件子类不能削弱后置条件(承诺更少)
不变式子类必须维持父类的不变式
历史约束子类不能意外修改继承的状态
如何检测违反 LSP 的情况
  • 子类抛出父类不会抛出的异常
  • 父类返回对象时,子类返回 null
  • 子类意外忽略或覆盖父类行为
  • 调用方法前进行 instanceof 检查
  • 接口方法的实现为空或抛出异常
快速检查
// If you see this, LSP might be violated
if (bird instanceof Penguin) {
    // don't call fly()
} else {
    bird.fly();
}

I - 接口隔离原则(ISP)

“客户端不应被迫依赖自己不会使用的接口。”

违反示例
// ❌ BAD: Fat interface forces unnecessary implementations
public interface Worker {
    void work();
    void eat();
    void sleep();
    void attendMeeting();
    void writeReport();
}

// Robot can't eat or sleep!
public class Robot implements Worker {
    @Override public void work() { /* OK */ }
    @Override public void eat() { /* Can't eat! */ }
    @Override public void sleep() { /* Can't sleep! */ }
    @Override public void attendMeeting() { /* OK */ }
    @Override public void writeReport() { /* Maybe */ }
}

// Intern doesn't attend meetings or write reports
public class Intern implements Worker {
    @Override public void work() { /* OK */ }
    @Override public void eat() { /* OK */ }
    @Override public void sleep() { /* OK */ }
    @Override public void attendMeeting() { /* Not allowed! */ }
    @Override public void writeReport() { /* Not expected! */ }
}
重构后
// ✅ GOOD: Segregated interfaces

public interface Workable {
    void work();
}

public interface Feedable {
    void eat();
    void sleep();
}

public interface Manageable {
    void attendMeeting();
    void writeReport();
}

// Combine what you need
public class Employee implements Workable, Feedable, Manageable {
    @Override public void work() { /* ... */ }
    @Override public void eat() { /* ... */ }
    @Override public void sleep() { /* ... */ }
    @Override public void attendMeeting() { /* ... */ }
    @Override public void writeReport() { /* ... */ }
}

public class Robot implements Workable {
    @Override public void work() { /* ... */ }
    // No unnecessary methods!
}

public class Intern implements Workable, Feedable {
    @Override public void work() { /* ... */ }
    @Override public void eat() { /* ... */ }
    @Override public void sleep() { /* ... */ }
    // No meeting/report methods!
}
如何检测违反 ISP 的情况
  • 实现中包含空方法或 throw new UnsupportedOperationException()
  • 接口有 10 个以上的方法
  • 不同客户端使用的方法子集完全不同
  • 接口变更会影响无关的实现
Java 标准库中的违反示例
// java.util.List has many methods - but this is acceptable for collections
// However, be careful with your own interfaces!

// ❌ This interface is too fat for most use cases
public interface Repository<T> {
    T findById(Long id);
    List<T> findAll();
    T save(T entity);
    void delete(T entity);
    void deleteById(Long id);
    List<T> findByExample(T example);
    Page<T> findAll(Pageable pageable);
    List<T> findAllById(Iterable<Long> ids);
    long count();
    boolean existsById(Long id);
    // ... 20 more methods
}

// ✅ Better: Split by use case
public interface ReadRepository<T> {
    Optional<T> findById(Long id);
    List<T> findAll();
}

public interface WriteRepository<T> {
    T save(T entity);
    void delete(T entity);
}

D - 依赖倒置原则(DIP)

“高层模块不应依赖低层模块。两者都应依赖抽象。”

违反示例
// ❌ BAD: High-level depends on low-level directly
public class OrderService {
    private MySqlOrderRepository repository;  // Concrete class!
    private SmtpEmailSender emailSender;      // Concrete class!

    public OrderService() {
        this.repository = new MySqlOrderRepository();  // Hard dependency
        this.emailSender = new SmtpEmailSender();      // Hard dependency
    }

    public void createOrder(Order order) {
        repository.save(order);
        emailSender.send(order.getCustomerEmail(), "Order confirmed");
    }
}

问题:

  • 无法在没有真实 MySQL 数据库的情况下测试
  • 无法替换邮件服务提供商
  • OrderService 了解 MySQL、SMTP 的细节
重构后
// ✅ GOOD: Depend on abstractions

// Abstractions (interfaces)
public interface OrderRepository {
    void save(Order order);
    Optional<Order> findById(Long id);
}

public interface NotificationSender {
    void send(String recipient, String message);
}

// High-level module depends on abstractions
public class OrderService {
    private final OrderRepository repository;
    private final NotificationSender notificationSender;

    // Dependencies injected
    public OrderService(OrderRepository repository,
                        NotificationSender notificationSender) {
        this.repository = repository;
        this.notificationSender = notificationSender;
    }

    public void createOrder(Order order) {
        repository.save(order);
        notificationSender.send(order.getCustomerEmail(), "Order confirmed");
    }
}

// Low-level modules implement abstractions
public class MySqlOrderRepository implements OrderRepository {
    @Override
    public void save(Order order) { /* MySQL specific */ }

    @Override
    public Optional<Order> findById(Long id) { /* MySQL specific */ }
}

public class SmtpEmailSender implements NotificationSender {
    @Override
    public void send(String recipient, String message) { /* SMTP specific */ }
}

// Easy to test with mocks!
public class InMemoryOrderRepository implements OrderRepository {
    private Map<Long, Order> orders = new HashMap<>();

    @Override
    public void save(Order order) {
        orders.put(order.getId(), order);
    }

    @Override
    public Optional<Order> findById(Long id) {
        return Optional.ofNullable(orders.get(id));
    }
}
使用 Spring 实现 DIP
// Spring handles dependency injection automatically

@Service
public class OrderService {
    private final OrderRepository repository;
    private final NotificationSender notificationSender;

    // Constructor injection (recommended)
    public OrderService(OrderRepository repository,
                        NotificationSender notificationSender) {
        this.repository = repository;
        this.notificationSender = notificationSender;
    }
}

@Repository
public class JpaOrderRepository implements OrderRepository {
    // Spring provides implementation
}

@Component
@Profile("production")
public class SmtpEmailSender implements NotificationSender { }

@Component
@Profile("test")
public class MockEmailSender implements NotificationSender { }
如何检测 DIP 违规
  • 业务逻辑中出现 new ConcreteClass()
  • 导入语句包含实现包(例如 com.mysql、org.apache.http)
  • 难以轻松替换实现
  • 测试需要真实基础设施(数据库、网络)

SOLID 审查清单

审查代码时,请检查:

原则问题
SRP这个类是否有多个变更原因?
OCP添加新类型/功能是否需要修改这个类?
LSP在需要父类的任何地方,是否都可以使用子类?
ISP是否存在空方法实现或抛出异常的方法实现?
DIP高层代码是否依赖具体实现?

常见重构模式

违规重构
SRP - 上帝类提取类、移动方法
OCP - 类型切换策略模式、工厂
LSP - 受损的继承关系组合优于继承、提取接口
ISP - 臃肿的接口拆分接口、角色接口
DIP - 硬依赖依赖注入、抽象工厂

相关技能

  • design-patterns - 实现模式(工厂、策略、观察者等)
  • clean-code - 代码级原则(DRY、KISS、命名)
  • java-code-review - 综合审查清单

评论

支持 Markdown