设计模式6原则之:单一职责原则

概念

一个类或者模块只负责完成一个职责(或者功能)

理解

  1. 不要设计大而全的类,要设计粒度小、功能单一的类
  2. 一个类包含了两个或者两个以上业务不相干的功能,那我们就说它职责不够单一,应该将它拆分成多个功能更加单一、粒度更细的类

如何判断?

  1. 类中的代码行数、函数或属性过多,会影响代码的可读性和可维护性,我们就需要考虑对类进行拆分

    像四五千行的代码就可以考虑拆分了。。。一般来说一个类几百行代码算合理的,类函数和属性最好不要超过 20 个

  2. 类依赖的其他类过多,或者依赖类的其他类过多,不符合高内聚、低耦合的设计思想,我们就需要考虑对类进行拆分

  3. 私有方法过多,我们就要考虑能否将私有方法独立到新的类中,设置为 public 方法,供更多的类使用,从而提高代码的复用性

  4. 比较难给类起一个合适名字,很难用一个业务名词概括,或者只能用一些笼统的 Manager、Context 之类的词语来命名,这就说明类的职责定义得可能不够清晰

  5. 类中大量的方法都是集中操作类中的某几个属性,那就可以考虑将这几个属性和对应的方法拆分出来

方法单一

比如现在我们要新增或者更新小测验,会这么写

func (qz *Quiz) SaveQuiz(params map[string]string) {
	if id, ok := params["id"]; ok {
		fmt.Println("更新小测验", id)
	} else {
		fmt.Println("保存小测验", id)
	}
}

如果 id 存在就走更新分支,否则新增。之所以这么写是因为更新和新增的逻辑会有不同的地方,比如更新可能会走后台线程打包成课件包,新增不会。这样放在一个方法里面会让代码变得复杂而模糊,让后期维护也变得复杂,不符合高内聚低耦合的思想。

这时候就要考虑拆分成 2 个方法:

package quizservice

import "fmt"

type IQuiz interface {
	SaveQuiz(params map[string]string)
	UpdateQuiz(params map[string]string)
}

type Quiz struct {
}

func (qz *Quiz) SaveQuiz(params map[string]string) {
	fmt.Println("添加小测验")
}

func (qz *Quiz) UpdateQuiz(params map[string]string) {
	fmt.Println("更新小测验")
}

接口单一

比如添加一些课件小节,包括视频小节,文档小节。其中视频小节包括基础模块、视频模块;文档小节包括基础模块、文档模块。

type IChapter interface {
	AddBaseModule()
	AddVideoModule()
	AddDocumentModule()
}

type DocumentChapter struct {
}

func (d *DocumentChapter) AddBaseModule() {
	fmt.Println("组装基础模块")
}
func (d *DocumentChapter) AddVideoModule() {
	fmt.Println("不需要组装...")
}
func (d *DocumentChapter) AddDocumentModule() {
	fmt.Println("组装文档模块")
}

type VideoChapter struct {
}

func (d *VideoChapter) AddBaseModule() {
	fmt.Println("组装基础模块")
}
func (d *VideoChapter) AddVideoModule() {
	fmt.Println("组装视频模块")
}
func (d *VideoChapter) AddDocumentModule() {
	fmt.Println("不需要组装...")
}

像上面这样都实现了接口方法,但是文档不需要组装视频模块,视频也不需要组装文档模块,这样就会导致接口的职责不够单一。

修改如下:

type IChapter interface {
	AddBaseModule()
}

type IDocument interface {
	IChapter
	AddDocumentModule()
}

type IVideo interface {
	IChapter
	AddVideoModule()
}

type DocumentChapter struct {
}

func (d *DocumentChapter) AddBaseModule() {
	fmt.Println("组装基础模块")
}
func (d *DocumentChapter) AddDocumentModule() {
	fmt.Println("组装文档模块")
}

type VideoChapter struct {
}

func (d *VideoChapter) AddBaseModule() {
	fmt.Println("组装基础模块")
}
func (d *VideoChapter) AddVideoModule() {
	fmt.Println("组装视频模块")
}