类(实例化产生对象)是面向对象编程中最基本的组成单元,将逻辑和数据封装其中,以提高软件的重用性、灵活性和扩展性等。它相比人类社会组成,系统/子系统、组件/(微)服务、模块/包这些相当于社会中不同层次的实体或虚拟的组织机构;而类则相当于一类自然人,一个对象相当一个自然人。一个类在系统中承担着一种的 ”角色“ ,从事一种职业。
大多数人只从事一种职业,也就是单一职责原则。同理若一个类只关注的就是自身职责的完成,也就是单一职责原则。
面向对象设计的五个基本原则(SOLID),排在第一就是单一职责原则(SRP:Single responsibility principle)。SRP的原话解释是:There should never be more than one reason for a class to change。应该有且仅有一个原因引起类的变更。
单一职责原则是指导”高内聚,低耦合“的基本原则,但也是最难实施的原则。
类是什么,两个角度来看:
上述两个角度,可能是导致两种不同的类代码结构排版的原因?
当然也可能是我的无稽之谈,由于个人主要使用Java系语言,建议类按如下顺序组成,这只是一种编码风格:
以前看过 Martin Fowler
写的一篇文章叫”贫血模型”,批判贫血领域模型不够优雅、不够面向对象,提倡使用充血领域模型。若此观点应用在普通的类设计上非常有争议,至少在面向对象的语言体系中,这两种模型都存在,适用不同的场景。
贫血模型是指对象只有属性(getter/setter),或者包含少量的CRUD方法,而业务逻辑都不包含在其中,而是放在单独的业务处理逻辑层。JavaBean就是最为典型的代表,像Scala,Kotin在语言层次都存在数据类的概念,用于只描述数据的构成。
该模型的确是不够面向对象,对象只是作为保存状态(如数据层的表映射)或者传递状态(如方法中的出入参数)使用,所以就说只有数据没有行为的对象不是真正的对象。
在Java体系中,非常流程的就是这种设计,接口门面层(Controller)-> 业务逻辑处理层(Service)-> 数据访问层(ORM)。
充血模型是指对象里即有数据和状态,也有行为,行为负责维持本身的数据和状态,具有内聚性,最符合面向对象的设计,满足单一职责原则。这也是我们是为常见的对象设计方式。
Martin Fowler
主张这种模型,他是从领域驱动开发(DDD)中领域模型对象来分析的,领域模型(Domain Model)是一个商业建模范畴。从一个模型的封装性来说,即有状态又有行为是合理的,但领域模型并非直接映射为单一类对象,它要比类的模型大很多,可能是由一组类聚合而成。
遵循充血模型的规范,出发点非常好,但对开发人员要求非高,随着变化与演进,最后可能一个类充满了乱七八糟的内容,反而忘记初心,违背单一原则。
inFusion是一款非常不错的软件设计度量工具,它能帮助我们发现代码上坏味道。借助它分析,也讲讲inFusion中提到了哪些类的坏味道,他们违反了单一原则。
上帝类通常为过多地操作其它类的数据,从而破坏了类的封装类,上帝类从其它类中获得功能,却增加了自身的耦合性,通常会导致自己体积过大和较大的复杂度。
导致出现上帝类一般是出现在业务逻辑层,没有对逻辑层合理的分层。此类有点像八爪鱼,手上攥了东西太多,聚合太多其它对象在一个对象中直接组合所有逻辑;另一个原因是被引用的对象的封装性不好,不够内聚,暴露太多数据需要其它类来完成它自身的职责。
复杂类,它具有体积大(通常超过千行),高度复杂的特征。
导致出现复杂类,除了类中的方法存在行数过大的原因之外。另一个原因是一个类最早只有简单的CRUD方法,每个方法复杂度不高。后面随着需求的增加,一种场景是方法实现的场景分支越来越多,导致方法复杂度变高;另一个场景是方法个数增加太多,如Query方法,一开始只有Query1,后面不断增加Query2,Query3…以满足不同的查询条件以及响应内容等等。
紊乱类,一个类本应该一种抽象,完成一类责任。而该类确完成完成两种或以上的抽象,会影响类的理解和修改。特点是定义大量的接口方法,以及被不同的Client使用。
导致出现复杂类,一般出现在界面类(如Controller)、工具类(Util)中,即提供太多公共方法,又同时处理相应的业务逻辑。
再回到单一职责,结合上面怎么进一步理解它,关键是职责的划分,但也是难点。
职责的划分是一定的范围与层次,比如关注的是华为手机和其它东西的区别,那华为手机就是一个整体,就是用来实现手机的功能,不是用来切水果的,所以切水果的方法,不应该实现在华为手机中。当要关注华为手机的内部结构时,那它的模块肯定是隔离的,显示屏只关注显示,通信模块只关注通信,其实每个模块都是单一职责,最终聚合在一起,就是一个手机。
在实际操作中非常难,正如上面”职责“是一个相对的概念,没有一个明确的划分原则,什么才是单一的。我们可以尝试按下面去思考一个类的设计,从多个角度来考虑,如类的组成结构,以及类的规模:
遵循单一职责有不少的好处:
由于职责划分无量化的标准,在实际中,我们尽量根据项目需求的不同角度去划分职责。像充血模型一样,生搬硬套单一职责原则会引起类的体积膨胀。过细的职责划分,也导致类的数量膨胀,造成整个系统的复杂。单一职责关键是要看职责的范围与层次,在一定范围内的类足够封装性,引起它的变化只有一类原因。