[架构师训练营第 1 期] 第二周学习总结
如何进行优雅的设计,使软件架构更加富有弹性和灵活、更加易于扩展和维护?
也就是程序应该如何设计、程序的架构应该怎么做?
回顾软件编程的历史,我们就能找到答案。
通过回顾历史,反思现在我们是否做到了从历史的总结中吸取教训。
通过反思现在,指导未来我们应该怎么做。
回顾软件编程的历史
软件编程的历史:(回顾历史,寻找答案,探寻本质)
软件编程是如何从面向过程发展到面向对象的?
为什么今天大多数编程语言都是面向对象为主?
——本质上是领域问题发生了变化。
面向对象编程语言的本质特征是什么?
何为软件的框架及其作用?
如何设计软件的框架?
计算机思想的演变:
数值计算机 -> 莱布尼茨(微积分和二进制) -> 阿达(织布机,循环和子程序) -> 冯诺依曼(ENIAC)
数值计算机读取数值,进行计算后得出结果。数据计算机读取数据,进行执行后得出结果。
数据本身就包含了数值计算的逻辑。而数值计算机的计算逻辑则是固化的。
因此数据计算机(通用计算机)是可编程的,输入程序(数据),执行程序,输出结果。
编程方式的演进:
接线 -> 打孔纸带(二进制机器语言) -> 低级语言(汇编语言) -> 高级语言(Basic、Pascal、Fortran、Perl、C)
机器语言和汇编语言直接对机器编程,需要程序员自己关注机器指令、寄存器、硬件等,效率低下,思维局限。
高级语言让程序员跳出机器的束缚,使程序员能按照人类的思维方式进行编程,同时将关注点更多放在要实现的功能上,且极大提高了效率,对功能实现的思考也不再受限。
领域问题的扩张:
早期计算机通常用于进行诸如弹道轨迹等计算。
后来高级语言出现后,人们就用它来进行科学计算和研究等任务。
随着高级语言的出现,编程效率得到极大提高,人们发现还可以使用计算机进行企业管理、太空探索、火箭或导弹控制等。领域问题出现了多样化,而单一领域所包含的问题则越来越丰富,表现为领域问题的扩张。
随着领域问题的不断扩张,为了解决领域问题,就出现了复杂而庞大的计算机软件系统,通常几千人员,花费数亿美元。
软件系统的日趋复杂和庞大对软件开发过程提出了新的挑战。
不同软件开发人员对问题的思考方式以及功能的编码实现千奇百怪,代码中充斥着大量的 if-else 和循环,却不能反映领域问题的真正解决逻辑,或者事物的客观运行逻辑,而只能反映程序员对解决领域问题的主观理解。
这一点在大型系统中尤为显著。于是 70 年代出现了第一次软件危机。
软件危机的出现导致投入大量人力物力,却开发出糟糕的软件系统,出现了严重的运行事故。
人们开始思考软件危机的问题出现在哪,并着手解决。
一方面,从软件工程的角度去控制软件开发的成本和进度。
另一方面,从软件编程语言的角度去寻找解决方案。
第一次软件危机的问题本质:面向过程的线性逻辑开发方式在大规模复杂的软件系统的开发过程中容易产生耦合,并且其耦合程度随着软件系统规模和复杂性的扩大而越发提高。就像面条一样,单独一根不容易产生纠缠,而当越来越多的面条放在一起时,彼此之间就越容易发生纠结,最后变成一团乱麻,系统难以维护。
第一次软件危机的解决方案:面向对象的编程思想。编程的时候,应该讲领域问题的解法各个部分看成一个个对象,通过对象之间的合理交互,从而完成系统的开发和设计。面向对象的编程思想将客观世界的事物都作为对象处理,将客观事物的运行规律抽象成对象之间的交互(逻辑)映射到我们要开发的计算机软件中,从而实现对客观世界领域问题的实际解决。
基于面向对象的编程思想,就出现了面向对象的编程语言。
反思现在的软件编程
那么我们今天使用着面向对象的编程语言,是否按照面向对象的编程思想去编程了呢?
要判断这一点,首先要理解编程语言的本质,以及面向对象编程的特征。
编程语言的本质:
编程的实质是从现实世界事物到计算机模型的映射,从而使计算机解决现实世界问题。
这种映射过程包含了对领域问题的分析和抽象、以及得出计算机所能理解的模型。
编程语言则将这些模型进一步设计、抽象、开发成为计算机所能执行的软件系统。
要使得这个映射过程尽可能精确和有效,就需要这个模型既能真实反映领域问题、与客观事物的交互方式一一对应,又能恰当符合计算机逻辑、与软件对象的交互方式一一对应。
面向对象的编程语言的目标就在于此。
如果我们使用面向对象语言的手段偏离了这个目标,就是没有按照面向对象的编程思想去编程和解决问题。
为什么没有做到?应该如何做到?
使用或学习一项技术,我们容易只触及皮毛而忽略本质,对其概念和用法听之任之而缺乏深入理解。
作为架构师,或者是真正想要把一个问题解决好的人,应该更多地思考:
这项技术是如何走到今天的?这项技术是想解决什么问题的?为什么过往的技术没有解决得更好?我们真正地把这项技术用到位了吗?它要解决的问题、要达成的目的真正被我们实现了吗?
如果这些都没有做到,而仅仅是抓住一些皮毛看一些样子,写一些似是而非似懂非懂的代码,可能也能解决一些问题或找到一份工作,那么我们凭什么成为一个真正的架构师。如果我们对技术的理解仅仅达到一个表明的程度,那么如何给别人提供令人信服的技术支持?
面向对象编程语言这项技术是如此,后面要学习的设计原则、设计模式、领域驱动设计亦然。
要有这样的思考,只学习一些表面的东西而抓不到问题的本质是无法成为一名优秀的架构师的。
否则,当遇到问题的时候,就容易产生怀疑、陷入僵局。
问题领域的抽象:
问题领域就是包含于系统所要解决的问题相关的实物和概念的空间。
比如如何通过开关控制灯泡的问题,其领域就包括开关和灯泡这些实物及其相关概念。
机器语言或汇编语言对该问题领域的抽象就是从控制指令的角度,思考如何通过控制指令与控制灯泡的开关。这样的抽象级别是计算机层面的,只能与计算机的硬件和指令一一对应,而不能与问题领域中的实物和概念一一对应。这是一种面向机器的编程方式,它只能解决简单问题。
非结构化的高级语言则将问题领域抽象为一个个逻辑过程。这是一种面向对象的编程方式,虽然抽象级别达到了人的层面,但它仍然是一种线性逻辑,不能与问题领域中的实物和概念一一对应,因而只适用于开发小规模复杂度低的软件系统,在开发大规模复杂度高的软件系统时会产生严重问题。
面向对象的程序设计则直接对问题领域中的元素(实物和概念)进行表达,忽略其中的具体实现(开关如何控制灯,指令是怎样的,处理逻辑是怎样的,if-else 或循环怎么写),主要关注其中要抽象的点(如灯开关本身的特性、灯本身的特性、它们各自的边界和接口等),抽象级别达到了客观世界的层面,因而能够较好地应对大规模和复杂软件的开发。
面向机器 -> 面向人 -> 面向客观业务领域(对象,可以是订单、商品、用户、数据等)
指导未来的软件设计
面向对象编程的 Smalltalk 描述:
万物皆对象,程序是对象的集合,对象之间通过消息沟通
每个对象都有其类型,同一类型的不同对象接收相同消息
每个对象都有其存储,每个对象的存储由其它对象所构成
面向对象编程的三要素:(但都不是面向对象语言独有)
封装性——隐藏对象实现,构建对象边界,使得对象之间只能通过接口来交互,不彼此依赖或侵入代码(C 语言的头文件也具备封装性)
继承性——实现接口重用(C 语言的 A 结构体包含 B 结构体定义也可以理解为 A 继承了 B)
多态性——实现对象互换(C 语言的函数指针也可以实现多态性,但是很危险)
面向对象分析:充血模型与贫血模型、领域驱动设计
面向对象的设计目标:高内聚、低耦合。
从而使系统:易扩展、更健壮、可移植、更简单。
如何做到:基于目标产生出来的一些设计原则和设计模式,以及基于这些设计原则和设计模式构建出来的一些框架和工具。
面向对象的设计原则:开闭、依赖倒置、里式替换、单一职责、接口分离。
面向对象的设计模式:基本设计模式(GoF)、细分领域设计模式(并发编程、Java EE、等)
重点是如何基于设计原则构建框架,如何基于设计模式进行代码重构。
框架:用于实现某一类应用的结构性程序,是对某一类架构方案可复用的设计与实现。
它的作用是:简化开发者工作,集成多种设计模式,从而为开发者编写结构良好的应用程序赋能。
不同领域的框架有:
微软为 Windows 编程而设计开发的 MFC 框架
Java 为其 GUI 编程而设计开发的 AWT 框架
Web 服务器框架:Tomcat
其他开源框架:MyBatis、Spring 等
框架调用代码,代码依附于框架,代码调用工具。
架构师用框架保证架构落地,用工具提高开发效率。
设计原则及应用示例
(注:下图“优化过程”应为“腐化过程”)
版权声明: 本文为 InfoQ 作者【猫切切切切切】的原创文章。
原文链接:【http://xie.infoq.cn/article/5b0082629fbf1e64441137761】。文章转载请联系作者。
评论