当前位置: 首页 > news >正文

面向对象的设计原则

Note: This article has been written with the assistance of AI.

单一职责原则(SRP)

一个类应该只有一个引起它变化的原因。

通俗地讲,就是一个类只负责一项职责或功能。不要设计“万能”类,把不相关的功能塞在一起。如果一个类承担了太多的职责,就等于把这些职责耦合在了一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。

核心思想: 高内聚,低耦合。

违反 SRP 的类会带来很多问题:

  1. 可读性差: 一个庞大的类,代码量多,逻辑复杂,难以理解和维护。
  2. 难以维护和修改: 当你需要修改某个功能时,你不得不在这个庞大的类中进行,很容易“牵一发而动全身”,导致意想不到的 Bug。
  3. 难以复用: 你只想复用这个类中的某一个功能,但却不得不把整个“庞然大物”都引入进来。
  4. 稳定性差: 一个职责的变更,可能会影响到其他不相关功能的正常运行。

违反 SRP 的设计

假设我们有一个 User 类,它负责处理用户的所有事情:

// 违反单一职责原则的类
public class User {private String name;private String email;// 构造函数、getter、setter 省略...// 职责1:用户属性相关的业务逻辑(这是合理的)public boolean isValid() {return email != null && email.contains("@");}// 职责2:数据持久化 - 将用户保存到数据库public void saveToDatabase() {// 连接数据库,执行 INSERT 语句...System.out.println("用户 " + name + " 已保存到数据库。");}// 职责3:通知 - 发送欢迎邮件public void sendWelcomeEmail() {// 连接邮件服务器,发送邮件...System.out.println("已发送欢迎邮件给 " + email);}
}

遵循 SRP 的改进设计

我们将上述三个职责拆分到三个不同的类中:

// 职责1:用户实体模型 - 只负责用户数据和基础验证
public class User {private String name;private String email;// ... 构造函数、getter、setter ...public boolean isValid() {return email != null && email.contains("@");}
}// 职责2:用户数据访问 - 只负责数据库操作
public class UserRepository {public void save(User user) {// 连接数据库,执行 INSERT 语句...System.out.println("用户 " + user.getName() + " 已保存到数据库。");}// 还可以有 findById, delete 等方法
}// 职责3:通知服务 - 只负责发送消息
public class NotificationService {public void sendWelcomeEmail(User user) {// 连接邮件服务器,发送邮件...System.out.println("已发送欢迎邮件给 " + user.getEmail());}// 未来可以轻松添加新方法,而不影响 User 和 UserRepositorypublic void sendSms(User user) {// 发送短信的逻辑}
}

开放封闭原则(OCP)

软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。

这个原则听起来可能有些矛盾,但它表达了一个非常深刻的思想:

  • 对扩展开放(Open for Extension): 当需求发生变化时,我们可以通过添加新的代码来扩展模块的行为,从而满足新的需求。
  • 对修改关闭(Closed for Modification): 扩展模块行为时,不应该修改原有的、已经测试通过的、正在稳定运行的代码。

核心思想: 通过抽象构建框架,通过实现扩展细节。用抽象定义结构,用具体实现来应对变化。

违反 OCP 会带来严重的问题:

