[读书笔记] 《修炼之道:.NET 开发要点精讲》

《修炼之道:.NET 开发要点精讲》

目录

《修炼之道:.NET 开发要点精讲》

第 1 章 另辟蹊径:解读.NET

1.7 本章思考 > 位置 465

第 2 章 高屋建瓴:梳理编程约定

2.2 方法与线程的关系 > 位置 519

2.7 线程安全 > 位置 595

2.8 调用与回调 > 位置 661

2.9 托管资源与非托管资源 > 位置 666

2.13 协议 > 位置 751

第 3 章 编程之基础:数据类型

3.1 引用类型与值类型 > 位置 844

3.3 赋值与复制 > 位置 1118

第 4 章 物以类聚:对象也有生命

4.1 堆和栈 > 位置 1235

4.2 堆中对象的出生与死亡 > 位置 1272

4.3 管理非托管资源 > 位置 1381

4.4 正确使用 IDisposable 接口 > 位置 1547

4.6 本章思考 > 位置 1587

第 5 章 重中之重:委托与事件

5.1 什么是.NET 中的委托 > 位置 1625

5.2 事件与委托的关系 > 位置 1860

5.3 使用事件编程 > 位置 1940

5.4 弱委托 > 位置 2077

5.5 本章回顾 > 位置 2151

第 6 章 线程的升级:异步编程模型

6.1 异步编程的必要性 > 位置 2171

6.2 委托的异步调用 > 位置 2202

6.3 非委托的异步调用 > 位置 2358

6.6 本章思考 > 位置 2446

第 7 章 可复用代码:组件的来龙去脉

7.1 .NET 中的组件 > 位置 2457

7.2 容器 – 组件 – 服务模型 > 位置 2499

7.3 设计时与运行时 > 位置 2672

7.4 控件 > 位置 2776

第 8 章 经典重视:桌面 GUI 框架揭秘

8.2 Win32 应用程序的结构 > 位置 2841

8.4 Windows Forms 框架 > 位置 3148

8.5 Winform 程序的结构 > 位置 3180

第 9 章 沟通无碍:网络编程

9.1 两种 Socket 通信方式 > 位置 3518

9.3 UDP 通信的实现 > 位置 3840

9.4 异步编程在网络编程中的应用 > 位置 3991

9.6 本章思考 > 位置 4062

第 10 章 动力之源:代码中的 “泵”

10.2 常见的 “泵” 结构 > 位置 4150

第 11 章 规绳矩墨:模式与原则

11.1 软件的设计模式 > 位置 4308

11.5 本章思考 > 位置 4605

第 12 章 难免的尴尬:代码依赖

12.1 从面向对象开始 > 位置 4738

12.2 不可避免的代码依赖 > 位置 4815

12.3 降低代码依赖 > 位置 4936

12.4 框架的 “代码依赖” > 位置 4998

12.6 本章思考 > 位置 5022

作者:周见智;博图轩


第 1 章 另辟蹊径:解读.NET

1.7 本章思考 > 位置 465

1. 简述. NET 平台 中 CTS、 CLS 以及 CLR 的 含义 与 作用。

A: CTS 指 公共 类型 系统, 是. NET 平台 中 各种 语言 必须 遵守 的 类型 规范; CLS 指 公共 语言 规范, 是. NET 平台 中 各种 语言 必须 遵守 的 语言 规范; CLR 指 公共 语言 运行时, 它是 一个 虚拟 机,. NET 平台 中 所有 的 托管 代码 均需 要在 CLR 中 运行, 可将 其 视为 另外 一个 操作系统。

 

第 2 章 高屋建瓴:梳理编程约定

2.2 方法与线程的关系 > 位置 519

只要 我们 确定 了 两个 方法 只会 运行 在 同一个 线程 中, 那么 这 两个 方法 就不 可能 同时 执行, 跟 方法 所处 的 位置 无关。

只可 能 一前一后 执行, 此时 我们 不需要 考虑 方法 中 访问 的 公共 资源 的 线程 是否 安全。

 

2.7 线程安全 > 位置 595

如果 操作 一个 对象( 比如 调用 它的 方法 或者 给 属性 赋值) 为 非 原子 操作, 即可 能 操作 还没 完成 就 暂停 了, 这个时候 如果 有 另外 一个 线程 开始 运行 同时 也 操作 这个 对象, 访问 了 同样 的 方法( 或 属性), 那么 这时 就可能 会 出现 一个 问题:前一 个 操作 还未 结束, 后 一个 操作 就 开始 了, 前后 两个 操作 一起 出现 混乱。当 多个 线程 同时 访问 一个 对象 时, 如果 每次 执行 都得 到 不一样 的 结果, 甚至 出现 异常, 那么 这个 对象 便是 “非 线程 安全”。造成 一个 对象 非 线程 安全 的 因素 有 很多, 除了 前面 提到 的 由于 非 原子 操作 执行 到 一半 就 中断 以外, 还有 一种 情况 是由 多个 CPU 造成 的, 即 就算 操作 没有 中断, 由于 多个 CPU 可以 真正 实现 多 线程 同时 运行, 所以 就有 可能 出现 “ 对 同一 对象 同时 操作 出现 混乱” 的 情况,如图 2- 4 所示。

 

2.7 线程安全 > 位置 627

在 Winform 编程 中, 我们 之所以 经常 会 遇见 “不在 创建 控 件 的 线程 中 访问 该 控 件” 的 异常, 原因 就 是对 UI 控 件 的 操作 几乎 都不 是 线程 安全 的( 部分 是)。一般 UI 控 件 只能 由 UI 线程 操作, 其余 的 所有 操作 均需 要 投递 到 UI 线程 之中 执行, 否则 就会 像 前面 讲过 的 那样, 程序 出现 异常 或不 稳定。

可以 使用 Control. InvokeRequired 属性 去 判断 当前 线程 是否 是 创建 控 件 的 线程( UI 线程), 如果 是, 则 该 属性 返回 false, 可以 直接 操作 UI 控 件, 否则, 返回 true, 不能 直接 操作 UI 控 件。

注:Control 含有 若干个 线程 安全 的 方法 和 属性, 常见 的 主要 有 Control. InvokeRequired 属性、 Control. Invoke 方法、 Control. BeginInvoke 方法( Control. Invoke 的 异步 版本)、 Control. EndInvoke 方法 以及 Control. CreateGraphics 方法。这些 属性 和 方法 都可以 在 非 UI 线程 中 使用, 并且 跨线 程 访问 这些 方法 和 属性 时不 会 引起 程序 异常。

 

2.8 调用与回调 > 位置 661

.NET 平台 开发 中的 回 调 主要 是 通过 委托 来 实现 的。委托 是一 种 代理, 专门 负责 调用 方法。

 

2.9 托管资源与非托管资源 > 位置 666

.NET 中 对象 使 用到 的 非 托管 资源 主要 有 I/ O 流、 数据库 连接、 Socket 连接、 窗口 句柄 等 各种 直接 与 操作系统 相关 的 资源。

 

2.13 协议 > 位置 751

图 2- 11   网络 七 层 协议

 

第 3 章 编程之基础:数据类型

3.1 引用类型与值类型 > 位置 844

通常 值 类型 又 分为 以下 两个 部分。

(1) 简单 值 类型:包括 类似 int、 bool、 long 等. NET 内置 类型。它们 本质上 也是 一种 结构 体。

