引言
最近公司团队每两周进行一次Code Review,了不起心里有点慌,毕竟平常都不注重代码的开发规范,更别说代码的可读性、可维护性了,心里想着就是能跑起来就行。这不,偷偷做了点关于代码规范和编程原则的功课,暗地里把公司的代码重构了一遍,避免在Code Review时被领导喷。本文将会介绍一些编程设计原则,以帮助各位好汉编写出更健壮、可维护的代码。
SOLID 原则
单一职责原则 (SRP)
单一职责原则要求一个类应该只有一个引起它变化的原因。这意味着一个类应该只负责一项职责。
代码语言:javascript复制// 不遵循:Order类承担了两个职责:计算订单总金额和打印发票。这违反了SRP
public class Order {
public void calculateTotal() {
// 计算订单总金额
// 同时处理打印发票逻辑
}
}
// 遵循:将计算订单总金额和打印发票的职责分离成两个类,分别是Order和InvoicePrinter,遵循了SRP
public class Order {
public void calculateTotal() {
// 计算订单总金额
}
}
public class InvoicePrinter {
public void printInvoice(Order order) {
// 打印发票
}
}
开闭原则 (OCP)
开闭原则要求软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这意味着我们应该能够在不修改现有代码的情况下扩展软件的功能。
代码语言:javascript复制// 不遵循:添加新的形状需要修改Shape类的现有代码,违反了OCP
public class Shape {
public double calculateArea(String shapeType, double value) {
if (shapeType.equals("circle")) {
// 计算圆形面积
} else if (shapeType.equals("square")) {
// 计算正方形面积
}
}
}
// 遵循:我们可以轻松地添加新的形状类,而不需要修改Shape接口或现有的Circle和Square类,这符合OCP
public interface Shape {
double calculateArea();
}
public class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public double calculateArea() {
return Math.PI * radius * radius;
}
}
public class Square implements Shape {
private double side;
public Square(double side) {
this side = side;
}
public double calculateArea() {
return side * side;
}
}
里氏替换原则 (LSP)
里氏替换原则要求子类能够替代其基类,而不引起不一致性。这意味着在任何需要基类的地方,都可以使用其子类来替代。
代码语言:javascript复制// 不遵循:Ostrich是Bird的子类,但它覆盖了父类的飞行方法并将其留空。这违反了LSP,因为调用Ostrich的fly方法将导致不符合预期的行为。
public class Bird {
public void fly() {
// 飞行操作
}
}
public class Ostrich extends Bird {
// 鸵鸟不会飞行
}
// 遵循:遵循了LSP,子类Ostrich不再需要实现无意义的飞行操作
public abstract class Animal {
public void eat() {
// 吃
}
}
public interface Bird {
void fly();
}
public class Sparrow extends Animal implements Bird {
@Override
public void fly() {
// 麻雀的飞行
}
}
public class Ostrich extends Animal {
// 鸵鸟不会飞行,继承自Animal,不再实现Bird接口
}
接口隔离原则 (ISP)
接口隔离原则要求客户端不应该强制依赖于其不使用的接口。这意味着接口应该相对小而专注,不应该强迫客户端实现不需要的方法。
代码语言:javascript复制// 不遵循:Waiter类不需要实现不相关的eat方法,这违反了ISP
public interface Worker {
void work();
void eat();
}
public class Engineer implements Worker {
public void work() {
// 工程师的工作
}
public void eat() {
// 工程师的用餐
}
}
public class Waiter implements Worker {
public void work() {
// 服务员的工作
}
public void eat() {
// 服务员不需要用餐
}
}
// 遵循:将接口拆分成更小的接口Worker和Eater,以遵循ISP
public interface Worker {
void work();
}
public interface Eater {
void eat();
}
public class Engineer implements Worker, Eater {
public void work() {
// 工程师的工作
}
public void eat() {
// 工程师的用餐
}
}
public class Waiter implements Worker {
public void work() {
// 服务员的工作
}
}
依赖倒置原则 (DIP)
依赖倒置原则要求高层模块不应该依赖于底层模块,二者都应该依赖于抽象。此原则强调了使用接口或抽象类来实现松耦合。
代码语言:javascript复制// 不遵循:Switch类依赖于具体的LightBulb类,违反了DIP。这会使得Switch和LightBulb之间存在较高的耦合度,当需要替换或扩展不同类型的灯泡时,可能需要修改Switch类的代码。
public class LightBulb {
public void turnOn() {
// 打开灯泡
}
public void turnOff() {
// 关闭灯泡
}
}
public class Switch {
private LightBulb bulb;
public Switch(LightBulb bulb) {
this.bulb = bulb;
}
public void operate() {
if (/* 检查条件 */) {
bulb.turnOn();
} else {
bulb.turnOff();
}
}
}
// 遵循:Switch类依赖于抽象的Switchable接口,而不依赖于具体的LightBulb或Fan类。这遵循了DIP,使得Switch类更加灵活,可以操作各种实现了Switchable接口的设备,而不需要修改Switch类的代码。这降低了耦合度,提高了可维护性。
public interface Switchable {
void turnOn();
void turnOff();
}
public class LightBulb implements Switchable {
public void turnOn() {
// 打开灯泡
}
public void turnOff() {
// 关闭灯泡
}
}
public class Fan implements Switchable {
public void turnOn() {
// 打开风扇
}
public void turnOff() {
// 关闭风扇
}
}
public class Switch {
private Switchable device;
public Switch(Switchable device) {
this.device = device;
}
public void operate() {
if (/* 检查条件 */) {
device.turnOn();
} else {
device.turnOff();
}
}
}
迪米特法则 (LoD)
迪米特法则(Law of Demeter,LoD)要求一个对象应当尽可能减少与其他对象之间的交互,只与其直接的朋友(直接依赖)通信。这有助于降低系统中对象之间的耦合度,提高代码的可维护性。
代码语言:javascript复制public class School {
private List<Student> students;
public School() {
students = new ArrayList<>();
}
public void admitStudent(Student student) {
students.add(student);
}
public void conductExams() {
for (Student student : students) {
student.takeExam();
}
}
}
public class Student {
private String name;
public Student(String name) {
this.name = name;
}
public void takeExam() {
// 学生参加考试
}
}
public class Teacher {
private School school;
public Teacher(School school) {
this.school = school;
}
public void startExams() {
school.conductExams();
}
}
Teacher类只与其直接的朋友School类通信,而不需要直接与Student类交互。这遵循了迪米特法则,降低了耦合度。
其他编程原则
"Tell, Don't Ask" 原则
代码语言:javascript复制// 不遵循:ShoppingCart类不遵循"Tell, Don't Ask"原则,因为它在添加商品时首先查询商品的状态,然后决定是否添加。这会导致较高的耦合度和不够清晰的代码。
public class ShoppingCart {
private List<Item> items = new ArrayList();
public void addItem(Item item) {
if (item.isAvailable()) {
items.add(item);
}
}
}
// 遵循:ShoppingCart类遵循"Tell, Don't Ask"原则,它直接告诉商品对象要添加到购物车,而不再查询商品的状态。这种方式降低了耦合度,使代码更加清晰和可维护。
public class ShoppingCart {
private List<Item> items = new ArrayList();
public void addItem(Item item) {
item.addToCart(this);
}
}
public class Item {
private boolean available;
public void addToCart(ShoppingCart cart) {
if (isAvailable()) {
cart.add(this);
}
}
private boolean isAvailable() {
return available;
}
}
结论
遵循SOLID原则以及其他编程原则,是编写出高质量、可维护代码的关键。这些原则有助于减少代码中的重复、降低耦合度、提高扩展性和可读性。我们在日常的开发中应当积极应用这些原则,以创建更可靠的软件系统。