  1. 稳定性风险: 修改已有的、稳定的代码,就像在已经建好的大楼上动地基,极易引入新的 Bug,导致系统变得脆弱。
  2. 测试负担重: 每次修改代码后,都需要对修改过的模块进行全面的回归测试,以确保没有破坏现有功能。
  3. 难以扩展: 系统会变得僵硬,添加新功能变得越来越困难,因为牵一发而动全身。

违反 OCP 的设计

假设我们有一个 AreaCalculator 类,用于计算一组图形的总面积。

// 违反开放封闭原则的设计
public class AreaCalculator {// 计算所有图形的总面积public double calculateTotalArea(Object[] shapes) {double totalArea = 0;for (Object shape : shapes) {// 问题核心:如果新增一种图形,就必须修改这里的 if-else 逻辑if (shape instanceof Rectangle) {Rectangle rect = (Rectangle) shape;totalArea += rect.getWidth() * rect.getHeight();} else if (shape instanceof Circle) {Circle circle = (Circle) shape;totalArea += Math.PI * circle.getRadius() * circle.getRadius();}// 如果需要添加三角形,必须在这里添加新的 else if 分支...// else if (shape instanceof Triangle) { ... }}return totalArea;}
}// 矩形类
public class Rectangle {private double width;private double height;// ... 构造函数、getter ...
}// 圆形类
public class Circle {private double radius;// ... 构造函数、getter ...
}

遵循 OCP 的改进设计

解决方案是使用抽象。我们创建一个抽象的 Shape 接口,让所有具体的图形类都实现这个接口,并自己负责计算自己的面积。这样,面积计算器就不再需要关心具体的图形类型,它只依赖于一个稳定的抽象。

// 步骤1:定义抽象接口
public interface Shape {double calculateArea(); // 所有图形都必须实现这个方法来计算自己的面积
}// 步骤2:让具体图形类实现接口
public class Rectangle implements Shape {private double width;private double height;public Rectangle(double width, double height) {this.width = width;this.height = height;}@Overridepublic double calculateArea() {return width * height;}
}public class Circle implements Shape {private double radius;public Circle(double radius) {this.radius = radius;}@Overridepublic double calculateArea() {return Math.PI * radius * radius;}
}// 步骤3:重构面积计算器,使其依赖于抽象(Shape),而非具体实现(Rectangle, Circle)
public class AreaCalculator {// 现在这个方法对修改是“关闭”的。无论未来有多少新图形,都无需修改它。public double calculateTotalArea(List<Shape> shapes) {double totalArea = 0;for (Shape shape : shapes) {// 直接调用抽象方法,多态机制会自动分发到具体实现totalArea += shape.calculateArea();}return totalArea;}
}

里氏替换原则(LSP)

所有引用基类(父类)的地方必须能够透明地使用其子类的对象。

更通俗的解释是:子类必须能够完全替换掉它们的父类,并且替换后不会产生任何错误或异常行为。 换句话说,如果你把程序中的父类对象全部替换成它的某个子类对象,程序应该仍然能够正常运行。

这个原则是以计算机科学家 Barbara Liskov 的名字命名的。

违反 LSP 会破坏面向对象的继承体系,导致严重问题:

  1. 破坏多态性: 多态是面向对象的核心特性,而 LSP 是保证多态能够正确工作的基石。如果子类不能替换父类,那么基于父类设计的代码将无法安全地使用子类。
  2. 引发运行时错误: 在替换后,程序可能会抛出异常、产生错误结果或进入异常状态。
  3. 增加代码复杂度: 使用父类对象时,开发者不得不担心具体是哪个子类,需要做类型检查和特殊处理,这违背了面向抽象编程的原则。

里氏替换原则不仅仅是语法上的"is-a"关系,更重要的是行为上的兼容性。"is-a"关系不仅仅是分类学上的,更重要的是行为上的可替换性。。它要求子类在继承父类时:

