论软件架构设计中被普遍误读误用的原则——分层
2018-01-31 12:46
507 查看
在看到一个又一个的项目、一批又一批的程序员不断掉进同一个坑里以后,我决定写此文把这个问题好好梳理总结一下,
很可能大多数人根本没有意识到这是一个问题,也就注定了不可避免的重复这样的错误。
被误解和滥用的分层原则,结局必然是API泥潭
自十多年前Spring Framework大范围流行以来,java项目的架构质量“看上去“有了巨大改善——组件化、分层架构、依赖注入、面向接口编程 这些优秀的实践实施起来 变得比过去容易很多也自然很多。
于是出现了一场“分层运动”,程序员们一窝蜂式的把代码分了层、层与层之间用interface隔离用Spring把组件装配成一个轻量级架构,
然后就宣称设计出了一个架构优良的系统——面向对象、松耦合、实现灵活可替换。。。
但是,事情并没有这么简单——OO强调的“高内聚 低耦合”,大家只记住了后半句——低耦合,高内聚的原则完全被违背了,或者说根本就没有被理解。
在这场一窝蜂的“分层解耦运动”中,很多没有经验的程序员从一个极端(上帝类 不分层 硬编码 硬连接 意大利面架构)走到了另一个极端(过度设计 过度分层 为了用接口而用接口);
最显著特征就是API泛滥,这是过度分层的必然结果,因为层与层之间不存在继承关系(因此protected和private不管用),只能是上层调用下层组件暴露的public方法——API,于是项目中很快就开始充斥大量啰嗦、雷同、含义模糊的public方法。
合理的分层架构 应该呈现倒金字塔的形状——越接近顶层(前端展现)组件的数量越随项目规模线性增长,因此数量也越多;越往底层 组件数量会越精简——经过项目初期的增长后,后面就基本稳定不再增长;
所以,当你发现系统有两个相邻的层 组件数量和API数量基本相当,那说明这两个层可以合并,因为其中必然有一层很单薄 只做了传声筒;
传声筒不仅造成巨大的浪费(徒增了一层代码、大量重复的代码),而且这样的设计会不断的给开发者带来困惑——一个新的功能到底应该在哪层实现,最终必然会出现不一致的选择和变成风格——于是每一层都被放置了一部分逻辑——于是破坏了内聚性——一个level的逻辑散落在了多处!这个问题看似没什么大不了的,但是熟知“破窗效应”的人马上就会意识到,这个设计从一开始就制造了很多broken window,并且在鼓励后续开发维护人不断制造新的破窗。。。不夸张的说,无数项目就是死在这个陷阱里。
高内聚和低耦合是OO的基本原则,说白了就是进行合理的抽象设计,将一个level的逻辑放在一处实现 不同level的逻辑分开放置;
分层只是体力劳动,真正重要的是前面的“合理抽象设计”这个技术活儿。
很多程序员用着spring 用着大量接口 用着分层设计,最终干的还是面向过程编程,这样做 甚至还不如不分层——不分层我维护起来还更方便些——在一个类能看到所有实现细节。
解决方案:减少分层,在层内进行面向对象的分层抽象设计
为了避免API泛滥的泥潭,我们需要退一步,首先要避免过度分层,但是这并不是要大家退回到一个上帝class搞定一个业务模块的年代。
我的解决方案 简单归纳就是:
1 在controller展现控制层 和db之间 只保留一个service层,消灭dao层,service直接依赖通用的dao utils,因为dao utils不随着业务线性增长 因此不算一个层,只能算lib,就好比你不会把string utils看作一个层。
2 在这个丰满的service层内,通过合理使用design patterns进行分层抽象设计 开发出高内聚低耦合的业务逻辑组件。
听起来有点绕——分层抽象跟分层 有什么不同,看下三段代码对比你就明白了,实现1是上帝类搞定一切,实现2是常见的service和dao分层架构,实现3应用template method设计模式以分层继承方式组织代码。
如上三个实现的质量优劣应该是显而易见的,实现3具有更高的扩展性 复用性,对熟悉设计模式的人来说 也具有最好的代码可读性,
因此最终获得了最好的可维护性 最低的修改成本。
分层抽象 本质上是将不变或很少变的逻辑封装在顶层基类,将易变多变的逻辑(实现细节)下放到具体子类,因此对实现细节的改动变得容易很多。
不变的部分封装在基类 平时无需关注 不会浪费维护人精力。
分层抽象能获得高内聚性低耦合——不同level的逻辑可以聚在一处,尤其是对于具体子类 ,所有实现细节聚在一个子类中 ,同时又被不同的override重载函数优雅的划分开来,既有高内聚的方便又有低耦合的灵活性。
所谓开闭原则——对修改关闭 对扩展开放,说的就是这个模式,基类final方法封装核心逻辑对修改关闭,abstract protected方法便于子类扩展具体实现 。
最后,父子类之间通过protected方法交互 彻底杜绝了public方法的泛滥——API泥潭。
OO的好处再怎么强调也不过分,但是也实在没有必要在2018年再去鼓吹了,OO面向对象本就应该是所有JAVA程序员的本本能!
很可能大多数人根本没有意识到这是一个问题,也就注定了不可避免的重复这样的错误。
被误解和滥用的分层原则,结局必然是API泥潭
自十多年前Spring Framework大范围流行以来,java项目的架构质量“看上去“有了巨大改善——组件化、分层架构、依赖注入、面向接口编程 这些优秀的实践实施起来 变得比过去容易很多也自然很多。
于是出现了一场“分层运动”,程序员们一窝蜂式的把代码分了层、层与层之间用interface隔离用Spring把组件装配成一个轻量级架构,
然后就宣称设计出了一个架构优良的系统——面向对象、松耦合、实现灵活可替换。。。
但是,事情并没有这么简单——OO强调的“高内聚 低耦合”,大家只记住了后半句——低耦合,高内聚的原则完全被违背了,或者说根本就没有被理解。
在这场一窝蜂的“分层解耦运动”中,很多没有经验的程序员从一个极端(上帝类 不分层 硬编码 硬连接 意大利面架构)走到了另一个极端(过度设计 过度分层 为了用接口而用接口);
最显著特征就是API泛滥,这是过度分层的必然结果,因为层与层之间不存在继承关系(因此protected和private不管用),只能是上层调用下层组件暴露的public方法——API,于是项目中很快就开始充斥大量啰嗦、雷同、含义模糊的public方法。
合理的分层架构 应该呈现倒金字塔的形状——越接近顶层(前端展现)组件的数量越随项目规模线性增长,因此数量也越多;越往底层 组件数量会越精简——经过项目初期的增长后,后面就基本稳定不再增长;
所以,当你发现系统有两个相邻的层 组件数量和API数量基本相当,那说明这两个层可以合并,因为其中必然有一层很单薄 只做了传声筒;
传声筒不仅造成巨大的浪费(徒增了一层代码、大量重复的代码),而且这样的设计会不断的给开发者带来困惑——一个新的功能到底应该在哪层实现,最终必然会出现不一致的选择和变成风格——于是每一层都被放置了一部分逻辑——于是破坏了内聚性——一个level的逻辑散落在了多处!这个问题看似没什么大不了的,但是熟知“破窗效应”的人马上就会意识到,这个设计从一开始就制造了很多broken window,并且在鼓励后续开发维护人不断制造新的破窗。。。不夸张的说,无数项目就是死在这个陷阱里。
高内聚和低耦合是OO的基本原则,说白了就是进行合理的抽象设计,将一个level的逻辑放在一处实现 不同level的逻辑分开放置;
分层只是体力劳动,真正重要的是前面的“合理抽象设计”这个技术活儿。
很多程序员用着spring 用着大量接口 用着分层设计,最终干的还是面向过程编程,这样做 甚至还不如不分层——不分层我维护起来还更方便些——在一个类能看到所有实现细节。
解决方案:减少分层,在层内进行面向对象的分层抽象设计
为了避免API泛滥的泥潭,我们需要退一步,首先要避免过度分层,但是这并不是要大家退回到一个上帝class搞定一个业务模块的年代。
我的解决方案 简单归纳就是:
1 在controller展现控制层 和db之间 只保留一个service层,消灭dao层,service直接依赖通用的dao utils,因为dao utils不随着业务线性增长 因此不算一个层,只能算lib,就好比你不会把string utils看作一个层。
2 在这个丰满的service层内,通过合理使用design patterns进行分层抽象设计 开发出高内聚低耦合的业务逻辑组件。
听起来有点绕——分层抽象跟分层 有什么不同,看下三段代码对比你就明白了,实现1是上帝类搞定一切,实现2是常见的service和dao分层架构,实现3应用template method设计模式以分层继承方式组织代码。
/** 上帝类 模块化编程,毕业设计水平 */ public class ServiceA{ public void funA(){ .....doSth this.funA1(); .....doSth this.funA2(); .....doSth } private void funA1(){.....doSth} private void funA2(){.....doSth} private void funA3(){.....doSth} }
/** 分层架构,入门程序员水平 */ @Service public class ServiceA{ @Rersource ServiceB serviceB; @Resource RepositoryC repoC; public void funA () { repoC.funA1(); //.....doSth repoC.funA2();//.....doSth serviceB.funA3();//.....doSth } }
/** 面向对象设计,专业程序员水平 */ public abstract class AbstractServiceA{ public final void funA () { .....doSth this.funA1(); .....doSth this.funA2(); this.doSth(); } abstract protected void funA1(); abstract protected void funA2(); /** 默认实现 可被override */ protected void funA3(){...} protected final void doSth(){...} } public class ServiceA extends AbstractServiceA{ protected void funA1(){} protected void funA2(){} }
如上三个实现的质量优劣应该是显而易见的,实现3具有更高的扩展性 复用性,对熟悉设计模式的人来说 也具有最好的代码可读性,
因此最终获得了最好的可维护性 最低的修改成本。
分层抽象 本质上是将不变或很少变的逻辑封装在顶层基类,将易变多变的逻辑(实现细节)下放到具体子类,因此对实现细节的改动变得容易很多。
不变的部分封装在基类 平时无需关注 不会浪费维护人精力。
分层抽象能获得高内聚性低耦合——不同level的逻辑可以聚在一处,尤其是对于具体子类 ,所有实现细节聚在一个子类中 ,同时又被不同的override重载函数优雅的划分开来,既有高内聚的方便又有低耦合的灵活性。
所谓开闭原则——对修改关闭 对扩展开放,说的就是这个模式,基类final方法封装核心逻辑对修改关闭,abstract protected方法便于子类扩展具体实现 。
最后,父子类之间通过protected方法交互 彻底杜绝了public方法的泛滥——API泥潭。
OO的好处再怎么强调也不过分,但是也实在没有必要在2018年再去鼓吹了,OO面向对象本就应该是所有JAVA程序员的本本能!
相关文章推荐
- 软件架构设计原则和模式(上):分层架构设计
- 架构 理论 设计原则 软件 总结
- 软件架构——设计原则
- 软件分层架构下的另类设计框架-工厂模式的使用
- 软件架构——设计原则
- 软件的架构与设计模式:层次原则
- SoC嵌入式软件架构设计之三:代码分块(Bank)设计原则
- 软件架构设计原则和大数据平台架构层
- SoC嵌入式软件架构设计之三 :代码分块(Bank)设计原则
- 嵌入式软件架构设计之分层设计
- 软件的架构与设计模式之层次原则
- 软件架构设计的六大原则
- 软件架构设计的六大原则
- 软件架构——设计原则
- 软件架构设计的5步原则-2008-01-28
- 软件架构——设计原则
- 软件架构设计原则
- 软件架构设计之分层架构(三层架构)
- 软件的架构与设计模式之层次原则