IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> 一文搞懂开闭原则 -> 正文阅读

[开发测试]一文搞懂开闭原则

定义

对扩展开放,对修改关闭
详细说就是新增一个业务功能的时候,在已有代码的基础上去扩展(新增模块、类、方法),不要去修改已有代码(修改模块、类、方法)

什么是合格的修改

只要它没有破坏原有的代码的正常运行,没有破坏原有的单元测试,我们就可以说,这是一个合格的代码改动。
添加一个新功能,不可能任何模块、类、方法的代码都不“修改”,这个是做不到的。哪怕在已有代码基础上扩展一个新的类,类需要创建、组装、并且做一些初始化操作,才能构建成可运行的的程序,这部分代码是无法避免的。
我们要做的是尽量让修改操作更集中、更少、更上层,尽量让最核心、最复杂、最底层的那部分逻辑代码保持不变。

如何做到"对扩展开放,修改关闭"

指导思想

为了尽量写出扩展性好的代码,我们要时刻具备扩展意识、抽象意识、封装意识。
写代码的时候,多花时间往前思考一下未来这段代码的需求会发生什么变化,如何设计代码结构,预留扩展点,在需求发生变更的时候,不改动代码整体结构,以最小的代码改动代价将新功能实现代码插到扩展点上。
还有,识别出可变部分和不可变部分,将可变部分封装起来,隔离变化,提供抽象的稳定接口给上层使用,这样具体实现发生变化的时候,只需要新增一个接口的实现,然后替换掉原来的实现即可,上游不会感知下游的变化。

具体方法论

常用来提高可扩展性的方法有:多态、面向接口而非实现编程、依赖注入,以及一些设计模式(策略、模板方法、状态、装饰器、责任链等等)

如何在项目中灵活运用开闭原则?

运用好开闭原则的关键是预留扩展点

如何识别出扩展点

如果开发的是业务系统,那么就需要我们对业务足够了解,知道未来可能会扩展哪些需求;如果开发的底层框架、类库、组件,那么就需要知道框架会被如何使用,以后打算扩展哪些功能。
当然,我们不一定能识别出所有的扩展点,即使识别出所有的扩展点,预留这些扩展点的成本也很大,没有必要。
合适的做法是,对于一些短期内确定会扩展的需求,或者需求变动时对代码结构的影响较大,或者扩展点实现成本不高的地方我们去预留扩展点。
提高扩展性也是有代价的,有时会降低代码的可读性,所以我们要权衡,某些场景下扩展性很重要那么可以牺牲一点可读性,某些场景下可读性很重要那么可以牺牲一点扩展性。

最佳实践

这是一段 API 接口监控告警的代码。其中,AlertRule 存储告警规则,可以自由设置。Notification 是告警通知类,支持邮件、短信、微信、手机等多种通知渠道。NotificationEmergencyLevel 表示通知的紧急程度,包括
SEVERE(严重)、URGENCY(紧急)、NORMAL(普通)、TRIVIAL(无关紧要),不同的紧急程度对应不同的发送渠道。关于 API 接口监控告警这部分,更加详细的业务需求分析和设计,我们会在后面的设计模式模块再拿出来进一步讲解,这里你只要简单知道这些,就够我们今天用了。
设计实现这段代码,在新增一个报警需求时做到尽量小的代码改动

版本1

type AlterRule struct{}

func (r *AlterRule) GetMatchedMaxTps(api string) int {
	return 1000
}

func (r *AlterRule) GetMatchedMaxErrorCount(api string) int {
	return 1000
}

type Notification struct{}

func (n *Notification) Notify(level string, msg string) {
	fmt.Printf("level:%v, send msg: %v", level, msg)
}

type Alter struct {
	AlterRule    *AlterRule
	Notification *Notification
}

func NewAlter(rule *AlterRule, notification *Notification) *Alter {
	return &Alter{
		AlterRule:    rule,
		Notification: notification,
	}
}

func (a *Alter) Check(api string, reqCount int, errCount int, durationSecond int) {
	tps := reqCount / durationSecond
	if tps > a.AlterRule.GetMatchedMaxTps(api) {
		a.Notification.Notify("URGENCY", "Tps More Than 1000")
	}
	if errCount > a.AlterRule.GetMatchedMaxErrorCount(api) {
		a.Notification.Notify("ERROR", "Error More Than 1000")
	}
}

现在我们要扩展一个新的报警处理功能,比如超时的请求个数超过100个就报警,那么就需要修改Check方法的定义和代码实现,这样就会影响代码的正常运行,也会影响Check方法的单测,不是一个合适的改动

怎么样遵循开闭原则呢?
我们可以看出这里的可变部分就是不同的报警处理功能,我们可以将报警功能封装起来,对外暴露一个AlterHandler接口,新增报警处理功能时只需要新增一个接口的实现即可

版本2

type AlterRuleV2 struct{}

func (r *AlterRuleV2) GetMatchedMaxTps(api string) int {
	return 1000
}

func (r *AlterRuleV2) GetMatchedMaxErrorCount(api string) int {
	return 1000
}

func (r *AlterRuleV2) GetMatchedMaxTimeoutCount(api string) int {
	return 100
}

type NotificationV2 struct{}

func (n *NotificationV2) Notify(level string, msg string) {
	fmt.Printf("level:%v, send msg: %v", level, msg)
}

type ApiStatInfo struct {
	Api            string
	ReqCount       int
	ErrCount       int
	TimeoutCount   int
	DurationSecond int
}

type IAlterHandler interface {
	Check(info *ApiStatInfo)
}

type AbstractAlterHandler struct {
	AlterRule    *AlterRuleV2
	Notification *NotificationV2
}

type TpsAlterHandler struct {
	AbstractAlterHandler
}

func (h *TpsAlterHandler) Check(info *ApiStatInfo) {
	tps := info.ReqCount / info.DurationSecond
	if tps > h.AlterRule.GetMatchedMaxTps(info.Api) {
		h.Notification.Notify("URGENCY", "Tps More Than 1000")
	}
}

type ErrorAlterHandler struct {
	AbstractAlterHandler
}

func (h *ErrorAlterHandler) Check(info *ApiStatInfo) {
	if info.ErrCount > h.AlterRule.GetMatchedMaxErrorCount(info.Api) {
		h.Notification.Notify("ERROR", "Error More Than 1000")
	}
}

type TimeoutAlterHandler struct {
	AbstractAlterHandler
}

func (h *TimeoutAlterHandler) Check(info *ApiStatInfo) {
	if info.TimeoutCount > h.AlterRule.GetMatchedMaxTimeoutCount(info.Api) {
		h.Notification.Notify("ERROR", "Timeout More Than 100")
	}
}

type AlterV2 struct {
	AlterHandlerList []IAlterHandler
}

func (a *AlterV2) AddAlterHandler(alterHandlerList []IAlterHandler) {
	a.AlterHandlerList = append(a.AlterHandlerList, alterHandlerList...)
}

func (a *AlterV2) Check(info *ApiStatInfo) {
	for i := range a.AlterHandlerList {
		a.AlterHandlerList[i].Check(info)
	}
}
  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章      下一篇文章      查看所有文章
加:2021-09-07 11:07:04  更:2021-09-07 11:07:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/18 0:14:23-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码