Java 面向对象设计原则是指导开发者编写高内聚、低耦合、易于维护和扩展的软件系统的准则。以下是其中的主要原则:
1. 单一职责原则 (SRP)
意味着一个类或者模块应当只有一个明确的责任或职责,即只做一件事。这样的设计使得类的功能单一、职责清晰,易于理解、测试和维护。当一个类承担了过多职责时,可能会导致复杂度增加,且任何职责的变化都可能影响到整个类,违反了“高内聚、低耦合”的原则。
示例:
假设有一个 Employee
类,最初包含了员工的基本信息、工资计算、考勤管理等多个职责。按照 SRP,应将其拆分为多个类:
// 单独负责员工基本信息的类
class Employee {
private String name;
private String id;
// ... 其他基本信息属性和对应的 getter/setter 方法
}
// 负责薪资计算的类
class SalaryCalculator {
public double calculateSalary(Employee employee, List<AttendanceRecord> attendanceRecords) {
// 根据员工信息和考勤记录计算工资
}
}
// 负责考勤管理的类
class AttendanceManager {
public void recordAttendance(Employee employee, Date date, boolean isPresent) {
// 记录员工考勤
}
public List<AttendanceRecord> getAttendanceRecords(Employee employee) {
// 获取员工考勤记录
}
}
2. 开闭原则 (OCP)
提倡软件实体(如类、模块和函数)应对扩展开放,对修改关闭。这意味着在不修改现有代码的基础上,能够通过扩展来增加新功能或改变行为。通常通过抽象和多态(如使用接口或抽象类)来实现,具体实现通过派生新的子类或提供新的模块来扩展系统功能。
示例:
考虑一个邮件发送服务,最初仅支持发送文本邮件。为了遵循 OCP,设计时使用接口和抽象类:
interface EmailService {
void send(Email email);
}
class TextEmailService implements EmailService {
@Override
public void send(Email email) {
// 发送文本邮件逻辑
}
}
// 后续需要添加 HTML 邮件支持时,无需修改已有代码
class HtmlEmailService implements EmailService {
@Override
public void send(Email email) {
// 发送 HTML 邮件逻辑
}
}
// 使用方只需依赖 EmailService 接口,新增服务无需更改使用代码
class MailSender {
private final EmailService emailService;
public MailSender(EmailService service) {
this.emailService = service;
}
public void sendEmail(Email email) {
emailService.send(email);
}
}
3. 里氏替换原则 (LSP)
子类应当可以替换其基类在任何地方出现,且不会导致程序逻辑的错误或异常行为。换句话说,任何使用基类的地方,都可以无缝地使用其子类对象,而不必关心具体的实现细节。这一原则强调了继承关系中,子类应当保持与基类的行为一致性。
示例:
假设有一个 Shape
抽象类和两个子类 Rectangle
和 Circle
。计算形状面积的接口在基类中定义,子类需确保遵守 LSP:
abstract class Shape {
public abstract double getArea();
}
class Rectangle extends Shape {
private double width, height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double getArea() {
return width * height;
}
}
class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
// 使用方可以使用 Shape 的任何子类,计算面积时无需关心具体类型
void printArea(Shape shape) {
System.out.println("Area: " + shape.getArea());
}
4. 依赖倒置原则 (DIP)
两个关键点:
- 高层模块不应该依赖低层模块,两者都应该依赖于抽象(接口或抽象类);
- 抽象不应该依赖细节,细节(具体实现类)应该依赖于抽象。
示例:
假设有一个 ReportGenerator
类需要持久化报告到数据库。遵循 DIP,不直接依赖具体数据库实现,而是依赖于抽象接口:
interface ReportRepository {
void saveReport(Report report);
}
class FileReportRepository implements ReportRepository {
@Override
public void saveReport(Report report) {
// 保存报告到文件系统的逻辑
}
}
class DatabaseReportRepository implements ReportRepository {
@Override
public void saveReport(Report report) {
// 保存报告到数据库的逻辑
}
}
class ReportGenerator {
private final ReportRepository repository;
public ReportGenerator(ReportRepository repository) {
this.repository = repository;
}
public void generateAndSaveReport(Data data) {
Report report = generateReport(data);
repository.saveReport(report);
}
private Report generateReport(Data data) {
// 生成报告的逻辑
}
}
// 使用时注入所需的 ReportRepository 实现
ReportGenerator generator = new ReportGenerator(new DatabaseReportRepository());
generator.generateAndSaveReport(someData);
5. 接口隔离原则 (ISP)
客户端(使用者)不应该被迫依赖于它不需要的接口方法。也就是说,一个庞大的接口应该拆分成多个更小的、更具体的接口,每个接口只包含客户端真正需要的方法。这样可以减少不必要的耦合,提高接口的使用灵活性,防止因接口方法变动对无关客户端造成影响。
示例:
假设有一个 ImageEditor
接口,包含了多种图像处理操作。为了遵循 ISP,可以将其拆分为更细粒度的接口:
interface Resizeable {
void resize(int newWidth, int newHeight);
}
interface Filterable {
void applyFilter(Filter filter);
}
class AdvancedImageEditor implements Resizeable, Filterable {
// 实现 Resizeable 和 Filterable 接口的方法
}
// 使用方仅依赖所需功能的接口
class ThumbnailGenerator {
private final Resizeable imageEditor;
public ThumbnailGenerator(Resizeable editor) {
this.imageEditor = editor;
}
public void createThumbnail(Image image) {
imageEditor.resize(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
}
}
class FilterApplicator {
private final Filterable imageEditor;
public FilterApplicator(Filterable editor) {
this.imageEditor = editor;
}
public void applyGrayscaleFilter(Image image) {
imageEditor.applyFilter(new GrayscaleFilter());
}
}
6. 迪米特法则 (LOD)
一个对象应当对其它对象有最少的了解,只和直接的朋友(直接交互的对象)交流。这一原则限制了对象之间的交互深度,降低了系统的耦合度,使系统更易于理解和维护。遵循迪米特法则的设计通常会采用中间件、代理模式等来限制对象之间的直接访问。
示例:
考虑一个 Order
类和 Customer
类。如果 Order
直接访问 Customer
的详细信息(如地址、联系方式等),则违反了 LOD。为遵循此原则,可以引入中间类:
class Customer {
private Address address;
private ContactInfo contactInfo;
// ... 其他属性和方法
}
class Order {
private OrderDeliveryDetails deliveryDetails;
public void setDeliveryDetails(OrderDeliveryDetails details) {
this.deliveryDetails = details;
}
// ... 其他属性和方法
}
class OrderDeliveryDetails {
private Address deliveryAddress;
private ContactInfo deliveryContact;
// ... 构造函数、getter/setter 和其他方法
// OrderDeliveryDetails 可以从 Customer 中获取必要信息,而非 Order 直接访问 Customer
public void populateFromCustomer(Customer customer) {
deliveryAddress = customer.getAddress();
deliveryContact = customer.getContactInfo();
}
}
// 使用时,Order 仅与 OrderDeliveryDetails 交互,减少了与 Customer 的直接耦合
Customer customer = new Customer(...);
Order order = new Order();
OrderDeliveryDetails details = new OrderDeliveryDetails();
details.populateFromCustomer(customer);
order.setDeliveryDetails(details);
这些原则并不是孤立的,它们相互关联、互为补充,共同指导开发者编写出具有良好结构、易于维护和扩展的面向对象软件。在实际开发过程中,需要根据具体情况灵活运用这些原则,以达到最佳的设计效果。通过这些原则指导的设计能够提高代码的可读性、可维护性、可扩展性和降低模块间的耦合度。