概况
在软件开发中,为了提高软件系统的可维护性与可复用性,增加软件系统的可扩展性与灵活性,程序员要尽量遵循这七条原则去开发程序。遵循设计原则的开发,可以很好地提高软件开发效率、节约软件开发与维护成本。
这七种设计原则的侧重点不同。其中,开闭原则是总纲,它告诉我们要对扩展开放,对修改关闭;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;单一职责原则告诉我们实现类要职责单一;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合度;合成复用原则告诉我们要优先使用组合或者聚合关系复用,少用继承关系复用。
但在实际的软件开发实践中,并不是一定所有代码都遵循设计原则,而是要综合考虑人力、时间和成本,不刻意去追求完美,要在适当的场景下去遵循适用的设计原则。这是一种平衡取舍,可以帮助我们设计出更有质量、更优雅的代码。
七大软件设计原则包括开闭原则、依赖倒置原则、 单一职责原则、接口隔离原则、迪米特法则、里氏替换原则、合成复用原则:
1. 单一职责原则(Single Responsibility Principle, SRP)
一个类应该只有一个引起变化的原因。换句话说,一个类应该只有一个职责。这样做的好处是使代码更加高内聚,修改某一个职责不会影响到其他职责。
public class UserManager {
public void createUser(User user) {
// 创建用户的逻辑
}
public void updateUser(User user) {
// 更新用户的逻辑
}
public void deleteUser(int userId) {
// 删除用户的逻辑
}
}
2. 开放封闭原则(Open Closed Principle, OCP)
软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。换句话说,当需求变化时,我们应该尽量通过扩展已有的实体来实现,而不是修改原有的代码。这样做的好处是降低修改代码的风险,并且便于维护和扩展。
public interface Shape {
double calculateArea();
// 可以添加其他计算相关的方法
}
public class Rectangle implements Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double calculateArea() {
return width * height;
}
}
public class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
3. 里氏替换原则(Liskov Substitution Principle, LSP)
子类必须完全实现父类的方法,并且在使用父类的地方可以完全替代父类。换句话说,子类不应该破坏父类的原有行为和约束。这样做的好处是提高代码的可复用性,并且保持代码的稳定性。
public class Rectangle {
protected double width;
protected double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public double calculateArea() {
return width * height;
}
}
public class Square extends Rectangle {
public Square(double side) {
super(side, side);
}
@Override
public void setWidth(double width) {
super.setWidth(width);
super.setHeight(width);
}
@Override
public void setHeight(double height) {
super.setWidth(height);
super.setHeight(height);
}
}
4. 依赖倒置原则(Dependency Inversion Principle, DIP)
抽象不应该依赖于具体实现,而是具体实现应该依赖于抽象。换句话说,高层模块不应该依赖于低层模块,两者都应该依赖于抽象。这样做的好处是减少模块间的耦合,提高代码的灵活性和可测试性。
public interface Database {
void saveData(String data);
}
public class MySQLDatabase implements Database {
@Override
public void saveData(String data) {
// 使用MySQL数据库保存数据的逻辑
}
}
public class OracleDatabase implements Database {
@Override
public void saveData(String data) {
// 使用Oracle数据库保存数据的逻辑
}
}
public class DataManager {
private Database database;
public DataManager(Database database) {
this.database = database;
}
public void saveData(String data) {
database.saveData(data);
}
}
5. 接口隔离原则(Interface Segregation Principle, ISP)
任何一个客户端不应该依赖它不需要的接口。换句话说,一个类不应该强迫它的客户端依赖于它们不需要的方法。这样做的好处是减少接口的冗余和复杂性,增强代码的可读性。
public interface ReportGenerator {
void generatePDFReport();
void generateCSVReport();
void generateHTMLReport();
}
public class PDFReportGenerator implements ReportGenerator {
@Override
public void generatePDFReport() {
// 生成PDF报告的逻辑
}
@Override
public void generateCSVReport() {
throw new UnsupportedOperationException();
}
@Override
public void generateHTMLReport() {
throw new UnsupportedOperationException();
}
}
public class CSVReportGenerator implements ReportGenerator {
@Override
public void generatePDFReport() {
throw new UnsupportedOperationException();
}
@Override
public void generateCSVReport() {
// 生成CSV报告的逻辑
}
@Override
public void generateHTMLReport() {
throw new UnsupportedOperationException();
}
}
6. 迪米特法则(Law of Demeter, LoD)
一个对象应该对其他对象有尽可能少的了解。换句话说,一个对象应该只与其直接的朋友进行交流,不和陌生的对象进行交流。这样做的好处是减少代码的耦合和依赖,提高代码的灵活性和可维护性。
public class Teacher {
private String name;
public Teacher(String name) {
= name;
}
public void giveHomework(Student student) {
student.doHomework();
}
}
public class Student {
private String name;
public Student(String name) {
= name;
}
public void doHomework() {
// 做作业的逻辑
}
}
7. 合成复用原则(Composite Reuse Principle, CRP)
尽量使用合成/聚合,而不是继承。换句话说,尽量通过组合和委托来实现代码的复用,而不是通过继承来实现。这样做的好处是降低代码的耦合度,增强代码的灵活性和可维护性。
public class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.start();
}
}
public interface Engine {
void start();
}
public class ElectricEngine implements Engine {
@Override
public void start() {
// 电动引擎启动的逻辑
}
}
public class GasolineEngine implements Engine {
@Override
public void start() {
// 汽油引擎启动的逻辑
}
}
总结
上述软件设计七大原则可以使用一句话概括,如下所示:
设计原则 |
一句话归纳 |
目的 |
开闭原则 |
对扩展开放,对修改关闭 |
降低维护带来的新风险 |
依赖倒置原则 |
高层不应该依赖底层 |
更利于代码结构的升级扩展 |
单一职责原则 |
一个类只干一件事 |
便于理解,提高代码的可读性 |
接口隔离原则 |
一个接口只干一件事 |
功能解耦,高聚合、低耦合 |
迪米特法则 |
不该知道的,不要知道 |
只和朋友交流,不喝默认人说话,减少代码臃肿 |
里式替换法则 |
子类重写方法功能发生变化,不应该影响父类方法的含义 |
防止继承泛滥 |
合成复用原则 |
尽量使用组合实现代码复用,而不是用继承 |
降低代码耦合 |