(2) 复合 值 类型:使用 Struct 关键字 定义 的 结构 体, 比如 System. Drawing. Point 等。复合 值 类型 可以 由 简单 值 类型 和 引用 类型 组成。

 

3.3 赋值与复制 > 位置 1118

引用 类型 的 赋值、 浅 复制、 深 复制 的 区别, 如图 3- 15 所示。

 

值 类型 的 赋值、 浅 复制、 深 复制 的 区别, 如图 3- 16 所示。

 

对象 深 复制 的 过程, 如图 3- 17 所示。

 

3.3 赋值与复制 > 位置 1155

NET 中 可以 使用 “序列 化 和 反 序列 化” 的 技术 实现 对象 的 深 复制, 只要 一个 类型 及其 所有 成员 的 类型 都 标示 为 “ 可 序列 化”, 那么 就可以 先 序列 化 该类 型 对象 到 字 节流, 然后 再将 字节 流 反序 列 化成 源 对象 的 副本。这样一来, 源 对象 与 副本 之间 没有 任何 关联, 从而 达到 深 复制 的 效果。

 

第 4 章 物以类聚:对象也有生命

4.1 堆和栈 > 位置 1235

栈 主要 用来 记录 程序 的 运行 过程, 它有 严格 的 存储 和 访问 顺序;而 堆 主要 存储 程序 运行 期间 产生 的 一些 数据, 几乎没有 顺序 的 概念。

 

4.1 堆和栈 > 位置 1269

堆 跟 栈 的 本质 都是 一段 内存块

 

4.2 堆中对象的出生与死亡 > 位置 1272

栈 中的 对象 由 系统 负责 自动 存入 和 移 除, 正常 情况下, 跟我 们 程序 开发 的 关联 并不 大。

 

4.2 堆中对象的出生与死亡 > 位置 1354

谨慎 使用 对象 的 析构方法。析 构 方法 由 CLR 调用, 不受 程序控制, 而且 容易 造成 对象 重生。析 构 方法 除了 用作 管理 非 托管 资源 外, 几乎 不能 用作 其他 用途。

 

4.3 管理非托管资源 > 位置 1381

GC. SuppressFinalize 方法 请求 CLR 不要 再 调用 本 对象 的 析 构 方法, 原因 很 简单, 既然 非 托管 资源 已经 释放 完成, 那么 CLR 就 没 必要 再继续 调用 析 构 方法。

 

注:CLR 调用 对象 的 析 构 方法 是一 个 复杂 的 过程, 需要 消耗 非常 大的 性能, 这也 是 尽量 避免 在 析 构 方法 中 释放 非 托管 资源 的 一个 重要 原因, 最好 是 彻底 地 不 调用 析 构 方法。

 

4.4 正确使用 IDisposable 接口 > 位置 1547

如果 一个 类型 使 用了 非 托管 资源, 或者 它 包含 使 用了 非 托管 资源 的 成员, 那么 开发者 就应 该 应用 “ Dispose 模式”:正确地 实现( 间接 或 直接) IDisposable 接口, 正确地 重写 Dispose( bool disposing) 虚 方法。

 

4.6 本章思考 > 位置 1587

调用 一个 对象 的 Dispose() 方法 后, 并不 意味着 该 对象 已经 死亡, 只有 GC 将对 象 实例 占用 的 内存 回收 后, 才 可以说 对象 已死。但是 通常 情况下, 在调 用 对象 的 Dispose() 方法 后, 由于 释 放了 该 对象 的 非 托管 资源, 因此 该 对象 几乎 就 处于 “无用” 状态,“ 等待 死亡” 是它 正确 的 选择。

 

第 5 章 重中之重:委托与事件

5.1 什么是.NET 中的委托 > 位置 1625

像 声明 一个 普通 方法 一样, 提供 方法 名称、 参数、 访问 修饰 符 以及 返回 值, 然后 在前面 加上 Delegate 关键字, 这样 就 定义 了 一个 委托 类型。

委托 类型 定义 完成 后, 怎样 去 实例 化 一个 委托 对象 呢?其实 很 简单, 跟 实例 化 其他 类型 对象 一样, 我们 可以 通过 new 关键字 创建 委托 对象。

 

5.1 什么是.NET 中的委托 > 位置 1655

使用 委托 调用 方法 时, 我们 可以 直接 使用 “委托对象( 参数 列表);” 这样 的 格式, 它 等效 于 “ 委托对象.Invoke( 参数 列表)”。

给 委托 赋值 的 另外 一种 方式 是:委托对象 = 方法。

 

怎样 让 一个 委托 同时 调用 两个 或者 两个 以上 的 方法 呢?答案 是 直接 使用 加法 赋值 运算符( =) 将 多个 方法 附加 到 委托 对象 上。

 

5.1 什么是.NET 中的委托 > 位置 1744

委托 内部 的 “链 表” 结构 跟 单向 链 表 的 实现 原理 却不 相同。它 并不是 通过 Next 引用 与 后续 委托 建立 关联, 而是 将 所有 委托 存放 在 一个 数组中, 如图 5- 6 所示。

每一个 委托 类型 都有 一个 公开 的 GetInvocationList() 的 方法, 可以 返回 已 附加 到 委托 对象 上 的 所有 委托, 即 图 5- 6 中 数组 列表 部分。另外, 我们 平时 不 区分 委托 对象 和 委托 链 表, 提到 委托 对象, 它 很有可能 就 表示 一个 委托 链 表, 这 跟 单向 链 表 只 包含 一个 节点 时 道理 类似。

 

5.1 什么是.NET 中的委托 > 位置 1781

委托 跟 String 类型 一样, 也是 不可改变 的。换句话说, 一旦 委托 对象 创建 完成 后, 这个 对象 就不能 再被 更改, 那么 我们 前面 讲到 的 将 一个 委托 附加 到 另外 一个 委托 对象 上 形成 一个 委托 链 表 又 该 如何 解释 呢?其实 这个 跟 String. ToUpper() 过程 类似, 我们 对 委托 进行 附加、 移 除 等 操作 都会 产生 一个 全新 的 委托, 这些 操作 并不 会 改变 原有 委托 对象。

 

5.1 什么是.NET 中的委托 > 位置 1821

框架 有两 种 调用 框架 使用者 编写 的 代码 的 方式, 一种 便是 面向 抽象 编程, 即 框架 中 尽量 不出 现 某个 具体 类型 的 引用, 而是 使用 抽象化 的 基 类 引用 或者 接口 引用 代替。只要 框架 使用者 编写 的 类型 派生 自 抽象化 的 基 类 或 实现 了 接口, 框架 均可 以 正确地 调用 它们。

框架 调用 框架 使用者 代码 的 另外 一种 方式 就是 使用 委托, 将 委托 作为 参数( 变量) 传递 给 框架, 框架 通过 委托 调用 方法。

 

5.2 事件与委托的关系 > 位置 1860

问题,. NET 中 提出 了 一种 介于 public 和 private 之间 的 另外 一种 访问 级别:在 定义 委托 成员 的 时候 给出 event 关键字 进行 修饰, 前面 加了 event 关键字 修饰 的 public 委托 成员, 只 能在 类 外部 进行 附加 和 移 除 操作, 而 调用 操作 只能 发生 在 类型 内部。

 

我们 把 类 中 设置 了 event 关键字 的 委托 叫作 “事件”。“ 事件” 本质上 就是 委托 对象。事件 的 出现, 限制 了 委托 调用 只能 发生 在 一个 类型 的 内部, 如图 5- 12 所示。

