今天继续打卡学习极客时间上的专栏“设计模式之美”, 本篇是专栏第16节的学习笔记,介绍面向对象设计原则中的开闭原则。
笔记
面向对象有很多经典的设计原则:
- SOLID5原则分别是
- 单一职责原则(SRP)、开闭原则(OCP)、里氏替换原则(LSP)、接口隔离原则(ISP)、依赖反转原则(DIP)
- KISS - Keep It Simple, Stupid
- YAGNI - You aren’t gonna need it
- DRY - Don’t Repeat Yourself
- LOD - Law of Demeter(迪米特法则,又叫做最少知识原则)
开闭原则
开闭原则(Open Closed Principle): 即软件实体(模块、类、方法等)对扩展开发,对修改关闭。 通俗的理解即添加一个新的功能应该是在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。 在GOF 23种经典设计模式中,大部分设计模式是为了解决代码的扩展性问题而存在的,主要遵从的设计原则就是开闭原则。
开闭原则就是讲的代码的扩展性问题,如果某段代码在应对未来需求变化的时候,能够做到"对扩展开放、对修改关闭",那就说名这段代码的扩展性比较好。
为了尽量写出扩展性好的代码,要随时具有扩展意识、抽象意识、封装意识,在写代码的时候多思考,留好扩展点,但也不要过度设计,因为唯一不变的就是变化。
另外,识别出代码可变部分和不可变部分,将可变部分封装起来,隔离变化,提供抽象化的不可变接口给上层系统使用。当具体实现发生变化时,只需基于相同的抽象接口,扩展一个新的实现,替换掉老的实现即可,而上游系统的代码几乎不需要修改。
在众多设计原则、设计思想、设计模式中,常用来提高代码扩展性的方法有: 多态、依赖注入、基于接口(而非实现)编程、使用大部分设计模式(如: 装饰、策略、模板、职责链、状态等)。实际上很多设计原则、设计思想、设计模式都是相通的。
另外,还要清楚开闭原则不是免费的,有些情况下代码的扩展性会跟可读性相冲突,应用设计模式很可能会引入过多的类以及复杂的关系,而且设计模式只是给我们的代码带来更好的扩展性而非性能。
代码重写
用Go语言中的接口型函数重写文中的API接口监控告警示例
下面的代码采用基于接口(而非实现)编程的设计思想,隔离了变化,同时借助Go语言中的接口型函数特性,当有新的监控告警处理逻辑要加入时只需编写一个新的HandlerFunc添加到其中即可。
package main
import (
"fmt"
)
type ApiStatInfo struct {
Api string
RequestCount int64
ErrorCount int64
DurationOfSeconds int64
}
type AlertHandler interface {
Check(apiStatInfo *ApiStatInfo) error
}
// 接口型函数
type AlertHandleFunc func(apiStatInfo *ApiStatInfo) error
func (f AlertHandleFunc) Check(apiStatInfo *ApiStatInfo) error {
return f(apiStatInfo)
}
type Alert struct {
AlertHandlers []AlertHandler
}
func NewAlert() *Alert {
return &Alert{AlertHandlers: []AlertHandler{}}
}
func (a *Alert) AddHandler(alertHandler AlertHandler) {
a.AlertHandlers = append(a.AlertHandlers, alertHandler)
}
func (a *Alert) AddHandlerFunc(f func(apiStatInfo *ApiStatInfo) error) {
a.AddHandler(AlertHandleFunc(f))
}
func (a *Alert) Check(apiStatInfo *ApiStatInfo) error {
for _, alertHandler := range a.AlertHandlers {
if err := alertHandler.Check(apiStatInfo); err != nil {
return err
}
}
return nil
}
func main() {
alert := NewAlert()
alert.AddHandlerFunc(HandleTpsAlert)
alert.AddHandlerFunc(HandleErrorAlert)
apiStatInfo := &ApiStatInfo{}
// ...省略对apiStatInfo属性赋值
alert.Check(apiStatInfo)
}
func HandleTpsAlert(apiStatInfo *ApiStatInfo) error {
// if ...
fmt.Println("hanlde tps alert")
return nil
}
func HandleErrorAlert(apiStatInfo *ApiStatInfo) error {
// if ...
fmt.Println("handle error alert")
return nil
}个人理解
开闭原则是软件设计的核心原则,是大方向上的指导思想、指导方针。 在做软件设计时应始终以开闭原则为指导评审设计。