连载35:软件体系设计新方向:数学抽象、设计模式、系统架构与方案设计(简化版)(袁晓河著)

分离机制

 

“道生一、一生二、二生三、三生万物”《道德经》

分离机制的目标是为了更好的解除系统的耦合,规范其处理的机制。然而,我们不要将此理解为一切都是为了分离,需要明白的是,我们的任何分离的最终目的就是为了更好的合并。分离和合并是达到目标而使用的方法和手段,是矛盾的两个方面。如果没有根据的“分”,那么其就无法达到有效的“合”,所以分合是相生相克的。闲话一点:道德经中为了更好的阐述这样的原则,所以是用“生”来表达。其实就是要理解“分合”的统一性。

所以,下面所举的几个维度上的分离,最终也要求将部分进行独立以后进行机制化处理,让其不耦合于具体的分化,通过独立出机制以后,具有能够融入更多的具体划分的能力,这也是相辅相成的过程。

关注点分离:

解耦的根本目的和效果是:

1、 把变与不变隔离,把相互独立的不同的变化隔离。

2、 关注点隔离(有利于项目并行开发,独立维护)

好的架构从需求开始,需求是驱动力。

软件演进需要同时关注商业和技术两个视角,因为刺激来自于这两个方面,如环境、组织、进程、技术和利益相关人的需要,这些变更刺激反应为软件结构或(和)功能,通常一个业务系统的复杂性来自于并发控制盒异常处理两个方面。

基于事件的发布  订阅模型

发布者和订阅者在三个层面进行解耦:

1、 时间上解耦:发布者和订阅者不必同时在线,它们之间不必同时参与交互。

2、 空间上解耦:发布者和订阅者不必相互知道对方所在的位置,发布者和订阅者不需要拥有直接到对方的引用,也不必知道有多少订阅者或发布者参与交互。

3、 同步上解耦:发布者和订阅者是异步模式,发布者不断的产生事件,而订阅者则可以异步的得到产生事件的通知。

订阅发布机制的解耦,消除了依赖,增加了系统的可扩展性

 

数据结构和操作分离

 

下面我们讨论分离数据结构和操作机制。

这个机制体现的最为明显的是STL,虽然在面向对象中将数据和操作捆绑在一起,形成一个对象的整体,但是这个世界永远都是殊途同归,在STL中将数据结构和操作完全分离,也一样能够达到目的,而且也开创了一个不一样的新天地。

在面向对象中将数据与算法结合起来,形成我们概念上认识的对象。但是对对象的认识方式是我们唯一的方式吗?答案是否定的,在STL中展示的是另一种认识方式,这种方式是将所有事物的共性抽象出来(不是面向对象中将对象体抽象出来),这些事物包含形态(数据结构)和操作(算法),也就是说形态和操作都可以独立地被抽象出来,它们其实并非相互依赖,也就是说是独立存在的,于是在STL中大家可以看到有容器的概念,也有函数对象(操作的抽象)的概念,由于不是相互依赖所以这两个概念就是可正交的,可以分属于不同维度的变化,就有点像坐标中的X,Y。这样划分以后,大家的代码实例化以后就是将XY赋值,两个维度上就形成具体类(包括数据结构和算法)。我们说这种处理方式就是一个泛化的过程(一般化的过程),于是开拓出一个新的领域---泛型编程。

当然,这里如果我们针对操作进行进一步的抽象和处理,那么可以让之转换为算法的处理,由此数据结构和操作的分离,就可以转换为数据结构和算法的相互分离。而在泛型编程中这种思想体现的非常突出。

使用这样分离的优势就在于数据结构和操作独立变化,只要支持相互之间连接的处理,例如迭代器、函数对象等,则就能达到独立变化的目的。分离以后能够让我们具有选择的好处,一旦此数据结构或算法不能满足要求,则我们可以进行替换,而且能够达到替换以后也能够正常的运转。

 

控制与业务分离


如果将控制和业务两者混杂在一起,那么会导致因为业务上的功能变化(扩展原有业务能力)或控制方式的变化(例如集中方式转化为分布式方式),都会导致散弹式的修改代码,此时我们应该怀疑我们已经将业务和控制耦合在一起。那么如何进行分离呢?其实主要的是如何识别哪些是业务,哪些是控制处理,这里让我们先从最底层的代码来看看。

这样我们通过接口的方式将业务处理进行抽象,让控制和业务在类的级别上进行分离,当然在更高的层次上,我们也可以通过使用相关的组件方式,将业务处理独立成一个特性模块,通过动态加载方式来进行加载,这样能够更好进行控制和业务分离。

