动静分离

动静分离是最重要的架构思维之一,是将静态资源与动态资源分离,通过不同的系统进行访问的架构设计方法,在设计架构时一定要注意二者的结合,因为既涉及流程、用例等动态分析内容,又涉及数据、类等静态建模内容,需要高度协作来完成。

动静分离的适用场景如下。

◎ 页面对应的URL通常固定。

◎ 在页面中不包含时间因素、地域因素,以及浏览者相关的因素。

◎ 页面访问量大,服务器负载高、I/O等问题导致用户访问卡顿。

◎ 页面数量大,导致服务器存储空间不够。

◎ 某个文件在特定的时间段内需要高速下载,且并发下载量高。

◎ 不能包含Cookie等私有数据。

动静分离的使用场景如图4.4所示。

程序员架构图技巧(程序员架构修炼)(1)

图4.4

可以看出,动静分离的好处如下。

◎ 减少Web服务器负载,静态文件全部通过CDN进行访问。

◎ 采用价格低廉的云存储服务器,存储费用最低。

◎ 海量存储空间,无须考虑存储架构的升级。

◎ 主要使用CDN流量,流量费用低。

复用

复用是另外一种重要的架构思维,也可以将其理解为SOA参考架构的核心思考模式,包括最近广受关注的业务能力组件化、组件能力服务化、平台 应用、共享中心建设、共性能力下沉等都是复用的表现。即使设计一个小系统,也需要将各个模块需要用到的共性功能抽取为可复用的共性组件。

好 的 系 统 设 计 具 备 可 扩 展 性 ( Extensibility ) 、 灵 活 性(Flexibility)和可插入性(Pluggability)。一个复用性较好的系统,就是一个易维护的系统。但实际上,可维护性和可复用性是两个维度的内容。

(1)系统维护:系统维护就是系统的再生。一个可维护性较好的

系统,必须允许新的元素以比较容易和平稳的方式加入已有的系统中,让这个系统不断焕发活力,其系统维护工作应该以容易、准确、

安全和经济的形式进行。造成系统可维护性差的原因如下。

◎ 过于僵硬:指在系统中加入一个新的功能,不管大小都很难,这个新功能会波及很多其他模块,最后造成跨越几个模块的改动。

◎ 过于脆弱:与系统的过于僵硬同时存在的,是系统在修改已有代码时过于脆弱。修改一个地方,会导致系统到处发生故障。

◎ 复用率低:指一个系统的组成部分可以在同一个项目的不同地方甚至不同的项目中重复利用。复用率低,指已有的代码、函数、模块的功能在新的模块或系统中使用时发现依赖很多其他因素,导致很难被再次利用。

◎ 耦合度过高:实现一个需求有两种方式,第 1 种方式是不改变原有设计意图和原有架构设计,这对于未来有利;第2种方式是改变原有设计意图和原有架构设计,这只能解决短期的需求,只是一个权宜之计,会牺牲中长期利益。如果一个系统采用第2种方式比采用第1种方式更容易、更简单,这个系统的耦合度就很高。

(2)系统复用的重要性如下。

◎ 有较高的生产效率。

◎ 有较高的系统质量。

◎ 恰当运用复用可以改善系统的可维护性。

系统复用的分类

我们可以将系统复用分为常规复用和系统层复用。

常规复用可分为如下三种。

(1)代码复用。利用编辑器(IDE)可以很方便地减少抄写代码的人力成本。将一个写好的工具函数自发地复制到另一个功能或项目中,可能是入门级程序员习惯性的自发的复用软件形式,这种方式有着明显的缺点,会造成很多代码的重复、冗余。

(2)算法复用。各种算法如排序算法都已经得到了大量的研究,几乎不需要我们重写自己的算法,各种语言通常也实现了这些常用算法,因此直接复用即可。

(3)数据结构的复用。与算法一样,类似数组、队列、栈、列表等都得到了人们的深入研究,直接复用即可。

系统层复用可分为如下两种。

(1)设计复用。设计复用是比代码、算法、数据结构复用更高级的复用,受实现环境的影响较少,使可复用的组件被复用的机会更多,所需的修改更少。这种复用有如下三种途径。

◎ 从现有系统的设计结果中提取一些可复用的设计组件,并将这些组件应用于新系统的设计中。

