最近在实践微服务化过程中,对其“单一职责”原则深有体会。那么只有微服务化才可以单一职责,才可以解耦吗?答案是否定的。

单一职责原则是这样定义的:单一的功能,并且完全封装起来。

我们做后端Java开发的,应该最熟悉的就是标准的3层架构了,尤其是使用Spring.io体系的:Controller、Service、Dao/Repository。为什么要分层?就是为了保证单一职责,数据模型的事情交给Controller,业务逻辑的事情交给Service,和数据打交道的事情就交给Dao/Repository。有时候或者有些人会分层分的更多,4层,5层,我自己也这样干过,说白了也是为了保证单一职责,3层不能满足单一职责了,耦合度高了,就分。

我们都知道一个webapp在经过一定时间的开发后,就惨不忍睹,即便是有标准的分层,页面或模板文件一大堆,最初的很清晰的3层标准架构也变味了,Controller,Service,Dao/Repository各层之间、Service之间、Dao/Repository之间互相调用,一团乱麻。这个时候没改一行代码都有可能一个老鼠害了一锅汤,bug就如同蚂蚁洞。

这些问题最后就造成:

  • 可扩展性灵活性差,出现性能问题
  • 业务变更和开发困难,维护成本很高,交付时间长
  • 回归测试量很大
  • ...

为了解决这些问题,就需要时时刻刻清楚的记住“单一职责”,单一职责可以用到软件开发的任何地方。

应该说职责分离来解耦是最常用最有效的架构方法,这能够很大限度的简化一切。

下面就从软件开发、设计、架构,以及重构/演进/进化,从小到大几个方面来说说单一职责

 

类方法/函数

这应该是最小的能体现单一职责的程序单元了。最熟悉的最典型的莫过于Helper/Utils类方法了,但这种类方法的特征很明显,也很容易遵循单一职责,99%以上的开发人员都可以做到。但不仅仅这样的类方法要遵循单一职责原则,每一个类方法都应该遵循单一职责原则,尤其是一些处理业务逻辑的类方法更要遵循单一职责原则,处理业务的类方法通常要配合类的单一职责原则进行,下节中讨论。

因此,这也是为什么很多Team Leader要求类方法代码行数保持在20行左右,其实就是为了保证单一职责,20行左右是一个经验粗略数字,当然,10行或者30行来完成类方法也是可以的。大部分单一职责的类方法用20行左右的代码就够了,如果超过20行就要考虑是否保证了单一职责了。那我们在迭代重构的过程中就要考虑拆分这样的类方法来保证单一职责。

类方法的单一职责是最单纯的,很具体的,不掺杂任何额外信息,只关心输入、输出、和职责;一定要明确地定义类方法的职责,保证在迭代中不被错误的扩展,不被调用者错误地使用。

类/函数文件

要用面向对象的设计方法,单一职责原则来定义类。开发人员一定要很好地理解“单一职责原则”,具有面向对象的抽象思维能力。

当在迭代中一个类过于庞大或者快速膨胀,说明已经有坏味道了,这时候就需要考虑用单一职责原则或者面向对象的分析方法来重构和重新定义类了,通常就是要抽象和拆分类,否则将来会变成一个方法容器。

把类比作一个人,她的职责就是完成自己职责范围内的事情,如果她什么事情都管,就叫多管闲事,可以想象她多管闲事的后果,会搅得鸡犬不宁。同样,类也是,类如果多管闲事,那会搅得整个应用不稳定,漏洞百出,还很难修复。所以说定义一个类,要明确这个类的职责。使用面向对象的分析和设计方法,能很好地准确定义一个类的职责范围,通常会用到封装、继承、多态和抽象等设计方法。

包结构/文件夹

分层就是最常用的架构方法之一,分层具体体现在分包和分类,就是分门别类的意思。俗话说,物以类聚,人以群分。

包结构在单一职责原则上是类的补充,职责范围进一步扩大。如果把一个类叫做一个人,那么包就是一个最小单位的团队,职责就是负责一类特定事情。
如何分包呢?那就要用到分类学的知识了,要以什么特征来分,可能不仅仅只有一种特征,比如,先用公司域名来做基础包名,这里叫一级包名;然后再用一个特定的有意义的标识作为二级子包名;之后按分层(web,dao,service等等)方法做三级包名,也可以先按照业务再按分层。例如:

域名:tietang.wang
有个项目叫:social
那么我可以这样分:
wang.tietang
    - social
        - web
        - service
        - dao
        - commons

也可以这样:

wang.tietang
    - commons
    - user
        - web
        - service
        - dao
    - relation
        - web
        - service
        - dao           

多工程/module

通常以多maven module或者gradle 多module形式存在,来保证单一职责。

当业务量还没有达到服务拆分的火候,通常在一个APP发展的太庞大时或者在工程建设初期时,需要规范和整理项目结构。这个时候需要多工程从文件系统上隔离,通过module依赖来集成。需要注意的是这样的架构或拆分不是随意的,要以单一职责原则来拆分,更具体一点就是要根据业务、技术框架功能等特性来拆分。

比如,按技术组件拆分,通常会有一些技术组件,可以把她放到commons module,如果有多种类型的技术组件,就拆分为commons module的子module;也可以直接将这些技术组件拆分为独立的工程,存在于独立的git/svn仓库,独立管理,专人负责,其他哪些module需要就依赖她。那拆分的这些技术组件的每一个应该遵循单一职责原则,例如数据分片的框架、NIO基础网络框架等等。

比如,按业务拆分,例如有用户、订单、商品、支付,那么就按照这些业务拆分为子module,每一个子module就只负责自己的业务逻辑,也遵循单一职责。

那每个module的职责范围又比类和包更大,这个时候职责也更模糊,有时候很难把握,对于技术组件可能相对清晰,而业务module就要熟悉业务,明确业务边界。

多module拆分后也是为将来服务化埋下伏笔,同时在物理文件系统比较清晰了,那在依赖管理上也要掌握好保持清晰的依赖逻辑,把握好单一职责原则。

微服务/可部署单元

微服务,从运行时隔离,但业务量发展到一定时候,从单体或者多module工程拆分或演化出来,可独立打包可独立部署并复合单一原则的application,当然了微服务所体现的价值不仅仅是隔离和独立部署,还有很多这里可以参考单体应用与微服务优缺点辨析。单一职责在微服务中的价值是最重要的,包含了app层面和开发app的团队层面,微服务的大部分优点都可以围绕单一职责来展开。

团队

先引用《韩非子·扬权》中的一段文字:

夫物者有所宜,材者有所施,各处其宜,故上下无为。
使鸡司夜,令狸执鼠,皆用其能,上乃无事。
上有所长,事乃不方。
矜而好能,下之所欺:辩惠好生,下因其材。
上下易用,国故不治。

各得其所,各司其职。所以,团队也要遵循单一职责原则,这样才能很好地管理团队成员的时间,提高效率。一个人专注做一件事情的效率远高于同时关注多件事情。同样一个人一直管理和维护同一份代码要比多人同时维护多份代码的效率高很多。每一个人都有自己的个性,他有自己的擅长,让每一个人专注自己擅长的事情,那肯定事半功倍,整个团队绩效肯定也很突出。

总之,引用古文名句说明了所有:

  • 物以类聚,人以群分。
  • 天下之事,分合交替,分久必合,合久必分!
  • 使鸡司夜,令狸执鼠,皆用其能,上乃无事。

参考