在 图 5- 12 中 由于 server 中的 委托 使用 了 event 关键字 修饰, 因此 委托 只能 在 server 内部 调用, 对 外部 也 只能 进行 附加 和 移 除 方法 操作。当 符合 某一 条件 时, server 内部 会 调用 委托, 这个 时间 不由 我们( client) 控制, 而是 由 系统( server) 决定。因此 大部分 时候, 事件 在 程序 中 起 到了 回 调 作用。

 

调用 加了 event 关键字 修饰 的 委托 也称 为 “ 激发事件”。其中, 调用 方( 图 5- 12 中的 server) 被称为 “ 事件发布者”;被 调用 方( 图 5- 12 中的 client) 被称为 “ 事件注册者”( 或 “ 事件观察者”、“ 事件订阅者” 等, 本书 中统 一 称之为 “事件 注册 者”);附加 委托 的 过程 被 称之为 “ 注册事件”( 或 “ 绑定事件”、“ 监听事件”、“ 订阅事件” 等, 本书 中统 一 称之为 “注册 事件”);移 除 委托 的 过程 被 称之为 “ 注销事件”。通过 委托 调用 的 方法 被称为 “ 事件处理程序”。

 

5.3 使用事件编程 > 位置 1940

在调 用 委托 链 时, 如果 某一个 委托 对应 的 方法 抛出 了 异常, 那么 剩下 的 其他 委托 将 不会 再 调用。这个 很容易 理解, 本来 是按 先后 顺序 依次 调用 方法, 如果 其中 某一个 抛出 异常, 剩下 的 肯定 会被 跳过。为了 解决 这个 问题, 单单 是将 激发 事件 的 代码 放在 try/catch 块 中 是 不够 的, 我们 还 需要 分 步调 用 每个 委托, 将 每 一步 的 调用 代码 均 放在 try/catch 块 中。

 

5.3 使用事件编程 > 位置 1967

除了 事件 本身 的 命名, 事件 所属 委托 类型 的 命名 也 同样 有 标准 格式, 一般以 “ 事件 名  EventHandler” 这种 格式 来给 委托 命名, 因此 前面 提到 的 NewEmailReceived 事件 对应 的 委托 类型 名称 应该 是 “NewEmailReceivedEventHandler”)。激发 事件 时会 传递 一些 参数, 这些 参数 一般 继承 自 EventArgs 类型( 后者 为. NET 框架 预定 义 类型), 以 “ 事件 名  EventArgs” 来 命名, 比如 前面 提到 的 NewEmailReceived 事件 在 激发 时 传递 的 参数 类型 名称 应该 是 “NewEmailReceivedEventArgs”。

 

5.3 使用事件编程 > 位置 2004

之所以 要把 激发 事件 的 代码 放在 一个 单独 的 虚 方法 中, 是 为了 让 从 该 类型( EmailManager) 派生 出来 的 子类 能够 重写 虚 方法, 从而 改变 激发 事件 的 逻辑。

 

虚 方法 的 命名 方式 一般 为 “ On 事件 名”。另外, 该 代码 中的 虚 方法 必须 定义 为 “protected”, 因为 派生 类 中 很可能 要 调用 基 类 的 虚 方法。

 

5.3 使用事件编程 > 位置 2039

图 5- 13   属性 和 事件 的 作用

 

5.4 弱委托 > 位置 2077

在 事件 编程 中, 委托 的 Target 成员, 就是 对 事件 注册 者 的 强 引用, 如果 事件 注册 者 没有 注销 事件, 强 引用 Target 便会 一直 存在, 堆 中的 事件 注册 者 内存 就 一直 不 会被 CLR 回收, 这对 开发 人员 来讲, 几乎 是 很难 发觉 的。

 

像 “A a= new A();” 中的 a 被称为 “ 显式强引用( Explicit Strong Reference)”, 类似 委托 中 包含 的 不明显 的 强 引用, 我们 称之为 “ 隐式强引用( Implicit Strong Reference)”。

弱引用 与 对象 实例 之间 属于 一种 “弱 关联” 关系, 跟 强 引用 与 对象 实例 的 关系 不一样, 就算 程序 中有 弱 引用 指向 堆 中 对象 实例, CLR 还是 会把 该 对象 实例 当作 回收 目标。程序 中 使用 弱 引用 访问 对象 实例 之前 必须 先 检查 CLR 有没有 回收 该 对象 内存。换句话说, 当 堆 中 一个 对象 实例 只有 弱 引用 指向 它 时, CLR 可以 回收 它的 内存。使用 弱 引用, 堆 中 对象 能否 被 访问, 同时 掌握 在 程序 和 CLR 手中。

创建 一个 弱 引用 很 简单, 使用 WeakReference 类型, 给 它的 构造 方法 传递 一个 强 引用 作为 参数 即可。

 

5.4 弱委托 > 位置 2105

在 编程 过程中, 由于 很难 管理 好强 引用, 从而 造成 不必 要的 内存 开销。尤其 前面 讲到 的 “隐式 强 引用”, 在 使用 过程中 不易 发觉 它们 的 存在。弱 引用 特别 适合 用于 那些 对 程序 依赖 程度 不高 的 对象, 即那 些 对象 生命 期 主要 不是 由 程序控制 的 对象。比如 事件 编程 中, 事件 发布者 对 事件 注册 者 的 存在 与否 不是 很 关心, 如果 注册 者 在, 那就 激发 事件 并 通知 注册 者;如果 注册 者 已经 被 CLR 回收 内存, 那么 就不 通知 它, 这 完全 不会 影响 程序 的 运行。

 

5.4 弱委托 > 位置 2109

前面 讲到 过, 委托 包含 两个 部分:一个 Object 类型 的 Target 成员, 代表 被 调用 方法 的 所有者, 如果 方法 为 静态 方法, 则 Target 为 null;另一个 是 MethodInfo 类型 的 Method 成员, 代表 被 调用 方法。由于 Target 成员 是 一个 强 引用, 所以 只要 委托 存在, 那么 方法 的 所有者 就会 一直 在 堆 中 存在 而 不 能被 CLR 回收。如果 我们将 委托 中的 Target 强 引用 换成 弱 引 用的 话, 那么 不管 委托 存在 与否, 都不 会 影响 方法 的 所有者 在 堆 中 内存 的 回收。这样一来, 我们 在使 用 委托 调用 方法 之前, 需要 先 判断 方法 的 所有者 是否 已经 被 CLR 回收。我们 称 将 Target 成员 换成 弱 引用 之后 的 委托 为 “ 弱委托”, 弱 委托 定义 代码 如下:

//Code 5- 26
class WeakDelegate
{ WeakReference _weakRef; //NO. 1MethodInfo _method; //NO. 2public WeakDelegate(Delegate d) { _weakRef = new WeakReference(d.Target);_methodInfo = d.Method;} public object Invoke( param object[] args) {object obj = _weakRef.Target;if(_weakRef.IsAlive) //NO. 3{ return _method.Invoke(obj, args); //NO. 4} else { return null;} } 
}

 

弱 委托 将 委托 与 被 调用 方法 的 所有者 之间 的 关 系由 “强 关联” 转换 成了 “ 弱 关联”, 方法 的 所有者 在 堆 中的 生命 期 不再 受 委托 的 控制, 如图 5- 16 所示, 为 弱 委托 的 结构。

