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

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7type ApiStatInfo struct {
 8	Api               string
 9	RequestCount      int64
10	ErrorCount        int64
11	DurationOfSeconds int64
12}
13
14type AlertHandler interface {
15	Check(apiStatInfo *ApiStatInfo) error
16}
17
18// 接口型函数
19type AlertHandleFunc func(apiStatInfo *ApiStatInfo) error
20func (f AlertHandleFunc) Check(apiStatInfo *ApiStatInfo) error {
21	return f(apiStatInfo)
22}
23
24type Alert struct {
25	AlertHandlers []AlertHandler
26}
27
28func NewAlert() *Alert {
29	return &Alert{AlertHandlers: []AlertHandler{}}
30}
31func (a *Alert) AddHandler(alertHandler AlertHandler) {
32	a.AlertHandlers = append(a.AlertHandlers, alertHandler)
33}
34func (a *Alert) AddHandlerFunc(f func(apiStatInfo *ApiStatInfo) error) {
35	a.AddHandler(AlertHandleFunc(f))
36}
37func (a *Alert) Check(apiStatInfo *ApiStatInfo) error {
38	for _, alertHandler := range a.AlertHandlers {
39		if err := alertHandler.Check(apiStatInfo); err != nil {
40			return err
41		}
42	}
43	return nil
44}
45
46func main() {
47	alert := NewAlert()
48	alert.AddHandlerFunc(HandleTpsAlert)
49	alert.AddHandlerFunc(HandleErrorAlert)
50	apiStatInfo := &ApiStatInfo{}
51	// ...省略对apiStatInfo属性赋值
52	alert.Check(apiStatInfo)
53}
54
55
56func HandleTpsAlert(apiStatInfo *ApiStatInfo) error {
57    // if ...
58	fmt.Println("hanlde tps alert")
59	return nil
60}
61func HandleErrorAlert(apiStatInfo *ApiStatInfo) error {
62    // if ...
63	fmt.Println("handle error alert")
64	return nil
65}

个人理解

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

参考