Article
开闭原则:像太极拳那样加招式,不改拳理
· 8 分钟阅读
张三丰的太极拳以柔克刚,圆转如意,对扩展开放,对修改关闭。开闭原则就如同太极拳的理念,在面对新的需求时,我们不是去修改已有的代码,而是通过扩展现有代码来实现新的功能。就像太极拳在应对不同的对手时,通过调整招式的变化来克敌制胜,而不是改变拳法的根本原理。这样可以保证代码的稳定性和可维护性,避免因为频繁修改代码而引入新的错误。

对扩展开放,对修改封闭
开闭原则强调:软件实体(如类、模块和函数)应该对扩展开放,对修改封闭。这意味着我们应当能够添加新的功能,而不改动现有的代码。
示例:形状的扩展
把形状抽象出来
// 抽象类:形状
abstract class Shape {
abstract area(): number;
}
// 具体类:圆形
class Circle extends Shape {
private radius: number;
constructor(radius: number) {
super();
this.radius = radius;
}
area(): number {
return Math.PI * this.radius * this.radius;
}
}
// 具体类:矩形
class Rectangle extends Shape {
private width: number;
private height: number;
constructor(width: number, height: number) {
super();
this.width = width;
this.height = height;
}
area(): number {
return this.width * this.height;
}
}
扩展新的形状类
当我们需要增加新的形状,例如三角形时,可以通过扩展现有代码,而无需修改 Shape 类及其现有的子类。
// 具体类:三角形
class Triangle extends Shape {
private base: number;
private height: number;
constructor(base: number, height: number) {
super();
this.base = base;
this.height = height;
}
area(): number {
return 0.5 * this.base * this.height;
}
}
加一种新形状,不改旧代码
const shapes: Shape[] = [new Circle(10), new Rectangle(5, 6), new Triangle(4, 3)];
shapes.forEach(shape => console.log(shape.area()));
// 输出:314.1592653589793, 30, 6
这种设计方式使得系统具有很好的扩展性,我们可以随时添加新的形状类型,而不会影响到已有的形状类或系统的其他部分。
类图

体现开闭原则的设计模式
- 策略模式(Strategy Pattern):策略模式通过将算法封装到独立的策略类中,实现了对扩展开放、对修改封闭的要求。客户端可以在运行时选择具体的策略,而不需要修改已有代码。
- 装饰器模式(Decorator Pattern):装饰器模式通过将附加功能封装到装饰器对象中,使得可以在不修改具体组件代码的情况下,动态扩展对象的功能。
- 观察者模式(Observer Pattern):观察者模式通过将通知逻辑封装到观察者类中,可以在不修改被观察对象的情况下,动态添加新的观察者,扩展系统功能。
- 命令模式(Command Pattern):命令模式通过将请求封装到独立的命令类中,使得可以通过扩展新的命令类,增加新的请求处理功能,无需修改已有的代码。
- 工厂模式(Factory Pattern):工厂模式通过使用工厂方法创建对象,使得可以通过扩展工厂类或方法来创建新类型的对象,而不需要修改客户端代码。
需求变化时,改哪一端
组件渲染
在组件渲染场景中,可以通过扩展新的渲染策略类,实现不同的渲染方案,例如根据设备或浏览器环境选择不同的渲染策略。
class Renderer {
render(strategy: RenderStrategy, element: HTMLElement): void {
strategy.render(element)
}
}
// 抽象策略类
interface RenderStrategy {
render(element: HTMLElement): void
}
// 具体策略类:现代渲染
class ModernRender implements RenderStrategy {
render(element: HTMLElement): void {
element.innerHTML = "<div class='modern'>Modern Rendering</div>"
}
}
// 具体策略类:兼容渲染
class LegacyRender implements RenderStrategy {
render(element: HTMLElement): void {
element.innerHTML = "<div class='legacy'>Legacy Rendering</div>"
}
}
// 使用示例
const element = document.getElementById('myElement')
const renderer = new Renderer()
renderer.render(new ModernRender(), element) // 应用现代渲染
renderer.render(new LegacyRender(), element) // 应用兼容渲染
功能模块插拔
在功能模块插拔场景中,可以通过扩展和替换功能模块,实现动态切换功能,如换肤功能、多种排序算法等。
class Sorter {
sort(strategy: SortStrategy, data: number[]): number[] {
return strategy.sort(data)
}
}
// 抽象策略类
interface SortStrategy {
sort(data: number[]): number[]
}
// 具体策略类:快速排序
class QuickSort implements SortStrategy {
sort(data: number[]): number[] {
if (data.length <= 1) return data
const pivot = data[Math.floor(data.length / 2)]
const left = data.filter((x) => x < pivot)
const middle = data.filter((x) => x === pivot)
const right = data.filter((x) => x > pivot)
return [...this.sort(left), ...middle, ...this.sort(right)]
}
}
// 具体策略类:冒泡排序
class BubbleSort implements SortStrategy {
sort(data: number[]): number[] {
const n = data.length
for (let i = 0; i < n - 1; i++) {
for (let j = 0; j < n - 1 - i; j++) {
if (data[j] > data[j + 1]) {
;[data[j], data[j + 1]] = [data[j + 1], data[j]]
}
}
}
return data
}
}
// 使用示例
const sorter = new Sorter()
const data = [5, 3, 8, 2, 1]
console.log(sorter.sort(new QuickSort(), data)) // 使用快速排序
console.log(sorter.sort(new BubbleSort(), data)) // 使用冒泡排序
Validator.js 与 Day.js
1. Validator.js
Validator.js 是一个字符串验证库,通过策略模式实现了多种验证算法,如邮箱验证、手机号验证、URL 验证等,体现了开闭原则。
import validator from 'validator'
// 验证示例
console.log(validator.isEmail('[email protected]')) // true
console.log(validator.isMobilePhone('1234567890', 'en-US')) // true
console.log(validator.isURL('https://example.com')) // true
2. Day.js
Day.js 是一个轻量级的日期处理库,通过策略模式实现了各种日期格式化和操作算法,可以根据需求灵活使用不同的日期操作,体现了开闭原则。
import dayjs from 'dayjs'
// 示例
console.log(dayjs().format('YYYY-MM-DD')) // 输出当前日期
console.log(dayjs('2022-01-01').add(1, 'month').format('YYYY-MM-DD')) // 2022-02-01
console.log(dayjs('2022-01-01').isBefore('2022-12-31')) // true
Keep Reading