03. 单一功能原则

标准定义

单一功能原则(Single responsibility principle,SRP)的定义如下:

每个类都应该有一个单一的功能,并且该功能应该由这个类完全封装起来

也可以定义为:

就一个类而言,应该只有一个引起它变化的原因

生活案例

到底什么是单一功能?什么是只有一个引起它变化的原因?
有这么一个历史故事:

相传远古时候,在阳城有一位很有才能、很有修养的人,名叫许由。他在箕山隐居,人们都很敬佩他。
当时尧帝想把帝位让给许由,尧帝对他说:“你看,天上的日月已经出来了,这时还不熄灭蜡烛的火光,它的光同日月比起来,太微不足道了!天上的及时雨已经降落了,这时还要用人工去灌溉,难道不是徒劳吗?先生很有才华,要是当了帝王,一定会治理好天下。如果让我继续占着这个帝位,我心里觉得惭愧。请允许我把天下交给您吧!”
许由不愿接受帝位,连忙推辞说:“您已经把天下治理得很好了,我再来代替你,这是为什么?鹪鹩在森林里筑巢,占一根树枝的地方就行了,鼹鼠在河边饮水,顶多喝满一肚子也就够了。算了吧,我的君主!我要天下干什么用呢?厨师在祭祀的时候,又做菜,又备酒,忙得不可开交,可是掌管祭祀的人,并不能因为厨师很忙,忘记自己的本职工作,丢下手中的祭祀用具,去代替厨师做菜、备酒啊!你就是丢开天下不管,我也决不会代替你的职务。”说罢,许由就到田间劳动去了。

这个故事就是“越俎代庖”的故事,“越俎代庖”的含义就是用以比喻超出自己的职责,越权办事或包办代替。如同故事当中的祭祀的人,他不能因为厨师忙,就跑去帮厨师做菜、备酒,而丢下自己的工作不管。在古代,祭师是一个有着神圣的地位的人,他是上天和凡人通信的入口,在那个看天吃饭的年代里面,庄稼有多少收获?人是否百子千孙?都得通过祭师的烧制的甲骨破碎情况来预测,并且做出相关的反应。一个祭师如果就这么跑去帮厨子做菜那岂不是招人耻笑?(虽然易中天认为祭师本身就是个厨子,但是那是另当别论了)。所以,祭祀是祭祀,厨子是厨子,他们的工作不重叠,也不能重叠。

这就是单一功能原则。

程序例子

假设我们有这样的一个业务场景,打印机打印一份文档。

在这里,我们首先采用非单一功能原则:

要打印文档,那么就需要用到打印机,墨水和纸,所以我们根据需求定义3个类,分别是打印机Printer,墨水Ink,纸Paper,在整个打印的过程中,我们需要选择墨水的颜色,选择纸张的大小,添加添加墨水,添加纸张,完成这些之后,我们才能完成一份文档的打印。

这样,我们定义了下面的接口:

interface IPrinter {
  void selectInkColor(String color);
  void selectPaperSize(String size);
  void addInk(Ink ink);
  void addPaper(Paper paper);
  void print()
}

这个接口,会不会出现问题?能用是吧?我们假设有这样的一个场景,这个接口的方法不仅仅是5个,而是100个,而且这个项目不仅仅只有这样的一个接口,而是有100个不同的接口,他们的方法都是乱放的,没有明确的区分,那问一个问题:我怎么在这1w个方法当中,找到那个选择墨水颜色的方法?或许你可以说使用全局查找去找,当然也不是不可以,但是,这个查找的前提必须是,能够标准进行命名,这里的选择墨水的颜色使用了selectInkColor命名,那如果是selectColor,或者是color来命名,那这个查找就无济于事了。

然而,这是一个夸张的例子,实际的项目当中确实很少出现,但是我们需要的是有规范的思维,不管项目的大小,都按规矩走。

按照原则进行分析,这里的选择墨水的颜色,选择纸张的大小,添加添加墨水,添加纸张,打印,有三个引起这个接口变化的原因:选择墨水的颜色,选择纸张的大小和添加添加墨水、添加纸张、打印。这是不符合原则的。

我们采用单一功能原则进行设计,按照第一映象,选择墨水的颜色应该是墨水这个类做的,同理,选择纸张的大小应该是纸做的。各个类应该做好各个类的事情。

重新设计的接口如下

interface IInk {
  void selectInkColor(String color);
}

interface IPaper {
  void selectPaperSize(String size);
}

interface IPrinter {
  void addInk(Ink ink);
  void addPaper(Paper paper);
  void print()
}

重新设计以后,凭直觉,我们就知道从哪里去选择墨水的颜色了! get√

https://www.jianshu.com/p/5d031e0b33a7

你可能感兴趣的:(03. 单一功能原则)