然而,很多时候我们是很难将控制和业务区别开的,因为业务需要受环境等影响,例如我们在客户机上进行缓存,于是我们需要在业务处理中进行一个CRC校验,或者进行签名验证,此时这个校验是业务还是控制,很难进行区分,但是这里有一个好的处理方式就是讲这种具有模棱两可的东西独立成新的特性,通过中间处理的方式和控制以及业务进行交互。

 

目标和策略分离

 

当这些列表中的记录处理大体一致,但是还存在一些较少的差异时,此时解决方式使用再注册的方式。但这个接口不能混入策略接口中,因为其不属于策略接口,其判定的原则就是这个接口是否是策略调用者可选的,如果可选则其为策略信息,而此处理应该是不同记录信息间在处理机制的不同,如图3-5所示:

 连载35:软件体系设计新方向:数学抽象、设计模式、系统架构与方案设计(简化版)(袁晓河著)

 

3-5

 

实现这种机制和策略的分离有很多方法,而且很多时候如果我们换一种角度去看也许更能有一种豁然开朗的感觉。上的分离方式是按照strategy的设计模式来处理的,其实我们使用一个“躺”作的proxy方式也能实现这种分离,如图3-6所示。

 连载35:软件体系设计新方向:数学抽象、设计模式、系统架构与方案设计(简化版)(袁晓河著)

 

3-6

 

机制和策略分离中我们需要明确,这种分离以后,机制往往是一些非常原始的操作行为,这样行为可能正在策略方面看来是多么的简单和不可思议。

例如,在操作系统中计算机是如何做出并发处这样的,在底层,机制处理其实就是一种分时的处理机制,将系统化成不同时间片的处理,而策略就呈现五花八门的情况,例如可以使用进程进行并发的处理,也可以采用线程的并发,而针对这些进程间或者线程间的调度可以采用最适合当前应用环境的策略,或者可以将线程划分为内核态和用户态不同的层次,然后通过保护现场的方式,来连接机制和策略的共同作用,所以在这样的处理中机制其实需要非常的“淳朴”,不需要过多的考虑如何进行策略变化。而在现实中我们往往在考虑机制的过程中过多的考虑了策略的变化,导致了整个系统分离程度不够而导致系统非常高的耦合。那么我们有哪些更值得推荐的方法来进行机制和策略的建立以及相互之间界限的划分和分离呢?

首先,机制最好是完整的和紧凑的,就像我们数学中的实数一样。很多时候我们对其进行的处理机制最好是对称的,因为对称的方式才能让策略展开灵活性,同时也是紧凑的,多余的处理是不存在的,这些机制的设计多一个和少一个都是不正确的,而且相互之间是独立的。

其次,机制的操作最好是原始的,考虑的是其最基本的元素。

再次,机制和策略最好能够有一个明晰的分层,而且此层次中,机制的内部实现和策略的内部实现可以不用暴露给对方,要有封装,需要提供的服务收拢为单一的接口,至少机制可以使用接口方式来提供,这样其耦合性就会相对更小。

 

动态和静态分离

 

将动态和静态结合在一起,其实更能够完成一些绝妙的好处。例如java语言中的反射机制,就是通过动态的方式,获取到静态的类型信息,通过这样的处理,能够在动态的方式下进行动态类的创建。

将静态和动态进行分离的一个优势之一就是需要利用静态的方法进行检测和验证,如果是在动态情况下,所花费的成本高昂,取得的效果却非常低下。而如何进行有效的分离呢?那就是如何更好的通过静态来“置换”动态。

过去的很多描述都是静态结构,其结构决定了其架构,但是其静态结构不是全部,动态结构是一个更广阔的话题,在我们所涉及的结构中有哪些是动态结构呢?如何将静态结构转化为动态结构呢?其动态结构是否具有不断的平衡和演化的能力?

首先要有动态结构的机制:1MFC的动态类(继承*)。2Java中依赖注入。这里我们如何设计出能够根据需求和运行时实际的变化能够将其结构发生变化以适应新的环境呢?

从对这些动态结构的分析,然后反向给出这些机制,提供新的需求和问题,让机制更加完善和有效。

ü 继承与组合相互之间的动态演变。

ü 关注点分离在动态结构中的演变,一旦其某个横切点上发生变化,于是就产生策略的设计模式结构。

抽象工厂的缺点,难以支持新种类的产品,难以扩展抽象工厂以生产新种类的产品,这是因为抽象工厂接口确定了可以被创建的产品集合,支持新种类的产品需要扩展该工厂接口,这将设计抽象工厂类及其所以子类的改变。所以STL函数对象,就是一个简单的访问者模式,还是一个command设计模式。

在现在互联网进行的灰度发布和快速扩展,其所使用的设计方法和思想也是将静态和动态进行有效的分离,也就是说不受静态结构的限制,向一些动态加载变化结构,这可有助于进行有效的灰度升级。