今天继续打卡学习极客时间上的专栏“设计模式之美”, 本篇是专栏第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添加到其中即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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
}

个人理解

开闭原则是软件设计的核心原则,是大方向上的指导思想、指导方针。 在做软件设计时应始终以开闭原则为指导评审设计。

参考