  • 不强化前置条件(子类对输入的要求不能比父类更严格)
  • 不弱化后置条件(子类对输出的承诺不能比父类更少)
  • 保持父类的约束条件(如不变量)

示例1:违反LSP的经典"正方形/矩形"问题

// 长方形类
class Rectangle {private double width;private double height;public double getWidth() { return width; }public double getHeight() { return height; }public void setWidth(double width) { this.width = width; }public void setHeight(double height) { this.height = height; }public double calculateArea() {return width * height;}
}// 正方形类 - 从数学上说,正方形"is-a"长方形
class Square extends Rectangle {@Overridepublic void setWidth(double width) {// 正方形需要保持宽高相等super.setWidth(width);super.setHeight(width); // 这里修改了高度!}@Overridepublic void setHeight(double height) {// 正方形需要保持宽高相等super.setHeight(height);super.setWidth(height); // 这里修改了宽度!}
}

现在考虑一个使用 Rectangle 的客户端代码:

public class Test {// 这个方法期望接收一个Rectangle对象public static void testRectangle(Rectangle rect) {rect.setWidth(5);rect.setHeight(4);// 根据长方形特性,面积应该是20// 但如果传入的是Square对象,面积会是16!System.out.println("Expected area: 20, Actual area: " + rect.calculateArea());// 断言检查 - 对于Square会失败assert rect.calculateArea() == 20 : "违反长方形行为约定!";}public static void main(String[] args) {Rectangle rect = new Rectangle();testRectangle(rect); // 正常工作:面积=20Rectangle square = new Square(); // 用子类替换父类testRectangle(square); // 出现问题:面积=16!}
}

Square 虽然语法上继承自 Rectangle,但它的行为与 Rectangle 的约定不一致。RectanglesetWidth()setHeight() 应该是独立操作的,而 Square 修改了这个行为。因此,Square 不能透明地替换 Rectangle

对于"正方形/矩形"问题,更好的设计是不要使用继承:

// 使用组合而不是继承
interface Shape {double calculateArea();
}class Rectangle implements Shape {private double width;private double height;public Rectangle(double width, double height) {this.width = width;this.height = height;}@Overridepublic double calculateArea() {return width * height;}// getter方法...
}class Square implements Shape {private double side;public Square(double side) {this.side = side;}@Overridepublic double calculateArea() {return side * side;}// getter方法...
}

示例2:违反LSP的"企鹅/鸟"问题

// 鸟类
class Bird {public void fly() {System.out.println("I can fly");}public void eat() {System.out.println("I can eat");}
}// 燕子类
class Swallow extends Bird {// 燕子会飞,符合Bird的行为
}// 企鹅类 - 从生物学上说,企鹅"is-a"鸟
class Penguin extends Bird {@Overridepublic void fly() {// 但企鹅不会飞!这里抛出异常或什么都不做都违反了LSPthrow new UnsupportedOperationException("Penguins can't fly!");}
}

考虑使用 Bird 的客户端代码:

public class BirdTest {public static void makeBirdFly(Bird bird) {// 这个方法期望所有Bird都能flybird.fly(); // 如果传入Penguin,这里会抛出异常!}public static void main(String[] args) {Bird swallow = new Swallow();makeBirdFly(swallow); // 正常工作Bird penguin = new Penguin(); // 用子类替换父类makeBirdFly(penguin); // 抛出异常!违反LSP}
}

对于"企鹅/鸟"问题,通过接口隔离来遵循LSP:

// 细化的接口
interface Bird {void eat();
}interface Flyable {void fly();
}interface Swimmable {void swim();
}// 燕子实现Flyable
class Swallow implements Bird, Flyable {@Overridepublic void eat() { System.out.println("Swallow eating"); }@Overridepublic void fly() { System.out.println("Swallow flying"); }
}// 企鹅实现Swimmable但不实现Flyable
class Penguin implements Bird, Swimmable {@Overridepublic void eat() { System.out.println("Penguin eating"); }@Overridepublic void swim() { System.out.println("Penguin swimming"); }
}

现在客户端可以安全地使用:

public class BirdTest {public static void makeBirdFly(Flyable flyableBird) {flyableBird.fly(); // 这里保证传入的对象一定能fly}public static void makeBirdSwim(Swimmable swimmableBird) {swimmableBird.swim(); // 这里保证传入的对象一定能swim}
}

应用 LSP 总结

  1. 子类方法的前置条件不能强于父类:子类对输入参数的要求不能比父类更严格。
  2. 子类方法的后置条件不能弱于父类:子类对输出结果的承诺不能比父类更少。
  3. 子类不应该抛出父类没有声明的异常:或者抛出的异常应该是父类异常的子类。
  4. 子类应该保持父类的约束条件:如某些字段的取值范围等。
  5. 当发现需要频繁进行类型检查时(如 if (obj instanceof SomeSubclass)),这通常是违反LSP的信号。

接口隔离原则(ISP)

客户端不应该被迫依赖于它不使用的接口。或者说,一个类对另一个类的依赖应该建立在最小的接口上。

通俗地讲:不要创建庞大臃肿的接口,而应该将接口拆分成更小、更具体的接口,这样客户端只需要知道它们感兴趣的方法。

违反 ISP 会带来很多问题:

