一、术语
1.1 模式与模型
模式:模式(Pattern)是解决某一类问题的方法论,把解决某类问题的方法总结归纳到理论高度,就是模式。
模型:模型可以指实物。也可以是概念的模型,模型是由元素、关系、操作以及控制其相互作用的规则组成的概念系统。
既然模型是系统,那么它也应该满足系统的三要件:组成要素、连接方式、功能或目标。
1.2 思想-范式-语言
编程思想(仅仅是思想层面) → 编程范式(已经形成一套方法论) → 编程语言(编程范式的具体实现,从API的设计中能很明显看出)。
所以,编程语言是对编程思想的实现。如实现面向对象编程思想的编程语言,其标准库就会暴露出功能对象以供使用。
二、编程范式
编程范型、编程范式或程序设计法(Programming paradigm):范即模范、典范之意,范式即模式、方法
- 是一类典型的编程风格,是指从事软件工程的一类典型的风格(可以对照方法学)。
- 编程范型提供了(同时决定了)程序员对程序执行的看法。例如:
- 在面向对象编程中,程序员认为程序是一系列相互作用的对象,
- 在函数式编程中,一个程序会被看作是一个无状态的函数计算的序列。
许多编程范式以其禁止的技术和启用的技术而闻名。例如:纯函数式编程不允许有副作用;结构化编程不允许使用goto。可能是因为这个原因,新的范型常常被那些习惯于较早的风格的人认为是教条主义或过分严格。然而,避免使用某些技术可以更容易地理解程序行为,并证明有关程序正确性的定理。
2.1 编程范式和编程语言
正如软件工程中不同的群体会提倡不同的“方法学”一样,不同的编程语言也会提倡不同的“编程范型”。 编程范型和编程语言之间的关系可能十分复杂:
- 一些语言是专门为某个特定的范型设计的(如Smalltalk和Java支持面向对象编程,而Haskell和Scheme则支持函数式编程)
- 还有另一些编程语言支持多种范型(如Ruby、Common Lisp、Python和Oz)。例如,C++设计时,支持过程化编程、面向对象编程以及泛型编程。然而,设计师和程序员们要考虑如何使用这些范型元素来构建一个程序。一个人可以用C++写出一个完全过程化的程序,另一个人也可以用C++写出一个纯粹的面向对象程序,甚至还有人可以写出杂揉了两种范型的程序。
2.2 常见的编程范式
- 非结构化编程(对比:结构化)—— 更详细全面的分类可以查看维基百科 — 编程范式
- 结构化编程(对比:非结构化)
- 指令式编程(对比:声明式(宣告式)编程)
- 声明式(宣告式)编程(对比:指令式)
三、结构化编程
3.1 结构化
结构:各个组成部分的搭配和排列。
结构化,是指将逐渐积累起来的数据加以归纳和整理,使之条理化、纲领化,做到纲举目张。
多说一句:知识是逐渐积累的,但在头脑中不应该是堆积的。心理学研究发现:优生头脑中的知识是有组织、有系统的,知识点按层次排列,而且知识点之间有内在联系,具有结构层次性。
结构化对知识学习具有重要作用,因为当知识以一种层次网络结构的方式进行储存时,可以大大提高知识应用时的检索效率。
3.2 结构化编程
一种编程典范(编程范式)。它采用子程序、块结构、for循环以及while循环等结构,来取代传统的 goto。希望借此来改善计算机程序的明晰性、质量以及开发时间,并且避免写出面条式代码。
- “面向结构”的程序设计方法即结构化程序设计方法,是“面向过程”方法的改进,结构上将软件系统划分为若干功能模块,各模块按要求单独编程,再由各模块连接,组合构成相应的软件系统。
- 结构化程序设计采用自顶向下、逐步求精的设计方法,各个模块通过“顺序、选择、循环”的控制结构进行连接,并且只有一个入口、一个出口。
3.3 补充:结构化数据
结构化数据也称作行数据,是由二维表结构来逻辑表达和实现的数据,严格地遵循数据格式与长度规范,主要通过关系型数据库进行存储和管理。
- 表现为二维形式的数据。一般特点是:数据以行为单位,一行数据表示一个实体的信息,每一行数据的属性是相同的。
- json可以拆分成多个二维表,所以也算是结构化数据。
与结构化数据相对的是非结构化数据。
- 非结构化数据的数据结构不规则或不完整,没有预定义的数据模型,不方便用数据库二维逻辑表来表现的数据。
- 包括所有格式的办公文档、XML、HTML、各类报表、图片和音频、视频信息等。
支持非结构化数据的数据库采用多值字段、了字段和变长字段机制进行数据项的创建和管理,广泛应用于全文检索和各种多媒体信息处理领域。
四、声明式编程
4.1 概述
声明式编程(Declarative programming)或译为宣告式编程,是一种编程范式,通常被定义为除命令式以外的编程范式。它描述目标的性质,让电脑明白目标,而非流程。
- 声明式编程不用告诉电脑问题领域,从而避免随之而来的副作用。而命令式编程则需要用算法来明确的指出每一步该怎么做。
- 声明式编程通过函数、推理规则或重写规则,来描述变量之间的关系(如React.js UI = f(state))。它的语言运行器(编译器或解释器)采用了一个固定的算法,以从这些关系产生结果。
声明式语言包包括数据库查询语言(SQL,XQuery),正则表达式,逻辑编程,函数式编程和组态管理系统。
声明式编程语言通常用作解决人工智能和约束满足问题。
React是一个声明式编程框架,使得使用者只需要描述目标/状态,怎么实现交给我(框架底层)。
4.2 React与SwiftUI
近年来,随着编程技术和思想的进步,使用声明式或者函数式的方式来进行界面开发,已经越来越被接受并逐渐成为主流。最早的思想大概是来源于 Elm,之后这套方式被 React 和 Flutter 采用,这一点上 SwiftUI 也几乎与它们一致。总结起来,这些 UI 框架都遵循以下步骤和原则:
使用各自的 DSL 来描述「UI 应该是什么样子」,而不是用一句句的代码来指导「要怎样构建 UI」。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17/* 传统的 UIKit,我们会使用这样的代码来添加一个 “Hello World” 的标签,它负责:
“创建 label”,
“设置文字”,
“将其添加到 view 上”
*/
func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
label.text = "Hello World"
view.addSubview(label)
// 省略了布局的代码
}
// 相对起来,使用 SwiftUI 我们只需要告诉 SDK 我们需要一个文字标签:
var body: some View {
Text("Hello World")
}接下来,框架内部读取这些 view 的声明,负责将它们以合适的方式绘制渲染。
注意,这些 view 的声明只是纯数据结构的描述,而不是实际显示出来的视图,因此这些结构的创建和差分对比并不会带来太多性能损耗。相对来说,将描述性的语言进行渲染绘制的部分是最慢的,这部分工作将交由框架以黑盒的方式为我们完成。
如果
View
需要根据某个状态 (state) 进行改变,那我们将这个状态存储在变量中,并在声明 view 时使用它:1
2
3
4var name: String = "Tom"
var body: some View {
Text("Hello \(name)")
}状态发生改变时,框架重新调用声明部分的代码,计算出新的 view 声明,并和原来的 view 进行差分,之后框架负责对变更的部分进行高效的重新绘制。
SwiftUI 的思想也完全一样,而且实际处理也不外乎这几个步骤。使用描述方式开发,大幅减少了在 app 开发者层面上出现问题的机率。
声明式编程是一个大的概念,除了一些特定的领域专属语言之外,一些有名的编程范型也被归类为其子范型。常见的如:
- 函数式:函数式编程,特别是纯函数式编程,尝试最小化状态带来的副作用,因此被认为是声明式的。
- 响应式
4.3 响应式编程
响应式编程或反应式编程(Reactive programming)是一种面向数据流和变化传播的声明式编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。
不需要考虑调用顺序,只需要知道考虑结果,类似于蝴蝶效应,产生一个事件,会影响很多东西,这些事件像流一样的传播出去,然后影响结果,借用面向对象的一句话,万物皆是流。
例如:对于 a=b+c 这个表达式的处理,在命令式编程中,会先计算 b+c 的结果,再把此结果赋值给 变量a,因此 b,c 两值的变化不会对 变量a 产生影响。但在响应式编程中,变量a 的值会随时跟随 b,c 的变化而变化。
使用场景:
- 电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似”=B1+C1”的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。
- 在MVC软件架构中,响应式编程允许将相关模型的变化自动反映到视图上,反之亦然。
- iOS中常用的ReactiveCocoa被描述为 函数响应式编程(FRP)框架。
响应式编程最初是为了简化交互式用户界面的创建和实时系统动画的绘制而提出来的一种方法,但它本质上是一种通用的编程范式。
五、面向切面编程(AOP)
面向方面编程 (Aspect-oriented programming,AOP) 是一种编程范式,旨在通过允许分离横切关注点(cross-cutting)来增加模块化。AOP会将横切关注点封装到切面(aspects)模块中。这允许对处理封装横切关注点的代码进行干净的隔离和重用。
它可以实现在不修改代码本身的情况下,向现有代码添加行为(又称是一个advice)。通过“切入点(point-cut)”规范指定要修改哪些代码,例如:如果函数名称以’set’开头,记录这些函数的调用。
这允许将不属于业务逻辑核心的行为(例如日志记录)添加到程序中,而不会使功能的代码核心混乱。
概念:
- 关注点是对计算机程序代码(的执行结果)有影响的一组特定信息。关注点可以很宽泛,比如“应用程序的硬件细节”、“数据库交互的细节”;也可以很具体,比如“要实例化的类的名称”。
- Cross-cutting concern:横切关注点,也是程序的一部分,但它会依赖或影响程序的其他多个部分。
- 一般是不会影响到系统核心功能的信息,其通常会作为一些附加功能,横切多个核心关注点模块。
- 比如:日志记录模块。因为日志记录策略必然会影响系统的每个已记录部分。因此,日志记录横切所有记录的类和方法,即日志代码会复制分散到各个相关位置。其他如信息安全、监控、数据验证模块等。
- Pointcuts:切入点,是一组连接点(join points)。切入点指定了需要应用advice的确切位置,也就是嵌入修改代码的地方。切入点通常使用类名或方法名指定,在某些情况下使用匹配类名或方法名的正则表达式。
- Advice:描述了一类函数,它会在某些函数运行时,对其进行修改。应用的位置由Pointcuts指定。
通俗点总结:这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。一般,我们管切入到指定类指定方法的代码片段称为切面(aspect),而切入到哪些类、哪些方法则叫切入点。
注意,aspect此处不要理解为“方面“,“切面“更符合语义(和一个模块相切的一个面)。
有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。
AOP其实只是OOP的一个很有力的补充而已。OOP是从二维上区分出一个个的类来,而AOP则是加上了时间维度,能够从纵向上向对象中加入特定的代码。有了AOP,OOP变得立体了。从技术上来说,Java上的AOP基本上是通过代理机制实现的。