内容简介:类(实例化产生对象)是面向对象编程中最基本的组成单元,将逻辑和数据封装其中,以提高软件的重用性、灵活性和扩展性等。它相比人类社会组成,系统/子系统、组件/(微)服务、模块/包这些相当于社会中不同层次的实体或虚拟的组织机构;而类则相当于一类自然人,一个对象相当一个自然人。一个类在系统中承担着一种的 ”角色“ ,从事一种职业。大多数人只从事一种职业,也就是单一职责原则。同理若一个类只关注的就是自身职责的完成,也就是单一职责原则。面向对象设计的五个基本原则(SOLID),排在第一就是单一职责原则(SRP:Sing
理解类
类(实例化产生对象)是面向对象编程中最基本的组成单元,将逻辑和数据封装其中,以提高软件的重用性、灵活性和扩展性等。它相比人类社会组成,系统/子系统、组件/(微)服务、模块/包这些相当于社会中不同层次的实体或虚拟的组织机构;而类则相当于一类自然人,一个对象相当一个自然人。一个类在系统中承担着一种的 ”角色“ ,从事一种职业。
单一职责
大多数人只从事一种职业,也就是单一职责原则。同理若一个类只关注的就是自身职责的完成,也就是单一职责原则。
面向对象设计的五个基本原则(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行左右
遵循单一职责有不少的好处:
- 可以降低类的复杂度:一个类只负责一项职责,其逻辑肯定要比负责多项职责简单。
- 提高代码的可读性:类内中复杂度降低了,容易理解,也提升了整个系统的可维护性。
- 降低变更产生的风险:变更是必然,单一职责遵守好,修改一个功能时,可以对其它功能无影响。
结语
由于职责划分无量化的标准,在实际中,我们尽量根据项目需求的不同角度去划分职责。像充血模型一样,生搬硬套单一职责原则会引起类的体积膨胀。过细的职责划分,也导致类的数量膨胀,造成整个系统的复杂。单一职责关键是要看职责的范围与层次,在一定范围内的类足够封装性,引起它的变化只有一类原因。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Programming Collective Intelligence
Toby Segaran / O'Reilly Media / 2007-8-26 / USD 39.99
Want to tap the power behind search rankings, product recommendations, social bookmarking, and online matchmaking? This fascinating book demonstrates how you can build Web 2.0 applications to mine the......一起来看看 《Programming Collective Intelligence》 这本书的介绍吧!