  1. 接口污染: 客户端被迫实现它们根本不需要的方法,这违反了单一职责原则。
  2. 代码冗余: 对于不需要的方法,客户端通常只能提供空实现或抛出异常,这会产生大量无用的代码。
  3. 耦合性增加: 客户端与不需要的方法产生了依赖,当接口发生变化时,即使客户端不关心那些方法,也不得不重新编译和部署。
  4. 难以理解和维护: 庞大的接口难以理解,也很难知道一个类到底实现了哪些真正有用的功能。

违反 ISP 的设计

假设我们有一个庞大的 Animal 接口,包含了所有动物可能具有的行为:

// 一个"臃肿"的接口,违反了接口隔离原则
public interface Animal {void eat();void sleep();void fly();   // 问题:不是所有动物都会飞void swim();  // 问题:不是所有动物都会游泳void run();   // 问题:不是所有动物都会跑
}// 鸟类实现 - 被迫实现所有方法
public class Bird implements Animal {@Overridepublic void eat() {System.out.println("Bird is eating");}@Overridepublic void sleep() {System.out.println("Bird is sleeping");}@Overridepublic void fly() {System.out.println("Bird is flying"); // 鸟确实会飞}@Overridepublic void swim() {// 但大多数鸟不会游泳!被迫提供空实现或抛出异常// throw new UnsupportedOperationException("Birds don't swim!");// 或者什么都不做,但这违反了"最小惊讶原则"}@Overridepublic void run() {System.out.println("Bird is running");}
}// 鱼类实现 - 同样被迫实现所有方法
public class Fish implements Animal {@Overridepublic void eat() {System.out.println("Fish is eating");}@Overridepublic void sleep() {System.out.println("Fish is sleeping");}@Overridepublic void fly() {// 鱼根本不会飞!被迫提供无意义的实现throw new UnsupportedOperationException("Fish can't fly!");}@Overridepublic void swim() {System.out.println("Fish is swimming"); // 鱼确实会游泳}@Overridepublic void run() {// 鱼不会跑!throw new UnsupportedOperationException("Fish can't run!");}
}

遵循 ISP 的改进设计

// 拆分成多个专门的接口// 基本生物接口
public interface LivingBeing {void eat();void sleep();
}// 飞行能力接口
public interface Flyable {void fly();
}// 游泳能力接口  
public interface Swimmable {void swim();
}// 奔跑能力接口
public interface Runnable {void run();
}

与其提供一个什么都做的万能接口,不如提供多个专注的专用接口。

依赖倒置原则(DIP)

它的核心定义包含两个部分:

  1. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象
  2. 抽象不应该依赖于细节,细节应该依赖于抽象

这里的"倒置"指的是与传统依赖方向的对比。在传统设计中,高层模块直接依赖低层模块,而DIP将这种依赖关系"倒置"了。

违反 DIP 会导致的问题:

