(论文)系统分析师系列(四)面向对象设计原则 面向对象设计原则OOP Design Principles面向对象设计原则是编写高可读、易扩展、易维护、低耦合代码的核心准则也是设计模式的基础。它们不是语法规则而是工程实践中总结的最佳实践。一、六大核心设计原则SOLID 另外 1 个最经典、面试必考、开发最常用的就是SOLID 原则迪米特法则。1. S – 单一职责原则 (Single Responsibility Principle)一个类只负责一件事只因为一个原因被修改。通俗理解一个人不要既当厨师又当司机又当会计好处代码清晰、修改不影响其他功能、易测试反例一个类既处理用户登录又处理订单支付又写日志2. O – 开闭原则 (Open/Closed Principle)对扩展开放对修改关闭。增加新功能时不修改原有代码只新增代码是所有设计原则中最重要的一条实现方式继承、接口、多态3. L – 里氏替换原则 (Liskov Substitution Principle)子类必须能完全替换父类且不破坏程序逻辑。通俗理解儿子必须能完全代替父亲干活核心子类不能破坏父类已有的行为规范反例正方形继承长方形会导致计算面积出错4. I – 接口隔离原则 (Interface Segregation Principle)客户端不应该依赖它不需要的接口。接口要小而专不要大而全避免一个接口包含几十上百个方法导致实现类被迫实现无用方法5. D – 依赖倒置原则 (Dependency Inversion Principle)依赖抽象不依赖具体实现。高层模块不依赖低层模块二者都依赖抽象面向接口编程而不是面向实现编程好处解耦、方便替换实现类比如换数据库、换缓存6. 迪米特法则 (LoD) / 最少知道原则一个对象应该对其他对象知道得越少越好。只和直接朋友通信不和陌生人说话降低类之间的耦合朋友当前类、成员变量、方法参数、方法创建的对象二、另外两个常用原则进阶7. 合成/聚合复用原则 (CARP)优先使用组合/聚合而不是继承来实现复用。继承强耦合组合低耦合口诀多用组合少用继承8. 共同封闭/共同重用原则包设计原则了解即可属于包模块级别原则一般架构设计时用到。正方形继承长方形→里氏替换原则 经典反例核心前提长方形Rectangle长、宽互相独立setWidth()、setHeight()互不影响。正方形Square长宽修改任意一边另一边必须同步变化。1、先写父类 RectangleclassRectangle{protectedintwidth;protectedintheight;publicvoidsetWidth(intw){this.widthw;}publicvoidsetHeight(inth){this.heighth;}publicintgetArea(){returnwidth*height;}}2、子类 Square 继承 Rectangle正方形必须保证宽高所以重写方法classSquareextendsRectangle{OverridepublicvoidsetWidth(intw){super.setWidth(w);super.setHeight(w);// 改宽高同步变}OverridepublicvoidsetHeight(inth){super.setHeight(h);super.setWidth(h);// 改高宽同步变}}3、关键测试代码出错根源里氏替换原则要求子类完全替代父类逻辑不变// 方法专门给长方形做拉伸publicstaticvoidstretch(Rectangler){r.setWidth(10);r.setHeight(5);// 预期宽10高5 → 面积 50System.out.println(r.getArea());}① 传父类Rectangle正常RectanglernewRectangle();stretch(r);// 输出50完全正常② 子类Square替换父类直接崩坏SquaresnewSquare();stretch(s);执行过程r.setWidth(10)→ 正方形宽10高强制10r.setHeight(5)→ 正方形高5宽强制5最终宽5高5 → 面积 5 × 5 25 5 \times 5 255×525预期50结果25 → 逻辑完全错误4、核心矛盾长方形长≠宽是常态正方形长≡宽是强制约束继承会强行继承父类行为子类为了满足自身约束篡改父类原有逻辑一旦子类篡改父类行为子类就不能安全替换父类→ 直接违反里氏替换原则。5、正确做法不要用继承改用「组合」正方形不继承长方形共同实现一个Shape接口getArea各自独立实现避免约束冲突。is-a 才用继承猫是动物 → 继承has-a 才用组合正方形有长方形结构 → 组合口诀多用组合少用继承彻底避免里氏替换翻车。一句话先定调依赖倒置原则高层 → 抽象接口不依赖具体实现换数据库 只换「底层实现类」上层代码一行不改一、错误写法依赖具体换库崩溃1. 高层业务类 直接写死 MySQL// 底层具体实现类classMysqlDb{publicvoidquery(){System.out.println(MySQL 查询数据);}}// 高层业务类classUserService{// 直接依赖【具体MySQL】privateMysqlDbdbnewMysqlDb();publicvoidgetUser(){db.query();}}2. 问题以后想换成PostgreSQL / Oracle / MongoDB必须修改 UserService 源码改代码 → 容易出 bug → 违反开闭原则耦合死死绑在 MySQL 上二、正确写法依赖抽象轻松换库1. 先定义【抽象接口】核心// 抽象数据库操作接口interfaceDbInterface{voidquery();}2. 不同数据库 各自实现接口// 实现1MySQLclassMysqlDbimplementsDbInterface{Overridepublicvoidquery(){System.out.println(MySQL 查询);}}// 实现2PostgreSQLclassPostgresDbimplementsDbInterface{Overridepublicvoidquery(){System.out.println(PostgreSQL 查询);}}// 实现3MongoDBclassMongoDbimplementsDbInterface{Overridepublicvoidquery(){System.out.println(MongoDB 查询);}}3. 高层业务类 只依赖【抽象接口】classUserService{// 只依赖抽象不依赖具体数据库privateDbInterfacedb;// 构造注入传入任意实现类publicUserService(DbInterfacedb){this.dbdb;}publicvoidgetUser(){db.query();}}三、实战一秒切换数据库① 用 MySQLDbInterfacedbnewMysqlDb();UserServiceservicenewUserService(db);service.getUser();② 想换 PostgreSQL只换一行DbInterfacedbnewPostgresDb();UserServiceservicenewUserService(db);service.getUser();③ 想换 MongoDB再换一行DbInterfacedbnewMongoDb();UserServiceservicenewUserService(db);✅核心UserService 源码完全不用改→ 符合「开闭原则」→ 彻底解耦→ 随便替换底层实现四、对应现实框架Spring 例子接口MyBatisMapper / JpaRepository实现MySQL 驱动、Oracle 驱动、PG 驱动业务层Service只调用抽象接口换数据库换依赖包改配置文件业务代码 0 修改依赖具体 绑死数据库换库要改代码依赖抽象 面向接口编程换数据库 替换「接口实现类」上层代码不动这就是依赖倒置原则最核心的价值顺带一提接口隔离、组合复用也都是为了「好替换、低耦合」