网站建设工作基本流程,网站首页制作教程,快速收录网,少儿编程培训加盟品牌费用文章目录 零 五大设计原则一 单一职责原则#xff08;SRP#xff09;1.1 单一职责原则1.2 Java中的单一职责原则1.2.1 反例#xff1a;违反 SRP 的类1.2.2 正确做法#xff1a;拆分职责 1.3 四 SRP 的“边界”1.4 初学者常见误区1.5 思考与延伸 二 开闭原则#xff08;OCP… 文章目录 零 五大设计原则一 单一职责原则SRP1.1 单一职责原则1.2 Java中的单一职责原则1.2.1 反例违反 SRP 的类1.2.2 正确做法拆分职责 1.3 四 SRP 的“边界”1.4 初学者常见误区1.5 思考与延伸 二 开闭原则OCP2.1 开闭原则2.2 Java中的开闭原则2.2.1 初始实现未遵守开闭原则2.2.2 改进的实现遵守开闭原则2.2.3 关键点分析 2.3 深度探究2.4 初学者建议 三 里氏替换原则LSP3.1 里氏替换原则3.2 Java中的里氏替换原则3.2.1 初始实现未遵守LSP3.2.2 改进的实现遵守LSP 3.3 深度探究 四 接口隔离原则ISP4.1 接口隔离原则4.2 Java中的接口隔离原则4.2.1 初始实现未遵守ISP4.2.2 改进的实现遵守ISP 4.3 深入理解接口隔离原则4.4 接口隔离原则在设计模式中的体现 五 依赖反转原则DIP5.1 为什么要遵循依赖反转原则5.2 Java中的依赖反转原则5.2.1 初始实现不遵循DIP5.2.2 改进的实现遵守DIP 5.3 深入理解依赖倒置的两个原则5.4 依赖反转原则的实现技巧5.4.1 使用接口或抽象类5.4.2 依赖注入DI 5.5深度探究5.5.1 DIP的优势5.5.2 限制与注意事项 零 五大设计原则
五大设计原则也被称为SOLID原则是面向对象设计中的五个核心原则旨在帮助开发者设计出更易于理解、维护和扩展的软件系统。它们由Robert C. Martin“Uncle Bob”提出广泛应用于软件工程中。
原则全称主要思想SSingle Responsibility Principle一个类只做一件事职责单一OOpen/Closed Principle对扩展开放对修改关闭LLiskov Substitution Principle子类可以替换父类行为一致IInterface Segregation Principle不依赖没用到的接口接口拆分细化DDependency Inversion Principle依赖抽象依赖注入 S - Single Responsibility Principle单一职责原则 一个类应该只有一个引起它变化的原因也就是说一个类应当仅承担一个职责。解释 每个类都应专注于一项职责避免“杂多责任”导致的类变得难以理解或维护。 示例一个“用户”类负责存储用户信息不要让它同时负责用户的存储和发送邮件。 示例一个“用户”类负责存储用户信息不要让它同时负责用户的存储和发送邮件。O - Open/Closed Principle开闭原则软件实体类、模块、函数等应对扩展开放对修改关闭。解释 在不改变已有代码的前提下通过扩展新功能使系统可以应对变化。 示例使用抽象类或接口定义基本操作然后通过继承或实现扩展具体功能而不是修改原有代码。L - Liskov Substitution Principle里氏替换原则子类应当可以替换掉它们的父类对象而程序的行为不会发生改变。解释 如果类B继承自类A那么在任何使用A的地方都可以用B替换且不会引起错误或异常。示例如果有一个Bird类定义了fly()方法那么所有继承Bird的类都应能飞不能有“不能飞”的子类破坏这个原则。I - Interface Segregation Principle接口隔离原则客户端不应依赖于它不使用的接口。解释 避免“胖接口”建议为不同客户设计专门的接口让实现类只依赖它们真正需要的方法。 示例不要让一个接口定义所有操作比如print(), scan(), fax()而是拆分成多个接口。D - Dependency Inversion Principle依赖反转原则高层模块不应依赖于低层模块两者都应依赖于抽象抽象不应依赖细节细节应依赖抽象。解释 依赖于抽象而不是具体实现减少模块之间的耦合使系统更易于扩展和维护。 示例 高层模块依赖于接口如MessageService而具体实现如EmailService通过依赖注入提供。 一 单一职责原则SRP
1.1 单一职责原则
单一职责原则Single Responsibility Principle是面向对象设计中 SOLID 原则 中的 S其核心思想是**一个类或方法应该只有一个引起它变化的原因。**换句话说一个类只做一件事职责单一。 为什么需要单一职责原则
提高代码可维护性职责清晰修改时定位更准确。增强代码复用性单一功能模块更容易被其他模块调用。降低耦合度各司其职减少类之间的相互影响。便于测试和调试功能集中单元测试更容易覆盖。 单一职责 ≠ 一个类一个方法而是让一个类只干一件事并把它做好。 关键词说明内聚性类内部方法围绕一个中心职责可维护性修改一处不会影响其他功能扩展性新需求可通过新增类实现而非修改旧类
1.2 Java中的单一职责原则
1.2.1 反例违反 SRP 的类
public class User {private String name;public void saveToDatabase() {// 模拟保存到数据库逻辑System.out.println(name saved to database.);}public void sendEmail(String email) {// 模拟发送邮件逻辑System.out.println(Email sent to email);}
}问题分析User 类承担了两个职责管理用户数据如保存、发送邮件逻辑。如果将来数据库操作或邮件服务发生变更都需要修改这个类违背了 SRP。
1.2.2 正确做法拆分职责
// 用户实体类
public class User {private String name;private String email;public String getName() {return name;}public String getEmail() {return email;}
}// 数据访问层
public class UserRepository {public void save(User user) {System.out.println(user.getName() saved to database.);}
}// 邮件服务类
public class EmailService {public void sendEmail(String email) {System.out.println(Email sent to email);}
}✅ 改进点
每个类职责单一 User描述用户信息。UserRepository负责持久化。EmailService负责邮件发送。 修改任何一个功能只需改动对应的类符合 SRP。 1.3 四 SRP 的“边界”
粒度控制要合理不能过度拆分。职责划分应基于业务场景与变化频率。可借助接口抽象来解耦职责。 1.4 初学者常见误区
误区正确认识把所有功能都写在一个类里应该按职责分离认为每个类只能有一个方法方法可以有多个只要它们属于同一职责拆分太细导致类爆炸合理划分避免过度设计 1.5 思考与延伸
SRP 是构建高内聚、低耦合系统的基础。在实际开发中结合 依赖注入DI 和 接口编程 效果更好。Spring 框架中大量使用 SRP比如 Service、Repository、Controller 分离。 二 开闭原则OCP
2.1 开闭原则 开闭原则Open-Closed Principle由“面向对象设计的四大基本原则”之一提出者是著名的软件工程师 Bertrand Meyer。它的核心思想是**软件实体类、模块、函数等应对扩展开放对修改关闭。**也就是说一旦一个类或模块被开发完成并投入使用后尽量不去修改它的源代码而是通过扩展的方式来增加新的功能。 **为什么要遵守开闭原则**提高系统的可维护性 避免频繁修改已有代码减少引入新错误的风险增强系统的扩展性 可以在不改变已有代码的基础上增加新功能满足不断变化的需求。 2.2 Java中的开闭原则
假设有一个简单的需求计算不同类型的图形的面积。
2.2.1 初始实现未遵守开闭原则
public class ShapeCalculator {public double calculateArea(Object shape) {if (shape instanceof Circle) {Circle circle (Circle) shape;return Math.PI * circle.radius * circle.radius;} else if (shape instanceof Rectangle) {Rectangle rectangle (Rectangle) shape;return rectangle.width * rectangle.height;}return 0;}
}class Circle {public double radius;public Circle(double radius) {this.radius radius;}
}class Rectangle {public double width;public double height;public Rectangle(double width, double height) {this.width width;this.height height;}
}问题 每新增一种图形就需要修改ShapeCalculator违反开闭原则。 2.2.2 改进的实现遵守开闭原则
为了避免频繁修改ShapeCalculator可以设计一个抽象的接口让每个图形自己负责计算面积。
// 定义接口
interface Shape {double getArea();
}// 实现不同的图形
class Circle implements Shape {private double radius;public Circle(double radius) {this.radius radius;}Overridepublic double getArea() {return Math.PI * radius * radius;}
}class Rectangle implements Shape {private double width;private double height;public Rectangle(double width, double height) {this.width width;this.height height;}Overridepublic double getArea() {return width * height;}
}// 计算器类
public class ShapeCalculator {public double calculateArea(Shape shape) {return shape.getArea();}
}2.2.3 关键点分析
封装变化点 每新增一个图形只需要实现Shape接口不需要修改ShapeCalculator符合“对扩展开放对修改关闭”的原则。代码扩展方便 增加新图形只需实现Shape接口即可。 2.3 深度探究
为什么这符合开闭原则 多态的应用 通过接口Shape实现多态ShapeCalculator无需关心具体是哪种图形只需调用统一的接口方法。降低耦合度 不同图形的具体实现与计算器解耦系统变得更灵活。 2.4 初学者建议
理解多态和接口的基本概念熟悉设计原则的思想。在实际开发中养成“少修改、多扩展”的习惯。反复练习尝试将已有的“巨石式”代码改造成符合开闭原则的结构。
三 里氏替换原则LSP
当然可以作为一名致力于帮助初学者理解设计模式的“博客大师”我将用浅显易懂的语言结合Java示例深入探讨里氏替换原则Liskov Substitution Principle, LSP。这是面向对象设计中的一项核心原则对于写出灵活、可扩展的代码非常重要。 3.1 里氏替换原则
里氏替换原则就是如果用父类类型定义的对象可以被任何子类对象替换而程序的行为都不受影响那么这个继承关系就是符合里氏替换原则的。 通俗理解 你可以用子类的对象替换父类的对象而程序仍然能正常工作没有错误或者异常。核心思想子类可以替代父类行为一致不引入预料之外的副作用或错误。 为什么要遵守里氏替换原则 保证代码的可扩展性当你用子类替换父类不会破坏原有的功能。增强代码的灵活性方便未来增加新子类不需要修改现有代码。符合面向对象的设计理念强调“开闭原则”对扩展开放对修改封闭。 3.2 Java中的里氏替换原则
3.2.1 初始实现未遵守LSP
假设有一个Rectangle矩形类和一个Square正方形类Square继承自Rectangle。
class Rectangle {protected double width;protected double height;public void setWidth(double width) {this.width width;}public void setHeight(double height) {this.height height;}public double getArea() {return width * height;}
}class Square extends Rectangle {Overridepublic void setWidth(double width) {this.width this.height width;}Overridepublic void setHeight(double height) {this.width this.height height;}
}问题 如果你写一个方法接受Rectangle对象调用setWidth()和setHeight()期望得到一个矩形但如果传入的是Square对象行为会变得不可预测。
public void resizeRectangle(Rectangle rect) {System.out.println(Area: rect.getArea());
}调用
Rectangle rect new Rectangle();
rect.setWidth(2);
rect.setHeight(3);
resizeRectangle(rect); // 输出Area: 6
Square square new Square();
square.setWidth(2);
square.setHeight(3);
resizeRectangle(square); // 输出Area: 9Square的行为破坏了Rectangle预期的行为不符合里氏替换原则。 3.2.2 改进的实现遵守LSP
为了遵守LSP我们应该避免让Square继承Rectangle或者设计一个更合理的继承关系。一种解决方案是定义一个通用的接口或抽象类比如Shape分别实现Rectangle和Square它们都实现自己的行为。
interface Shape {double getArea();
}class Rectangle implements Shape {protected double width;protected double height;public void setWidth(double width) { this.width width; }public void setHeight(double height) { this.height height; }public double getArea() { return width * height; }
}class Square implements Shape {private double side;public void setSide(double side) { this.side side; }public double getArea() { return side * side; }
}这样传入的Shape类型对象无论是矩形还是正方形都可以正常使用符合LSP。 3.3 深度探究
更深入的理解LSP要求子类在继承时不仅要遵循类型关系更要遵循“行为契约”。这意味着子类不能缩减父类的功能也不能增加不符合父类预期的行为。
设计建议 避免让子类改变父类已有的方法的行为。避免在子类中重写父类的方法导致行为偏离预期。使用接口或抽象类定义明确的行为契约让子类实现。设计时考虑“预期行为”确保子类符合父类的规范。 实际应用中
在重写方法时要保证行为不变或更宽松如返回值、异常处理等。使用设计模式如策略模式、组合优于继承减少继承带来的潜在问题。利用单元测试验证子类是否忠实地替代父类。 四 接口隔离原则ISP
4.1 接口隔离原则
接口隔离原则Interface Segregation Principle, ISP是面向对象设计的五大原则之一SOLID原则中的“I”。它的核心思想是 “客户端不应该依赖它不使用的方法”。换句话说一个接口Interface应该尽量小而专一让实现接口的类只需要关注自己真正用到的功能避免被迫实现无关的方法。 4.2 Java中的接口隔离原则
4.2.1 初始实现未遵守ISP
假设有一个很大的接口里面定义了很多方法
public interface Animal {void eat();void fly();void swim();
}如果一只鸟实现这个接口理论上它要实现eat()、fly()和swim()但是鸟不会游泳呀如果一只鱼实现这个接口鱼不会飞也要实现fly()方法勉强实现可能只是空方法或者抛异常。
这就违反了接口隔离原则造成了
实现类负担过重实现无用的方法代码冗余。代码难以维护修改接口可能影响不相关的实现。设计不灵活难以扩展和复用。 4.2.2 改进的实现遵守ISP
将一个胖接口拆成多个功能单一的小接口。比如
public interface Eater {void eat();
}public interface Flyer {void fly();
}public interface Swimmer {void swim();
}然后不同的类只实现自己需要的接口
public class Bird implements Eater, Flyer {Overridepublic void eat() {System.out.println(Bird is eating);}Overridepublic void fly() {System.out.println(Bird is flying);}
}public class Fish implements Eater, Swimmer {Overridepublic void eat() {System.out.println(Fish is eating);}Overridepublic void swim() {System.out.println(Fish is swimming);}
}这样不同的动物只关心自己相关的行为实现起来更清晰、易维护。 4.3 深入理解接口隔离原则
面向接口编程ISP强调接口设计要简洁、专注。接口是定义行为的契约不应该臃肿。提高灵活性和可扩展性拆分接口后新增功能只需新增接口不必修改已有接口和类符合开闭原则。减少耦合客户端只依赖相关接口避免因接口变更影响无关代码。配合依赖倒置原则ISP与依赖倒置原则DIP相辅相成设计出灵活的系统结构。 4.4 接口隔离原则在设计模式中的体现
很多设计模式都体现了接口隔离原则
策略模式定义多个小的策略接口客户端只用其中一种策略避免一个策略接口承担过多职责。装饰器模式装饰接口通常小而单一装饰类只负责增强相关功能。观察者模式定义观察者接口只包含通知相关的方法避免包含无关行为。 五 依赖反转原则DIP
**依赖反转原则Dependency Inversion Principle, DIP**是五大设计原则SOLID原则之一。它旨在降低模块之间的耦合度提高系统的灵活性和可维护性。简单来说DIP强调 高层模块即业务逻辑不应该依赖于低层模块具体实现。两者都应该依赖于抽象接口或抽象类。抽象不依赖于细节细节依赖于抽象。 这听起来可能抽象但实际上它是为了让我们的系统更易于扩展和变化。 依赖反转原则DIP是设计灵活、可维护系统的重要原则。它鼓励我们通过抽象来降低模块之间的耦合让系统更易于扩展和测试。
5.1 为什么要遵循依赖反转原则
假设你在开发一个支付系统你可能会有不同的支付方式比如支付宝、微信支付、银行卡支付等。如果你的代码直接依赖于具体的支付类就会导致更换支付方式时需要修改大量代码单元测试变得困难因为具体实现绑死在代码中。DIP的目标是让“高层”业务逻辑不依赖于“低层”具体实现而是都依赖于抽象接口从而达到松耦合、易扩展、便于测试。
5.2 Java中的依赖反转原则
5.2.1 初始实现不遵循DIP
class Light {void turnOn() { System.out.println(Light is ON); }void turnOff() { System.out.println(Light is OFF); }
}class Switch {private Light light new Light();void operate() {light.turnOn();// 其他操作}
}在这个例子中Switch直接依赖于Light的具体实现若想切换到其他灯就需要修改Switch类。
5.2.2 改进的实现遵守DIP
// 定义一个抽象接口
interface Switchable {void turnOn();void turnOff();
}// 具体实现
class Light implements Switchable {public void turnOn() { System.out.println(Light is ON); }public void turnOff() { System.out.println(Light is OFF); }
}class Fan implements Switchable {public void turnOn() { System.out.println(Fan is ON); }public void turnOff() { System.out.println(Fan is OFF); }
}// 高层模块
class Switch {private Switchable device;public Switch(Switchable device) {this.device device;}void operate() {device.turnOn();// 其他逻辑}
}关键点
Switch依赖于Switchable接口而非具体实现。可以随意切换Light或Fan无需修改Switch类。 5.3 深入理解依赖倒置的两个原则
DIP实际上包含两个方面
高层模块不依赖于低层模块。这意味着高层模块如业务逻辑只依赖抽象。抽象不依赖于细节细节依赖于抽象。这意味着具体实现细节依赖于抽象。
简要总结
面向接口编程通过接口或抽象类依赖。依赖注入Dependency Injection将依赖的具体实现通过构造器、Setter等注入减少硬编码依赖。 5.4 依赖反转原则的实现技巧
5.4.1 使用接口或抽象类
定义接口让高层模块依赖接口而不是具体实现。
interface MessageService {void sendMessage(String message);
}class EmailService implements MessageService {public void sendMessage(String message) {System.out.println(Sending email: message);}
}class Notification {private MessageService messageService;public Notification(MessageService messageService) {this.messageService messageService;}public void notify(String message) {messageService.sendMessage(message);}
}5.4.2 依赖注入DI
通过构造器或框架如Spring将实现注入。这样Notification类不关心具体的实现只依赖接口增强了灵活性。
public class Main {public static void main(String[] args) {MessageService email new EmailService();Notification notification new Notification(email);notification.notify(Hello World);}
}5.5深度探究 5.5.1 DIP的优势
增强系统的可扩展性添加新功能如新支付方式只需实现接口无需修改现有代码。提升可测试性可以用模拟Mock对象替代具体实现进行单元测试。降低耦合度高层模块与低层模块解耦。
5.5.2 限制与注意事项
过度设计在简单场景中过度使用接口可能增加不必要的复杂度。依赖注入的复杂性引入框架如Spring可能增加学习成本。设计平衡应结合实际需求合理应用。