SOLID 開放封閉原則
SOLID 開放封閉原則(OCP, Open-Closed Principle)
-5 min read程式碼的設計應該是「開放擴展」(Open for extension),同時「封閉修改」(Closed for modification),換句話說,當需求改變的時候,程式碼的設計應該可以輕鬆擴展,而不必修改原有的程式碼。
例如,當新增一個新功能時,我們可以透過新增新的程式碼,而不是直接修改現有的程式碼來實現。這樣可以降低對原有程式碼的風險,因為修改可能會導致意外的錯誤。同時,透過擴展現有的程式碼,可以避免重複程式碼,從而提高程式碼的可重用性。
生活中的範例
對於更換刮刀的頭,我們不需要去修改內部構造,只需要換刀頭然後啟動電源就好。
如果對於刮刀的頭有不同的需求,只需要符合刮刀的「介面」,讓刮刀頭能接上刮刀,重新開發一個頭就好了,不需要整個刮刀都修改。
- 對於擴展是開放的 — 當需求變更時模組行為可以新增的
- 對於修改是封閉的 — 當進行擴展時,不需修改既有的程式碼
JavaScript 類物件導向範例
錯誤範例
假設我們現在有一個 Shape 類別,它有一個 calculateArea 方法,用於計算圓形(circle)和正方形(square)的面積。現在我們想要增加一個新的圖形,例如矩形(rectangle),並且也想讓 Shape 類別能夠計算矩形的面積。如果我們直接在 Shape 類別中添加新的計算矩形面積的程式碼,這就違反了開放封閉原則,因為我們修改了現有的程式碼。在這個例子中,當我們需要添加新的圖形時,必須修改 Shape 類別的 calculateArea 方法。但是,這會導致類別的行為發生變化,因為它不再只計算圓形和正方形的面積,而是增加了計算矩形面積的功能。
class Shape { constructor(type, width, height, radius) { this.type = type this.width = width this.height = height this.radius = radius } calculateArea() { if (this.type === 'circle') { return Math.PI * this.radius ** 2 } else if (this.type === 'square') { return this.width ** 2 } else if (this.type === 'rectangle') { // 添加新的計算矩形面積的程式碼,違反了開放封閉原則 return this.width * this.height } }}
正確範例
如果我們希望符合開放封閉原則,我們可以將 Shape 類別設計成抽象類別,並定義一個 calculateArea 方法的抽象方法,讓子類別實現這個方法來計算不同圖形的面積。下面是一個修改後符合開放封閉原則的範例:
class Shape { constructor(type, width, height, radius) { this.type = type this.width = width this.height = height this.radius = radius } calculateArea() { throw new Error('calculateArea method must be implemented') }}class Circle extends Shape { constructor(radius) { super('circle', null, null, radius) } calculateArea() { return Math.PI * this.radius ** 2 }}class Square extends Shape { constructor(width) { super('square', width, null, null) } calculateArea() { return this.width ** 2 }}class Rectangle extends Shape { constructor(width, height) { super('rectangle', width, height, null) } calculateArea() { return this.width * this.height }}
Table of Contents