◎ 将一个现有系统的全部设计文档都在新的系统上重新实现,也就是将一个设计运用于多个具体的实现中。

◎ 与任何应用都无关,独立设计和开发可复用的设计组件。

(2)分析复用。分析复用是比设计复用更高级别的复用,可复用的分析组件是针对业务领域中某些设计或问题抽象出的组件,受设计技术及实现条件的影响很少,所以可复用的机会更大,复用的途径也有三种。

◎ 从已有系统的分析结果中提取可复用的组件用于新系统的架构设计。

◎ 用一份完整的分析文档作为输入,产生针对不同系统和其他实现条件的多项设计。

◎ 与任何应用都无关,独立设计和开发可复用的分析组件。

可复用性和可维护性的关系

可复用性和可维护性的关系如下。

◎ 适当地提高系统的可复用性,可以同时提高系统的可维护性,就是在保持甚至提高系统的可维护性的同时,实现系统的复用。

◎ 适当地提高系统的可复用性,可以同时提高系统的可扩展性。

系统的可扩展性由“开-闭”原则、里氏代换原则、依赖倒转原则和组合/聚合复用原则保证。

◎ 适当地提高系统的可复用性,可以同时提高系统的灵活性。系统的灵活性由“开-闭”原则、迪米特法则、接口隔离原则保证。

◎ 适当地提高系统的可复用性,可以同时提高系统的可插入性。

系统的可插入性由“开-闭”原则、里氏代换原则、组合/聚合复用原则和依赖倒转原则保证。

可维护性地复用的设计原则

在系统设计中,在支持可维护性的同时,提高系统的可复用性是一个至关重要的问题,如何同时提高一个系统的可维护性和可复用性是面向对象设计需要解决的核心问题之一。在面向对象设计中,可维护性地复用是以设计原则为基础的。每个原则都蕴含一些面向对象设计的思想,可以从不同的角度提升一个系统架构的设计水平。

1.“开-闭”原则:OCP

任何系统都需要面临一个很重要的问题,即它们的需求会随时间的推移而发生变化。当系统需要面对新的需求时,我们应该尽量保证系统的设计框架是稳定的。如果一个系统设计符合“开-闭”原则,那么可以非常方便地对系统进行扩展,而且在扩展时无须修改现有代码,使得系统在具备适应性和灵活性的同时,具备较好的稳定性和延续性。随着系统的规模越来越大,系统的寿命越来越长,系统的维护成本也越来越高,设计满足“开-闭”原则的系统也变得越来越重要。

为了满足“开-闭”原则,需要对系统进行抽象化设计,抽象化是“开-闭”原则的关键,可以为系统定义一个相对稳定的抽象层,将不同的实现行为移至具体的实现层中完成。在很多面向对象编程的语言中都提供了接口、抽象类等机制,可以通过它们定义系统的抽象层,再通过具体的类进行扩展。如果需要修改系统的行为,则无须对抽象层进行任何改动,只需增加新的具体类来实现新的业务功能即可,实现在不修改已有代码的基础上扩展系统的功能,达到“开-闭”原则的要求。

2.里氏代换原则:LSP

在系统中将一个基类对象替换成它的子类对象时,程序不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象,那么它不一定能够使用基类对象。里氏代换原则是实现“开-闭”原则的重要方式之一,由于在使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型定义对象,在运行时再确定其子类类型,用子类对象来替换父类对象。

在使用里氏代换原则时需要注意如下几个问题。

◎ 子类的所有方法都必须在父类中声明,或子类必须实现在父类中声明的所有方法。根据里氏代换原则,为了保证系统的扩展性,在程序中通常使用父类进行定义,如果一个方法只存在于子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法。

◎ 尽量将父类设计为抽象类或者接口,让子类继承父类或实现父类接口,并实现在父类中声明的方法,在运行时用子类实例替换父类实例。我们可以很方便地扩展系统的功能,无须修改原有子类的代码,在增加新的功能时可以通过增加一个新的子类来实现。里氏代换原则是“开-闭”原则的具体实现手段之一。

3.依赖倒转原则:DIP

依赖倒转原则要求我们在程序中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不用具体类做这些事情。为了确保该原则的应用,一个具体类应当只实现接口或在抽象类中声明过的方法,而不用给出多余的方法,否则将无法调用在子类中增加的新方法。