本 小节 示例 代码 中的 WeakDelegate 类型 并没有 提供 类似 Delegate. Combine 以及 Delegate. Remove 这样 操作 委托 链 表 的 方法, 当然 也 没有 弱 委托 链 表 的 功能。这些 功能 可以 仿照 单向 链 表 的 结构 去 实现, 把 每个 弱 委托 都 当作 链 表中 的 一个 节点。其 方法 可 参照 5. 1. 2 小节 中 讲到 的 单向 链 表。

 

5.5 本章回顾 > 位置 2151

委托 的 3 个 作用:第一, 它 允许 把 方法 作为 参数, 传递 给 其他 的 模块;第二, 它 允许 我们 同时 调用 多个 具有 相同 签名 的 方法;第三, 它 允许 我们 异步 调用 任何 方法。这 3 个 作用 奠定 了 委托 在. NET 编程 中的 绝对 重要 地位。

 

第 6 章 线程的升级:异步编程模型

6.1 异步编程的必要性 > 位置 2171

通常 情况下, 调用 一个 方法 都 符合 这样 一个 规律:调用 线程 开始 调用 方法 A 后, 在 A 返回 之前, 调用 线程 得不到 程序 执行 的 控制 权。也就是说, 方法 A 后面 的 代码 是 不可能 执行 的, 直到 A 返回 为止, 这种 调用 方式 被 称之为 “ 同步调用”;相反, 如果 调用 在 返回 之前, 调用 线程 依旧 保留 控制 权, 能够 继续 执行 后面 的 代码, 那么 这种 调用 方式 被称为 “ 异步调用”。

 

同步 调用 也 被 一些 学者 称为 “ 阻塞调用”;一些 相对 的 异步 调用 则 被称为 “ 非阻塞调用”。

 

6.2 委托的异步调用 > 位置 2202

理论上 讲, 任何 一个 方法, 通过 委托 包装 后, 都可以 实现 异步 调用。

 

.NET 编译器 定义 的 每个 委托 类型 都 自动 生成 了 两个 方法:BeginInvoke 和 EndInvoke。这 两个 方法 专门 用来 负责 异步 调用 委托。

BeginInvoke 返回 一个 IAsyncResult 接口 类型, 它可 以 唯一 区分 一个 异步 调用 过程。BeginInvoke 一 执行 就能 马上 返回, 不会 阻塞 调用 线程。 EndInvoke 表示 结束 对 委托 的 异步 调用, 但这 并不 意味着 它可 以 中断 异步 调用 过程, 如果 异步 调用 还未 结束, EndInvoke 则 只能 等待, 直到 异步 调用 过程 结束。另外, 如果 委托 带有 返回 值, 我们 必须 通过 EndInvoke 获得 这个 返回 结果。

 

6.2 委托的异步调用 > 位置 2233

委托 异步 调用 开始 后, 系统 会在 线程 池 中 找到 一个 空闲 的 线程 去 执行 委托。

 

6.2 委托的异步调用 > 位置 2296

异步 调用 委托 时, 由于 方法 实际 运行 在 其他 线程 中( 线程 池 中的 某一 线程, 非 当前 调用 线程), 因此 当前 线程 捕获 不了 异常, 那么 我们 怎样 才能 知道 异步 调用 过程中 到底 是否 会有 异常 呢?答案 就在 EndInvoke 方法 上, 如果 异步 调用 过程 有 异常, 那么 该 异常 就会 在 我们 在 调用 EndInvoke 方法 时 抛出。所以 我们 在 调用 EndInvoke 方法 时, 一定 要把 它 放在 try/catch 块 中。

 

6.3 非委托的异步调用 > 位置 2358

.NET 中 提供 异步 方法 的 类型 有 Stream( 或 其 派生 类)、 Socket( 或 其 派生 类) 以及 访问 数据库 的 SqlConnection 类型 等。它们 的 使用 方式 跟 委托 的 BeginInvoke 和 EndInvoke 方法 类似, 只是 命名 有所 差别, 基本上 都是 “Begin 操作” 和 “ End 操作” 这种 格式。比如 FileStream. BeginRead 表示 开始 一个 异步 读 操作, 而 FileStream. EndRead 则 表示 结束 异步 读 操作。

 

6.6 本章思考 > 位置 2446

异步 编程 与 多 线程 编程 的 效果 类似, 都是 为了 能够 并行 执行 代码, 达到 同时 处理 任务 的 目的。 异步编程 时, 系统 自己 通过 线程池 来 分配 线程, 不需要 人工干预, 异步 编程 逻辑 复杂 不易 理解, 而 多 线程 编程 时, 完全 需要 人为 去 控制, 相对 较 灵活。

 

第 7 章 可复用代码:组件的来龙去脉

7.1 .NET 中的组件 > 位置 2457

在. NET 编程 中, 我们 把 实现( 直接 或者 间接) System.ComponentModel.IComponent 接口 的 类型 称为 “组件”,

 

7.1 .NET 中的组件 > 位置 2472

组件 和 控 件 不是 相等 的, 组件包含控件, 控 件 只是 组件 中的 一个 分类。

 

7.1 .NET 中的组件 > 位置 2477

图 7- 2   Windows Forms 中 控 件 之间 的 关系

 

所 有的 控 件 均 派生 自 Control 类, Control 类 又 属于 组件, 因此 所有 控 件 均 具备 组件 的 特性。

不管 组件 还是 控 件, 它们 都是 可以 重复 使用 的 代码 集合, 都 实现 了 IDisposable 接口, 都 需要 遵循 第 4 章 中 讲到 的 Dispose 模式。如果 一个 类型 使 用了 非 托管 资源, 它 实现 IDisposable 接口 就可以 了, 那 为什么 还要 在. NET 编程 中 又 提出 组件 的 概念 呢?

这样做 可以 说完 全是 为了 实现 程序 的 “ 可视化开发”, 也就是 我们 常说 的 “所见 即 所得”。在 类似 Visual Studio 这样 的 开发 环境 中, 一切 “ 组件” 均 可被 可 视 化 设计, 换句话说, 只要 我们 定义 的 类型 实现 了 IComponent 接口, 那么 在开 发 阶段, 该 类型 就可以 出现 在窗 体 设计 器 中, 我们 就可以 使用 窗体 设计 器 编辑 它的 属性、 给 它 注册 事件。它 还能 被 窗体 设计 器 中 别的 组件 识别。

 

7.2 容器 – 组件 – 服务模型 > 位置 2499

在. NET 编程 中, 把 所有 实现( 直接 或 间接) System. ComponentModel.IContainer 接口 的 类型 都 称之为 逻辑 容器( 以下 简称 “容器”)。

容器 是 为 组件 服务 的。

.NET 框架 中有 一个 IContainer 接口 的 默认 实现:System. ComponentModel.Container 类型, 该类 型 默认 实现 了 IContainer 接口 中的 方法 以及 属性。

 

7.2 容器 – 组件 – 服务模型 > 位置 2514

传统 容器 仅仅 是在 空间 上 简单 地 将 数据 组织 在一起, 并不 能为 数据 之间 的 交互 提供 支持。而 本章 讨论 的 逻辑容器, 在 某种 意义上 讲, 更为 高级。它 能为 组件( 逻辑 元素) 之间 的 通信 提供 支持, 组件 与 组件 之间 不再 是 独立 存在。此外, 它 还能 直接 给 组件 提供 某些 服务。物理 容器 和 逻辑 容器 分别 与 元素 之间 的 关系, 如图 7- 4 所示。

