设计模式 - 漫谈软件编程背后的系统化思维

简介: 设计模式 - 漫谈软件编程背后的系统化思维

image.png

组合思维


Unix 操作系统诞生于 20 世纪 60 年代,经过几十年的发展,技术日臻成熟。在这个过程中,Unix 独特的设计哲学和美学也深深地吸引了一大批技术开发人员,他们在维护和使用 Unix 的同时,Unix 也影响了他们的思考方式和看待世界的角度。


Unix 哲学是一套基于 Unix 操作系统顶级开发者们的经验所提出的软件开发的准则和理念。


也就是说,Unix 哲学并不是正统的计算机科学理论,它的形成更多是以经验为基础。你一定听说过模块化、解耦、高内聚低耦合这些设计原则,还有类似开源软件和开源社区文化,这些最早都是起源于 Unix 哲学。可以说 Unix 哲学是过去几十年里对软件行业影响意义最深远的编程文化。


Unix 设计哲学,主张组合设计,而不是单体设计;主张使用集体智慧,而不是某个人的特殊智慧。


对编程的启示:


  • 启示一:保持简单清晰性,能提升代码质量


代码之间的相互影响越多,软件越复杂。比如,A 依赖 B,B 依赖 C……一直这样循环下去,程序就会变得非常复杂,也就是我们编程中常说的,如果一个类文件写了上万行代码,那么代码逻辑将会非常难理解。


软件复杂度一般有以下三个来源。

  • 代码库规模。

这个就与开发工具、编程语言等有关了,不过需要注意,代码行数与复杂度并不呈正相关。比如,Java 语言编写的库通常会比 C++ 的库的代码行数更多(语言特性决定),但不能说 Java 类库就一定比 C++ 的类库更复杂。


技术复杂度。


这个指的是不同的编程语言、编译器、服务器架构、操作系统等能够被开发人员理解的难易程度。比如,Netty 库,对于很多 Java 程序员来说,理解起来就有一定的难度,这就是有一定的技术复杂度。


实现复杂度。


不同的编程人员,对于需求的理解不同,在编程时就会有截然不同的编写风格,比如,前端程序员和后端程序员网页分页的代码实现风格就会明显不同。


该如何降低软件复杂度呢?


首先,在代码库规模方面,可以通过减少硬编码来控制代码量。


比如,使用设计模式中的策略模式来替换大量的 if-else 语句,使用通用工具类来减少重复的方法调用。除此之外,还可以利用语言特性来减少代码量,比如,在 Java 8 中使用 lambda 表达式来精简语句。


其次,对于技术复杂度来说,要想在整体上保持简单性,需要在设计时就做好技术选型。


换句话说,好的技术选型能够有效控制组件引入技术复杂度的风险。比如,在做系统设计时,引入像 Kafka 这样的消息中间件之前,你需要从系统吞吐量、响应时间要求、业务特性、维护成本等综合维度评估技术复杂度,如果你的系统并不需要复杂的消息中间件,那么就不要引入它,因为一旦引入后,就会面临指派人员学习与维护、出现故障后还要能及时修复等问题。


最后,就降低实现复杂度而言,可以使用统一的代码规范。


比如,使用 Google 开源项目的编码规范,里面包含了命名规范、注释格式、代码格式等要求。这样做的好处在于,能快速统一不同开发人员的编程风格,避免在维护代码时耗费时间去适应不同的代码风格。


所以,Unix 哲学中所说的保持简单性,并不单单是做到更少的代码量,更是在面对不同复杂度来源时也能始终保持简单清晰的指导原则。


  • 启示二:借鉴组合理念,有效应对多变的需求


对于任何一个开发团队来说,最怕遇见的问题莫过于:不停的需求变更导致不停的代码变更。


即便你花费了大量的时间,在项目前期做了详细的需求分析和系统的分析设计,依然不能完全阻挡需求的变化,而一旦需求发生变更,那么就意味着开发团队需要加班加点地修改代码。


事实上,Unix 在设计之初就已经遇见过这些问题,那它是怎么解决的呢?下面我们就来看一下 Unix 那些能够“任意组合”的例子。


所有的命令都可以使用管道来交互

这样,所有命令间的交互都只和 STD_IN、STD_OUT 设备相关。于是,就可以使用管道来任意地拼装不同的命令,以完成各式各样的功能。


可以任意地替换程序

比如,我喜欢 zsh,你喜欢 bash,我们可以各自替换;你喜欢 awk,我不喜欢 awk,也可以替换为 gawk。快速切换到熟悉的程序,每个程序就像一个零件一样,任意插拔。


自定义环境变量

比如,Java 编译环境有很多版本,你可能用到的有版本 8、11 和 14,通过自定义 JAVA_HOME 环境变量,你就可以快速启用不同的编译环境。