软件开发中的单一职责(转至INFOQ)的更多相关文章

  1. Atitit. 软件开发中的管理哲学--一个伟大的事业必然是过程导向为主 过程导向 vs 结果导向

    Atitit. 软件开发中的管理哲学--一个伟大的事业必然是过程导向为主    过程导向 vs 结果导向 1. 一个伟大的事业必然是过程导向为主 1 1.1. 过程的执行情况(有明确的执行手册及标准) ...

  2. Atitit 软件开发中 瓦哈比派的核心含义以及修行方法以及对我们生活与工作中的指导意义

    Atitit 软件开发中 瓦哈比派的核心含义以及修行方法以及对我们生活与工作中的指导意义 首先我们指明,任何一种行动以及教派修行方法都有他的多元化,只看到某一方面,就不能很好的评估利弊,适不适合自己使 ...

  3. 软件开发中的完整测试所包括的环节UT、IT、ST、UAT

    软件开发中的完成测试环境所包括的环节包括:UT.IT.ST.UAT UT = Unit Test 单元测试 IT = System Integration Test 集成测试ST = System T ...

  4. 关于软件开发中兼容win7注册表的解决方案

    关于软件开发中兼容win7注册表的解决方案   编写人:CC阿爸 2014-3-14 l  近来在开发一winform程序时,发现在xp 系统访问注册表一切正常.可偏这个时候,微软又提醒大家.Xp今年 ...

  5. 软件开发中 SQL SERVER 任务的用法

    在软件开发中,经常性会用到定时任务.这个时候你可能会想到线程.但是事实中,线程方法比较麻烦.容易出错,资源竞争等问题,设计起来让你很头痛. 现在给大家提供一个新的思路,用SQL SERVER 的任务管 ...

  6. UML在软件开发中各个阶段的作用和意义

    经典的软件工程思想将软件开发分成5个阶段:需求分析,系统分析与设计,系统实现,测试及维护五个阶段. 之所以如此,是因为软件开发中饣含了物和人的因素,存在着很大的不确定性,这使得软件工程不可能像理想的, ...

  7. Java软件开发中迭代的含义

    软件开发中,各个开发阶段不是顺序执行的,而各个阶段都进行迭代并行执行的,然后在进入下一个阶段的开发. 这样对于开发中的需求变化,及人员变动都能得到更好的适应. 软件开发过程汇总迭代模型如下图所示:

  8. 软件开发中oracle查询常用方法总结

    上次新霸哥和大家讲解了一些关于oracle的知识发现大家对oracle还是比较感兴趣的,下面新霸哥就大家比较关系的oracle中常用的查询有哪几种?做个和oracle相关的开发的朋友可能会知道答案,但 ...

  9. 最简单直接地理解Java软件设计原则之单一职责原则

    理论性知识 定义 单一职责原则, Single responsibility principle (SRP): 一个类,接口,方法只负责一项职责: 不要存在多余一个导致类变更的原因: 优点 降低类的复 ...

随机推荐

  1. Hex string convert to integer with stringstream

    #include <sstream>#include <iostream>int main() { unsigned int x; std::stringstream ss; ...

  2. Objective-C 协议(protocol)

    协议(protocol)是Objective-c中一个非常重要的语言特性,从概念上讲,非常类似于JAVA中接口. 一个协议其实就是一系列有关联的方法的集合(为方便后面叙述,我们把这个协议命名为myPr ...

  3. loadrunner java协议脚本要点

    常见问题 1. Error: Thread Context: Call to service of the driver failed, reason - thread context wasn't ...

  4. VS2013与MySql建立连接;您的项目引用了最新实体框架;但是,找不到数据链接所需的与版本兼容的实体框架数据库 EF6使用Mysql的技巧

    因为之前都是看别人的项目,而没有自己从头到尾建立一个项目,所以这次尝试搭建时就出现了问题,主要是ASP.Net MVC项目中VS2013和MySql的连接. 第一个问题: 数据库表已建好,相应的数据库 ...

  5. [扫描线]POJ2932 Coneology

    题意:有n个圆 依次给了半径和圆心坐标  保证输入的圆不相交(只有 相离 和 内含/外含 的情况)   问 有几个圆 不内含在其他圆中,并分别列出这几个圆的编号(1~n) (n的范围是[1, 4000 ...

  6. QT插件开发方式(作者有RemOjbects文档翻译(48)篇)

    创建一个QT的库项目,删除自动生成的.h和.cpp文件,添加一个接口定义.h文件和一个接口实现类(一个.h一个.cpp).代码如下: 1.接口文件源码 #ifndef PLUGININTERFACE_ ...

  7. POJ1942——Paths on a Grid(组合数学)

    Paths on a Grid DescriptionImagine you are attending your math lesson at school. Once again, you are ...

  8. System.Drawing.Graphics中比较重要的几个方法

    方法 常见参数 绘制的图形 DrawLine 钢笔.起点和终点 一段直线 DrawRectangle 钢笔.位置和大小 空心矩形 DrawEllipse 钢笔.位置和大小 空心椭圆 FillRecta ...

  9. java的Serialization 机制

    基本使用方法               Serialization是指把类或者基本的数据类型持久化(persistence)到数据流(Stream)中,包括文件.字节流.网络数据流.         ...

  10. unity3d中获得物体的尺寸(size)

    1:获得诸如Plane.Cube的size.    1):可以为它们添加Collider,然后使用XXX.collider.bounds.size;该方法获得的size和缩放比例有关,是一一对应的,缩 ...