物理 容器 中的 元素 之间 不能 相互 通信, 物理 容器 也不 可能 为 其内 部 元素 提供 服务;逻辑 容器 中的 组件 之间 可以 通过 逻辑 容器 作为 桥梁, 进行 数据 交换;同时, 逻辑 容器 还能 给 各个 组件 提供 服务。所谓 服务, 就是 指 逻辑 容器 能够 给 组件 提供 一些 访问 支持。比如 某个 组件 需要 知道 它的 所属 容器 中共 包含 有 多少 个 组件, 那么 它 就 可以向 容器 发出 请求。容器 收到 请求 后 会为 它 返回 一个 获取 组件 总数 的 接口。

 

在 本章 7. 1. 1 小节 中 我们 提 到过 IComponent 接口 中有 一个 ISite 类型 的 属性。当时 说 它是 起到 一个 “定位” 的 作用。现在 看来, 组件 与 容器 之间 的 纽带 就 是它, 组件 通过 该 属性 与 它 所属 容器 取得 了 联系。

 

7.2 容器 – 组件 – 服务模型 > 位置 2574

Component、 Site 以及 Container 3 个 类型 均 包含 有 获取 服务 的 方法 GetService。现在 我们 可以 整理 一下 组件 向 容器 请求 服务 的 流程, 如图 7- 6 所示。

 

注:容器 将 组件 添加 进来 时( 执行 Container. Add), 会 初始化 该 组件 的 Site 属性, 让 该 组件 与 容器 产生 关联, 只有 当 这一 过程 发生 之后, 组件 才能 获取 容器 的 服务。

 

7.2 容器 – 组件 – 服务模型 > 位置 2601

在 我们 向 窗体 设计 器 中 拖动控件 时, 是 会 执行 类似 “new Button();” 这样 的 代码, 在 内存 中 实例化 一个 组件 实例。

 

7.2 容器 – 组件 – 服务模型 > 位置 2655

图 7- 10   窗体 设计 器 中的 组件 与 生成 的 源 代码

在 图 7- 10 中, 图中 左边 显示 我们 拖放 到 设计 器 中的 一个 Button 控 件。在 这个 过程中, 窗体 设计 器 除了 会 实例 化 一个 Button 控 件( 图中 左边 Form2 中), 还会 为我 们 生成 右边 的 代码。

 

7.3 设计时与运行时 > 位置 2672

任何 组件 都有 两种 状态:设计时 和 运行时。

判断 组件 的 当前状态 有 以下 两种 方法。

(1) 判断 组件 的 DesignMode 属性。每个 组件 都有 一个 Bool 类型 的 DesignMode 属性, 正如 它的 字面 意思, 如果 该 属性 为 true, 那么 代表 组件 当前 处于 设计 时 状态;否则 该 组件 处于 运行时 状态。

(2) 随便 请求 一个 服务, 看 返回 来的 服务 接口 是否 为 null。前面 提 到过, 当 一个 组件 不属于 任何 一个 容器 时, 那么 它 通过 GetService 方法 请求 的 服务 肯定 返回 为 null。

 

注:(1)(2) 方法 均不 适合 嵌套组件, 因为 窗体 设计 器 只会 将 最外 层 组件 的 DesignMode 属性 值 设置 为 true。

有 一种 可以 解决 嵌套 组件 中 无法 判断 其 子 组件 状态 的 方法, 那就 是 通过 Process 类 来 检查 当前 进程 的 名称。看 它是 否 包含 “devenv” 这个 字符串。如果 有, 那么 说明 组件 当前 处于 Visual Studio 开发 环境 中( 即 组件 处于 设计 时), if( Process. GetCurrentProcess (). ProcessName. Contains(”devenv”)) 为 假, 说明 组件 处于 运行时。这种 方法 也有 一个 弊端, 很 明显, 如果 我们 使用 的 不是 Visual Studio 开发 环境( 即 进程 名 不 包含 devenv), 或者 我们自己 的 程序 进程 名称 本身 就 已经 包含 了 devenv, 那么 该 怎么办 呢?

 

在开 发 一些 需要 授权 的 组件 时, 就可以 用到 组件 的 两种 状态。这些 需要 授权 的 组件 收费 对象 一般 是 开发者。因此, 在 开发者 使用 这些 组件 开发 系统 的 时候( 处于 开发 阶段), 就应 该有 授权 入口, 而 当 程序 运行 之后, 就不 应该 出现 授权 的 界面。

 

7.4 控件 > 位置 2776

无论是 复合 控 件、 扩展 控 件 还是 自定义 控 件, 我们 均可 以 重写 控 件 的 窗口过程:WndProc 虚 方法, 从 根源 上 接触 到 Windows 消息, 这个 做法 并不是 自定义 控 件 的 专利。

 

第 8 章 经典重视:桌面 GUI 框架揭秘

8.2 Win32 应用程序的结构 > 位置 2841

程序 是 无法 直接 识别 用户 键盘 或者 鼠标 等 设备 的 输入 信息, 这些 输入 必须 先由 操作系统 转换 成 固定 格式 数据 之后, 才能 被 程序 使用。

在 Windows 编程 中, 我们 把 由 操作系统 转换 之后 的 固定 格式 数据 称为 Windows 消息。Windows 消息 是一 种 预 定义 的 数据 结构( 比如 C 中的 Struct), 它 包含 有 消息 类型、 消息 接收者 以及 消息 参数 等 信息。我们 还把 程序 中 获取 Windows 消息 的 结构 称之为 Windows 消息循环。Windows 消息 循环 在 代码 中就 是一 个 循环 结构( 比如 while 循环), 它 不停 地 从 操作系统 中 获取 Windows 消息, 然后 交给 程序 去 处理。

 

8.2 Win32 应用程序的结构 > 位置 2895

在 Windows 中, 其实 将 消息 分成 了 两类, 一类 需要 存入 消息 队列, 然后 由 消息 循环 取出 来之 后才 能被 窗口 过程 处理, 这类 消息 被 称之为 “ 队列消息”( Queued Message)。这类 消息 主要 包括 用户 的 鼠标 键盘 输入 消息、 绘制 消息 WM_ PAINT、 退出 消息 WM_ QUIT 以及 时间 消息 WM_ TIMER。另 一类 是 不需要 存入 消息 队列, 也不 经过 消息 循环, 它们 直接 传递 给 窗口 过程, 由 窗口 过程 直接 处理, 这类 消息 被 称之为 “ 非队列消息”( Nonqueued Message)。当 操作系统 想要 告诉 窗口 发生了 某 件事 时, 它 会 给 窗口 发送 一个 非 队列 消息, 比如 当 我们 使用 SetWindowPos API 移动 窗口 后, 系统 自动 会 发送 一个 WM_ WINDOWPOSCHANGED 消息 给 该 窗口 的 窗口 过程, 告诉 它 位置 发生 变化 了。

 

8.4 Windows Forms 框架 > 位置 3148

在 Windows Forms 框架 中, 以 Control 为 基 类, 其他 所有 与 窗体 显示 有关 的 控 件 几乎 都 派生 自 它;Control 类 中的 WndProc 虚 方法 就是 我们 在 Win32 开发 模式 中 所 熟悉 的 窗口 过程。另外, 前面 也 讲到 过, 窗体 和 控 件 本质上 是一 个 东西, 只是 它们 有着 不同 的 属性, 所以 我们 可以 看到, 窗体 类 Form 间接 派生 自 Control 类。

 

