关于设计模式:正交设计

65次阅读

共计 5121 个字符,预计需要花费 13 分钟才能阅读完成。

前置介绍

Kent Beck(肯特·贝克)

设计模式

重构

《重构:改善既有代码的设计》,代码坏滋味

极限编程

一个轻量级的、乖巧的软件开发办法;同时它也是一个十分谨严和周密的办法。它的根底和价值观是 交换、奢侈、反馈和勇气 ;即,任何一个软件我的项目都能够从 四个方面 动手进行改善:增强交换;从简略做起;寻求反馈;敢于捕风捉影。XP 是一种近螺旋式的开发方法,它将简单的开发过程合成为一个个绝对比较简单的小周期;通过踊跃的交换、反馈以及其它一系列的办法,开发人员和客户能够十分分明开发进度、变动、待解决的问题和潜在的艰难等,并依据理论状况及时地调整开发过程。

麻利软件开发 的一种,极限编程和 传统方法学 的实质不同在于它 更强调可适应性 以及面临的艰难。

测试驱动开发

英文全称 Test-Driven Development,简称 TDD

简略设计准则

 Kent Beck 给出的答案:

  1. 通过所有测试(Passes its tests)
  2. 尽可能打消反复 (Minimizes duplication)
  3. 尽可能清晰表白 (Maximizes clarity)
  4. 更少代码元素 (Has fewer elements)

以上四个准则的重要水平顺次升高。

这组定义被称做 简略设计准则

1、通过所有测试(实现性能)

这里提到的测试,真正的意思是 客户验收 。如果你的我的项目通过了客户的所有 验收条件 (Acceptance Criteria),那就阐明你们曾经实现与客户约定的全副需要。至于验收形式是靠 人工 还是靠 自动化 测试则无关紧要。

所以,这句话强调的是 对外部需要——包含功能性需要和非功能性需要——正确的实现。

2、尽可能打消反复(易于重用)

反复,意味着 低内聚,高耦合 。而打消反复的过程,也就意味着是让软件走向 高内聚,低耦合 ,达到良好 正交性 的过程。辨认和打消反复,对于加强软件应答变动能力的重要水平,怎么强调都不为过。

不过,并不是所有的反复都能够打消:比方,C++ 一个源文件里对外部公开的类,其每个 public 办法原型,除了在源文件里定义时,须要申明一次,还须要在头文件里再次申明。这样的反复,是语言机制的要求,无奈打消。

因此,这条准则被形容为 最小化反复 ,而不是 打消反复

3、尽可能清晰表白(易于了解)

清晰性,指的是一个设计容易了解的水平。留神:这不仅仅是对 整洁代码 (Clean Code)及 申明式设计(Declarative Design)的强调。

#### 申明式设计

申明式设计,形容想要让一个事物达到的指标状态,由这个工具本人外部去解决如何令这个事物达到目标状态。

命令式设计 或者 过程式设计,形容的是一系列的动作,这一系列的动作如果被正确执行,最终后果将达到咱们冀望的指标状态。

申明式设计和命令式设计的区别

申明式设计,是通知计算机你想要什么,由计算机本人去设计执行门路,如 SQL;

命令式设计,是间接向计算机收回服务器要执行的操作命令;

相比于更 关注状态和后果 的申明式设计形式,命令式设计形式 更强调过程

幻数

magic number,弊病:代码可读性差 批改不不便

4、更少代码元素(没有冗余)

这一条是点睛之笔,正是因为它的存在,这组准则才被称做 简略设计准则,从而区别于其它设计准则。

在这里,常量,变量,函数,类,包 …… 都属于代码元素。代码元素的数量,通常反映了设计的复杂度。因此,这句话强调的是:尽可能升高复杂度,放弃简略

总结

简略设计准则,通过对 需要 易批改性 可了解性 复杂度,这四个在设计决策中最要害的因素给出了排序,让简略设计不再一个语义含糊的口号,而是对设计决策给出了清晰判断规范。

正交设计

「正交」是一个数学概念:所谓正交,就是指两个向量的内积为零。简略的说,就是这两个向量是垂直的。在一个正交零碎里,沿着一个方向的变动,其另外一个方向不会发生变化。为此,Bob大叔将「职责」定义为「变动的起因」。

「正交性」,意味着更高的内聚,更低的耦合。为此,正交性能够用于掂量零碎的可重用性。

1、一个出发点

软件设计的目标只有一个:性能实现。这是一个软件存在的根本原因。

但随着 软件越来简单 ,繁多过程的复杂度曾经 超出掌控极限 。这逼迫人们必须对大问题进行合成, 分而治之

这就是 模块化设计 的最后动机。

2、两个问题

一旦开始进行进行 模块化拆分,就必须解决如下两个问题:

  1. 到底软件模块该 怎么划分才是正当的?
  2. 将一个大单元划分为多个小单元之后,它们之间必然要通过 连接点 进行单干。如果咱们把这些连接点看作 API,那么问题就变为: 怎么定义 API 才是正当的?

