该原则指出软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。也就是说,在添加新功能时,应该通过扩展现有代码来实现,而不是直接修改已有的代码。这样可以确保现有代码的稳定性,并且减少对其他部分的影响。
// 开闭原则示例
// 原始功能实现类
class OriginalFunctionality {
performAction() {
console.log("Original functionality");
}
}
// 扩展功能实现类
class ExtendedFunctionality extends OriginalFunctionality {
performAction() {
super.performAction();
console.log("Extended functionality");
}
}
// 使用示例
const functionality = new ExtendedFunctionality();
functionality.performAction();
在上述例子中,有一个原始功能实现类 OriginalFunctionality
,它定义了一个 performAction
方法来执行某些功能。
根据开闭原则,需要通过扩展而不是修改原始功能类来添加新功能。因此,创建一个扩展功能实现类 ExtendedFunctionality
,它继承自 OriginalFunctionality
并重写了 performAction
方法,在执行新功能之前先调用了原始功能。
通过应用开闭原则,可以减少对现有代码的修改,从而提高代码的稳定性和可维护性。同时,它也使得代码更易于扩展和重用,提供了更灵活的架构设计。
这里,再介绍一个常见的开闭原则示例:
// 原始的 if-else 结构
function performAction(option) {
if (option === 'A') {
console.log("Performing action A");
} else if (option === 'B') {
console.log("Performing action B");
} else if (option === 'C') {
console.log("Performing action C");
} else {
console.log("Invalid option");
}
}
// 使用 key-value 形式执行特定逻辑
const actions = {
A: () => console.log("Performing action A"),
B: () => console.log("Performing action B"),
C: () => console.log("Performing action C"),
};
function performAction(option) {
const action = actions[option];
if (action) {
action();
} else {
console.log("Invalid option");
}
}
// 使用示例
performAction('A'); // 输出: Performing action A
performAction('B'); // 输出: Performing action B
performAction('C'); // 输出: Performing action C
performAction('D'); // 输出: Invalid option
将 if-else
结构改成 key-value
形式来执行特定逻辑,可以被视为一种应用开闭原则的方式。这样的改变允许通过添加新的键值对来扩展代码,而不需要修改原有的逻辑。
该原则提倡每个类或模块应该只负责一个单一的功能或任务。这样可以提高代码的可读性、可维护性和重用性。当一个类具有多个职责时,建议将其拆分为多个独立的类,每个类专注于一个职责。
// 组件的单一职责示例
// 用户列表组件,负责渲染用户列表
class UserList {
render(users) {
// 渲染用户列表逻辑...
console.log("User list rendered:", users);
}
}
// 用户管理组件,负责处理用户的增删改操作
class UserManager {
createUser(userData) {
// 创建用户逻辑...
console.log("User created:", userData);
}
updateUser(userId, userData) {
// 更新用户逻辑...
console.log("User updated:", userId, userData);
}
deleteUser(userId) {
// 删除用户逻辑...
console.log("User deleted:", userId);
}
}
// 模块的单一职责示例
// 用户相关功能模块,仅负责用户相关的功能
const userModule = {
createUser(userData) {
// 创建用户逻辑...
console.log("User created:", userData);
},
updateUser(userId, userData) {
// 更新用户逻辑...
console.log("User updated:", userId, userData);
},
deleteUser(userId) {
// 删除用户逻辑...
console.log("User deleted:", userId);
}
};
// 函数的单一职责示例
// 检查用户名是否唯一
function checkUsernameUnique(username) {
// 检查用户名唯一性逻辑...
console.log("Checking username uniqueness:", username);
return true;
}
// 验证密码
function validatePassword(password) {
// 验证密码逻辑...
console.log("Validating password:", password);
return true;
}
// 使用示例
const userList = new UserList();
userList.render(["John", "Mike"]);
const userManager = new UserManager();
userManager.createUser({ name: "John", age: 25 });
userModule.updateUser(1, { name: "Mike" });
checkUsernameUnique("john123");
validatePassword("password123");
上述示例展示了组件、模块和函数的单一职责原则应用。
组件的单一职责:
UserList
组件负责渲染用户列表,UserManager
组件负责处理用户的增删改操作。每个组件只负责一个特定的功能,使得代码更加清晰和易于维护。模块的单一职责:
userModule
是一个用户相关功能的模块,其中包含了创建、更新和删除用户的功能。该模块只关注用户相关的功能,保持了模块的单一职责。函数的单一职责:
checkUsernameUnique
函数用于检查用户名是否唯一,validatePassword
函数用于验证密码。每个函数负责一个具体的功能,使得函数的职责清晰可见。
通过应用单一职责原则,可以将不同的功能分别封装到不同的组件、模块和函数中,使代码更具可读性、可维护性和重用性。这种设计方式帮助我们遵循独立职责的原则,提高代码的可扩展性,并减少不必要的耦合。
该原则提倡通过抽象来解耦代码之间的依赖关系。高层模块应该依赖于抽象接口,而不是具体实现细节。这样可以降低模块之间的耦合度,并且使得系统更容易扩展和修改,同时也更易于进行测试。
// 不符合依赖倒置原则
class UserService {
getUser(userId) {
// 获取用户逻辑...
}
}
class UserController {
constructor() {
this.userService = new UserService();
}
getUser(userId) {
const user = this.userService.getUser(userId);
// 处理用户数据逻辑...
}
}
// 符合依赖倒置原则
class UserService {
getUser(userId) {
// 获取用户逻辑...
}
}
class UserController {
constructor(userService) {
this.userService = userService;
}
getUser(userId) {
const user = this.userService.getUser(userId);
// 处理用户数据逻辑...
}
}
// 使用示例
const userService = new UserService();
const userController = new UserController(userService);
userController.getUser(123);
上述示例展示了一个不符合依赖倒置原则的情况和一个符合依赖倒置原则的情况。
在不符合原则的情况下,UserController
类内部直接创建了 UserService
的实例。这种硬编码的依赖关系导致了紧耦合,不利于代码的扩展和维护。
通过采用依赖注入的方式,可以将 UserService
作为参数传递给 UserController
的构造函数。这样,UserController
不再关心具体的 UserService
实现,而是依赖于抽象接口,降低了组件之间的耦合性。