Winform 程序 中 包含 3 个 部分:消息 队列、 UI 线程 以及 控 件。

 

8.5 Winform 程序的结构 > 位置 3180

在 每个 Winform 程序 的 Program. cs 文件 中, 都有 一个 Main 方法, 该 Main 方法 就是 程序 的 入口 方法。每个 程序 启动 时 都会 以 Main 方法 为 入口, 创建 一个 线程, 这个 线程 就是 UI 线程。可能 你会 问 UI 线程 怎么 没有 消息 循环 呢?那是 因为 Main 方法 中 总是 会 出现 一个 类似 Application. Run 的 方法, 而 消息 循环 就 隐 藏在 了 该 方法 内部( 具体 参见 下一 小节 内容)。一个 程序 理论上 可以 有 多个 UI 线程, 且 每个 线程 都有 自己的 消息 队列( 由 操作系统 维护)、 消息 循环、 窗体 等 元素, 如图 8- 13 所示。

 

由于 UI 线程 之间 的 数据 交换 比较 复杂, 因此 在 实际 开发 中, 在 没有 特殊 需求 的 情况下, 一个 程序 一般 只 包含 有一个 UI 线程。

 

8.5 Winform 程序的结构 > 位置 3265

对 UI 线程 的 认识:

① 一个 程序 可以 包含 有 多个 UI 线程, 我们 完全可以 通过 System. Threading. Thread 类 新创 建 一个 普通 线程, 然后 在 该 线程 中 调用 Application. Run 方法 来 开启 消息 循环;

② 一个 UI 线程 结束( 该 线程 中的 消息 循环 个数 为 “0”) 后, 将会 激发 Application. ThreadExit 事件, 告知 有 UI 线程 结束, 只有 当 所有 UI 线程 都 结束( 程序 中 消息 循环 总数 为 “ 0”), 才会 激发 Application. Exit 事件, 告知 应用 程序 退出。

最后 我们 再来 看一下 Windows Forms 中 消息 循环 的 结构图, 如图 8- 14 所示。

 

8.5 Winform 程序的结构 > 位置 3319

Windows Forms 框架 将 窗体 和 窗口 过程 封 装在 了 一起, Control( 或 其 派生 类, 下同) 类 中的 WndProc 虚 方法 就是 控 件 的 窗口 过程。之所以 将 窗口 过程 声明 为 虚 方法, 这是 因为 Windows Forms 框架 是 面向 对象 的, 它 充分 地利 用了 面向 对象 编程 中的 “多 态” 特性。因此, 所有 Control 类 的 派生 类 均可 以 重写 它的 窗口 过程, 从而 从 源头 上 拦截 到 Windows 消息, 处理 自己 想要 处理 的 Windows 消息。

 

窗口 过程 做了 一个 非常 重要的 事:将 Window 消息 转换 成了. NET 中的 事件。

 

第 9 章 沟通无碍:网络编程

9.1 两种 Socket 通信方式 > 位置 3518

TCP)数据 是按 顺序 走在 建立 的 一条 隧道 中, 那么 数据 就应 该 遵循 “先走 先 到达” 的 规则, 并且 隧道 中的 数据 以 “ 流” 的 形式 传输。发送 方 发送 的 前后 两次 数据 之间 没有 边界, 需要 接收 方自 己 根据 事先 规定 好的 “ 协议” 去 判断 数据 边界。

 

9.1 两种 Socket 通信方式 > 位置 3555

UDP 通信 中, 数据 是以 “数 据报” 的 形式 传输, 以 一个 整体 发送、 以 一个 整体 接收, 因此 UDP 存在 数据 边界。但是 UDP 接 收到 的 数据 是 无序 的, 先 发送 的 可能 后接 收, 后 发送 的 可能 先 接收, 甚至 有的 接收 不到。

 

9.1 两种 Socket 通信方式 > 位置 3605

在. NET 中 有关 Socket 通信 编程 的 类型 主要 有 5 种, 见表 9- 1。

 

TcpListener 和 TcpClient 的 关系 如图 9- 9 所示

图 9- 9 中, TcpListener 侦听 来自 客户 端 的 “连接” 请求, 返回 一个 代理 TcpClient, 该 代理 与 客户 端 的 TcpClient 进行 数据 交换。

UdpClient 在 UDP 通信 中 所处 的 角色 如图 9- 10 所示:

 

NetworkStream 类型 是 System. IO. Stream 类 的 一个 派生 类。NetworkStream 只能 用于 TCP 通信 中。

Socket 类型 是一 个 相对 较 基础 的 通信 类型。它 既能 实现 TCP 通信 也能 实现 UDP 通信, 可以 认为 TcpListener、 TcpClient 以及 UdpClient 进一步 封装 了 Socket 类型。

 

9.3 UDP 通信的实现 > 位置 3840

之所以 将 TCP 通信 中 应用 层 协议 的 数据 结构设计 成 字节流 的 形式, 是因为 TCP 通信 中 数据 是以 流的 形式 传输, 以 字节 流的 形式 格式化 数据 后, 更 方便 程序 判断 数据 边界。

而在 UDP 通信 中, 数据 以 数据 报的 形式 传输, 每次 接 收到 的 数据 是 完整 的, 对于 当前 局域网 即时 通信 实例 来讲, 协议 使用 文本 的 形式 更 方便 程序 处理 数据, 当然, 我们 完全 也可以 将 UDP 通信 中 应用 层 协议 的 数据 结构设计 成 字节 流的 形式。

 

9.4 异步编程在网络编程中的应用 > 位置 3991

使用 Socket. BeginSendTo() 方法 开始 一个 异步 发送 过程, 并为 该 方法 提供 一个 AsyncCallback 的 回 调 参数。该 方法 的 调用 不会 阻塞 调用 线程。我们 在 回 调 方法 OnSend 中 可使用 Socket. EndSendTo() 方法, 结束 异步 发送 过程, 该 方法 返回 实际 发送 的 数据 长度。

 

9.4 异步编程在网络编程中的应用 > 位置 4011

异步 编程 也能 实现 循环 接收 数据, 但却 看 不到 显 式 地 创建 的 线程, 也 看不 到 类似 while 这样 的 循环 语句。

 

9.6 本章思考 > 位置 4062

所有 的 通信协议 本质上 都是 一种 数据结构。通信 双方 都 必须 按照 这种 数据 结构 规定 的 形式 去 发送 或 接收( 解析) 数据。

基于 TCP 协议 的 通信 在 进行 数据 交互 之前 需要 先 建立 连接, 类似 打电话。这种 通信 方式 保证 了 数据 传输 的 正确性、 可靠性。基于 UDP 协议 的 通信 在 进行 数据 传输 之前 不需要 建立 连接, 类似 发 短信。这种 通信 方式 不能 保证 数据 传输 的 正确性。

 

第 10 章 动力之源:代码中的 “泵”

10.2 常见的 “泵” 结构 > 位置 4150

桌面 程序 的 UI 线程 中 包含 一个 消息 循环( 确切 地说, 应该 是 While 循环)。该 循环 不断 地 从 消息 队列 中 获取 Windows 消息, 最终 通过 调用 对应 的 窗口 过程, 将 Windows 消息 传递 给 窗口 过程 进行 处理。

 

10.2 常见的 “泵” 结构 > 位置 4179

