前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >单一职责原则

单一职责原则

作者头像
数据小冰
发布2022-08-15 14:49:21
3040
发布2022-08-15 14:49:21
举报
文章被收录于专栏:数据小冰

本系列文章从场景代码入手,通过代码review指出当前存在的问题,然后思考改进,最后进行提炼总结,即通过”代码-问题-改进-总结“的方式学习编程模式,感受思考的乐趣,To be a better coder.

场景代码

现在有一个struct statistic,它的功能是统计给定目录下的文件的代码行数,并将统计结果输出。小明接到这个需求,心里很Happy😁,这还不简单,分分钟搞定,于是写下了如下的代码。

代码语言:javascript
复制
type statistic struct {
 data map[string]int
}

func (s *statistic) Statistic(path string) error {
 // TODO 统计每个文件中的代码函数,存储到data中
 // data中的key为文件名 value为代码行数
 return nil
}

func (s statistic) Output(writer io.Writer) {
 for path, result := range s.data {
  fmt.Fprintf(writer, "%s -> %d\n", path, result)
 }
}

过了一会,同事小A说,能不能加个功能,将统计结果以csv格式输出,这样我可以直接用excel软件打开,方便查看。小明说没问题,加个方法不就可以了。于是,得到如下代码。

代码语言:javascript
复制
func (s statistic) OutputCSV(writer io.Writer) {
 for path, result := range s.data {
  fmt.Fprintf(writer, "%s,%d\n", path, result)
 }
}
代码Review

代码提交之后来到了review环节,技术经理开始审查代码了。很快审查报告出来了,说代码输出功能可扩展性差。有了OutputCSV说不定以后还有Outputxxx,职责不够单一,添加一种输出方式就需要修改statistic代码,不满足single responsibility principle(SRP)原则。

如何改进

小明按照评审官的意见重新审视自己的代码,并输出了statistic的结构图,如下图所示。

statistic的功能是统计,按职责来说输出信息并不是它要做的事。所以需要进行拆分,将输出信息分离出去单独成为一个Printer class(在golang中把class理解为struct)。现在我们分别从statistic和Printer以及函数调用方的角度来看他们之间的关系, statistic只需完成自己的统计功能,Printer只需完成输出功能,它的输入数据来自statistic,因为statistic数据存储在map中,所以Printer接收参数定义为map,可以实现Printer和statistic完全解耦。调用方main函数拿到statistic和Printer对象便可以完成统计输出。在来看扩展性,我们将Printer定义为接口,新增一种输出方式,只需要扩展一个class,不用改已有的代码,非常好。小明很快完成改进,得到如下代码。这次代码终于得到评审官的肯定😁。

代码语言:javascript
复制
type statistic struct {
 data map[string]int
}

func (s *statistic) Statistic(path string) error {
 // TODO 统计每个文件中的代码函数,存储到data中
 // data中的key为文件名 value为代码行数
 return nil
}

func (s *statistic) GetData() map[string]int{
 // TODO 拷贝s.data返回
 return nil
}

type Printer interface {
 Output(data map[string]int)
}

type defaultPrinter struct {
 Writer io.Writer
}

func (d *defaultPrinter) Output(data map[string]int) {
 for path, result := range data {
  fmt.Fprintf(d.Writer, "%s -> %d\n", path, result)
 }
}

type CSVPrinter struct {
 Writer io.Writer
}

func (c *CSVPrinter) Output(data map[string]int) {
 for path, result := range data {
  fmt.Fprintf(c.Writer, "%s,%d\n", path, result)
 }
}

提炼总结

本文代码改进中我们遵循了单一职责原则(SRP),单一职责原则的核心要点是什么呢?一个类只负责一个职责或者功能,就是类(struct)的设计不要大而全,用一个类搞定一切,要设计粒度小、功能单一的类型。单一职责的目标是实现代码高内聚、低耦合,提高代码的复用性、可读性和可维护性。

怎么判断一个类是否职责单一呢?有什么直观的评价依据吗?这其实没有明确的标准,对一个类型的职责是否单一,不同的人可能有不同的判断结果。在工程实践中要结合场景具体业务具体分析,不能生搬硬套,如果遇到一个类的代码行数很多,一个struct中定义了很多字段,有可能不满足单一职责原则,考虑是否可以拆分简化代码复杂性。

什么时候进行拆分呢?有同学会说像上面的代码在第一次写的时候想不到拆分怎么办?像上面的例子,如果没有后面新需求要输出csv格式,将Output方法直接定义在statistic对象上,一般也想不到扩展,因为没有需求吗?过渡设计扩展意义不大。拆分一般出现在对功能扩展的时候,如果出现了重复的功能、重复的代码,要敏锐的想到是否需要进行重构拆分了。如果不关注,代码可能会这样慢慢膨胀💥,越来越难维护。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-08-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数据小冰 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 场景代码
  • 代码Review
  • 如何改进
  • 提炼总结
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档