架构设计或面向对象程序设计的主要是职责就是识别对象和定义它们之间的基于时序的交互。
设计原则帮助我们设计合理的,健壮的系统结构。
设计模式提供了常用或特定场景下,可供借鉴的系统交互模式。
UML2是一套符号语言,用来描述识别和定义的内容。
封装
任何代码块不管多复杂,无论其独立存在还是作为系统的一部分,根据单一职责原则,其要完成的任务目标是明确的。
任何代码块能够被其他系统使用,其接口不可能是无限复杂并且毫无关联的。越是被需要,那么其接口成熟度越高,越是需要易于使用。
代码块对外的接口应当是简单的,其复杂性被封装在内部。面向对象的封装,就是关注模块的边界,而不是内部的实现。这也体现了接口隔离原则。
可以用长方形框来表示模块。它们之间存在着关联关系,最主要的还是调用和依赖关系,使用有向线段连接。
使用线框图,在一些层次上,是可以表明清楚这个系统的核心逻辑的。
许多人诟病的ppt架构师,他们可以用一些列线框图将系统说明清楚,其实不是谁都能做到的。
合理的抽象水平和抽象粒度,没有足够的经验是做不到的。
局限性
每一个模块都有局限性。它不能解决全部的问题,并且它也解决不了自己带来的问题。
它解决不了的问题,就需要交给它的上下文去处理。
上下文解决不了的问题,那么就交给上下文的上下文去处理。
自己解决不了,就抛给上面。往上抛是解决问题的重要方法。
抛异常也是抛,自己完成不了的功能让上面去处理也是抛。
软件熵、可维护性与代码重构
软件的结构承载着功能。随着需求的迭代,软件所承载的功能必然不断变动,或累加或减少等。
随着时间的发展,如果软件的结构不适时的改变,则无法承载其系统设计的要求。
存在一个软件熵的概念,指软件在经过不断修改后,无序程度提高的现象。
软件的功能在不断累加之后,其复杂度也开始凸显。虽然许多信息软件不涉及到算法层面的难度,
但其结构变得庞大臃肿无序,让人理清其中各种逻辑线条,着实让人感到实在麻烦。
无序程度和复杂度提高以后,软件的可维护性急剧下降,后续的软件修改工作推进困难。
代码重构就是针对可维护性降低的情况,渐进的降低软件熵,提高可维护性,并为系统增加新特性。
代码重构具体实施就是对软件的结构进行渐进性的调整。
为了控制软件无序程度的发展,我们对软件的结构进行规划,让其发展后可以依然保证基本良好、基本清晰的边界。
规划或设计一般是进行分层和分块,进行逻辑上的区隔,各层各块各司其职,总体上满足高内聚低耦合的特点。
并且在时序上化解难度,一步干不了,分步进行,甚至是抽取通用逻辑,通过复用来降低成本,提高效率。
不过复用会导致链相交,所以复用模块的健壮性要求是很高的。
面向对象的设计模式,提供了可借鉴的模块间交互时序的样例,也为分层分块提供了思路。
确立代码中不易变和易变的部分。一般底层模块,公共模块,通用机制或框架里的代码,都是不易变的,要对其变化做管理。
而易变的部分,要想办法放在链的后端,甚至是设计成一次性的代码,可以随时删除或替换的。
原子化功能
软件的众多功能最好是原子化的,满足组合原则,可以通过组合达成软件目标。
一个功能就是经过一段时序处理,达成一个主要目标,可能同时达成多个次要目标。
可以认为,一条调用链主链负责达成主要目标,而其产生的支链则负责达成次要目标。
调用链从头到尾执行达成软件目标。这里有一个点需要说一下,就是控制流的控制权问题。
控制流的控制权
控制流从调用链的开头往后执行,直到执行完毕。
如果在中间的某个环节,依赖其他模块的未来的某个数据,那么该调用链在时序上要么将控制权交出去然后挂起等待被告知数据(时序上不可控),要么继续持有控制权然后轮询(时序上可控)。
原子化的功能的时序应当是清晰可控可描述的,是可以通过时序图进行描述的。
调用链在时序上应当是可控的,这样可以保证原子化的功能达成。
调用链在执行的时候,总是依赖执行前就已存在的数据,那么这些数据是调用链依赖的静态数据。
如果依赖一些可能发生变化的数据,那么这些数据是调用链依赖的动态数据。
依赖动态数据,要么自己去获取,要么把控制权交出去,让别人告诉自己。为了保证时序可控,我们应当采取主动获取的方式。万不得已采用交出控制权的方案。
控制权在某个位置交出去,然后交回来的时候,重新从挂起的位置开始执行,也是需要设计一套机制去实现的。
JS中的回调,会导致形式上时序不可控(无法从挂起的位置重新开始),而使用async/await把形式转为时序可控的,是一个比较好的实践方案。
(持续完善中,仅供交流,不喜勿喷~o~)