浏览器 每次 发送 http 请求 时, 都 必须 与 Web 服务器 建立 连接。Web 服务器 端 请求 处理 结束 后, 连接 立刻 关闭。浏览器 下一 次 发送 http 请求 时, 必须 再一次 重新 与 服务器 建立 连接。由此可见, 我们 所说 的 HTTP 协议 是 面向 无 连接 的, 具体 指 Web 服务器 一次 连接 只 处理 一个 请求, 请求 处理 完毕 后, 连接 关闭, 浏览器 在前 一次 请求 结束 到下 一次 请求 开始 之前 这段 时间, 它是 处于 “断开” 状态 的, 因此 称 HTTP 协议 是 “ 无连接” 协议。

Web 服务器 除了 跟 浏览器 之间 不会 保持 持久 性的 连接 之外, 它 也不 会 保存 浏览器 的 状态。也就是说, 同一 浏览器 先后 两次 请求 同一个 Web 服务器, 后者 不会 保留 第一次 请求 处理 的 结果 到 第二次 请求 阶段;如果 第二次 请求 需要 使用 第一次 请求 处理 的 结果, 那么 浏览器 必须 自己 将 第一次 的 处理 结果 回 传到 服务器 端。

 

第 11 章 规绳矩墨:模式与原则

11.1 软件的设计模式 > 位置 4308

程序 的 运行 意味着 模块 与 模块 之间、 对象 与 对象 之间 不停 地 有数 据 交换。 观察者模式 要 强调 的 是, 当 一个 目标 本身 的 状态 发生 改变( 或者 满足 某一 条件) 时, 它 会 主动 发出通知, 通知 对 该 变化 感兴趣 的 其他 对象。将 通知者 称为 Subject( 主体), 将被 通知者 称为 Observer( 观察者),

 

11.1 软件的设计模式 > 位置 4358

“观察者 模式” 是 所有 框架 使用 得 最 频繁 的 设计 模式 之一。原因 很 简单,“ 观察者 模式” 分隔 开了 框架 代码 和 框架 使用者 编写 的 代码。它是 “ 好莱坞原则”( Don\’ t call us, we will call you) 的 具体 实现 手段, 而 “好莱坞 原则” 是 所有 框架 都要 严格遵守 的。

 

11.1 软件的设计模式 > 位置 4361

在 Windows Forms 框架 中, 可以说 “观察者 模式” 无处不在。Windows Forms 框架 中的 “ 观察者 模式” 不是 通过 “ 接口 – 具体” 这种 方式 去 实现 的, 而是 更多 地 通过 使用. NET 中的 “ 委托 – 事件” 去 实现。这 在 第 8 章 讲 Winform 程序 结构 时 已经 有所 说明, 比如 控 件 处理 Windows 消息 时, 最终 是以 “事件” 的 形式 通知 事件 注册 者, 那么 这里 的 事件注册者 就是 观察者 模式 中的 “ 观察者”, 控件 就是 观察者 模式 中的 “ 主体”。

可以 认为, 事件 的 发布者 等于 观察者 模式 中的 “主体”( Subject), 而 事件 的 注册 者 等于 观察者 模式 中的 “ 观察者”,

 

11.1 软件的设计模式 > 位置 4397

根据 各种 设计 模式 的 作用, 将 常见 的 23 种 设计模式 分为 3 大类, 见表 11- 1。 

 

11.5 本章思考 > 位置 4605

五大原则 及 英文 全称 分别 如下。

① 单一 职责 原则( Single Responsibility Principle)。

② 开闭 原则( Open Closed Principle)。

③ 里 氏 替换 原则( Liskov Substitution Principle)。

④ 接口 隔离 原则( Interface Segregation Principle)。

⑤ 依赖 倒置 原则( Dependency Inversion Principle)。

 

第 12 章 难免的尴尬:代码依赖

12.1 从面向对象开始 > 位置 4738

类继承 强调 “我是( Is- A)” 的 关系, 派生 类 “ 是” 基 类( 注意 这里 的 “ 是” 代表 派生 类 具备 基 类 的 特性), 而 接口继承 强调 “我 能做( Can- Do)” 的 关系, 实现 了 接口 的 类型 具有 接口 中 规定 的 行为 能力( 因此 接口 在 命名 时 均以 “ able” 作为 后缀)。

 

12.1 从面向对象开始 > 位置 4743

在使 用 继承 时, 应 遵循 以下 准则

(1) 严格遵守 “里 氏 替换 原则”, 即 基 类 出现 的 地方, 派生 类 一定 可以 出现。因此, 不要 盲目 地 去使 用 继承, 如果 两个 类 没有 衍生 的 关系, 就不 应该 有 继承 关系。

(2) 由于 派生 类 会 继承 基 类 的 全部 内容, 所以 要 严格控制 好 类型 的 继承 层次, 不然 派生 类 的 体积 会 越来越大。另外, 继承 是 增加 耦合 的 最重要 因素, 基 类 的 修改 必然会 影响 到 派生 类。

(3) 继承 强调 类型 之 间的 通 性, 而非 特性。因此 一般 将 类型 都 具有 的 部分 提取 出来, 形成 一个 基 类( 抽象 类) 或者 接口。

 

12.2 不可避免的代码依赖 > 位置 4815

为了 衡量 对象 之间 依赖 程度 的 高低, 引进 了 “ 耦合” 这一 概念。耦合 度 越高, 说明 对象 之间 的 依赖 程度 越高。为了 衡量 对象 独立 性的 高低, 引进 了 “ 内聚” 这一 概念。内聚性 越高, 说明 对象 与外 界 交互 越少, 独立性 越 强。很 明显, 耦合 与 内 聚 是 两个 相互 对立 又 密切相关 的 概念。

 

12.3 降低代码依赖 > 位置 4936

除了 上面 说到 的 将相 同 部分 提取 出来 放到 一个 接口 中, 有时候 还需 要将 相同 部分 提取 出来, 生成 一个 抽象化 的 基 类, 如 抽象类。 接口 强调 相同 的 行为, 而 抽象 类 一般 强调 相同 的 属性, 并且 要使 用在 有 族群 层次 的 类型 设计 中。

 

12.3 降低代码依赖 > 位置 4987

通过 属性 产生 的 依赖 关系(属性注入) 比较 灵活, 它的 有效期 一般 介于 “构造 注入” 和 “ 方法 注入” 之间。

在 很多 场合, 3 种 依赖注入 的 方式 可以 组合 使用, 即 可以 先 通过 “构造 注入” 让 依赖 者 与 被 依赖 者 产生 依赖 关系, 后期 再 使用 “ 属性 注入” 的 方式 更改 它们 之间 的 依赖 关系。“ 需要 注意 的 是, 依赖 注入” 是以 “ 依赖 倒置”” 为 前提 的。

 

12.4 框架的 “代码依赖” > 位置 4998

注:“ 控制转换、 依赖倒置 以及 依赖注入 是 3 个 不同 性质 的 概念。“ 控制转换” 强调 程序控制 权 的 转移, 注重 软件 运行 流程;“ 依赖倒置” 是一 种 降低 代码 依赖 程度 的 理论 指导思想, 它 注重 软件 结构;“ 依赖注入” 是对 象之 间 产生 依赖 关系 的 一种 具体 实现 方式, 它 注重 编程 实现。

