转载

类的职责单一

理解类

类(实例化产生对象)是面向对象编程中最基本的组成单元,将逻辑和数据封装其中,以提高软件的重用性、灵活性和扩展性等。它相比人类社会组成,系统/子系统、组件/(微)服务、模块/包这些相当于社会中不同层次的实体或虚拟的组织机构;而类则相当于一类自然人,一个对象相当一个自然人。一个类在系统中承担着一种的 ”角色“ ,从事一种职业。

单一职责

大多数人只从事一种职业,也就是单一职责原则。同理若一个类只关注的就是自身职责的完成,也就是单一职责原则。

面向对象设计的五个基本原则(SOLID),排在第一就是单一职责原则(SRP:Single responsibility principle)。SRP的原话解释是:There should never be more than one reason for a class to change。应该有且仅有一个原因引起类的变更。

单一职责原则是指导”高内聚,低耦合“的基本原则,但也是最难实施的原则。

类的构建

类是什么,两个角度来看:

  • 组成派:是对一类事物的抽象,由成员属性和成员方法组成的数据结构,强调封装
  • 职责派:是为达成一种目标一组能力的集合,承担它所代表的抽象的职责,强调行为

上述两个角度,可能是导致两种不同的类代码结构排版的原因?

  • 先属性再是方法,莫非是组成派,首先考虑是类是由哪些元素(属性)组成,再是具备哪些行为操作(方法)
  • 先方法再是属性,莫非是职责派,首先考虑是类需要承担什么职责(方法),再是完成这些方法的功能需要哪些资源(属性)

当然也可能是我的无稽之谈,由于个人主要使用Java系语言,建议类按如下顺序组成,这只是一种编码风格:

  • 类的静态常量
  • 类的静态变量
  • 类的成员变量
  • 类的构建方法
  • 类的成员方法(public->protect->private)

类的模型

以前看过 Martin Fowler 写的一篇文章叫”贫血模型”,批判贫血领域模型不够优雅、不够面向对象,提倡使用充血领域模型。若此观点应用在普通的类设计上非常有争议,至少在面向对象的语言体系中,这两种模型都存在,适用不同的场景。

贫血模型

贫血模型是指对象只有属性(getter/setter),或者包含少量的CRUD方法,而业务逻辑都不包含在其中,而是放在单独的业务处理逻辑层。JavaBean就是最为典型的代表,像Scala,Kotin在语言层次都存在数据类的概念,用于只描述数据的构成。

该模型的确是不够面向对象,对象只是作为保存状态(如数据层的表映射)或者传递状态(如方法中的出入参数)使用,所以就说只有数据没有行为的对象不是真正的对象。

在Java体系中,非常流程的就是这种设计,接口门面层(Controller)-> 业务逻辑处理层(Service)-> 数据访问层(ORM)。

充血模型

充血模型是指对象里即有数据和状态,也有行为,行为负责维持本身的数据和状态,具有内聚性,最符合面向对象的设计,满足单一职责原则。这也是我们是为常见的对象设计方式。

Martin Fowler 主张这种模型,他是从领域驱动开发(DDD)中领域模型对象来分析的,领域模型(Domain Model)是一个商业建模范畴。从一个模型的封装性来说,即有状态又有行为是合理的,但领域模型并非直接映射为单一类对象,它要比类的模型大很多,可能是由一组类聚合而成。

遵循充血模型的规范,出发点非常好,但对开发人员要求非高,随着变化与演进,最后可能一个类充满了乱七八糟的内容,反而忘记初心,违背单一原则。

类的坏味道

inFusion是一款非常不错的软件设计度量工具,它能帮助我们发现代码上坏味道。借助它分析,也讲讲inFusion中提到了哪些类的坏味道,他们违反了单一原则。

God Class

上帝类通常为过多地操作其它类的数据,从而破坏了类的封装类,上帝类从其它类中获得功能,却增加了自身的耦合性,通常会导致自己体积过大和较大的复杂度。

导致出现上帝类一般是出现在业务逻辑层,没有对逻辑层合理的分层。此类有点像八爪鱼,手上攥了东西太多,聚合太多其它对象在一个对象中直接组合所有逻辑;另一个原因是被引用的对象的封装性不好,不够内聚,暴露太多数据需要其它类来完成它自身的职责。

Blob Class

复杂类,它具有体积大(通常超过千行),高度复杂的特征。

导致出现复杂类,除了类中的方法存在行数过大的原因之外。另一个原因是一个类最早只有简单的CRUD方法,每个方法复杂度不高。后面随着需求的增加,一种场景是方法实现的场景分支越来越多,导致方法复杂度变高;另一个场景是方法个数增加太多,如Query方法,一开始只有Query1,后面不断增加Query2,Query3…以满足不同的查询条件以及响应内容等等。

Schizophrenic Class

紊乱类,一个类本应该一种抽象,完成一类责任。而该类确完成完成两种或以上的抽象,会影响类的理解和修改。特点是定义大量的接口方法,以及被不同的Client使用。

导致出现复杂类,一般出现在界面类(如Controller)、工具类(Util)中,即提供太多公共方法,又同时处理相应的业务逻辑。

怎么做

再回到单一职责,结合上面怎么进一步理解它,关键是职责的划分,但也是难点。

职责的划分是一定的范围与层次,比如关注的是华为手机和其它东西的区别,那华为手机就是一个整体,就是用来实现手机的功能,不是用来切水果的,所以切水果的方法,不应该实现在华为手机中。当要关注华为手机的内部结构时,那它的模块肯定是隔离的,显示屏只关注显示,通信模块只关注通信,其实每个模块都是单一职责,最终聚合在一起,就是一个手机。

在实际操作中非常难,正如上面”职责“是一个相对的概念,没有一个明确的划分原则,什么才是单一的。我们可以尝试按下面去思考一个类的设计,从多个角度来考虑,如类的组成结构,以及类的规模:

  • 类自己的数据与状态的变化可能尽可能地能控制在类内部
  • 变化的来源只有一类原因,原因导致变化也是围绕完成同一层次的一件事
  • 类的每个方法逻辑处理足够简单,整个类的逻辑才会简单
  • 类的方法数量不宜过多,个人觉得是少于15个,整个类的代码行数少于1000行左右

遵循单一职责有不少的好处:

  • 可以降低类的复杂度:一个类只负责一项职责,其逻辑肯定要比负责多项职责简单。
  • 提高代码的可读性:类内中复杂度降低了,容易理解,也提升了整个系统的可维护性。
  • 降低变更产生的风险:变更是必然,单一职责遵守好,修改一个功能时,可以对其它功能无影响。

结语

由于职责划分无量化的标准,在实际中,我们尽量根据项目需求的不同角度去划分职责。像充血模型一样,生搬硬套单一职责原则会引起类的体积膨胀。过细的职责划分,也导致类的数量膨胀,造成整个系统的复杂。单一职责关键是要看职责的范围与层次,在一定范围内的类足够封装性,引起它的变化只有一类原因。

原文  http://lanlingzi.cn/post/thoughts/2019/0526_class/
正文到此结束
Loading...