在进行程序开发和设计时我们常常提到分层的概念,但是怎么样的分层才是好的分层呢,在这篇我谈谈我在如何分层这个问题上的一些体会,和大家探讨一下
为什么要分层
系统要分层主要我觉得主要是有两个原因:
- 存在着不同的利益相关者,有着不同的关注点
- 软件中的不同的部分,有不同的变化频率
上图是一个常用的三层架构,其中,用户界面层面向用户,变化频率高;业务界面层面向业务,变化频率相对较低;数据库访问层面向基础设施,变化频率低。这样的分层带来有效地隔离了业务逻辑与数据访问逻辑,使得这两个不同关注点能够相对自由和独立地演化
怎样才是好的分层设计
分层能降低软件设计和开发复杂度,但是不恰当的分层却会导致软件的复杂度的提升,那不好的分层有一个基本的特征就是:相邻的分层间在功能上很相似,增加的层次没有系统带来明显的能力,常常表现为方法透传,及新增加的层次仅仅做了很少的事情,及将相关请求发送到了下层去进行处理,如下面的代码所示:
public class AnimalServiceImpl implements AnimalService {
private static Logger logger = LoggerFactory.getLogger(AnimalServiceImpl.class);
@Inject
private AnimalDao animalDao;
@Override
public Animal queryById(long id) {
return animalDao.query();
}
@Override
public boolean update(Animal animal) {
return animalDao.update(animal);
}
@Override
public long insert(Animal animal) {
return animalDao.insert(animal);
}
........
}
这种分层方式在我们的日常编码中经常遇到,服务层仅仅是将用户界面的调用传递给数据库访问层,仅仅是方法和参数的透传,这种透传带来的最大的问题就是这种分层没有为软件带来任何的价值,如果这个层次中都充斥着这样的代码,这个层次与其相邻的层次很相似,一般会把这个分层认为是一种架构的坏味道
如何做到好的分层设计
说到分层,我们一般都会提到iso网络的七层模型,如下图:从上图中大家可以看到这七层模型中,每一层都其特定的功能,可以说是功能和其提供的价值都是极为明确的,所以如何能做好分层,就是要使每分离出来的层次都有明确的功能和价值
做好分层需要避免方法的透传
方法的透传在大多数场景下,都有一种没有增加价值,反而增加了坏复杂性的坏味道,除了向dispatch这种分发的场景,往往我们可以通过如下方法来解决方法的透传调用:
- 直接调用方法的最终提供者,这个有时会破坏层次之间的划分,需要综合考虑
- 将涉及的函数功能做重新的分布,对此功能所处的层次就行重新的考虑
-
进行层次的合并,考虑是否层次的划分出现的问题
在java中,I/O 处理的这套的架构体系遭到的批判是很多的,在I/O 处理中,java使用的装饰模式,实现了方法的透传调用,虽然在每个装饰层次中也提供了一些能力,如:buffer,但是提供的功能相对较少,并且在IO中,提供buffer的能力应该是绝大部分场景都需要的,所以我们对其进行功能的合并,而不是单独的将其功能实现在不同的层次中,增加了整个系统的复杂性
做好分层需要避免调用参数的透传
调用参数在各个层次的透传,会导致一些层次的接口需要了解不是其层次应该处理的知识,这样就会导致增加系统不必要的复杂性,一般来说们可以通过如下方法来解决函数的透传:
- 使用全局变量,这样每个层次都可以访问到这些参数,避免了参数在各个层次的透传
- 使用context来传递参数,context通过参数在各个层次中传递,每个层次只需要访问自己所需要的,不会引入不必要的复杂性
总结
做好分层,我们需要思考各个层次的功能和价值,就像 Robert C.Martin提出的尖叫架构一样,我们也需要尖叫的分层
注:相关图片来自于互联网