以下为看耗子叔的编程范式的一些笔记
编程范式
一类典型的编程风格
目标:写出更为通用、更具可重用性的代码或模块。
泛型编程
类型系统以及泛型编程的本质
泛型的本质
类型系统
- 动态类型
- 静态类型
- 强类型、弱类型
编译时就知道变量类型的是静态类型;运行时才知道一个变量类型的叫做动态类型。 不允许隐式转换的是强类型,允许隐式转换的是弱类型。
类型本质
屏蔽掉数据和操作数据的细节,让算法更通用,让编程者更多关注算法的结构,而不是在算法中处理不同的数据类型
- 类型是对内存的一种抽象。不同的类型,会有不同的内存布局和内存分配的策略。
- 不同的类型,有不同的操作。所以,对于特定的类型,也有特定的一组操作。
优势
- 程序语言的安全性 强类型语言
- 利于编译器的优化
- 代码的可读性
- 抽象化
泛型目标
- 算法的泛型
- 类型的泛型
- 数据结构(数据容器)的泛型
C语言
设计目标提供一种能以简易的方式编译、处理低层内存、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。
伟大之处——使用C语言的程序员在高级语言的特性至上还能简单地做任何底层的微观控制。C语言是高级语言中的汇编语言。
设计理念
- 相信程序员,不会阻止程序员做任何底层的事。
- 保持语言的最小和最简的特性
- 保证C语言的最快的运行速度,哪怕牺牲移植性。
优点
- 静态弱类型语言,使用变量时需要声明变量类型,但是类型之间支持隐式转换。
- 不同的变量类型可以用结构体组合在一起,以此来声明新的数据类型。
- C语言可以用typedef关键字来定义类型的别名,以此来达到变量类型的抽象。
- C语言传递参数一般是以值传递,也可以传递指针;
- 通过指针,C语言可以容易地对内存进行低级控制,然后这加大了编程难度。
- 一个有结构化程序设计、具有变量作用于以及递归功能的过程式语言。
- 编译预处理让C语言的编译更具有弹性,比如跨平台。
缺点
- 面向过程和底层,不适合复杂业务开发。
1. void *
内存复制
void swap(void* x, void* y, size_t size)
{
char tmp[size];
memcpy(tmp, y, size);
memcpy(y, x, size);
memcpy(x, tmp, size);
}
复制代码
2. 宏定义Define
字符串替换
坑:
- 代码膨胀,编译出可执行文件比较大。
- 字符串替换导致入参执行多次
#define swap(x, y, size) {\
char temp[size]; \
memcpy(temp, &y, size); \
memcpy(&y, &x, size); \
memcpy(&x, temp, size); \
}
复制代码
复杂demo:
Search函数
int search(int* a, size_t size, int target) {
for(int i=0; i<size; i++) {
if (a[i] == target) {
return i;
}
}
return -1;
}
复制代码
int search(void* a, size_t size, void* target,
size_t elem_size, int(*cmpFn)(void*, void*) )
{
for(int i=0; i<size; i++) {
// why not use memcmp()
// use unsigned char * to calculate the address
if ( cmpFn ((unsigned char *)a + elem_size * i, target) == 0 ) {
return i;
}
}
return -1;
}
复制代码
C++语言
在C的基础上掺杂了面向对象的概念,很大程度上用来解决C语言中的各种问题和不便的
- 用引用来解决指针的问题
- 用 namespace 来解决名字空间冲突的问题
- 通过try-catch来解决检查返回值编程的问题
- 用class来解决对象的创建、复制、销毁的问题,从而可以达到在结构体嵌套时可以深度复制的内存安全问题。
- 通过重载操作符来达到操作上的泛型。
- 通过模板template和虚函数的多态以及运行时识别来达到更高层次的泛型和多态。
- 用 RALL、智能指针(管理资源)的方式,解决了C语言中因为需要释放资源而出现的非常ugly并且容易出错的代码问题。
- 用STL解决了C语言中算法与数据结构的N多种坑
RALL Resource Acquisition Is Initialization(资源分配即初始化)
泛型
template<typename T, typename Iter>
Iter search(Iter pStart, Iter pEnd, T target)
{
for(Iter p = pStart; p != pEnd; p++) {
if ( *p == target )
return p;
}
return NULL;
}
复制代码
解释:
- 使用typename T 抽象了数据结构中存储数据的类型
- 使用typename Iter,不同数据类型自己实现的‘迭代器’,抽象掉了不同类型的数据结构
- 重载++操作符,泛型了遍历操作
- 函数的入参上使用pStart和pEnd来表示遍历的起止
- 使用*Iter来取得内容,通过重载*取值来达到泛型
泛型TODO
- 标准化掉类型的内存分配、释放和访问
- 标准化掉类型的操作。比如:比较操作、I/O操作、复制操作……
- 标准化掉数据容器的操作。比如:查找算法、过滤算法、聚合算法……
- 标准化掉类型上特有的操作。需要有标准化的接口来回调不同类型的具体操作……
Demo: C++做了什么
- 通过类中的构造、析构、拷贝构造、重载赋值操作符,标准化(隐藏)了类型的内存分配、释放和复制的操作。
- 通过重载操作符,可以标准化类型的比较等操作。
- 通过iostream,标准化了类型的输入输出控制。
- 通过模板技术(模板的特化),来为不同的类型生成类型专属的代码。重载
- 通过迭代器来标准化数据容器的遍历操作。
- 通过面向对象的接口依赖(虚函数),来标准化特定类型在特定算法上的操作。
- 通过函数式(函数对象),来标准化对于不同类型的特定操作
如何近一步抽象呢?
Reduce & Map & Filter
函数式编程
函数式编程的思想以及思维方式
思维方式
Describe what to do, rather than how to do it.
特点
- stateless 函数不维护啊任何状态。
- immutable 输入的数据是不变的
State is like a box of chocolates. You never know what you are gonna get.
- 惰性求值
- 确定性 优势
- 没有状态就没有伤害
- 并行执行无伤害
- Copy-Paste重构代码无伤害
- 函数的执行没有顺序上的问题
劣势
- 数据复制比较严重
函数式编程用到的技术
- First class function (头等函数)
- Tail recruision optimization (尾递归优化)
- map & reduce & filter
- pipeline (管道)
流式计算
- recursing (递归)
- currying (柯里化)
- higher order function (高阶函数)
函数思想
def inc(x):
def incx(y):
return x+y
return incx
inc2 = inc(2)
inc5 = inc(5)
print inc2(5) # 输出 7
print inc5(5) # 输出 10
复制代码
- 把函数当做变量来用,关注描述问题而不是怎么实现,代码更易读
Map&Reduce的高效
# 计算数组中正数的平均值
num = [2, -5, 9, 7, -2, 5, 3, 1, 0, -3, 8]
positive_num_cnt = 0
positive_num_sum = 0
for i in range(len(num)):
if num[i] > 0:
positive_num_cnt += 1
positive_num_sum += num[i]
if positive_num_cnt > 0:
average = positive_num_sum / positive_num_cnt
print average
复制代码
# 计算数组中正数的平均值
positive_num = filter(lambda x: x>0, num)
average = reduce(lambda x,y: x+y, positive_num) / len( positive_num )
复制代码
装饰器模式-工厂模式
面向切面编程
函数注解
- python语言实现
def hello(fn):
def wrapper():
print "hello, %s" % fn.__name__
fn()
print "goodbye, %s" % fn.__name__
return wrapper
@hello
def Hao():
print "i am Hao Chen"
Hao()
Hao = hello(Hao);
复制代码
- go语言
package main
import "fmt"
func decorator(f func(s string)) func(s string) {
return func(s string) {
fmt.Println("Started")
f(s)
fmt.Println("Done")
}
}
func Hello(s string) {
fmt.Println(s)
}
func main() {
decorator(Hello)("Hello, World!")
}
复制代码
面向对象编程
万物皆对象
优点
- 能和真实的世界交相呼应,符合人的直觉
- 面向对象和数据库设计类型,更多的关注对象间的模型设计
- 强调‘名词’而不是‘动词’,更多的关注对象和对象间的接口
- 根据业务的特征行程一个个高内聚的对象,有效的分离了抽象和具体实现,增强了可重用性和拓展性
- 拥有大量非常优秀的设计原则和设计模式
- SOLID(单一功能、开闭原则、里式替换、接口隔离以及依赖反转是面向对象的五个基本原则)、loC/DCP……
缺点
- 代码都依附在一个类上,侧面的支持了类型
- 代码需要通过对象来达到抽象的效果,导致了相当厚重的“代码粘合层”
- 由于太多的封装以及对状态的鼓励,导致了大量不透明在并发下的问题
面向对象三大特性:封装、继承和多态
- "Program to an 'interface', not an 'implementaion'"
- 使用者不需要知道数据类型、结构、算法的细节
- 使用者不需要知道实现的细节,只需要知道提供的接口
- 利于抽象、封装、动态绑定、多态
- 符合面向对象的特质和理念
- "Favor 'object composition' over 'class inheritance'"
- 继承需要给子类暴露一些父类的设计和实现细节
- 父类实现的改变会造成子类随之改变
- 我们认为继承主要是为了代码复用,但实际上在子类中需要重新实现很多父类的方法
- 继承更多的是为了多态
RALL (Resource Acquisition Is Initialization)
代理模式
class lock_guard {
private:
mutex &_m;
public:
lock_guard(mutex &m):_m(m) { _m.lock(); }
~lock_guard() { _m.unlock(); }
};
复制代码
mutex m;
void foo() {
lock_guard guard(m);
Func();
if ( ! everythingOk() ) {
return;
}
...
...
}
复制代码
IoC/DIP(控制反转/依赖倒置)
灯和开关样例,协议化
示例:
- 灯和开关
- 以物易物->以钱易物
- 集中处理电子商务订单流程。各个垂直业务线都需要通过平台来处理自己的交易流程,并以此提出很多个性化需求。这个时候,平台可以通过开发一个插件模型、工作流引擎和Pub/Sub系统,让业务方的个性化需求支持以插件的形式插入订单流程中。业务方自己的数据存在自己的库中,业务逻辑不侵入系统,并可以使用工作流引擎和Pub/sub系统的协议标准来自定义自己的业务步骤。
基于原型的编程范式
面向对象示例
逻辑编程
推理
- 逻辑编程的要点是将正规的逻辑风格带入计算机程序设计之中
- 逻辑编程建立了描述一个问题的世界的逻辑模型
- 逻辑编程的目标是对它的模型新的陈述
- 通过陈述事实——因果关系
- 程序自动推导出相关的逻辑
program mortal(X) :- philosopher(X).
philosopher(Socrates).
philosopher(Plato).
philosopher(Aristotle).
mortal_report:-
write('Known mortals are:'), nl, mortal(X),
write(X),nl,
fail.
复制代码
编程本质
回归到编程本质思考
有效的分离Logic、Control、Data是写出好程序的关键所在
- Pragrams = Algorithms + Data Structures
- Algorithms = Logic + Control
如果将代码的逻辑和控制有效的分开,那么代码就会变得更易于改进和维护。
正如以下业务常见代码
function check_form_x() {
var name = $('#name').val();
if (null == name || name.length <= 3) {
return { status : 1, message: 'Invalid name' };
}
var password = $('#password').val();
if (null == password || password.length <= 8) {
return { status : 2, message: 'Invalid password' };
}
var repeat_password = $('#repeat_password').val();
if (repeat_password != password.length) {
return { status : 3, message: 'Password and repeat password mismatch' };
}
var email = $('#email').val();
if (check_email_format(email)) {
return { status : 4, message: 'Invalid email' };
}
\.\.\.
return { status : 0, message: 'OK' };
}
复制代码
优化后
var meta_create_user = {
form_id : 'create_user',
fields : [
{ id : 'name', type : 'text', min_length : 3 },
{ id : 'password', type : 'password', min_length : 8 },
{ id : 'repeat-password', type : 'password', min_length : 8 },
{ id : 'email', type : 'email' }
]
};
var r = check_form(meta_create_user);
复制代码
所以代码复杂度的原因
- 业务逻辑的复杂度决定了代码的复杂度
- 控制逻辑的复杂度 + 业务逻辑的复杂度 => 程序代码的混乱不堪
- 绝大多数程序复杂的根本原因:业务逻辑与控制逻辑的耦合
So,如何分离Logic 和 Control?
- State Machine
- 状态定义
- 状态变迁条件
- 状态的Action
Redux
- DSL - Domain Specific Language
- Html、Sql、正则表达式、AWK、Unix Shell Script……
- 编程范式
- 面向对象:委托、策略、桥接、修饰、Ioc/DIP、MVC……
- 函数式编程:修饰、管道和封装
- 逻辑推导式编程:Prolog
总之
Logic部分才是真正有意义的(What)
Control部分还是影响Logic部分的效率 (How)
拓展链接
- DSL语言 draveness.me/dsl
- JavaScript博客 github.com/mqyqingfeng…