更简略的说:怎么分 ?而后再 怎么合

而这两个问题的答案,正是古代软件设计的外围关注点。

3、三方关系

为了找到这两个问题的答案,咱们须要从新回到最后的问题:为何要做软件设计?

Kent Beck 给出的答案是:软件设计是为了让软件在长期范畴内容易应答变动

在这个精炼的定义中,蕴含着三个关键词:长期 容易 变动。这意味着:

  1. 越是须要长期保护的我的项目,变动更多,也更难预测变动的形式;
  2. 软件设计,事关老本;
  3. 如何在难以预测的变幻无穷中,放弃低廉的变更老本,正是软件设计要解决的问题。

对此,Kent Beck 提出了一个更为精炼的准则:部分化影响 。意思是说, 咱们心愿,任何一个变动,对于咱们以后的软件设计影响范畴都能够管制在一个 尽量小的部分

如何能力做到?

内聚与耦合

一个易于应答变动的软件设计应该听从 高内聚,低耦合 准则。

内聚性:关注的是一个 软件单位外部 关联严密水平 。因此 高内聚 谋求的是 关联严密的事物应该被放在一起 ,并且 只有关联严密的事物才应该被放在一起。简略说,就是 Unix 的设计哲学:

Do One Thing, Do It Well。

耦合性:强调两个或多个 软件单位之间 的关联严密水平。因此 低耦合 谋求的是,软件单位之间尽可能不要相互影响。

对应最后的两个问题:

  1. 当咱们 划分模块 时, 要让每个模块都尽可能 高内聚;
  2. 而当咱们 定义模块之间的 API时,须要让单方尽可能 低耦合

除了 内聚 耦合 之外,下面这幅图还揭示了另外一种关系:正交 。具备 正交 关系的两个模块,能够做到一方的变动不会影响另外一方的变动。换句话说,单方各自单独变动,互不影响。

这幅图的右侧,正是咱们模块化的指标。它形容了 永恒的三方关系 客户 API 实现 ,以及它们之间的关系。这个三方关系图清晰的指出了咱们应该关注的 内聚性 耦合性 ,以及 正交性 都产生在何处。

4、四个策略

策略一:打消反复

对于 齐全反复 的代码进行打消,合二为一,会让零碎更加 高内聚、低耦合

对于模块之间存在 局部反复 的。如下图所示,两个模块存在着局部反复。站在零碎的角度看,它们之间存在着 不变 的局部(即反复的局部);也存在 变动 的局部(即差别的局部)。这意味着这两个模块都存在 两个变动起因或两重职责

对于这一类型的反复, 比拟典型的状况有两种:调用型反复 ,以及 回调型反复 。它们的命名来源于: 在反复打消后,反复与差别之间的关系是调用,还是回调

回调型反复 的打消,也是一个进步零碎 可扩展性 的过程。

拆散不同变动方向

拆散不同变动方向 ,指标在于进步 内聚度 。因为 多个变动方向,意味着一个模块存在多重职责。将不同的变动方向进行拆散,也意味着各个变动方向职责的单一化。

对于变动方向的拆散,也失去了另外一个谋求的指标:可扩展性

打消反复 拆散不同变动方向 是两个高度类似和关联的策略:

它们都是关注于如何对原有模块进行拆分,以进步零碎的 内聚性 。(尽管同时也往往随同着 耦合度 的升高,但这些 耦合度 的升高都产生在别处,并未涉及该如何定义 API 以升高客户与 API 之间 耦合度)。

打消反复:两个模块内有反复代码。

拆散不同变动方向:同一个模块内,有不同分支操作。

放大依赖范畴

后面两个策略解决了软件单元该如何划分的问题。当初关注模块之间的 粘合点——即 API——的定义问题。

须要强调的是:两个模块之间并不存在耦合,它们的都独特耦合在 API 上。因此 API 如何定义能力升高 耦合度,才是咱们应该关注的重点。

从这幅图能够看出,对于 API 定义所带来的耦合度影响,须要遵循如下准则:

  • 首先,客户 实现 模块的数量,会对耦合度产生重大的影响。它们数量越多,意味着 API 变更的老本越高,越须要花更大的精力来认真斟酌。
  • 其次,对于影响面大的 API(也意味着耦合度高),须要应用更加弹性的 API 定义框架,以有利于向前兼容性。

而具体到策略 放大依赖范畴,它强调:

  1. API 应蕴含尽可能少的常识。因为任何一项常识的变动都会导致单方的变动;
  2. API 也应该 高内聚,而不应该强制 API 的客户依赖它不须要的货色。

向稳固的方向依赖

