哈喽,大家好,欢迎回到小码农岛!关注公众号:小码农岛 可查看更多文章,所有文章都会优先发布在公众号上。
话不多说,我们回到正题。
开闭原则(OCP)
简单来说
能方便地加新功能,但别改老代码
就像玩乐高积木,你可以随时加新积木块(扩展),但不用拆已经搭好的部分(不改动原有代码)。
为啥这么重要?
- 改老代码容易带出bug
- 加新功能时不用重新测试老代码
代码
错误案例:
java
// 这个绘图工具每次加图形都得改代码
class GraphicEditor {
public void drawShape(Shape s) {
if (s.type == 1) drawRectangle(); // 加个三角形又得新增if判断
else if (s.type == 2) drawCircle();
// 以后每加新图形都要改这里
}
}
问题:每次新增图形类型都得修改核心方法,迟早要出问题。
正确姿势:
java
// 先定个规矩:所有图形都得会自己画自己
abstract class Shape {
public abstract void draw(); // 强制子类自己实现
}
// 圆形自己管怎么画
class Circle extends Shape {
@Override
public void draw() {
System.out.println("画个圆");
}
}
// 工具类根本不用改
class GraphicEditor {
public void drawShape(Shape shape) {
shape.draw(); // 甭管什么图形,调就完事了
}
}
好处 :要加三角形?直接新建个Triangle
类就行,其他代码不用动。
单一职责原则(SRP)
说人话
一个类只干一件事
就像餐厅里炒菜归厨师,端盘子归服务员,各司其职。
为什么要拆开?
- 改一个功能不影响其他
- 更容易重复使用
代码
错误案例:
java
class UserService {
// 又管数据库又管业务逻辑
public UserDTO getUser(String name) {
Connection conn = DriverManager.getConnection(...); // 直接连数据库
// 写SQL...
// 转DTO...
}
}
问题:哪天要换数据库,整个业务逻辑都得跟着改。
正确拆分:
java
// 专门管数据库的
class UserDao {
public User queryFromDB(String name) {
// 专业处理数据库操作
}
}
// 专门管业务的
class UserService {
private UserDao dao; // 用接口不直接依赖
public UserDTO getUser(String name) {
User user = dao.queryFromDB(name);
// 专心处理业务转换
return new UserDTO(user);
}
}
好处:换数据库只要改Dao层,业务代码稳如老狗。
里氏替换原则(LSP)
核心要点
子类要能当父类用
就像手机充电口,type-C可以替换USB,但功能不受影响。
关键注意
- 不能缩小方法权限(父类public的方法子类不能变private)
- 不能改变核心功能(父类算减法,子类不能改成加法)
经典案例
java
// 基础版:鸟会飞
class Bird {
public void fly() {
System.out.println("扑棱翅膀飞");
}
}
// 反面教材:企鹅继承鸟
class Penguin extends Bird { // 要闯祸!
@Override
public void fly() {
throw new UnsupportedOperationException(); // 企鹅不会飞啊
}
}
// 正确打开方式
interface Flyable { void fly(); }
interface Swimmable { void swim(); }
class Sparrow implements Flyable { ... } // 麻雀会飞
class Penguin implements Swimmable { ... } // 企鹅会游泳
启示:别让子类做不可能完成的任务,用接口更灵活。
依赖倒置原则(DIP)
说人话
对着接口编程,别死磕具体实现
就像用插座充电,管你是手机还是台灯,符合插头标准就能用。
实现套路
- 业务层定接口标准
- 具体实现类按标准来
- 用的时候把实现类"注射"进去
代码示例
java
// 先定义发消息的标准
interface MessageSender {
void send(String message);
}
// 短信发送实现
class SmsSender implements MessageSender {
public void send(String msg) {
System.out.println("发短信:" + msg);
}
}
// 邮件发送实现
class EmailSender implements MessageSender {
public void send(String msg) {
System.out.println("发邮件:" + msg);
}
}
// 业务类只管标准不管实现
class NotificationService {
private MessageSender sender;
// 要什么发送方式就从外面传
public NotificationService(MessageSender sender) {
this.sender = sender;
}
public void alert(String message) {
sender.send(message); // 按标准调用
}
}
优势:要换发送方式?换个实现类就行,业务代码纹丝不动。
接口隔离原则(ISP)
核心思想
宁要多个小接口,不要一个大而全
就像工具箱,螺丝刀归螺丝刀,锤子归锤子,比瑞士军刀好用。
什么时候拆?
- 接口里有些方法总用不上
- 不同客户端只用部分方法
代码示例
臃肿接口:
java
interface Animal {
void eat();
void fly(); // 企鹅:我招谁惹谁了?
void swim(); // 麻雀:关我啥事?
}
问题:实现类被迫写没用的方法。
优化方案:
java
interface Eatable { void eat(); }
interface Flyable { void fly(); }
interface Swimmable { void swim(); }
class Sparrow implements Eatable, Flyable {...}
class Penguin implements Eatable, Swimmable {...}
好处:需要什么功能就实现什么接口,清爽!
合成复用原则(CARP)
说人话
多用组合,少用继承
就像组装电脑,买显卡插主板(组合)比改造主板电路(继承)靠谱多了。
组合 vs 继承
继承 | 组合 | |
---|---|---|
关系 | 是父类的一种(汽车是交通工具) | 包含组件(汽车有发动机) |
灵活度 | 改父类影响所有子类 | 随时换零件 |
耦合度 | 高(绑死父类) | 低(接口对接) |
代码示例
java
// 用组合实现功能
class Car {
private Engine engine; // 发动机作为零件
public Car(Engine engine) {
this.engine = engine; // 想换引擎?传进来就行
}
public void start() {
engine.ignite(); // 调用引擎接口
}
}
interface Engine {
void ignite();
}
// 各种引擎实现
class GasEngine implements Engine {...}
class ElectricEngine implements Engine {...}
优势:油车改电车?换个电动引擎实现类就行。
迪米特法则(LOD)
简单理解
知道的越少越好
就像租房找中介,不用直接联系房东、物业、装修队。
注意事项
- 只和直接朋友打交道(自己的属性、方法参数、返回值)
- 不暴露复杂内部结构(返回List而不是ArrayList)
- 避免长链式调用(别写a.getB().getC().doSomething())
代码示例
java
// 违反法则的写法
class Teacher {
public void command(Monitor monitor) {
List<Student> students = monitor.getStudents(); // 拿到学生名单
for(Student s : students) { // 直接指挥每个学生
s.clean();
}
}
}
// 符合法则的写法
class Teacher {
public void command(Monitor monitor) {
monitor.cleanClassroom(); // 让班长负责具体安排
}
}
class Monitor {
public void cleanClassroom() {
// 内部怎么分配我不管
}
}
好处:老师不用知道学生怎么打扫,只管下命令就行。
总结对比表
原则名称 | 核心要点 | 常用场景 | 容易踩的坑 |
---|---|---|---|
开闭原则 | 加功能不改老代码 | 功能扩展、插件开发 | 过度设计搞出复杂抽象 |
单一职责 | 一个类只干一件事 | 代码分层、工具类拆分 | 拆太细导致类太多 |
里氏替换 | 子类能当父类用 | 接口实现、算法替换 | 子类乱改父类功能 |
依赖倒置 | 面向接口编程 | 模块解耦、依赖注入 | 直接new具体实现类 |
接口隔离 | 按需拆分小接口 | 权限管理、功能拆分 | 设计万能接口 |
合成复用 | 多用组合少继承 | 组件开发、功能扩展 | 继承层级太深 |
迪米特法则 | 减少不必要的交互 | 模块通信、系统封装 | 链式调用暴露内部结构 |
这些原则就像做饭的调料,用好了能提升代码质量,但别死板硬套。新手建议多练习代码重构,慢慢体会什么时候该用什么原则。记住:适合的才是最好的!
最后想说
如果还在为面试八股文头疼?关注公众号 小码农岛 ,后台发送 66 免费领取《面试高频题宝典》,涵盖Java/MySQL/Redis/分布式
等热门考点,助你轻松斩获Offer!
OK,今天的分享就到这里。欢迎「点赞+关注」支持!你对此有什么看法?欢迎在评论区分享您的观点。最后非常感谢大家的支持!你们的支持是我写作路上最大的动力。我们下期再见!