单一职责原则(Single Responsibility Principle, SRP)是Bob大叔提倡的S.O.L.I.D五大设计原则中的第一个。其中,职责(Responsibility)被表述为“变化的缘由”(reason to change);SRP被表述为“一个类应该有且只有一个变化的缘由”。但若是光从字面去理解,SRP很容易让人望文生义产生误解。本文但愿能阐明SRP的本质,达到避免误解和指导设计的目的。
对于设计原则的理解应该首先从它的动机入手,即遵循这个原则带来的好处是什么?对于SRP来说,若是遵循“一个类应该有且只有一个变化的缘由”是因,那么“任何一个变化只会影响一个类”就是果。可见SRP的动机主要是系统的可维护性,即下降适应变化的成本。 java
对于单功能的类来说,比较容易遵循SRP,好比:把string转换为DateTime类型的工具类。理解SRP的难点在于在多功能类的情形,即不容易理解多功能与单变化的矛盾。让咱们先来看一个Modem类的例子,Modem具备4个功能:拨号,挂断,发送数据,接收数据: 工具
class Modem { public void Dial(string aPno) {…} public void Hangup() {…} public void Send(char aData) {…} public char Receive() {…} }
咱们先来考察一下Modem类的变化点,并将其分为两类:1.Modem类的内部细节变化,好比Dial、Hangup、Send、Receive等具体实现发生了变化;2.Modem类总体性的变化,好比Modem类须要增长Poweron和Poweroff方法。上面的Modem类具备多个变化点,不符合SRP。 设计
咱们先来考察一下Modem类的变化点,并将其分为两类:1.Modem类的内部细节变化,好比Dial、Hangup、Send、Receive等具体实现发生了变化;2.Modem类总体性的变化,好比Modem类须要增长Poweron和Poweroff方法。上面的Modem类具备多个变化点,不符合SRP。 code
interface IDialable{ void Dial(string aPno); } class Dialer : IDialable{ public void Dial(string aPno){…} } class Modem{ public void Dial(string aPno) { m_dialer.Dial(aPno); } private IDialable m_dialer; … //hangup, send, receive相似 }
上面的设计将各个功能点抽象为单一的接口,将变化封装在稳定的接口背后,再经过组合的方式创建起总体的Modem类与局部的功能点的联系。这样就避免了低层的变化传导到高层。 接口
可见,理解SRP的关键在于理解类的抽象层次,高层次的类是高层概念的抽象,低层次的类是低层概念的抽象。低层的变化只影响低层类,高层的变化只影响高层类。对于遵照SRP的设计,必定具备很好的抽象层次,所以不妨以SRP为指导和检验,帮助咱们设计出好的类来。最后,我用几个关键词梳理SRP的脉络:ip
类只有一个变化的缘由 >> 一个变化只影响一个类 >> 变化只影响其相应层次的类ci