耦合的最大问题在于:耦合点的变动,会导致依赖方跟着变动。但这也意味着,如果耦合点从来不会变动,那么依赖方也就不会因而而变动。换句话说,耦合点越稳固,依赖方受耦合变动影响的概率就越低。

由此,失去最初一个策略:向着稳固的方向依赖

那么,到底什么样的 API 更偏向于稳固?不难晓得,站在 What,而不是 How 的角度;即 站在 需要 的角度,而不是 实现形式 的角度定义 API,会让其更加稳固。

而需要的提出方,肯定是 客户端 ,而不是 实现侧。这就意味着,咱们在定义接口时,应该站在客户的角度,思考用户的实质须要,由此来定义 API。而不是站在技术实现的不便水平角度来思考 API 定义。

而这正是 封装 信息暗藏 的要害。

这四个策略,前两者聚焦于 如何划分模块 ,后两个聚焦于 如何定义模块间的 API

这四个策略的背地能源十分明确:变动驱动 。前两者,都是在明确的变动方向被第一次辨认之后,进行策略使用,以让模块在变动背后越来越 高内聚。而后两者,则是在模块职责拆散之后,须要定义模块间 API 时, 尽可能思考不同的 API 定义形式对于依赖单方的影响。

因为这四个策略致力于让零碎朝着更具 正交性 的方向演进,因此它们也被称做 正交四准则 , 或者 正交策略

总结

先从 一个出发点 登程:为了升高软件复杂度,晋升可重用性,咱们须要 模块化

由此失去了 两个问题 :模块划分必然要解决如何划分, 以及模块间如何合作(API 定义) 的问题。

基于软件易于应答变动的角度登程。高内聚, 低耦合 准则是最为外围和要害的高层准则。基于此咱们失去了在模块化过程中, 咱们真正须要关注的 三方关系

为了让 高内聚、低耦合 更具指导性和操作性,咱们提出了 四个策略 。它们以 变动驱动 ,让零碎逐渐向更好的正交性演进的策略,因而也被称做 正交策略 正交准则

正交设计与 SOLID

正交设计,是广泛的设计准则,与粒度无关,与编程范式无关,更与具体的实现语言无关。

  1. 软件模块该如何划分?
  2. 模块间 API 该如何定义?

软件模块划分应该以基于 信息暗藏 为目标,以 职责划分 为伎俩,从而 封装变动 ,让软件更加容易批改(即Kent Beck 的现实:部分化影响)。

变动 及应答 变动,是软件设计最大的挑战,目标和意义。

SOLID

面向对象设计五个根本准则:繁多职责 凋谢关闭 里氏替换 接口隔离 依赖倒置

正交准则与 SOLID 的关系

繁多职责 凋谢关闭 ,更多的在强调类划分时的 高内聚 ;而 里氏替换 依赖倒置 接口隔离 则更多的强调类与类之间合作接口(即 API)定义的 低耦合

繁多职责

繁多职责 ,通过对 变动起因 的辨认,将一个承当多重职责的类,一直宰割为更小的,只具备繁多变动起因的类。繁多职责 准则自身,并没有明确批示咱们该如何断定一个类属于 繁多职责 的,以及如何达到繁多职责的状态。而策略 打消反复 拆散不同变动方向 ,正是让类达到 繁多职责 的策略与路径。

凋谢关闭

凋谢关闭 准则,正是通过将 不同变动方向 进行拆散,从而达到对于曾经呈现的变动方向,对于批改是关闭的,对于扩大是凋谢的

里氏替换

里氏替换 准则强调的是,一个 子类 不应该毁坏其 父类 客户 之间的契约。唯有如此,能力保障:客户与其父类所裸露的接口(即 API)所产生的依赖关系是稳固的。子类只应该成为暗藏在 API 背地的某种具体实现形式。

依赖倒置

依赖倒置 准则则强调:为了让依赖关系是稳固的,不应该由实现侧依据本人的技术实现形式定义接口,而后强制下层(即客户)依赖这种不稳固的 API 定义,而是应该站在下层(即客户)的角度去定义 API(正所谓 依赖倒置)。

然而,尽管接口由下层定义,但最终接口的实现却仍然由上层实现,因而 依赖倒置 形容为:下层不依赖上层,上层也不依赖下层,单方独特依赖于形象

接口隔离

接口隔离 准则强调的是:不应该强制客户依赖它不须要的货色 。显然,这是 放大依赖范畴 策略在面向对象范式下的产物。

总结

正交设计 是一种与范式,语言无关的设计准则。为了解决在 模块化 的过程中,如何让软件在长期范畴内更容易应答变动。

面向对象 是一种对于模块化进行良好反对的范式。通过 高内聚,低耦合 准则,或 正交策略 的使用,面向对象 范式下 SOLID 准则会天然的浮现。

正交的 React 组件

正交 React 组件的益处

参考

  • 正交设计准则与 SOLID
  • 变动驱动:正交设计
  • 简略设计准则
正文完
 0