趣学设计模式
设计模式
解决真实问题很重要
代码质量既是设计出来的,也是迭代优化出来的
程序设计是每个程序员的基本功。
但是,大多数人都只是对新技术充满热情,却很少有人愿意沉下心来,花几个月甚至一两年的时间来重温基础知识,修炼基本功。
学习设计模式其实就是一条捷径。
“主动学习+刻意练习”更有效
要想学好设计模式,你就得放弃这种被动学习的方式,要有目标、有系统地去主动学习:找寻好的资料,分析理解,开放思维。
Java 设计模式的一个特点就是入门相对容易,但是真正掌握并精通是非常困难的
- 涉及的知识面比较广。
- 效果检验比较困难。
- 被面向对象(OO)实现所误导。
- 资料比较零散,缺乏相关的实践案例。
编程思维
编程原则
编程模式
- 在每一天开始学习时,你得斗争着不打开手机玩王者荣耀,而是复习相关知识;
- 在每一次编程工作中,你得有意识地提醒自己不要滥用设计模式,而是寻找合适的设计;
- 在每一次失败的挫折里,你得鼓起勇气给自己打气,在下一次的实践中争取成功。
当你的产品里有你精心设计的功能时,你会发现原来在每一个漆黑的夜晚里,你的每一次艰难的抉择、每一次坚持与付出,给你带来这么多真正的无法言喻的喜悦。而这样的成功还会不断积累更多的成功。
而你只是缺乏有效的学习方法和路径,去重新认识一遍这些重要的知识,让这些知识发挥作用罢了。
学习思维
关键并不在于设计模式过于抽象或应用有难度,而在于我可能从一开始就没有搞清楚设计模式的应用范围和背景:设计模式到底解决什么问题?为什么要抽象这样的场景?又是如何解决这些问题的?
误解一:经典模式太抽象,很难学下去
- 过于抽象,难以快速理解。
- 经典之所以太抽象,是因为包含的知识密度太高,需要花时间解读。
- 我们需要先搞清楚设计模式能解决哪些范围的问题后,才能正确使用设计模式。
- 设计模式解决的是“可复用”的设计问题
设计模式从不同项目中总结出来的通用经验,是为了帮助我们快速理解现有的系统,并从中找出共性规律,如果没有足够的经验或者思考,反而容易引入错误的设计,造成更多的麻烦。
误解二:设计模式太单一,复杂业务场景难落地
- 在理论学习中,几乎所有的开发人员都认为它很重要。
- 在工作实践中,绝大部分开发人员在项目中又找不到合适的应用场景。
设计模式并不是一种全场景的解决方案,它需要考虑适用范围。
设计模式的提出就是为了解决限定领域的有限问题。
误解三:模式既然很好用,那么一切皆模式
好的设计从来不是看用的模式有多少,而是看如何合理利用模式的设计思想,以及如何利用模式解决真实的问题。
如何正确学习设计模式
掌握正确的学习方法和应用设计模式的思维模式。
- 首先,要摆正心态。
- 其次,搞清楚设计模式的背景知识。
- 再次,努力具备高手独立思考的习惯。
- 最后,从现在开始,坚持。
高手之所以成为高手,是因为高手不拘泥于某一知识的高低贵贱,而是保持明智的判断,始终朝着坚定的目标前行。
谨记
设计 ≠ 编码,愿望 ≠ 事实。
不要看了很多年源码,做了很多年工作,却依然做不好设计和编码。知道很多 How 时,一定还要多想想 Why 和 What。
学习设计模式的关键不在于你熟练掌握了多少设计模式,而在于能否真正灵活运用来解决更多复杂的现实问题。
只要你对设计模式进行不断地钻研与思考,设计模式就能带给你源源不断的新灵感和新希望。
组合思维
Unix 哲学是一套基于 Unix 操作系统顶级开发者们的经验所提出的软件开发的准则和理念。
可以说 Unix 哲学是过去几十年里对软件行业影响意义最深远的编程文化。
本质
Unix 设计哲学,主张组合设计,而不是单体设计;主张使用集体智慧,而不是某个人的特殊智慧。
当真正了解了 Unix 哲学后,你会发现今天的很多编程思想和编程方法都是对 Unix 哲学思想的一种传承或演化。
启示
- 编写可以做一件事并且做得很好的程序;
- 编写程序以协同工作;
- 编写程序来处理文本流,因为这是一个通用接口。
简单完备性、组合思维和数据驱动。
启示一:保持简单清晰性,能提升代码质量
一个程序只做一件事,并做得很好。
代码之间的相互影响越多,软件越复杂。
软件复杂度: 代码库规模。技术复杂度。实现复杂度。
- 通过减少硬编码来控制代码量。
- 在设计时就做好技术选型。
- 统一的代码规范。
Unix 哲学中所说的保持简单性,并不单单是做到更少的代码量,更是在面对不同复杂度来源时也能始终保持简单清晰的指导原则。
启示二:借鉴组合理念,有效应对多变的需求
不停的需求变更导致不停的代码变更。
- 所有的命令都可以使用管道来交互。
- 可以任意地替换程序。
- 自定义环境变量。
把软件设计成独立组件并能随意地组合,才能真正应对更多变化的需求。
解耦+模块化 (可替换的一致性)
高内聚、低耦合原则:模块内部尽量聚合以保持功能的一致性,模块外部尽量通过标准去耦合。
启示三:重拾数据思维,重构优化程序设计
处理数据是主要挑战
Unix 哲学在出现之初便提出了“数据驱动编程”这样一个重要的编程理念。也就是说,在 Unix 的理念中,编程中重要的是数据结构,而不是算法。
Unix 哲学提出的“数据驱动编程”会把代码和代码作用的数据结构分开,这样在改变程序的逻辑时,就只要编辑数据结构,而不需要修改代码了。
总结
Unix 设计哲学强调构建简单、清晰、模块化可扩展和数据驱动的代码。只有这样,代码才可以由其创建者以外的开发人员轻松维护和重新利用。
Unix 哲学传达给我们的不只是一种不断在编程中实践验证的理念,还是一种敢于挑战权威不断创新的思考方法。
分层思维
软件程序通常有两个层面的需求:
- 功能性需求
- 非功能性需求
非功能性需求所构建起来的正是我们所熟知的软件架构。
如果把软件比作一座高楼,那么软件架构就是那个钢筋混凝土的框架,代码就是那个框架里的砖石,正是因为有了那个框架,才能让每一个代码都能很好地运行起来。
代码分层架构是什么
软件分层架构是通过层来隔离不同的关注点(变化相似的地方)
代码分层架构就是将软件“元素”(代码)按照“层”(代码关系)的方式组织起来的一种结构。
分层架构核心的原则是:当请求或数据从外部传递过来后,必须是从上一层传递给下一层。
分层的本质就是为了让相似变化在各自的层内变化,而不造成层与层之间的相互影响。
代码分层架构解决什么问题
- 如何快速拆解功能问题?
- 如何提升代码的可扩展性?
通过分层来拆解问题
将这个笼统的复杂问题拆分为多个层次的子问题来解决。
最后真正需要关注的问题其实变少了。
所以说,从功能性需求角度来看,代码分层本身就是一种拆解复杂问题的好方法。
通过分层来提升代码可扩展性
组件自身的复用性也就提高了。
除了提高代码组件之间的复用性外,分层架构还让我们更容易做服务的横向扩展。
代码分层架构的优势和劣势
优势:
- 只用关注整个结构中的其中某一层的具体实现;
- 降低层与层之间的依赖;
- 很容易用新的实现来替换原有层次的实现;
- 有利于标准化的统一;
- 各层逻辑方便复用。
实现责任分离、解耦、组件复用和标准制定。
劣势:
- 开发成本变高
- 性能降低
- 代码复杂度增加
总结
软件分层架构是通过层来隔离不同的关注点(变化相似的地方),以此来解决不同需求变化的问题,使得这种变化可以被控制在一个层里。
代码分层架构的核心作用有两个:
- 对于功能性需求,将复杂问题分解为多个容易解决的子层问题;
- 对于非功能性需求,可以提升代码可扩展性。
总结来说,代码分层架构是一种软件架构设计方法。
- 从软件的功能性需求角度看,分层是为了把较大的复杂问题拆分为多个较小的问题,在分散问题风险的同时,让问题更容易被解决,也就是我们常说的解耦。
- 从架构(非功能性需求)角度看,分层能提升代码可扩展性,帮助开发人员在相似的变化中修改代码。
其实,复杂的设计概念和简单的代码之间存在一种平衡,这就是分层架构。
- 代码分层架构设计的思维模型是简化思维,本质是抽象与拆解。
- 代码分层架构设计的目的是将复杂问题拆分为更容易解决的小问题,降低实现难度。
- 代码分层架构设计的原则和方法是通用方法,可以应用到其他需要分层设计的地方。
分层架构从来不是目的,只是让我们的软件变得更好的其中一种思维方法而已。
工程思维
在软件开发过程中,已经有了一套相对完善的方法论——软件工程。
保证软件交付的效率和可靠性
- 从计算机科学角度看,软件开发需要关注软件本身运行的原理,比如时间复杂度、空间复杂度和算法的正确性。
- 从工程角度看,软件开发更多的是关注如何为用户实现价值。
打造尽量完美的软件
在软件开发时,我们总是容易太过于关注局部,而没能跳出局部去看整体。
第一,提升项目交付的效率。
如何在有限的条件下完成软件开发,并交付一个产品给用户?
如何通过科学的方法去帮助软件开发人员或项目管理人员合理地分配资源,并让资源发挥最大效用。
第二,提升项目交付的可靠性。
软件开发是一件极易出错的事,而测试就是为了发现问题。
保证软件开发每个阶段的可靠性才能保证软件产品最终的可靠性。软件工程对过程不断优化的理念,正是提升每一个软件项目交付可靠性的最佳解决方案。
什么是软件工程?
软件开发是一系列最终构建出软件产品的活动。
软件开发过程 = 定义与分析 + 设计 + 实现 + 测试 + 交付 + 维护。
软件工程(Software Engineering),是软件开发里对工程方法的系统应用。
软件工程研究的对象就是软件开发过程
软件工程 = 过程 + 方法 + 工具。
- 这里的“过程”,就是对软件开发过程的抽象而形成的过程模型,同时包含模型里所使用的方法。
- 而“方法”是指基于这些过程模型的方法集合。因为过程模型决定了软件开发过程是怎样的,进而才决定了应该使用什么样的开发方法。
- “工具”则对应的是过程模型不同阶段中的方法可能用到的工具。比如说,我们选用瀑布模型作为过程模型(如下图),那么对应的软件开发过程就应该是按照瀑布模型的分阶段来进行,对应的方法就是模型中的方法,像需求分析、架构设计……而对应就会使用一些需求分析工具、架构设计工具、文档管理工具等。
应用软件工程的建议
软件开发中还有另一个难题是:业务到技术实现。
用项目的视角去处理问题或业务,会有两点好处:一是培养你从整体看问题的习惯;二是通过做计划,提高你的时间管理能力。
盯紧目标主要体现在两个层面:一是负责到底,二是不断调校。
- 负责到底是基本要求。
- 不断调校是基于事实的合理改变。
提前计划
- 按时交付的人做事往往具备一个共同特质:做事有计划。
有效沟通
- 从定义问题开始,到业务方期望的目标是什么、实际上发生了什么、收集到了哪些信息,再到自己初步分析的思路是什么,最后自己的计划是什么,都清晰而又系统地表达出来了。
交付结果
实际上软件工程早已对此提出了一种解决办法——发布你的产品,用现在流行的话讲,叫具备闭环思维。
交付结果意味着你需要主动结束项目,包括结束确认、结束反馈、测试验证是否通过等,这和等待项目自动结束是完全不同的一种思考方式。
软件工程其实就是一种有目的、有计划、有步骤地解决问题的方法,并不是只有项目经理和架构师才需要具备,普通开发人员也需要学习和掌握,妥妥地提升编码能力和思考能力。
总结
这些年软件工程的知识一直在更新迭代中,其“过程+方法+工具”的本质并没有变,无非是各种方法和工具的推陈出新。
公式:
- 软件开发过程 = 定义与分析 + 设计 + 实现 + 测试 + 交付 + 维护;
- 软件开发 ≠ 软件编码;
- 软件工程 = 过程 + 方法 +工具。
实际上,软件工程对过程的改进思维,能够应用于很多方面,包括工作中的代码编写、架构设计、需求分析、制订计划、项目管理等。无论你是一个普通工程师还是项目管理者,都应该学点软件工程方法。
对象思维
因为面向对象编程是一门能让你轻松编写高质量软件的综合技术。
面向对象技术的出现就是为了解决软件的大规模可扩展性问题。
编程语言 VS 编程范式
编程语言,是一种标准化的通信方式,用来向计算机发出指令。
什么是编程范式?编程范式是一种根据编程语言的功能对编程语言进行分类的方法,它不针对具体的某种编程语言。
编程语言 ≠ 编程范式。
面向对象编程是一种编程范式,是基于部分特定编程语言下的编程经验总结,代表了程序员在编码时应该如何构建和执行程序的一种方法集合。
面向对象编程优势
优势一:模块化更适合团队敏捷开发
面向对象编程所提倡的模块化编程,在很大程度上直接解决了开发团队之间的合作问题,也就是不同团队和个人可以通过编写程序模块来进行功能交互,这让整个团队的开发效率得到了真正的提升。
尽可能解耦复杂的逻辑
我们所熟知的类库、框架,最早就是起源于面向对象组件重用的思想,目的就是提高多人协作编程的效率。
在面向对象编程中,我们可以通过给对象分配职责来划分不同模块的功能,让各个模块的功能更聚焦在一个关注点上,这样当代码发生修改时,影响的范围几乎能很快被定位到。
优势二:对象结构更能提升代码重用性、可读性
面向对象有三大特性:**封装、多态和继承**。这个结构是之前很多编程语言所没有的,而且它解决了结构化语言面临的以下两大难题。
第一个是全局变量问题。
-
第二个是可重用性范围问题。
评价代码质量的好坏通常有三个维度:**可读性、可测试性以及可维护性**。
优势三:组合和聚合思想让代码演进更重视组件化
什么是组件?简单来说就是**封装了一个或多个程序代码的二进制文件**,比如,Java 的 jar 包。
什么是组合、聚合关系?
- 聚合关系表示整体由部分组成,但是整体和部分不是强依赖的,整体不存在了部分还是会存在。
- 组合关系和聚合不同,组合中整体和部分是强依赖的,整体不存在了部分也不存在了。
总结
编程语言是一种标准化的通信方式,用来向计算机发出指令。编程语言是对编程范式的一种工具技术上的支撑,一种语言可以适用多种编程范式。编程范式是一种根据功能对编程语言进行分类的方式,但它并不针对某种编程语言。
所以,我们常说的面向对象编程其实是一种编程范式。
面向对象编程有三大优势:模块化、对象结构和组合/聚合思想。
你会发现,它们的核心理念都是在提升代码的可扩展性、可重用性和可维护性。80% 的时间里代码都是在被阅读的,如果一段代码很难阅读,那么维护人员修复起来就会非常耗时耗力,而且难读的代码扩展性也非常差,任何的新增功能都有可能引入更多未知的问题。
要想发挥面向对象编程的优势,遵循正确的方法很重要。
迭代思维
为什么你的编程效率不高呢?这是因为你把提升编程效率等同于提升编码速度了。
什么是高效编程?
整体编程效率之所以无法提升,是因为一直都只是关注写代码的效率。这便带来三个问题:
- 只关心代码是否正常运行,而对最终是否满足用户需求不在意;
- 容易陷入代码细节而忽略整体,比如,系统设计、项目进度、与他人协作等;
- 不太关心可测试性、可维护性,以及简洁的高质量代码该怎么写。
写代码的效率只是整体编程效率的一部分
高效编程除了需要提升细节上的编程效率外,还需要你能时常跳出细节思维,从整体的工作流程上去思考与改进。
高效编程其实是一种高效的工作流。
如何高效编程?
高效编程应该具备下面五个要素:
高效编程 = 原则 * 工具 * 编码 * 反馈 * 迭代
建立原则
**因为原则能让在你编程时,不会轻易遗忘一些重要的事情**
第一条原则:问题到你为止。
千万记住,**无论是不是你的问题,你都应该尝试去终结这个问题。**
第二条原则:多读、多写代码。编程是一门重视实践的技术,你不写代码就一定不能提升对代码细节的感受力。
- 方法一:多读别人的代码。无论是优秀的开源框架的源代码,还是通篇 if-else 的无注释业务代码,你都应该多读,通过多读才能发现什么是优秀代码,什么是烂代码。
- 方法二:多写自己的代码。编程犹如跑马拉松,需要付出很多汗水真实地训练,才有可能完成一次比赛。不要被一些原理讲解所误导,原理学习很重要,但是真实的编码同样重要,因为代码从编写到调试再到运行、发布、部署,会出现很多复杂的问题,解决这些问题同样对提升编程效率有很大的帮助。
第三条原则:打破砂锅问到底。其实,任何时候,对于问题都要有“打破砂锅问到底”的精神。
具体提问的技巧,可以采用 5WHY 法。例如:
Q:你在做什么?
A:解决一个 Bug。
Q:你为什么要解决这个 Bug?
A:测试提了一个 Bug。
Q:为什么测试会提这个 Bug?
A:测试认为这是一个 Bug。
Q:为什么测试会这么认为?
A:……
不断提问的时候,也是发现问题本质的一个过程。而解决编程上的本质问题越多,越能反过来提升编程上的效率。
打磨工具
需要一个**组件实验环境**和一个**工具代码库**。
有空闲时,**应该多尝试搭建一下新组件的实验环境,一方面可以熟悉组件特性,另一方面是培养你编程上多准备的习惯**。
要试着**建一个自己的工具库**。
**通过对工具的打磨,你能找到一些不变的代码特性,学习到优秀的代码设计应该怎么做**。
随着时间的推移,你的编程效率会随着工具效率的提升而变得更高。
实践编码
“高效”的反面是“低效”,低效编程有如下三大特征。
**第一个,靠运气编码**。
**第二个,重复硬编码**。
**第三个,写 PPT,开会**。
及时反馈
沟通的确会在某种程度上降低写代码的效率,但是它会在另外的地方补足:**团队效率**。
迭代更新
每一个迭代都应该有输入、处理和输出。
记录版本。
-
不断更新。
记录版本并且记录每一次关键修改信息。
更重要的是记录每一次关键修改信息,这是下一次迭代更新的输入。
你过去的经验没有被浪费,你能从信息中获得思考,更能减少重复犯低级错误的概率。
总结
如果你时常把编程干成这样:编程 = 写代码 + 写代码 + 写代码……那么你一定要及时停下来想想,编程到底意味着什么。
编程 ≠ 写代码。
不要把你的有效时间,浪费在只写代码上,这样你的编程能力甚至个人成长都会变慢。
编程不是一个体力活,编程应该是一个综合的脑力活:编程 = 写代码 + 讨论 + 学习 + 反思。
在学习编程知识和编程技巧的同时,应该多和同事讨论编程,从项目中积累更多实践的经验,不断应用到下一次的编程中去。
每一次的编码实践都是提升效率的好机会,更别忘记及时反馈遇到的问题,或者主动与他人分享你的实践想法。
只有当你把编码变成一次又一次的迭代,才能从短期的高效编程变成真正的长期高效编程