开闭原则是面向对象设计中的一个核心原则,它的内容是:
软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。
也就是说,当需求变化时,我们应该能够通过扩展现有的代码来实现新的功能,而不是修改现有的代码。这样可以减少现有功能的变动,避免引入新的错误,并且能够提高代码的可维护性和可扩展性。
对扩展开放:意味着我们可以通过添加新的代码来扩展现有功能,而不是修改原有的代码。扩展可以通过继承、实现接口等方式来实现。
对修改封闭:指的是已有的代码不应该被修改,因为修改现有代码容易引入错误,并且可能影响到系统的其他部分。相反,我们应该通过扩展的方式来增强功能。
假设我们有一个简单的图形绘制应用,支持绘制不同的形状(比如圆形和矩形)。如果不遵循开闭原则,当我们需要添加新的形状(例如三角形)时,我们可能需要修改现有的代码,这样会违背开闭原则。
gopackage main
import "fmt"
// 不符合开闭原则的设计
type Shape struct {
Type string
}
func (s *Shape) Draw() {
if s.Type == "circle" {
fmt.Println("Drawing a circle")
} else if s.Type == "rectangle" {
fmt.Println("Drawing a rectangle")
}
}
func main() {
circle := &Shape{Type: "circle"}
rectangle := &Shape{Type: "rectangle"}
circle.Draw()
rectangle.Draw()
}
在上面的代码中,Shape 类在绘制不同类型的形状时,硬编码了具体的形状类型。当我们要添加一个新的形状(比如三角形)时,我们需要修改 Shape 类的 Draw 方法,加入三角形的逻辑。这样做的问题是,每当需要新增功能时,我们都会修改现有代码,增加了潜在的风险,且容易引入错误。
为了解决这个问题,我们可以利用接口和多态来扩展功能,而不修改原有的代码。每个具体的形状(如 Circle 和 Rectangle)都实现一个公共的 Shape 接口,通过这种方式,我们可以为每个形状提供不同的实现。
gopackage main
import "fmt"
// 定义一个接口,所有形状都要实现它
type Shape interface {
Draw()
}
// 圆形
type Circle struct {}
func (c *Circle) Draw() {
fmt.Println("Drawing a circle")
}
// 矩形
type Rectangle struct {}
func (r *Rectangle) Draw() {
fmt.Println("Drawing a rectangle")
}
// 三角形
type Triangle struct {}
func (t *Triangle) Draw() {
fmt.Println("Drawing a triangle")
}
// 高层模块,依赖于接口而不是具体的实现
type DrawingApp struct {}
func (d *DrawingApp) DrawShape(s Shape) {
s.Draw()
}
func main() {
// 不需要修改任何已有代码,我们只需实现新的形状类型
circle := &Circle{}
rectangle := &Rectangle{}
triangle := &Triangle{}
app := &DrawingApp{}
app.DrawShape(circle)
app.DrawShape(rectangle)
app.DrawShape(triangle)
}
本文作者:曹子昂
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!