如何理解设计模式

不论是编码还是架构设计都逃不过设计模式, 因为合理使用一些设计模式可以怎么我们地图的扩展性,可维护性等,另外也简化了设计实现难度,有大量文章或代码可参考。然而很多开发发费了很多时间与经历去学习记忆GoF 23种设计模式, 但很少人在自己的实际项目中去应用这些设计模式,推脱的理由能找到很多,但大部分根本理由多设计模式只是学而没真正理解。 这部分人在学习设计模式的目的是不正确的, 常见有以下几种:

  • 考试或面试需要。 很多技术考试或面试会问到,需要应付考试或面试。
  • 增加知识面。 设计模式当知识点学习。
  • 跟风或被迫学习。 很多公司技术组有专题学习。

因为学习目的本就不是用的, 自然在实际编码过程中要嘛“忘了” 可以用,或“没想到” 能用,相信这种情况很多技术Leader在代码review是司空见惯的。 另外因为学习的目的与设计模式提出的目的不一致, 学习过程也是很痛苦的,需要记忆那么多类结构图。那么我们应该带什么样目的去学习设计模式呢?根据上文也可以猜出,个人认为最好目的是“用”,而要用好的前提是理解,知道什么时候适合用,什么时候不适合, 适合的是哪种或哪几种模式, 是否改变形或是否多模式组合使用等等。哪我们怎么去理解呢? 我觉得有两个方面: 什么是设计模式和设计模式起作用的原理是什么。

什么是设计模式?

都知道设计模式并不是软件开发工程专有的,各行特别是工程领域都有自己的设计模式。具体软件工程领域什么时候应用设计模式,不可知,但20世纪70年代GoF受建筑领域设计模式影响, 通过总结之前软件里的经验,写的一本著名的参考书《设计模式:可复用面向对象软件的基础》,极大的推动了设计模式被大家熟知并应用。GoF将设计模式定义为: 对被用来在特定场景下解决一般设计问题的类和相互对象的描述。我们可以适当泛化,理解为对某一类问题的通用解决方案。 明显,设计模式是来源于实践经验,是对之前最佳实践的提炼总结,是用来解决实际中特定问题的方案,而不是非常抽象的概念,是不能死记硬背,也不需要实际硬背的。如学习GoF 23种设计模式,我们应该深入理解各模式解决能解决问题是什么,了解使用该方案的好处是什么,而不是背诵概念与类图。

设计模式起作用的原理是什么?

我们在实际开发中经常遇到某些问题可以用设计模型,但是有好几种模式都可以,该如何选择;或者看着能用某种设计模式,但用了后又不能完全解决问题。这时候需要我们队设计模式原理有充分的了解,知道模式起作用的原理及代理优点和缺点,能通过灵活组合或变形模式来解决现有问题。 比如简单工厂模式,就是通过将对象创建与核心业务分离方式,降低核心业务的耦合度,是核心业务满足开闭原则,具有更好的扩展性并且实现简单,易理解等都是其优势。但也要认识到对整个系统来说其并未完全解决开闭问题, 扩展仍涉及到原有代码的修改。 如果系统对扩展有更高要求,比如需要指出第三方扩展,则应当适当扩展, 比如引入SPI。 其实,仔细分析下GoF各模式原理及其优劣,会发现没有完美的模式,只有适合的模式, 这些模式只是前人帮我们总结好的问题解决方案之一,不是唯一, 我们要学习不禁是这个答案,更多的是解决问题的思维方式。 相较来说,几大设计原则是更具通用性的, 我们在GoF 里的很多模式上都可以看到设计原则的体现。

总结来说, 个人认为学习设计模式的重点是理解原理, 各模式解决的问题有个印象,在遇到类似问题,能想起来有这样的模式, 能快速查找相关资料,并确定是否直接使用模式或变形使用。 对相关类图更是没有必要死记,现实工作中完全可以现查,并且往往现实业务中不能完全套用,而需在符合设计原则情况下优化调整。

如何理解高内聚、低耦合

首先需要理解高内聚、低耦合各自的意义。注意在不同的领域它们的意义是不同的。本文中主要描述的是它们在软件工程领域中的意义。

耦合:简单来讲可以理解为元素之间的依赖度。 这个依赖度往往决定于元素间进行依赖的方式。 如: 排课服务,排课时需要依赖老师的一些信息(职级,育龄等), 是通过教师服务接口依赖,还是公用库依赖,其耦合度是不一样的。

