架构师训练营第二周总结
架构师训练营第二周总结
1. 为什么要从编程的历史看编程的本质和未来?
编程的近代史是从纸带机、汇编语言开始的,进而不断发展出形形色色的编程语言,这是一个从机器执行角度思考,到从编程语言使用者角度思考的变迁。现在主流的几种编程范式,和各种所谓的高级语言的出现,都是为了提高软件开发人员解决现实问题的效率,降低出错的几率。
2. 面向对象设计——高内聚、低耦合
编程实际上是对现实问题抽象建模的过程,因为现实世界可以通过抽取事物的特性构成对象的方式进行抽象,所以面向对象设计和分析是一种常见和实用的问题解决方式。
但是面向对象编程语言(如 C++、Python、Java 等)的使用,并不代表着解决问题的方式是面向对象的设计,实际上很多场景下,开发人员是用过程化的方式思考和解决问题,抽象程度并不够高,导致设计出的系统不容易扩展,维护也很困难。如果问题的解决方式是面向对象设计
的,实现的编程语言即使是常见过程式语言,如 C 语言,也是面向对象编程。
我们可以通过定义,正确的面向对象设计的目的和原则来判断实现是否“面向对象”。
面向对象设计的目的
面向对象设计的原则
3. 软件框架和架构师
软件框架是用来实现某一类应用的结构性的程序,是对某一类结构方案可复用的设计与实现。
如同大厦的框架
简化应用开发者的工作
实现了多种设计模式,使应用开发者不需要花太大的气力,就能设计出结构良好的程序。
软件框架本质也是程序,是代码和配置构成的。框架不同于一般程序就在于,一个框架都专注于解决某一个应用领域的常见问题,解决的方式是从结构性设计出发,对该领域问题进行抽象,将基本对象和过程定义和实现,而应用开发者在遵守框架制定的规范下,可以不用关注框架的实现细节,就能够快速开发出满足功能和性能要求的应用和服务。
典型的框架例子如 Web 服务器 Tomcat,应用开发者遵守 J2EE serverlet 规范编写的代码,能够被 Tomcat 识别和执行。应用程序和服务开发框架 Spring 也是一种知名框架,开发者通过注解方式定义组件,可以被 Spring Framework 识别和正确运行起来。
所以框架和开发者开发的代码的关系是:框架调用应用程序代码,应用程序代码是在遵守框架制定的规则下编写。因为框架设计和实现时,应用程序代码是还未被开发的。
架构师应该具备有设计和实现框架的能力,框架的存在,能够保证架构师设计的框架正确和有效率的落地。因为框架的存在,使得框架的细节和复杂问题被封装起来,避免业务开发者乱用。业务开发者在遵守框架规范的情况下,能够快速开发更多的应用。
4. 面向对象设计的基本原则
这些基本原则是为了避免软件设计中的“臭味”。软件设计中的臭味就是与软件设计目标“高内聚、低耦合”相违背的行为。这些有“臭味”的行为存在这些特点:
僵硬——不易改变
脆肉——只想改变 A,结果 B 被意外破坏
不可移植——不能适应环境的变化
导致误用的陷阱——做错误的事比做正确的事容易,会引诱程序员破坏原有的设计
晦涩——代码难以理解
过度设计、copy-paste 代码
僵硬性(Rigidity):很难对系统进行改动,因为每个改动都会迫使对系统的其他部分发生改动。这都会带来难以控制的缺陷率和维护性。
脆弱性(Fragility):对系统的改动会导致系统中与改动位置无关的内容出现问题。
出现新问题的地方与改动的地方没有概念上的关联。要修正这些问题又会引出更多的问题,从而使开发团队就像一只不停追逐自己尾巴的狗一样。
牢固性(Immobility):很难解开系统的纠结,所以很难使之成为一些可在其他系统中重用的组件。
设计中包含了对其他系统有用的部分,而把这些部分从系统中分离出来所需的努力和风险是巨大的。
粘滞性(Viscosity):做正确的事情比做错误的事情要困难。
面临一个改动的时候,开发人员常常会发现会有多种改动的方法。有的方法会保持系统原来的设计,而另外一些则会破坏设计,当那些可以保持系统设计的方法比那些破坏设计的方法跟难应用是,就表明设计具有高的粘滞性,作错误的事情就很容易。
不必要的复杂性(Needless Complexity):设计中包含有不具任何直接好处的基础结构。
如果设计中包含有当前没有用的组成部分,他就含有不必要的复杂性。当开发人员预测需求的变化,并在软件中放置了处理那些潜在变化的代码时,常常会出现这种情况。
不必要的重复(Needless Repetition):设计中包含有重复的结构,而该重复的结构本可以使用单一的抽象进行统一。
当 copy,cut,paste 编程的时候,这种情况就会发生。
晦涩性(Opacity):很难阅读、理解。没有很好的表现出意图。
代码可以用清晰、富有表现力的方式编写,也可以用晦涩、费解的方式编写。一般说来,随着时间的推移,代码会变得越来越晦涩。
软件的腐化会导致臭味,那么是什么激发了软件的腐化?答案是需求的变化。由于需求没有按照初始设计预见的方式进行变化(通常也不会按照预设模式进行),从而导致了设计的退化。通常,这些改动都很急迫,并且进行改动的开发人员对原始的设计思路并不熟识。因而,虽然对设计的改动可以工作,但是它却以某种方式违反了原始的设计。随着改动的不断进行,这些违反不断地积累,设计开始出现臭味。
然而,我们不能因为设计的退化而责怪需求的变化。作为开发人员,我们对需求变化有非常好的了解。事实上,我们中的大多数人都认识到需求是项目中最不稳定的因 素。如果我们的设计由于持续、大量的需求变化而失败,那就表明我们的设计和实践本身是有缺陷的。我们必须要设法找到一种方法,使得设计对于变化具有弹性, 并且应用一些实践来防止设计腐化。
4.1 开闭原则(OCP)
OCP - Open/Closed Principle
对于扩展是开放的(Open for extension)
对于更改是封闭的(Closed for modification)
简言之:不需要修改软件实体(类、模块、函数等),就应该能实现功能的扩展。
4.2 依赖倒置原则(DIP)
DIP - Dependency Inversion Principle
高层模块不能依赖低层模块,而是大家都依赖于抽象;
抽象不能依赖实现,而是实现依赖抽象。
DIP 倒置了什么?
模块或包的依赖关系
开发顺序和职责
软件的层次化
高层决定低层
高层被重用
4.3 Liskov 替换原则(LSP)
一个正确的继承要符合 Liskov 替换原则。
1988 年,Barbara Liskov 描述这个原则:
若对每个类型 T1 的对象 o1,都存在一个类型 T2 的对象 o2,使得在所有针对 T2 编写的程序 P 中,用 o1 替换 o2 后,程序 P 的行为功能不变,则 T1 是 T2 的子类型。
简言之:子类型(subtype)必须能够替换掉它们的基类型(base type)。
需要注意的是,要在问题场景中使用 LSP。比如正方形在数学逻辑上是一种特殊的长方形,但是在软件应用领域中,两者通常是有不同的使用逻辑,会导致正方形不能作为长方形的子类。
4.4 单一职责原则(SRP)
SRP - Single Responsibility Principle
又被称为“内聚性原则(Cohesion)”,意为:
将它与引起一个模块改变的作用力相联,就形成了如下描述:
4.5 接口分离原则(ISP)
ISP - Interface Segregation Principle
不应该强迫客户程序依赖它们不需要的方法。
ISP 和 SRP 的关系
ISP 和 SRP 是相关的,都和“内聚性”有关。
SRP 指出应该如何设计一个类 —— 只能有一种原因才能促使类发生改变。
ISP 指出应该如何设计一个接口 —— 从客户的需要出发,强调不要让客户看到他们不需要的方法。
评论