业务代码只依赖接口,不关心存储实现细节。接口定义明确契约(如UserRepository的findById、save),不含技术参数或配置;实现类各自封装底层技术(JDBC、Redis等),通过依赖注入提供能力,确保业务层零修改即可切换存储方案。

核心就一句话:业务代码只认接口,不认数据库、Redis 或文件——谁来存、怎么存,它一概不管。
定义存储契约,不暴露技术细节
接口只说“要什么”,不说“怎么来”。比如用户查询,接口就叫 UserRepository,方法只有 findById(Long id) 和 save(User user)。里面不能出现 Connection、JdbcTemplate、RedisTemplate 这类词,也不能定义事务控制、超时时间等实现专属配置。
错误示范:
void save(User user, String dbType, int timeout) —— 把实现参数塞进接口,等于把数据库选型提前锁死。
正确做法:
User findById(Long id)
void save(User user)
所有技术细节(用 MySQL 还是 MongoDB、是否加缓存层、序列化方式)全由实现类内部消化。
实现类各自封装,互不影响
每个实现类只专注一件事,并把底层技术彻底包住:
- JdbcUserRepository:封装 DataSource、PreparedStatement、事务管理,对外只暴露接口方法
- RedisUserRepository:封装 Jedis 或 Lettuce 客户端、序列化逻辑、过期策略,调用方看不见 key 格式或 hash 结构
- CacheThenJdbcUserRepository(装饰器):先查 Redis,未命中再查 JDBC,再写回缓存——整个流程对 UserService 透明
关键点:所有实现类的构造函数只接收技术资源(如 DataSource、JedisPool),不暴露给业务层;它们之间不继承、不强关联,只是并列实现了同一个接口。
立即学习“Java免费学习笔记(深入)”;
业务层彻底隔离,靠依赖注入获取能力
UserService 不 new 任何存储类,也不 if-else 切换实现。它只声明依赖:
private final UserRepository userRepository;
通过构造器注入,运行时由 Spring 或测试代码传入具体实例。
好处立现:
• 换数据库?改配置或替换 Bean,UserService 零修改
• 加本地缓存?新增一个 CaffeineUserRepository 实现,注入时换掉就行
• 单元测试?直接传入 MockUserRepository,完全绕过真实存储
封装不只是隐藏字段,更是隐藏决策路径
真正的封装,是连“该不该查缓存”“失败要不要重试”都收在实现类里。比如:
- 接口不定义 retry() 方法,那是实现策略,不是契约
- 不暴露 isCached()、getHitRate() 这类监控方法——监控应走 AOP 或独立指标模块
- 异常统一转为业务语义:抛 UserNotFoundException,而不是 SQLException 或 RedisConnectionFailureException
这样,上层永远面对的是稳定、干净、面向领域的接口,底层哪怕从 JDBC 换成 gRPC 调用远端服务,只要行为一致,业务代码就不动。



