  1. 紧耦合:高层模块与低层模块紧密耦合,难以独立变化
  2. 难以测试:无法轻松替换依赖进行单元测试
  3. 难以扩展:添加新功能需要修改高层模块代码
  4. 违反开闭原则:对扩展不开放,对修改不关闭
  • 高层模块:包含核心业务逻辑、策略和应用程序规则的模块
  • 低层模块:包含具体实现、工具函数、基础设施细节的模块
  • 抽象:接口或抽象类,定义契约而不涉及具体实现

违反 DIP 的设计

// 低层模块 - 具体实现
public class EmailService {public void sendEmail(String message, String recipient) {System.out.println("发送邮件给 " + recipient + ": " + message);// 实际的邮件发送逻辑...}
}public class SmsService {public void sendSms(String message, String phoneNumber) {System.out.println("发送短信给 " + phoneNumber + ": " + message);// 实际的短信发送逻辑...}
}// 高层模块 - 业务逻辑
public class NotificationService {// 问题:高层模块直接依赖于低层模块的具体实现private EmailService emailService;private SmsService smsService;public NotificationService() {// 紧耦合:在构造函数中直接创建具体实例this.emailService = new EmailService();this.smsService = new SmsService();}public void sendNotification(String message, String user, String type) {if ("email".equals(type)) {emailService.sendEmail(message, user);} else if ("sms".equals(type)) {smsService.sendSms(message, user);}}
}// 使用示例
public class Main {public static void main(String[] args) {NotificationService notification = new NotificationService();notification.sendNotification("Hello", "user@example.com", "email");}
}

遵循 DIP 的改进设计

// 步骤1:定义抽象(接口)
public interface MessageService {void send(String message, String recipient);boolean supports(String type); // 支持的消息类型
}// 步骤2:低层模块实现抽象
public class EmailService implements MessageService {@Overridepublic void send(String message, String recipient) {System.out.println("发送邮件给 " + recipient + ": " + message);}@Overridepublic boolean supports(String type) {return "email".equals(type);}
}public class SmsService implements MessageService {@Overridepublic void send(String message, String recipient) {System.out.println("发送短信给 " + recipient + ": " + message);}@Overridepublic boolean supports(String type) {return "sms".equals(type);}
}public class WeChatService implements MessageService {@Overridepublic void send(String message, String recipient) {System.out.println("发送微信消息给 " + recipient + ": " + message);}@Overridepublic boolean supports(String type) {return "wechat".equals(type);}
}// 步骤3:高层模块依赖于抽象
public class NotificationService {// 高层模块依赖于抽象(接口),而不是具体实现private List<MessageService> messageServices;// 通过构造函数注入依赖 - 这就是依赖注入(DI)public NotificationService(List<MessageService> messageServices) {this.messageServices = messageServices;}public void sendNotification(String message, String recipient, String type) {for (MessageService service : messageServices) {if (service.supports(type)) {service.send(message, recipient);return;}}throw new IllegalArgumentException("不支持的消息类型: " + type);}// 可以轻松添加新功能而不修改现有代码public void broadcast(String message, String type) {for (MessageService service : messageServices) {if (service.supports(type)) {service.send(message, "all users");}}}
}

依赖注入的三种方式

构造函数注入

public class NotificationService {private final MessageService messageService;// 依赖通过构造函数注入public NotificationService(MessageService messageService) {this.messageService = messageService;}
}

Setter方法注入

public class NotificationService {private MessageService messageService;// 依赖通过setter方法注入public void setMessageService(MessageService messageService) {this.messageService = messageService;}
}

接口注入

public interface MessageServiceAware {void setMessageService(MessageService messageService);
}public class NotificationService implements MessageServiceAware {private MessageService messageService;@Overridepublic void setMessageService(MessageService messageService) {this.messageService = messageService;}
}

最少知识原则 (Law of Demeter)

一个对象应该对其他对象有最少的了解。或者说,只与直接的朋友通信。

通俗地讲:不要和陌生人说话,只和你的直接朋友通信。

这里的"朋友"指的是:

  • 当前对象本身 (this)
  • 以参数形式传入到当前对象方法中的对象
  • 当前对象的成员变量
  • 如果当前对象的成员变量是一个集合,那么集合中的元素也是朋友
  • 当前对象所创建的对象

违反 LoD 会带来很多问题:

  1. 耦合度增高:对象之间关系复杂,形成蜘蛛网式的依赖
  2. 难以维护:一个类的修改会影响到很多其他类
  3. 难以复用:类之间紧密耦合,难以单独复用
  4. 可测试性差:需要构建复杂的依赖关系才能进行测试

核心规则:"只使用一个点"

一个简单的判断方法是:在方法中,应该尽量避免使用多个连续的.(点)来访问对象。通常,只使用一个点是相对安全的。

违反 LoD 的设计

// 员工类
public class Employee {private String name;private Department department;public Employee(String name, Department department) {this.name = name;this.department = department;}// getter方法public String getName() { return name; }public Department getDepartment() { return department; }
}// 部门类
public class Department {private String name;private Company company;public Department(String name, Company company) {this.name = name;this.company = company;}// getter方法public String getName() { return name; }public Company getCompany() { return company; }
}// 公司类
public class Company {private String name;private Address address;public Company(String name, Address address) {this.name = name;this.address = address;}// getter方法public String getName() { return name; }public Address getAddress() { return address; }
}// 地址类
public class Address {private String city;private String street;public Address(String city, String street) {this.city = city;this.street = street;}// getter方法public String getCity() { return city; }public String getStreet() { return street; }
}// 报告生成类 - 违反迪米特法则!
public class ReportGenerator {// 问题:这个方法知道了太多它不应该知道的细节public void generateEmployeeReport(Employee employee) {// 违反LoD:使用了多个连续的点,深入了解了Employee的内部结构System.out.println("员工姓名: " + employee.getName());System.out.println("部门: " + employee.getDepartment().getName()); // 第一个点之后又一个点System.out.println("公司: " + employee.getDepartment().getCompany().getName()); // 两个连续的点System.out.println("公司地址: " + employee.getDepartment().getCompany().getAddress().getCity()); // 三个连续的点!}
}// 使用示例
public class Main {public static void main(String[] args) {Address address = new Address("北京", "海淀区中关村");Company company = new Company("某科技公司", address);Department department = new Department("技术部", company);Employee employee = new Employee("张三", department);ReportGenerator generator = new ReportGenerator();generator.generateEmployeeReport(employee);}
}
  • ReportGenerator 知道了 EmployeeDepartmentCompanyAddress 的所有内部结构
  • 如果 Company 类的结构发生变化(比如把 address 字段改名),ReportGenerator 也需要修改
  • 形成了紧密的耦合,ReportGenerator 依赖于整个对象链

遵循 LoD 的改进设计

// 员工类 - 添加业务方法,封装内部细节
public class Employee {private String name;private Department department;public Employee(String name, Department department) {this.name = name;this.department = department;}public String getName() { return name; }// 提供业务方法,而不是简单的getterpublic String getDepartmentName() {return department.getDepartmentName();}public String getCompanyName() {return department.getCompanyName();}public String getCompanyCity() {return department.getCompanyCity();}// 或者提供一个综合的方法public String getWorkInfo() {return String.format("%s在%s的%s工作", name, getCompanyCity(), getDepartmentName());}
}// 部门类 - 同样封装内部细节
public class Department {private String name;private Company company;public Department(String name, Company company) {this.name = name;this.company = company;}public String getDepartmentName() { return name; }// 封装对Company的访问public String getCompanyName() {return company.getCompanyName();}public String getCompanyCity() {return company.getCompanyCity();}
}// 公司类 - 封装内部细节
public class Company {private String name;private Address address;public Company(String name, Address address) {this.name = name;this.address = address;}public String getCompanyName() { return name; }// 封装对Address的访问public String getCompanyCity() {return address.getCity();}
}// 地址类保持不变
public class Address {private String city;private String street;public Address(String city, String street) {this.city = city;this.street = street;}public String getCity() { return city; }public String getStreet() { return street; }
}// 改进的报告生成类 - 遵循 LoD
public class ReportGenerator {// 现在这个方法只与直接朋友(Employee)通信public void generateEmployeeReport(Employee employee) {System.out.println("员工姓名: " + employee.getName());System.out.println("部门: " + employee.getDepartmentName()); // 只有一个点System.out.println("公司: " + employee.getCompanyName());   // 只有一个点System.out.println("工作地点: " + employee.getCompanyCity()); // 只有一个点// 或者使用综合方法System.out.println("工作信息: " + employee.getWorkInfo());}
}

外观模式

// 员工信息服务(外观类)
public class EmployeeInfoService {private Employee employee;public EmployeeInfoService(Employee employee) {this.employee = employee;}public String getBasicInfo() {return employee.getName();}public String getWorkInfo() {return String.format("在%s的%s部门工作", employee.getCompanyCity(), employee.getDepartmentName());}public String getFullReport() {return String.format("员工%s,在%s的%s部门,属于%s公司",employee.getName(),employee.getCompanyCity(),employee.getDepartmentName(),employee.getCompanyName());}
}// 简化的报告生成类
public class ReportGenerator {public void generateEmployeeReport(EmployeeInfoService infoService) {System.out.println("基本信息: " + infoService.getBasicInfo());System.out.println("工作信息: " + infoService.getWorkInfo());System.out.println("完整报告: " + infoService.getFullReport());}
}

让类保持"害羞" - 不要暴露太多内部细节,也不要打听太多别人的事情。

http://www.hskmm.com/?act=detail&tid=20790

相关文章:

  • 反电动势法控制BLDC电机的原理图分析
  • 完整教程:Altium Designer(AD)设计规则检查设置
  • 企业物联网安全必须优先考虑的5个不可否认的理由
  • PSM敏捷认证自考学习指南
  • 2025内网聊天工具排行 4款好用的内网聊天软件推荐
  • 独立开发在线客服系统手记:实现对 PostgreSQL 的支持,以及与 MySQL 的对比
  • 方言普通话识别大模型,支撑中英+202种方言识别
  • ffmpeg一些使用记录,防止忘记
  • BLE从机(20)BLE区分主机(IOS/安卓/WIN)
  • Windows 驱动开发基础
  • 基于MATLAB实现基于距离的离群点检测算法
  • 国产DevOps工具链的突围之路:Gitee如何重塑企业研发效能
  • 阿里云抵御CC攻击利器边缘安全加速ESA
  • 生产者-消费者问题
  • Manim实现闪电特效
  • QAction的使用
  • Gitee:中国开发者生态的数字化转型加速器
  • 大模型提示词技巧Prompt Engineering,看这一篇就够了 - 知乎
  • sg.测试 PySimpleGUI 取值方法
  • Gitee DevOps:本土化基因驱动中国企业研发效能革命
  • 快速查看Navicat数据库连接密码实战
  • 老旧系统接入统一认证
  • 每周读书与学习-初识JMeter 元件(三)
  • Playwright MCP浏览器自动化全攻略
  • 【IEEE出版、连续3届稳定EI检索】第四届能源互联网及电力系统国际学术会议(ICEIPS 2025)
  • 大内容 Python动漫信息管理系统 Django+Echarts 类型饼图 折线图分析 后台管理 智能推荐(源码)✅
  • 划分子网与连通性
  • Python 之创建虚拟目录
  • 深入解析:从“硬件能力比拼”到“生活价值交付”,方太智慧厨房重构行业竞争内核
  • 题解:CF1548E Gregor and the Two Painters