基于 CSP 的设计思想和 OOP 设计思想的异同


声明:本文转载自https://my.oschina.net/linker/blog/1507315,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

Go语言推崇的CSP编程模型和设计思想,并没有引起大部分人,包括Go的标准库的作者的重视。标准库的很多设计保留了很浓的OOP的味道。

需要强调的是OOP并没有什么特别的不好的,相反OOP具有巨大的优势,就是容易设计。

CSP之所以有存在的价值是因为,虽然设计上会改变即有的思路,耗费较多的脑力,但是在设计方案的简单和容易扩展方面具有巨大的优势。

下面,我们来看一个例子,如果我们有一个项目,需要做一个TCP连接中继器(请原谅我的用词)。那么我们按照OOP来设计下:

1. 系统的结构:需要有一个客户端和一个服务器端。分两个进程分别跑在不同机器上。

2. 系统对象关系拆分(这里有所简化,E-R图等省略):连接中继器类--系统的主类、config类--描述配置的类、connection类--每个连接一个conn类的实例、pipe类--提供一个管道,把上游的连接和下游的连接打通,把数据从A--pipe--B、encrypt工具类,提供各种加解密工具。

3. 理清楚系统中各种对象(类)的作用关系,设计接口的细节。这里的接口,其实就是对象之间相互发送的同步阻塞的消息。

4. 设计错误处理,日志等。

5. 从性能方面审视整个设计,优化。

=====

好,我们再用CSP的思路来设计下,是这么一个过程:

1. 我们需要一个 main 协程来处理各种命令行参数的配置,收集处理配置文件; 如果是server,那么初始化server的主协程 -- tcpRemote;如果是client,则启动client的主协程 -- tcpLocal;

2. 分别设计 tcpRemote和tcpLocal。tcpRemote 是server主协程,负责在指定的端口监听,如果有新的连接连入,则启动一个协程处理 -- serverConn ; tcpLocal是client的主协程,负责在客户机的指定端口监听,如果有连入连接,则启动一个协程处理 -- clientConn;

3. 分别设计 serverConn和clientConn。serverConn 处理新连入连接的请求,把连接交给shadow函数处理得到一个装饰后的连接,析出目的Addr,发起连接到远端,再交给 relay 函数处理。clientConn 处理新连入的连接请求,析出目的地址, 发起一个新的到server的连接,把连接交给shadow函数处理得到一个装饰后的连接,转发请求给server,接下来的工作交给 relay 函数来处理。

4. shadow函数,接受一个连接返回一个连接,标准的装饰器。由于需要实现多种加密算法,所以shadow函数有很多实现。程序启动时,在初始化阶段根据配置,注册好对应的shadow函数实现。

5. relay函数,一个桥接器,接受两个连接,异步转发两个连接中 上行 和 下行 的数据。分别用两个协程来实现数据的 上行 和 下行 的转发逻辑。

6. 设计错误处理,日志等。

7. 从性能方面审视整个设计,优化。

=====

OOP部分写的比较简略,但是设计思路还是能看出来的,OOP的设计 核心的围绕点是系统中的对象的种类、职责以及相互的关系;OOP在低并发的时代诞生,对于系统中动力分配是不怎么重视的。在遇到具有共性的点的时候,OOP多是用接口的形式表达,多个不同类实现同一个接口。

CSP的设计 核心的围绕点,是系统中的动力源,也就是系统中动力的来源。动力源在Go语言里就是goroutine;由于goroutine往往是通过闭包函数创建出来,所以闭包函数捕获的upvalue等,也就成了父goroutine和子goroutine之间的一种隐藏的协议。更重要的,每个协程,本质上就是在调度发生时,自动把 上下文 保存起来的回调函数。这大大简化了状态的维护工作。在遇到具有共性的点的时候,CSP是多用装饰器和桥接器,把系统中的共性用函数的参数表达出来。

这两种设计中,接口和函数其实可以相互转换,一个接口只有一个方法的等价于一个函数;而几个函数构成了固定的组合,在Go里面等价于实现了某个接口。所以,这种对共性抽象的方法并没有太大的差别,甚至有人就推崇在Java中,一个接口就只有一个方法。

=====

其实无论OOP/FP/CSP/Actor模型,都是可以相互转换替换和实现。问题的核心就是隐性表达或显性表达。

FP/CSP/Actor中大量用闭包,其实就是把OOP的结构体交给编译器去自动生成而已,每个闭包函数捕获的upvalues在各种支持闭包的语言中,多是交给编译生成一个特殊命名的结构体,并在闭包传递时一并生成实例传递而已。这样就使得一些地方用于消息传递的结构体可以省略,很多时候在chan中传递一个func()比传递一个消息更加的方便

OOP中的方法调用本质上就是一种同步阻塞的消息传递,这点在ObjC中表现的非常清楚,ObjC中每个方法调用本质上就是发送了一条消息给某对象(sendmsg是一个变参数函数)。OOP隐形约定了,所有的进程内的语言运行时级别的消息传递都是同步阻塞的。而Go/Erlang等CSP/Actor模型的语言,打破了这一点,提供了语言级别的异步非阻塞的消息传递。

=====

总结,OOP、FP、CSP、Actor等思想,其实都是在做取舍,究竟要隐藏那些细节暴露那些功能。如果什么都不考虑,那就是汇编了(近似的说法)。没有最优的设计思想只有合适的设计思想。

但是如果我们把软件设计分成 装配、动力驱动、可变性 三个方面考虑。

OOP的装配工作量比CSP要大的多。每个接口的每个方法都可以看成是一个螺丝,只有你紧固了每一个螺丝,OOP设计的软件才可以运行。而CSP设计的程序,每一个协程的创建,都是一个装配点,仰赖方便的闭包机制,装配所需螺丝是一次性自动紧固的。这就是CSP在设计上的优势之一吧。

在动力驱动方面,OOP由于假设了方法调用是同步阻塞的消息传递,其动力驱动也比较原始,大部分是依赖操作系统提供的线程和进程机制。但是CSP则提供了异步的非阻塞消息机制,以及自动上下文保存的可中断函数(也就是协程)。这些机制使得CSP的动力驱动简单高效。

在可变性方面,OOP的合约是由接口和结构体来约束的,而CSP的合约是由函数签名和闭包的upvalues来约束的。函数的参数和返回值可以都是空,只用upvalues来隐式表达约束。因此CSP在可变性方面也是更优秀的。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

本文发表于2017年08月13日 14:36
(c)注:本文转载自https://my.oschina.net/linker/blog/1507315,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 2168 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1