“控制 转换” 又称 “ 好莱坞 原则”, 它 建议 框架 与 开发者 编写 代码 之间 的 关系 是 Don\’ t call us, we will call you, 即 整个 程序 的 主动权 在 框架 手中。

 

12.6 本章思考 > 位置 5022

“依赖 倒置 原则” 中的 “ 倒置” 二字 作 何 解释?

A:正常 逻辑思维 中, 高层 模块 依赖 底层 模块 是 天经地义、 理所当然 的, 而 “依赖 倒置 原则” 建议 我们 所有 的 高层 模块 不应该 直接 依赖于 底层 模块, 而 都 应该 依赖于 一个 抽象。这里 的 “ 倒置” 二字 并不是 “ 反过来” 的 意思( 即 底层 模块 反过来 依赖于 高层 模块), 它 只是 说明 正常 逻辑思维 中的 依赖 顺序 发生了 变化, 把 所有 违背 了 正常 思维 的 东西 都 称之为 “ 倒置”。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/307199.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构 - 概述

存储方式 数据结构的存储方式只有顺序存储(对应数组)、链式存储(对应链表)两种。所有上层的数据结构,如树、堆、栈等,存储方式均属于以上两种。顺序存储的优势是支持随机访问,缺点是需要连续的…

ASP.NET Core 使用 gRPC 初探

(RPC通讯示意图)为什么突然说到gRPC呢,其实以前就想说一说这个东西,也想尝试使用一下,一直没有机会,一直看我公众号的小伙伴肯定都知道,这几天一直在录制一个《eShopOnContainer微服务架构》系列…

源码都没调试过,怎么能说熟悉 redis 呢?

一:背景 1. 讲故事记得在很久之前给初学的朋友们录制 redis 视频课程,当时结合了不少源码进行解读,自以为讲的还算可以,但还是有一个非常核心的点没被分享到,那就是源码级调试, 对,读源码还远远…

算法 - DFS/BFS

写DFS函数的时候首先确定当前位置是否已经加入路径 DFS函数大概率会传递“位置信息”,根据位置信息获取下一步的选择,(大部分是在循环中)选择、执行、回退 在哪做选择,就在哪退出选择,参考题9 def DFS()…

你想象中的Task后续,很简单?

【导读】前不久,写过一篇关于Task的简短文章,通过评论和转载得到好评,刚好我昨晚又写了一篇实现简单的消息队列也提到了Task,难道不应该是看具体执行什么操作,再考虑最佳方案?本文我们再次通过简短内容谈谈…

算法 - 动态规划

动态规划是一种自底向上的算法,通常用于解决最大、最小等最值问题。 能使用动态规划解决的问题,一定具备: 重叠子问题:和暴力搜索不同,需要记录子问题的解,避免重复求解(剪枝)最优…

5G在工业互联网应用的机遇与挑战

移动通讯经过十年一代的发展,已经从1G发展到了5G,峰值速率实现十年千倍的增长,1G到4G是面向个人的,而5G是面向产业互联网和智慧城市服务。5G是一个颠覆性的技术,低时延(每秒钟下载一部高清电影)…

算法 - 前缀和

记录在做hot100时遇到的前缀和的题目。 目前见过的题目,都是前缀和结合其它的方法一起使用:用于求取一段连续路径的和(最大值/最小值/目标出现次数)。 需要注意的是,前缀和的判定方法是node2.val-node1.val target&am…

[C#.NET 拾遗补漏]10:理解 volatile 关键字

要理解 C# 中的 volatile 关键字,就要先知道编译器背后的一个基本优化原理。比如对于下面这段代码:public class Example {public int x;public void DoWork(){x 5;var y x 10;Debug.WriteLine("x " x ", y " y);} }在 Releas…

跟我一起学.NetCore之MediatR好像有点火

前言随着微服务的流行,而DDD(领域驱动设计)也光速般兴起,CRQS(Command Query Responsibility Seperation--命令查询职责分离)、领域事件名词是不是经常在耳边环绕,而MediatR组件经常用来对其技术的落地,凭这,小伙伴们说…

数据结构 - 单调栈、单调队列

单调栈:每日温度 请根据每日 气温 列表 temperatures ,请计算在每一天需要等几天才会有更高的温度。如果气温在这之后都不会升高,请在该位置用 0 来代替单调栈基本只处理NGE问题(Next GreaterElement)。对序列中每个元…

不想写脚本清理 mongodb 中的垃圾数据,ttlIndex 能帮到你!

mongodb一直都在不断的更新,不断的发展,那些非常好玩也非常实用的功能都逐步加入到了mongodb中,这不就有了本篇对ttlindex的介绍,刚好我们的生产业务场景中就有一个案例。。。一:案例分析 生产的推荐系统要给用户发送短…

数据结构 - 最小堆最大堆

可以在O(nlogn)的时间复杂度内完成排序典型的用法是,寻找 第k个/前k个 最大/最小元素,k个有序序列合并 1.合并K个升序链表(最小堆实现) 或许可以改进成每次堆只存放K个元素? # Definition for singly-linked list. …

python程序启动其他python程序,如何使用Python启动应用程序的实例?

I am creating a Python script where it does a bunch of tasks and one of those tasks is to launch and open an instance of Excel. What is the ideal way of accomplishing that in my script?解决方案While the Popen answers are reasonable for the general case, I…

工作这几年所获、所感、所悟

【导读】截止到目前,给甲方所做项目已接近尾声,在此写下一点个人关于技术方面的感受。若后续时间上允许或充裕的话,打算私下花一点时间分享封装可通用的组件今年也是我首次带小伙伴,有刚毕业没什么技术经验,也有毕业不…

后端学习 - 基础 《Java编程的逻辑》读书笔记

文章目录一 基础概念1 有关Java2 JVM / JDK / JRE3 与C的联系和区别4 各类型数据占用空间大小5 和 equals() 的区别、hashCode() 方法6 包装类型7 final 关键字8 参数传递机制:值传递9 String 的内存情况10 访问修饰符11 引用拷贝、浅拷贝与深拷贝三 面向对象1 面向…

cheatengine找不到数值_彩票中奖500万,领了还不到一半?这些问题不解决,钱都拿不走...

长期以来,“一夜暴富”是很多人梦寐以求的梦想,而作为最能让人“一夜暴富”的方式要数我国的福利彩票了,这也是很多人最容易活动暴富的机会,不少彩民长久以来一直买彩票的梦想就是“一夜暴富”。而突然暴富是很多人的梦想&#xf…

一站式Web开发套件BeetleX.WebFamily

BeetleX.WebFamily是一款前后端分离的Web开发套件,但它并不依赖于nodejs/npm/webpack等相关工具;而使用自身实现的方式来完成前后端分离的Web应用开发;套件以组件的方式发布,只需要在项目引用相关组件即可实现前后端分离开发&…

.NET架构小技巧(2)——访问修饰符正确姿势

在C#中,访问修饰符是使用频率很高的一组关键字,一共四个单词六个组合:public,internal,protected internal,protected,private protected,private,如果你对这些关键字非常清楚,请跳过,节省时间;…

能源36号文解读_IDC报告预测:今年中国新能源汽车销量将达116万辆,未来五年复合增长率36%_详细解读_最新资讯_热点事件...

编者按:本文来自36氪「 未来汽车日报」,(微信公众号ID:auto-time),作者:秦章勇。 来源:IDC作者 | 秦章勇编辑 | 周游12月3日,在2020世界智能汽车大会上,IDG亚洲(国际数据(亚洲)集团)…