耦合越强,说明元素间关系越紧密, 元素之间越不能离开其依赖元素而存在或运行, 且替换其依赖越困难。另外耦合也是因存在依赖而存在, 所以软件工程中会说降低耦合,而不是去耦合。这里的元素可以是系统,服务、 组件, 模块,类,甚至是方法。软件工程中不同角色关注的元素不一样。如架构师主要关心系统、组件间耦合度、 初级开发主要关心类、方法的耦合。因此,耦合情况可以是评价软件工程各个角色工作质量的一个重要指标。耦合按从弱到强的顺序可分为以下几种类型

  • 非直接耦合:两模块间没有直接依赖关系,依赖完全是通过主控制模块对依赖模块调用来实现的。面向抽象编程, 能做到具体实现的变更,不影响主模块。
  • 数据耦合:一个模块访问另一模块,彼此间通过简单数据参数来交换输入、输出信息。这里的简单数据参数不同于控制参数、公共数据结构或外部变量。如Java 中方法参数为值时
  • 标记耦合:如一组模块通过参数表传递记录信息,就是标记耦合。这个记录是某一数据结构的子结构,不是简单变量。 如Java 中方法参数为类引用时。 
  • 控制耦合:一个模块通过传递开关、标志、名字等控制信息,明显的控制选择另一模块的功能。常见的有直接使用其他模块标志字段做逻辑控制。
  • 外部耦合:一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数传递该全局变量的信息。
  • 公共耦合:一组模块都访问同一个公共数据环境。该公共数据环境可以是全局数据结构、共享的通信区、内存的公共覆盖区等。 公共配置就是常见的公共耦合。 如Nacos 公共配置, 这个配置建立目的就是建设重复配置,简化各项目配置项,但当多个模块或多个服务都使用到该配置时,配置变更就需要关注各使用模块。如默认的数据库连接池配置,默认的注册中心配置等。
  • 内容耦合:一个模块直接修改另一个模块的数据,或直接转入另一个模块。最常见的就数据库表共用,如排课服务直接调用教师服务库中教师信息表来获取教师职级信息。这会导致教师服务变更需要考虑到排课服务,排课服务也可能要因为教师服务变更而变更。

在设计或编码过程,会发现要降低耦合,经常会增加实现的复杂度。如内容耦合,要降低耦合,就需要在模块A中提供符合模块B的业务能力的接口。所以在实际的编码和设计过程中,还是需要更加项目实际情况,选择适当耦合方式。

内聚: 可以简单理解为模块内各元素间是强耦合的, 其对外提供的能力是单一的,即职责单一。

单一,不是说一定是只有一个功能,也可以是一类。如: 一个排课服务,其提供排课申请,排查查询,排课变更等一类功能接口。

与耦合不同,内聚有“模块内”这个前提。内聚程度越高,说明模块内各元素关联度越高,元素间关系越强,说明模块划分的越合理,这里的模块同样可以是系统,服务、 组件, 模块,类,甚至是方法。内聚也有按程度低到高几种分类:

  • 偶然内聚: 指一个模块内的各处理元素之间没有任何联系。
  • 逻辑内聚: 指模块内执行几个逻辑上相似的功能,通过参数确定该模块完成哪一个功能。
  • 时间内聚: 把需要同时执行的动作组合在一起形成的模块为时间内聚模块。
  • 通信内聚: 指模块内所有处理元素都在同一个数据结构上操作(有时称之为信息内聚),或者指各处理使用相同的输入数据或者产生相同的输出数据。
  • 顺序内聚: 指一个模块中各个处理元素都密切相关于同一功能且必须顺序执行,前一功能元素输出就是下一功能元素的输入。
  • 功能内聚: 最强的内聚,指模块内所有元素共同完成一个功能,缺一不可。与其他模块的耦合是最弱的。

从分类可以看出,内聚是评估我们划分服务,模块,类甚至方法是否合理的重要指标。高内聚可以提升模块的鲁棒性,可靠性,可重用性,可读性等优点。

所以我们在说高内聚低耦合时,实际指的是模块间耦合性低, 但模块内的内聚度高。从软件生命周期可知,很多软件的维护期是远远高于其他周期的,而系统的维护的难易程度很大程度决定于系统的可扩展性,可重用性,及可测试性。而符合高内聚低耦合的系统,直观表现上有模块划分合理,模块本地的重用性好, 模块间耦合度低,替换难度低, 易扩展,易测试。

另外,需注意的一点从耦合、内聚定义可知,做好系统的高内聚低耦合,不是仅取决于系统架构师, 而是所有参与系统设计、研发编码的所有人员有关, 只是架构师或系统设计人员的影响会更大点而已。 那如何做好高内聚低耦合呢, 就技术本身没有固定方法,往往需要根据实际需求将高内聚低耦合作为目标,选择或组合适当的设计模式来实现。 对于开发来说,学习设计模式比较重要,但熟悉设计模式六大原则,然后根据设计原则灵活组合设计模式更重要。另外对于实际项目来说,内聚耦合不仅依赖于开发或架构技术水平, 还于项目的周期长短,可投入资源多少,项目性质有关系, 这也是为什么要求设计人员有实际项目经验的原因。

京ICP备12026520号-3