这充分说明了 Unix 哲学的组合思维:把软件设计成独立组件并能随意地组合,才能真正应对更多变化的需求。


然而,在实际工作中,你很多时候可能都只是在做“定制功能驱动”式的程序设计。比如,用户需要一个“上传文件的红色按钮”,你就实现了一个叫“红色上传按钮功能”的组件,过几天变为需要一个“上传文件的绿色按钮”时,你再修改代码满足要求……这不是组合设计,而是直接映射设计,看似用户是需要“上传”这个功能,但实际上用户隐藏了对“不同颜色”的需求。


很多时候看上去我们是一直在设计不同的程序,实际上对于真正多变的需求,我们并没有做到组合设计,只是通过不断地修改代码来掩饰烂设计罢了。


要想做到组合设计,Unix 哲学其实给我们提供了两个解决思路。


第一个是解耦


这是 Unix 哲学最核心的原则。代码与代码之间的依赖关系越多,程序就越复杂,只有将大程序拆分成小程序,才能让人容易理解它们彼此之间的关系。也就是我们常说的在设计时应尽量分离接口与实现,程序间应该耦合在某个规范与标准上,而不是耦合在具体代码实现逻辑上。


第二个是模块化


你可能已经非常熟悉这个词语了,不过模块化还有更深层的含义——可替换的一致性。什么叫可替换的一致性?比如,你想使用 Java RPC 协议,可以选择 Dubbo、gRPC 等框架,RPC 协议的本质是一样的,就是远程过程调用,但是实现的组件框架却可以不同,对于使用者来说,只要是支持 Java RPC 协议的框架就行,可随意替换,这是可替换。而不同的框架需要实现同一个功能(远程过程调用)来保持功能的一致性(Dubbo 和 gRPC 的功能是一致的),这是一致性。


实际上,这两个解决思路就是现在我们常说的高内聚、低耦合原则:模块内部尽量聚合以保持功能的一致性,模块外部尽量通过标准去耦合。


换句话说,就是提供机制而不是策略,就像上传文件那个例子里,分析时应该找出用户隐含的颜色变化的需求,并抽象出一个可以自定义颜色的功能模块,解耦上传文件模块,最后将颜色变化模块组合到上传文件模块来对外提供使用。这样当用户提出修改颜色时(修改策略),只需要修改自定义颜色模块就行了,而不是连同上传文件的机制也一起修改。


  • 启示三:重拾数据思维,重构优化程序设计


再高大上的架构设计,如果系统对数据的组织是混乱的,那么可以轻松预见随着系统的演进,系统必然会变得越来越臃肿和不可控。


Unix 哲学在出现之初便提出了“数据驱动编程”这样一个重要的编程理念。也就是说,在 Unix 的理念中,编程中重要的是数据结构,而不是算法。


当数据结构发生变化时,通常需要对应用程序代码进行修改,比如,添加新数据库字段、修改程序读写字段等。但在大多数应用程序中,代码变更并不是立即完成的。原因有如下:


对于服务端应用程序而言,可能需要执行增量升级,将新版本部署到灰度环境,检查新版本是否正常运行,然后再完成所有的节点部署;


对于客户端应用程序来说,升不升级就要看用户的心情了,有些用户可能相当长一段时间里都不会去升级软件。


这就意味着新旧版本的代码以及新旧数据格式可能会在系统中同时共存。这时,处理好数据的兼容性就变得非常重要了。如果不具备数据思维,很可能会假设数据格式的变更不会影响代码变更。


而 Unix 哲学提出的“数据驱动编程”会把代码和代码作用的数据结构分开,这样在改变程序的逻辑时,就只要编辑数据结构,而不需要修改代码了。


分层思维


软件程序通常有两个层面的需求:

  • 功能性需求,简单来说,就是一个程序能为用户做些什么,比如,文件上传、查询数据等;
  • 非功能性需求,这个是指除功能性需求以外的其他必要需求,比如,性能、安全性、容错与恢复、本地化、国际化等。

事实上,非功能性需求所构建起来的正是我们所熟知的软件架构。什么是软件架构?简单来说,就是软件的基本结构,包括三要素:代码、代码之间的关系和两者各自的属性。

如果把软件比作一座高楼,那么软件架构就是那个钢筋混凝土的框架,代码就是那个框架里的砖石,正是因为有了那个框架,才能让每一个代码都能很好地运行起来。


其中,最为经典的软件架构就是分层架构, 分层架构越是流行,我们的设计越容易僵化。这背后到底有哪些值得我们深思的地方呢?


从架构角度来聊聊为什么代码要做分层、主要用于解决什么问题,以及存在优势和劣势有哪些。


工程思维


对象思维


迭代思维