在引入抽象层后,系统将具有很好的灵活性,在程序中尽量使用抽象层进行编程,而将具体类写在配置文件中,这样一来,如果系统行为发生了变化,则只需对抽象层进行扩展,并修改配置文件,而无须修改原有系统的源代码,在不修改的情况下扩展系统的功能,满足

“开-闭”原则的要求。

在实现依赖倒转原则时,我们需要针对抽象层编程,将具体类的对象通过依赖注入的方式注入其他对象中。依赖注入指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。常用的注入方式有如下三种。

◎ 构造注入:指通过构造函数传入具体类的对象。

◎ 设值注入(Setter注入):指通过Setter方法传入具体类的对象。

◎ 接口注入:指通过在接口中声明的业务方法传入具体类的对象。这些方法在定义时使用的是抽象类型,在运行时再传入具体类型的对象,由子类对象覆盖父类对象。

4.接口隔离原则:ISP

当一个接口太大时,我们需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。每个接口都应该承担一种相对独立的角色,不干不该干的事,该干的事都要干。这里的“接口”往往有两种不同的含义:一种是指一个类型所具有的方法特征的集合,仅仅是一种逻辑上的抽象;另外一种是指某种语言具体的“接口”定义,有严格的定义和结构,比如Java语言中的interface。

对于这两种不同的含义,ISP的表达方式及含义都有所不同。

◎ 当将“接口”理解成一个类型所提供的所有方法特征的集合时,这就是一种逻辑上的概念,接口的划分将直接带来类型的划分。

可以将接口理解成角色,一个接口只能代表一个角色,每个角色都有其特定的一个接口,此时,这个原则可以叫作“角色隔离原则”。

◎ 如果将“接口”理解成狭义的特定语言的接口,那么 ISP 表达的意思是,接口仅仅提供客户端需要的行为,而将客户端不需要的行为隐藏起来,应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口。需要将大接口中的方法,根据其职责的不同分别放在不同的小接口中,以确保每个接口使用起来都较为方便,并都承担某个单一角色。接口应该尽量细化,接口中的方法也应该尽量少,在每个接口中只包含一个客户端(如子模块或业务逻辑类)所需的方法即可,这种机制也被称为“定制服务”,即为不同的客户端提供范围不同的接口。

5.组合/聚合复用原则:CARP

在一个新的对象里通过关联关系(包括组合关系和聚合关系)使用一些已有的对象,使之成为新对象的一部分;新对象通过委派调用已有对象的方法达到复用功能的目的。简而言之,在复用时要尽量使用组合或聚合关系(关联关系),少用继承。

在面向对象设计中,可以通过两种方法在不同的环境中复用已有的设计和实现,即通过组合或聚合关系,或者通过继承,但首先应该考虑使用组合或聚合关系,因为该方法可以使系统更加灵活,降低类与类之间的耦合度,使一个类的变化对其他类造成的影响相对较少;

其次考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,滥用继承反而会增加系统构建和维护的难度及系统的复杂度,因此需要慎重使用继承复用。

通过继承进行复用的主要问题在于继承复用会破坏系统的封装性,因为继承会将基类的实现细节暴露给子类,由于基类的内部细节通常对子类来说是可见的,所以这种复用又叫作“白箱”复用,如果基类发生改变,那么子类的实现也不得不发生改变;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性;而且继承只能在有限的环境中使用(例如类没有被声明为不能被继承)。

6.迪米特法则

迪米特法则(Law of Demeter,LoD)指我们在设计系统时,应该尽量减少对象之间的交互,如果在两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法,则可以通过第三方转发这个调用。简而言之,就是通过引入一个合理的第三方降低现有对象之间的耦合度。

运用迪米特法则时,要注意如下几点。

◎ 在类的定义中,应该尽量定义一个松耦合的类。类之间的耦合度越低,类的复用度就越高。一个松耦合的类在被修改时,所涉及的类的范围就会变小。

◎ 在类的声明中,每个类都应当尽量降低其成员变量和成员函数的访问权限。

◎ 在类的声明中,只要有可能,则都应该将一类操作设计成抽象类或接口。

◎ 在对象之间尽量没有直接调用关系。

本文给大家讲解的内容是程序员架构修炼:架构思维的动静分离与复用
  1. 下篇文章给大家讲解的是程序员架构修炼:架构思维的动静分层与模式 ;
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持
,