相关文章
|
18天前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###
|
3月前
|
设计模式 数据库连接 PHP
PHP编程中的面向对象与设计模式
在PHP编程世界中,掌握面向对象编程(OOP)和设计模式是提升代码质量和开发效率的关键。本文将深入浅出地介绍如何在PHP中应用OOP原则和设计模式,以及这些实践如何影响项目架构和维护性。通过实际案例,我们将探索如何利用这些概念来构建更健壮、可扩展的应用程序。
|
6天前
|
设计模式 算法 搜索推荐
Python编程中的设计模式:优雅解决复杂问题的钥匙####
本文将探讨Python编程中几种核心设计模式的应用实例与优势,不涉及具体代码示例,而是聚焦于每种模式背后的设计理念、适用场景及其如何促进代码的可维护性和扩展性。通过理解这些设计模式,开发者可以更加高效地构建软件系统,实现代码复用,提升项目质量。 ####
|
21天前
|
设计模式 监控 数据库连接
Python编程中的设计模式之美:提升代码质量与可维护性####
【10月更文挑战第21天】 一段简短而富有启发性的开头,引出文章的核心价值所在。 在编程的世界里,设计模式如同建筑师手中的蓝图,为软件的设计和实现提供了一套经过验证的解决方案。本文将深入浅出地探讨Python编程中几种常见的设计模式,通过实例展示它们如何帮助我们构建更加灵活、可扩展且易于维护的代码。 ####
|
29天前
|
设计模式 开发者 Python
Python编程中的设计模式:从入门到精通####
【10月更文挑战第14天】 本文旨在为Python开发者提供一个关于设计模式的全面指南,通过深入浅出的方式解析常见的设计模式,帮助读者在实际项目中灵活运用这些模式以提升代码质量和可维护性。文章首先概述了设计模式的基本概念和重要性,接着逐一介绍了几种常用的设计模式,并通过具体的Python代码示例展示了它们的实际应用。无论您是Python初学者还是经验丰富的开发者,都能从本文中获得有价值的见解和实用的技巧。 ####
|
25天前
|
设计模式 开发者 Python
Python编程中的设计模式应用与实践###
【10月更文挑战第18天】 本文深入探讨了Python编程中设计模式的应用与实践,通过简洁明了的语言和生动的实例,揭示了设计模式在提升代码可维护性、可扩展性和重用性方面的关键作用。文章首先概述了设计模式的基本概念和重要性,随后详细解析了几种常用的设计模式,如单例模式、工厂模式、观察者模式等,在Python中的具体实现方式,并通过对比分析,展示了设计模式如何优化代码结构,增强系统的灵活性和健壮性。此外,文章还提供了实用的建议和最佳实践,帮助读者在实际项目中有效运用设计模式。 ###
14 0
|
28天前
|
设计模式 存储 数据库连接
Python编程中的设计模式之美:单例模式的妙用与实现###
本文将深入浅出地探讨Python编程中的一种重要设计模式——单例模式。通过生动的比喻、清晰的逻辑和实用的代码示例,让读者轻松理解单例模式的核心概念、应用场景及如何在Python中高效实现。无论是初学者还是有经验的开发者,都能从中获得启发,提升对设计模式的理解和应用能力。 ###
|
2月前
|
设计模式 安全 Java
Java 编程中的设计模式:单例模式的深度解析
【9月更文挑战第22天】在Java的世界里,单例模式就像是一位老练的舞者,轻盈地穿梭在对象创建的舞台上。它确保了一个类仅有一个实例,并提供全局访问点。这不仅仅是代码优雅的体现,更是资源管理的高手。我们将一起探索单例模式的奥秘,从基础实现到高级应用,再到它与现代Java版本的舞蹈,让我们揭开单例模式的面纱,一探究竟。
40 11
|
3月前
|
设计模式 存储 前端开发
揭秘.NET架构设计模式:如何构建坚不可摧的系统?掌握这些,让你的项目无懈可击!
【8月更文挑战第28天】在软件开发中,设计模式是解决常见问题的经典方案,助力构建可维护、可扩展的系统。本文探讨了.NET中三种关键架构设计模式:MVC、依赖注入与仓储模式,并提供了示例代码。MVC通过模型、视图和控制器分离关注点;依赖注入则通过外部管理组件依赖提升复用性和可测性;仓储模式则统一数据访问接口,分离数据逻辑与业务逻辑。掌握这些模式有助于开发者优化系统架构,提升软件质量。
54 5
|
3月前
|
设计模式 算法 安全
Java编程中的设计模式:提升代码的可维护性和扩展性
【8月更文挑战第19天】在软件开发的世界里,设计模式是解决常见问题的一种优雅方式。本文将深入探讨Java编程语言中常用的几种设计模式,并解释如何通过这些模式来提高代码的可维护性和扩展性。文章不涉及具体的代码实现,而是侧重于理论和实践相结合的方式,为读者提供一种思考和改善现有项目的新视角。