<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>写好代码 on 元闰子的邀请</title>
    <link>https://yrunz.com/categories/%E5%86%99%E5%A5%BD%E4%BB%A3%E7%A0%81/</link>
    <description>Recent content in 写好代码 on 元闰子的邀请</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Sun, 08 Sep 2024 00:00:00 +0000</lastBuildDate><atom:link href="https://yrunz.com/categories/%E5%86%99%E5%A5%BD%E4%BB%A3%E7%A0%81/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>深入理解单元测试</title>
      <link>https://yrunz.com/p/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/</link>
      <pubDate>Sun, 08 Sep 2024 00:00:00 +0000</pubDate>
      
      <guid>https://yrunz.com/p/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/</guid>
      <description>荐语 本文要介绍的是 2020 年 O&amp;rsquo;Reilly 出版的书籍 Unit Testing Principles, Practices, and Patterns，一本在豆瓣评分高达 9.9 的好书。
作为一名软件开发工程师，你应该对单元测试（unit test）很熟悉，但单元测试的目的、Mock 的正确用法、单元测试和集成测试的区别等等，你真的懂吗？书中对这些内容都做了深入的介绍，并通过实际案例教你如何写出好的单元测试。读完这本书，你一定会有醍醐灌顶的感觉，原来单元测试要这么写！
书中的另一个重要观点认为，单元测试和代码架构是息息相关的，写不出好的单元测试，极有可能是代码架构设计得不好。为了写出好的单元测试，需要不断对代码架构的优化，它们相辅相成，从而让软件能够持续演进。
为什么要写单元测试？ 单元测试的目的 如今，在软件开发活动中，单元测试已经成为企业级软件不可或缺的一环，是代码门禁的必选项。
如果突然有人问你，“为什么要写单元测试？”，你很容易会想到这样的答案，1）为了找到代码中已有的 Bug；2）为了防止引入新的 Bug；3）让代码架构变得更好；甚至是 4）满足代码门禁中覆盖率的要求。
但这些都不是我们写单元测试的根本目的，它们只是“写单元测试”这一活动所带来的副产品。
书中认为，单元测试的目的是实现软件项目的可持续增长。
企业级软件应用通常都有着业务逻辑复杂度高和生命周期长的特点。随着产品的不断迭代，代码会变得越来越复杂乱，每次修改代码也会变得越来越难，这就是软件的熵增定律。而单元测试的目的就是降低熵增的加速度，使得重构代码或新增功能变得更加的容易。
单元测试也分好坏 代码并非资产，而是一种负债，单元测试代码也是如此。
然而，我们往往会将生产代码和测试代码区分对待，忽略了对测试代码的维护，久而久之，测试代码变得冗余、复杂，甚至出现误报的情况。这样的单元测试，只会让系统的演进更加难。
如何评判单元测试的好坏？最常用的方法是代码覆盖率，也即测试用例执行了多少源代码。其中，又可分为行覆盖率和分支覆盖率。
行覆盖率计算执行了多少代码行，比如下面的例子，return true 的代码行无法覆盖，因此代码行覆盖率为 4/5=80%：
// C# public static bool IsStringLong(string input) { if (input.Length &amp;gt; 5) // 无法执行  return true; return false; } public void Test() { bool result = IsStringLong(&amp;#34;abc&amp;#34;); Assert.Equal(false, result); } 但是，某些时候行覆盖率并不能放映真实的覆盖情况，比如将上述的 IsStringLong 使用三目运算符进行重构，行覆盖率 3/3=100% 了，但实际情况并未改善。</description>
    </item>
    
    <item>
      <title>【Go实现】实践GoF的23种设计模式：桥接模式</title>
      <link>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E6%A1%A5%E6%8E%A5%E6%A8%A1%E5%BC%8F/</link>
      <pubDate>Sat, 13 Apr 2024 00:00:00 +0000</pubDate>
      
      <guid>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E6%A1%A5%E6%8E%A5%E6%A8%A1%E5%BC%8F/</guid>
      <description>上一篇：【Go实现】实践GoF的23种设计模式：解释器模式
简单的分布式应用系统（示例代码工程）：https://github.com/ruanrunxue/Practice-Design-Pattern&amp;ndash;Go-Implementation
 简介 GoF 对桥接模式（Bridge Pattern）的定义如下：
 Decouple an abstraction from its implementation so that the two can vary independently.
 也即，将抽象部分和实现部分进行解耦，使得它们能够各自往独立的方向变化。
桥接模式解决了在模块有多种变化方向的情况下，用继承所导致的类爆炸问题。
举个例子，一个产品有形状和颜色两个特征（变化方向），其中形状分为方形和圆形，颜色分为红色和蓝色。如果采用继承的设计方案，那么就需要新增4个产品子类：方形红色、圆形红色、方形蓝色、圆形红色。如果形状总共有 m 种变化，颜色有 n 种变化，那么就需要新增 m * n 个产品子类！
现在我们使用桥接模式进行优化，将形状和颜色分别设计为抽象接口独立出来，这样需要新增 2 个形状子类：方形和圆形，以及 2 个颜色子类：红色和蓝色。同样，如果形状总共有 m 种变化，颜色有 n 种变化，总共只需要新增 m + n 个子类！
上述例子中，我们通过将形状和颜色抽象为一个接口，使产品不再依赖于具体的形状和颜色细节，从而达到了解耦的目的。桥接模式本质上就是面向接口编程，可以给系统带来很好的灵活性和可扩展性。如果一个对象存在多个变化的方向，而且每个变化方向都需要扩展，那么使用桥接模式进行设计那是再合适不过了。
当然，Go 语言从语言特性本身就把继承剔除，但桥接模式中分离变化、面向接口编程的思想仍然值得学习。
UML 结构 场景上下文 在 简单的分布式应用系统（示例代码工程）中，我们设计了一个 Monitor 监控系统模块，它可以看成是一个简单的 ETL 系统，负责对监控数据进行采集、处理、输出。监控数据来源于在线商场服务集群各个服务，当前通过消息队列模块 Mq 传递到监控系统，经处理后，存储到数据库模块 Db 上。
我们假设未来要上线一个不支持对接消息队列的服务、结果数据也需要存储到 ClickHouse 以供后续分析，为了应对未来多变的需求，我们有必要将监控系统设计得足够的可扩展。
于是，整个模块被设计为插件化风格的架构，Pipeline 是数据处理的流水线，其中包含了 Input、Filter 和 Output 三类插件，Input 负责从各类数据源中获取监控数据，Filter 负责数据处理，Output 负责将处理后的数据输出。</description>
    </item>
    
    <item>
      <title>【Go实现】实践GoF的23种设计模式：解释器模式</title>
      <link>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E8%A7%A3%E9%87%8A%E5%99%A8%E6%A8%A1%E5%BC%8F/</link>
      <pubDate>Sat, 23 Mar 2024 00:00:00 +0000</pubDate>
      
      <guid>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E8%A7%A3%E9%87%8A%E5%99%A8%E6%A8%A1%E5%BC%8F/</guid>
      <description>上一篇：【Go实现】实践GoF的23种设计模式：适配器模式
简单的分布式应用系统（示例代码工程）：https://github.com/ruanrunxue/Practice-Design-Pattern&amp;ndash;Go-Implementation
 简介 解释器模式（Interpreter Pattern）应该是 GoF 的 23 种设计模式中使用频率最少的一种了，它的应用场景较为局限。
GoF 对它的定义如下：
 Given a language, define a represention for its grammar along with an interpreter that uses the representation to interpret sentences in the language.
 从定义可以看出，解释器模式主要运用于简单的语法解析场景，比如简单的领域特定语言（DSL）。举个例子，我们可以使用解析器模式来对“1+2+3-4+1”这样的文本表达式完成解析，并得到最终答案“3”。
解释器模式的整体思想是分而治之，每一个语法规则都使用一个类或者结构体（我们称之为 Rule Struct）来定义，它们相互独立，比如前一个例子中，“+” 和 “-” 都各自定义为一个 Rule Struct。因此，解释器模式的可扩展性很好。
通常，我们还能使用抽象语法树（Abstract Syntax Tree，AST）来直观地表示待解释的表达式，比如“1+2+3-4+1”可以表示成这样：
UML 结构 解释器模式通常有 4 种角色：
 Context：解释上下文，包含了解释语法需要的所有信息，它是的生命周期贯穿整个解释过程，是一个全局对象。 AbstractExpression：声明了解释语法的方法，通常只有 Interpret(*Context) 一个方法。 TerminalExpression：实现了 AbstractExpression 接口，定义了终结表达式的解析逻辑。终结表达式在抽象语法树中作为叶子节点。 NonterminalExpression：实现了 AbstractExpression 接口，定义了非终结表达式的解析逻辑。在抽象语法树中，除了叶子节点，其他节点都是非终结表达式。NonterminalExpression 通常会比 TerminalExpression 更复杂一些。  场景上下文 在 简单的分布式应用系统（示例代码工程）中，db 模块用来存储服务注册信息和系统监控数据，它是一个 key-value 数据库。为了更高的易用性，它支持简单的 SQL 查询功能。用户在终端控制台上可以通过 SQL 语句来查询数据库中的数据：</description>
    </item>
    
    <item>
      <title>【Go实现】实践GoF的23种设计模式：适配器模式</title>
      <link>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F/</link>
      <pubDate>Sun, 10 Dec 2023 00:00:00 +0000</pubDate>
      
      <guid>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F/</guid>
      <description>上一篇：【Go实现】实践GoF的23种设计模式：备忘录模式
简单的分布式应用系统（示例代码工程）：https://github.com/ruanrunxue/Practice-Design-Pattern&amp;ndash;Go-Implementation
 简介 适配器模式（Adapter）是最常用的结构型模式之一，在现实生活中，适配器模式也是处处可见，比如电源插头转换器，它可以让英式的插头工作在中式的插座上。
GoF 对它的定义如下：
 Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.
 简单来说，就是适配器模式让原本因为接口不匹配而无法一起工作的两个类/结构体能够一起工作。
适配器模式所做的就是将一个接口 Adaptee，通过适配器 Adapter 转换成 Client 所期望的另一个接口 Target 来使用，实现原理也很简单，就是 Adapter 通过实现 Target 接口，并在对应的方法中调用 Adaptee 的接口实现。
UML 结构 场景上下文 在 简单的分布式应用系统（示例代码工程）中，db 模块用来存储服务注册信息和系统监控数据，它是一个 key-value 数据库。在 访问者模式 中，我们为它实现了 Table 的按列查询功能；同时，我们也为它实现了简单的 SQL 查询功能（将会在 解释器模式 中介绍），查询的结果是 SqlResult 结构体，它提供一个 toMap 方法将结果转换成 map 。</description>
    </item>
    
    <item>
      <title>【Go实现】实践GoF的23种设计模式：备忘录模式</title>
      <link>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%A4%87%E5%BF%98%E5%BD%95%E6%A8%A1%E5%BC%8F/</link>
      <pubDate>Sat, 25 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%A4%87%E5%BF%98%E5%BD%95%E6%A8%A1%E5%BC%8F/</guid>
      <description>上一篇：【Go实现】实践GoF的23种设计模式：命令模式
简单的分布式应用系统（示例代码工程）：https://github.com/ruanrunxue/Practice-Design-Pattern&amp;ndash;Go-Implementation
 简介 相对于代理模式、工厂模式等设计模式，备忘录模式（Memento）在我们日常开发中出镜率并不高，除了应用场景的限制之外，另一个原因，可能是备忘录模式 UML 结构的几个概念比较晦涩难懂，难以映射到代码实现中。比如 Originator（原发器）和 Caretaker（负责人），从字面上很难看出它们在模式中的职责。
但从定义来看，备忘录模式又是简单易懂的，GoF 对备忘录模式的定义如下：
 Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later.
 也即，在不破坏封装的前提下，捕获一个对象的内部状态，并在该对象之外进行保存，以便在未来将对象恢复到原先保存的状态。
从定义上看，备忘录模式有几个关键点：封装、保存、恢复。
对状态的封装，主要是为了未来状态修改或扩展时，不会引发霰弹式修改；保存和恢复则是备忘录模式的主要特点，能够对当前对象的状态进行保存，并能够在未来某一时刻恢复出来。
现在，在回过头来看备忘录模式的 3 个角色就比较好理解了：
 Memento（备忘录）：是对状态的封装，可以是 struct ，也可以是 interface。 Originator（原发器）：备忘录的创建者，备忘录里存储的就是 Originator 的状态。 Caretaker（负责人）：负责对备忘录的保存和恢复，无须知道备忘录中的实现细节。  UML 结构 场景上下文 在前文 【Go实现】实践GoF的23种设计模式：命令模式 我们提到，在 简单的分布式应用系统（示例代码工程）中，db 模块用来存储服务注册信息和系统监控数据。其中，服务注册信息拆成了 profiles 和 regions 两个表，在服务发现的业务逻辑中，通常需要同时操作两个表，为了避免两个表数据不一致的问题，db 模块需要提供事务功能:
事务的核心功能之一是，当其中某个语句执行失败时，之前已执行成功的语句能够回滚，前文我们已经介绍如何基于 命令模式 搭建事务框架，下面我们将重点介绍，如何基于备忘录模式实现失败回滚的功能。
代码实现 // demo/db/transaction.go package db // Command 执行数据库操作的命令接口，同时也是备忘录接口 // 关键点1：定义Memento接口，其中Exec方法相当于UML图中的SetState方法，调用后会将状态保存至Db中 type Command interface { Exec() error // Exec 执行insert、update、delete命令  Undo() // Undo 回滚命令  setDb(db Db) // SetDb 设置关联的数据库 } // 关键点2：定义Originator，在本例子中，状态都是存储在Db对象中 type Db interface {.</description>
    </item>
    
    <item>
      <title>【Go实现】实践GoF的23种设计模式：命令模式</title>
      <link>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F/</link>
      <pubDate>Thu, 22 Dec 2022 00:00:00 +0000</pubDate>
      
      <guid>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F/</guid>
      <description>上一篇：【Go实现】实践GoF的23种设计模式：代理模式
简单的分布式应用系统（示例代码工程）：https://github.com/ruanrunxue/Practice-Design-Pattern&amp;ndash;Go-Implementation
 简介 现在的软件系统往往是分层设计。在业务层执行一次请求时，我们很清楚请求的上下文，包括，请求是做什么的、参数有哪些、请求的接收者是谁、返回值是怎样的。相反，基础设施层并不需要完全清楚业务上下文，它只需知道请求的接收者是谁即可，否则就耦合过深了。
因此，我们需要对请求进行抽象，将上下文信息封装到请求对象里，这其实就是命令模式，而该请求对象就是 Command。
GoF 对命令模式（Command Pattern）的定义如下：
 Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
 也即，命令模式可将请求转换为一个包含与请求相关的所有信息的对象， 它能将请求参数化、延迟执行、实现 Undo / Redo 操作等。
上述的请求是广义上的概念，可以是网络请求，也可以是函数调用，更通用地，指一个动作。
命令模式主要包含 3 种角色：
 Command，命令，是对请求的抽象。具体的命令实现时，通常会引用 Receiver。 Invoker，请求的发起发起方，它并不清楚 Command 和 Receiver 的实现细节，只管调用命令的接口。 Receiver，请求的接收方。  命令模式，一方面，能够使得 Invoker 与 Receiver 消除彼此之间的耦合，让对象之间的调用关系更加灵活；另一方面，能够很方便地实现延迟执行、Undo、Redo 等操作，因此被广泛应用在软件设计中。
UML 结构 场景上下文 在 简单的分布式应用系统（示例代码工程）中，db 模块用来存储服务注册信息和系统监控数据。其中，服务注册信息拆成了 profiles 和 regions 两个表，在服务发现的业务逻辑中，通常需要同时操作两个表，为了避免两个表数据不一致的问题，db 模块需要提供事务功能:</description>
    </item>
    
    <item>
      <title>【Go实现】实践GoF的23种设计模式：代理模式</title>
      <link>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/</link>
      <pubDate>Sun, 16 Oct 2022 00:00:00 +0000</pubDate>
      
      <guid>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/</guid>
      <description>上一篇：【Go实现】实践GoF的23种设计模式：访问者模式
简单的分布式应用系统（示例代码工程）：https://github.com/ruanrunxue/Practice-Design-Pattern&amp;ndash;Go-Implementation
 简介 GoF 对代理模式（Proxy Pattern）的定义如下：
 Provide a surrogate or placeholder for another object to control access to it.
 也即，代理模式为一个对象提供一种代理以控制对该对象的访问。
它是一个使用率非常高的设计模式，在现实生活中，也是很常见。比如，演唱会门票黄牛。假设你需要看一场演唱会，但官网上门票已经售罄，于是就当天到现场通过黄牛高价买了一张。在这个例子中，黄牛就相当于演唱会门票的代理，在正式渠道无法购买门票的情况下，你通过代理完成了该目标。
从演唱会门票的例子我们也能看出，使用代理模式的关键在于，当 Client 不方便直接访问一个对象时，提供一个代理对象控制该对象的访问。Client 实际上访问的是代理对象，代理对象会将 Client 的请求转给本体对象去处理。
UML 结构 场景上下文 在 简单的分布式应用系统（示例代码工程）中，db 模块用来存储服务注册和监控信息，它是一个 key-value 数据库。为了提升访问数据库的性能，我们决定为它新增一层缓存：
另外，我们希望客户端在使用数据库时，并不感知缓存的存在，这些，代理模式可以做到。
代码实现 // demo/db/cache.go package db // 关键点1: 定义代理对象，实现被代理对象的接口 type CacheProxy struct { // 关键点2: 组合被代理对象，这里应该是抽象接口，提升可扩展性  db Db cache sync.Map // key为tableName，value为sync.Map[key: primaryId, value: interface{}]  hit int miss int } // 关键点3: 在具体接口实现上，嵌入代理本身的逻辑 func (c *CacheProxy) Query(tableName string, primaryKey interface{}, result interface{}) error { cache, ok := c.</description>
    </item>
    
    <item>
      <title>【Go实现】实践GoF的23种设计模式：访问者模式</title>
      <link>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E8%AE%BF%E9%97%AE%E8%80%85%E6%A8%A1%E5%BC%8F/</link>
      <pubDate>Thu, 06 Oct 2022 00:00:00 +0000</pubDate>
      
      <guid>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E8%AE%BF%E9%97%AE%E8%80%85%E6%A8%A1%E5%BC%8F/</guid>
      <description>上一篇：【Go实现】实践GoF的23种设计模式：迭代器模式
简单的分布式应用系统（示例代码工程）：https://github.com/ruanrunxue/Practice-Design-Pattern&amp;ndash;Go-Implementation
 简介 GoF 对访问者模式（Visitor Pattern）的定义如下：
 Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
 访问者模式的目的是，解耦数据结构和算法，使得系统能够在不改变现有代码结构的基础上，为对象新增一种新的操作。
上一篇介绍的 迭代器模式 也做到了数据结构和算法的解耦，不过它专注于遍历算法。访问者模式，则在遍历的同时，将操作作用到数据结构上，一个常见的应用场景是语法树的解析。
UML 结构 场景上下文 在 简单的分布式应用系统（示例代码工程）中，db 模块用来存储服务注册和监控信息，它是一个 key-value 数据库。另外，我们给 db 模块抽象出 Table 对象：
// demo/db/table.go package db // Table 数据表定义 type Table struct { name string metadata map[string]int // key为属性名，value属性值的索引, 对应到record上存储  records map[interface{}]record iteratorFactory TableIteratorFactory // 默认使用随机迭代器 } 目的是提供类似于关系型数据库的按列查询能力，比如：</description>
    </item>
    
    <item>
      <title>【Go实现】实践GoF的23种设计模式：迭代器模式</title>
      <link>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E8%BF%AD%E4%BB%A3%E5%99%A8%E6%A8%A1%E5%BC%8F/</link>
      <pubDate>Sat, 30 Jul 2022 00:00:00 +0000</pubDate>
      
      <guid>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E8%BF%AD%E4%BB%A3%E5%99%A8%E6%A8%A1%E5%BC%8F/</guid>
      <description>上一篇：【Go实现】实践GoF的23种设计模式：观察者模式
简单的分布式应用系统（示例代码工程）：https://github.com/ruanrunxue/Practice-Design-Pattern&amp;ndash;Go-Implementation
 简介 有时会遇到这样的需求，开发一个模块，用于保存对象；不能用简单的数组、列表，得是红黑树、跳表等较为复杂的数据结构；有时为了提升存储效率或持久化，还得将对象序列化；但必须给客户端提供一个易用的 API，允许方便地、多种方式地遍历对象，丝毫不察觉背后的数据结构有多复杂。
对这样的 API，很适合使用 迭代器模式（Iterator Pattern）实现。
GoF 对 迭代器模式 的定义如下：
 Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
 从描述可知，迭代器模式主要用在访问对象集合的场景，能够向客户端隐藏集合的实现细节。
Java 的 Collection 家族、C++ 的 STL 标准库，都是使用迭代器模式的典范，它们为客户端提供了简单易用的 API，并且能够根据业务需要实现自己的迭代器，具备很好的可扩展性。
UML 结构 场景上下文 在 简单的分布式应用系统（示例代码工程）中，db 模块用来存储服务注册和监控信息，它的主要接口如下：
// demo/db/db.go package db // Db 数据库抽象接口 type Db interface { CreateTable(t *Table) error CreateTableIfNotExist(t *Table) error DeleteTable(tableName string) error Query(tableName string, primaryKey interface{}, result interface{}) error Insert(tableName string, primaryKey interface{}, record interface{}) error Update(tableName string, primaryKey interface{}, record interface{}) error Delete(tableName string, primaryKey interface{}) error .</description>
    </item>
    
    <item>
      <title>【Go实现】实践GoF的23种设计模式：观察者模式</title>
      <link>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F/</link>
      <pubDate>Sat, 23 Jul 2022 00:00:00 +0000</pubDate>
      
      <guid>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F/</guid>
      <description>上一篇：【Go实现】实践GoF的23种设计模式：装饰者模式
简单的分布式应用系统（示例代码工程）：https://github.com/ruanrunxue/Practice-Design-Pattern&amp;ndash;Go-Implementation
 简介 现在有 2 个服务，Service A 和 Service B，通过 REST 接口通信；Service A 在某个业务场景下调用 Service B 的接口完成一个计算密集型任务，假设接口为 http://service_b/api/v1/domain；该任务运行时间很长，但 Service A 不想一直阻塞在接口调用上。为了满足 Service A 的要求，通常有 2 种方案：
  Service A 隔一段时间调用一次 Service B 的接口，如果任务还没完成，就返回 HTTP Status 102 Processing；如果已完成，则返回 HTTP Status 200 Ok。
  Service A 在请求 Service B 接口时带上 callback uri，比如 http://service_b/api/v1/domain?callbackuri=http://service_a/api/v1/domain，Service B 收到请求后立即返回 HTTP Status 200 Ok，等任务完成后再调用 Service A callback uri 进行通知。
  方案 1 须要轮询接口，轮询太频繁会导致资源浪费，间隔太长又会导致任务完成后 Service A 无法及时感知。显然，方案 2 更加高效，因此也被广泛应用。</description>
    </item>
    
    <item>
      <title>【Go实现】实践GoF的23种设计模式：装饰者模式</title>
      <link>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E8%A3%85%E9%A5%B0%E8%80%85%E6%A8%A1%E5%BC%8F/</link>
      <pubDate>Tue, 28 Jun 2022 00:00:00 +0000</pubDate>
      
      <guid>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E8%A3%85%E9%A5%B0%E8%80%85%E6%A8%A1%E5%BC%8F/</guid>
      <description>上一篇：【Go实现】实践GoF的23种设计模式：原型模式
简单的分布式应用系统（示例代码工程）：https://github.com/ruanrunxue/Practice-Design-Pattern&amp;ndash;Go-Implementation
 简介 我们经常会遇到“给现有对象/模块新增功能”的场景，比如 http router 的开发场景下，除了最基础的路由功能之外，我们常常还会加上如日志、鉴权、流控等 middleware。如果你查看框架的源码，就会发现 middleware 功能的实现用的就是装饰者模式（Decorator Pattern）。
GoF 给装饰者模式的定义如下：
 Decorators provide a flexible alternative to subclassing for extending functionality. Attach additional responsibilities to an object dynamically.
 简单来说，装饰者模式通过组合的方式，提供了能够动态地给对象/模块扩展新功能的能力。理论上，只要没有限制，它可以一直把功能叠加下去，具有很高的灵活性。
 如果写过 Java，那么一定对 I/O Stream 体系不陌生，它是装饰者模式的经典用法，客户端程序可以动态地为原始的输入输出流添加功能，比如按字符串输入输出，加入缓冲等，使得整个 I/O Stream 体系具有很高的可扩展性和灵活性。
 UML 结构 场景上下文 在简单的分布式应用系统（示例代码工程）中，我们设计了 Sidecar 边车模块，它的用处主要是为了 1）方便扩展 network.Socket 的功能，如增加日志、流控等非业务功能；2）让这些附加功能对业务程序隐藏起来，也即业务程序只须关心看到 network.Socket 接口即可。
代码实现 Sidecar 的这个功能场景，很适合使用装饰者模式来实现，代码如下：
// demo/network/socket.go package network // 关键点1: 定义被装饰的抽象接口 // Socket 网络通信Socket接口 type Socket interface { // Listen 在endpoint指向地址上起监听  Listen(endpoint Endpoint) error // Close 关闭监听  Close(endpoint Endpoint) // Send 发送网络报文  Send(packet *Packet) error // Receive 接收网络报文  Receive(packet *Packet) // AddListener 增加网络报文监听者  AddListener(listener SocketListener) } // 关键点2: 提供一个默认的基础实现 type socketImpl struct { listener SocketListener } func DefaultSocket() *socketImpl { return &amp;amp;socketImpl{} } func (s *socketImpl) Listen(endpoint Endpoint) error { return Instance().</description>
    </item>
    
    <item>
      <title>【Go实现】实践GoF的23种设计模式：原型模式</title>
      <link>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8E%9F%E5%9E%8B%E6%A8%A1%E5%BC%8F/</link>
      <pubDate>Thu, 02 Jun 2022 00:00:00 +0000</pubDate>
      
      <guid>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8E%9F%E5%9E%8B%E6%A8%A1%E5%BC%8F/</guid>
      <description>上一篇：【Go实现】实践GoF的23种设计模式：抽象工厂模式
简单的分布式应用系统（示例代码工程）：https://github.com/ruanrunxue/Practice-Design-Pattern&amp;ndash;Go-Implementation
 简介 原型模式（Prototype Pattern）主要解决对象复制的问题，它的核心就是 Clone() 方法，返回原型对象的复制品。
最简单直接的对象复制方式是这样的：重新实例化一个该对象的实例，然后遍历原始对象的所有成员变量， 并将成员变量值复制到新实例中。但这种方式的缺点也很明显：
 客户端程序必须清楚对象的实现细节。暴露细节往往不是件好事，它会导致代码耦合过深。 对象可能存在一些私有属性，客户端程序无法访问它们，也就无法复制。 很难保证所有的客户端程序都能完整不漏地把所有成员属性复制完。  更好的方法是使用原型模式，将复制逻辑委托给对象本身，这样，上述两个问题也都解决了。
UML 结构 场景上下文 在简单的分布式应用系统（示例代码工程）中，我们设计了一个服务消息中介（Service Mediator）服务，可以把它看成是一个消息路由器，负责服务发现和消息转发：
消息转发也就意味着它必须将上游服务的请求原封不动地转发给下游服务，这是一个典型的对象复制场景。不过，在我们的实现里，服务消息中介会先修改上行请求的 URI，之后再转发给下游服务。因为上行请求 URI 中携带了下游服务的类型信息，用来做服务发现，在转发给下游服务时必须剔除。
比如，订单服务（order service）要发请求给库存服务（stock service），那么：
 订单服务先往服务消息中介发出 HTTP 请求，其中 URI 为 /stock-service/api/v1/stock。 服务消息中介收到上行请求后，会从 URI 中提取出下游服务类型 stock-service ，通过服务注册中心发现库存服务的 Endpoint。 随后，服务消息中介将修改后的请求转发给库存服务，其中 URI 为 /api/v1/stock。  代码实现 如果按照简单直接的对象复制方式，实现是这样的：
// 服务消息中介 type ServiceMediator struct { registryEndpoint network.Endpoint localIp string server *http.Server sidecarFactory sidecar.Factory } // Forward 转发请求，请求URL为 /{serviceType}+ServiceUri 的形式，如/serviceA/api/v1/task func (s *ServiceMediator) Forward(req *http.</description>
    </item>
    
    <item>
      <title>【Go实现】实践GoF的23种设计模式：抽象工厂模式</title>
      <link>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E6%8A%BD%E8%B1%A1%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F/</link>
      <pubDate>Sun, 29 May 2022 00:00:00 +0000</pubDate>
      
      <guid>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E6%8A%BD%E8%B1%A1%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F/</guid>
      <description>上一篇：【Go实现】实践GoF的23种设计模式：工厂方法模式
简单的分布式应用系统（示例代码工程）：https://github.com/ruanrunxue/Practice-Design-Pattern&amp;ndash;Go-Implementation
 简述 上一篇我们介绍了工厂方法模式，本文，我们继续介绍它的兄弟，抽象工厂模式（Abstract Factory Pattern）。
在工厂方法模式中，我们通过一个工厂方法接口来创建产品，而创建哪类产品，由具体的工厂对象来决定。抽象工厂模式和工厂方法模式的功能很类似，只是把“产品”，变成了“产品族”。产品族就意味着这是一系列有关联的、一起使用的对象。我们当然也可以为产品族中的每个产品定义一个工厂方法接口，但这显得有些冗余，因为一起使用通常也意味着同时创建，所以把它们放到同一个抽象工厂来创建会更合适。
UML 结构 场景上下文 在简单的分布式应用系统（示例代码工程）中，我们有一个 Monitor 监控系统模块，该模块可以看成是一个简单的 ETL 系统，负责对监控数据的采集、处理、输出。整个模块被设计为插件化风格的架构，Pipeline是数据处理的流水线，其中包含了 Input、Filter 和 Output 三类插件，Input 负责从各类数据源中获取监控数据，Filter 负责数据处理，Output 负责将处理后的数据输出。更详细的设计思想我们在桥接模式一篇再做介绍，本文主要聚焦如何使用抽象工厂模式来解决各类插件的配置加载问题。
作为 ETL 系统，Monitor 模块应该具备灵活的扩展能力来应对不同的监控数据类型，因此，我们希望能够通过配置文件来定义 Pipeline 的行为。比如，下面就是一个 yaml 格式的配置内容：
name:pipeline_0# pipeline名称type:simple# pipeline类型input:# input插件定义name:input_0# input插件名称type:memory_mq# input插件类型，这里使用的是MemoryMQ作为输入context:# input插件的配置上下文topic:access_log.topic# 这里配置的是订阅的MemoryMQ主题filters:# filter插件链定义，多个filter插件组成一个filters插件链- name:filter_0# filter插件名称type:extract_log# filter插件类型- name:filter_1type:add_timestampoutput:# output插件定义name:output_0# output插件名称type:memory_db# output插件类型，这里使用的是MemoryDB作为输出context:# output插件上下文tableName:monitor_record_0# 这里配置的是MemoryDB表名另外，我们也希望 Monitor 模块支持多种类型的配置文件格式，比如，json 配置内容应该也支持：
{ &amp;#34;name&amp;#34;: &amp;#34;pipeline_0&amp;#34;, &amp;#34;type&amp;#34;: &amp;#34;simple&amp;#34;, &amp;#34;input&amp;#34;: { &amp;#34;name&amp;#34;: &amp;#34;input_0&amp;#34;, &amp;#34;type&amp;#34;: &amp;#34;memory_mq&amp;#34;, &amp;#34;context&amp;#34;: { &amp;#34;topic&amp;#34;: &amp;#34;access_log.topic&amp;#34; } }, &amp;#34;filters&amp;#34;: [ { &amp;#34;name&amp;#34;: &amp;#34;filter_0&amp;#34;, &amp;#34;type&amp;#34;: &amp;#34;extract_log&amp;#34; }, { &amp;#34;name&amp;#34;: &amp;#34;filter_1&amp;#34;, &amp;#34;type&amp;#34;: &amp;#34;add_timestamp&amp;#34; } ], &amp;#34;output&amp;#34;: { &amp;#34;name&amp;#34;: &amp;#34;output_0&amp;#34;, &amp;#34;type&amp;#34;: &amp;#34;memory_db&amp;#34;, &amp;#34;context&amp;#34;: { &amp;#34;tableName&amp;#34;: &amp;#34;monitor_record_0&amp;#34; } } } 所以，整体的效果是这样的：</description>
    </item>
    
    <item>
      <title>【Go实现】实践GoF的23种设计模式：工厂方法模式</title>
      <link>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%B7%A5%E5%8E%82%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8F/</link>
      <pubDate>Sat, 21 May 2022 00:00:00 +0000</pubDate>
      
      <guid>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%B7%A5%E5%8E%82%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8F/</guid>
      <description>上一篇：【Go实现】实践GoF的23种设计模式：建造者模式
简单的分布式应用系统（示例代码工程）：https://github.com/ruanrunxue/Practice-Design-Pattern&amp;ndash;Go-Implementation
 简述 工厂方法模式（Factory Method Pattern）跟上一篇讨论的建造者模式类似，都是将对象创建的逻辑封装起来，为使用者提供一个简单易用的对象创建接口。两者在应用场景上稍有区别，建造者模式常用于需要传递多个参数来进行实例化的场景；工厂方法模式常用于不指定对象具体类型的情况下创建对象的场景。
UML 结构 代码实现 示例 在简单的分布式应用系统（示例代码工程）中，我们设计了 Sidecar 边车模块， Sidecar 的作用是为了给原生的 Socket 增加额外的功能，比如流控、日志等。
Sidecar 模块的设计运用了装饰者模式，修饰的是 Socket 。所以客户端其实是把 Sidecar 当成是 Socket 来使用了，比如：
// demo/network/http/http_client.go package http // 创建一个新的HTTP客户端，以Socket接口作为入参 func NewClient(socket network.Socket, ip string) (*Client, error) { ... // 一些初始化逻辑 	return client, nil } // 使用NewClient时，我们可以传入Sidecar来给Http客户端附加额外的流控功能 client, err := http.NewClient(sidecar.NewFlowCtrlSidecar(network.DefaultSocket()), &amp;#34;192.168.0.1&amp;#34;) 在服务消息中介中，每次收到上游服务的 HTTP 请求，都会调用 http.NewClient 来创建一个 HTTP 客户端，并通过它将请求转发给下游服务：
type ServiceMediator struct { ... server *http.Server } // Forward 转发请求，请求URL为 /{serviceType}+ServiceUri 的形式，如/serviceA/api/v1/task func (s *ServiceMediator) Forward(req *http.</description>
    </item>
    
    <item>
      <title>【Go实现】实践GoF的23种设计模式：建造者模式</title>
      <link>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F/</link>
      <pubDate>Thu, 05 May 2022 00:00:00 +0000</pubDate>
      
      <guid>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F/</guid>
      <description>上一篇：【Go实现】实践GoF的23种设计模式：单例模式
简单的分布式应用系统（示例代码工程）：https://github.com/ruanrunxue/Practice-Design-Pattern&amp;ndash;Go-Implementation
 简述 在程序设计中，我们会经常遇到一些复杂的对象，其中有很多成员属性，甚至嵌套着多个复杂的对象。这种情况下，创建这个复杂对象就会变得很繁琐。对于 C++/Java 而言，最常见的表现就是构造函数有着长长的参数列表：
MyObject obj = new MyObject(param1, param2, param3, param4, param5, param6, ...) 对于 Go 语言来说，最常见的表现就是多层的嵌套实例化：
obj := &amp;amp;MyObject{ Field1: &amp;amp;Field1 { Param1: &amp;amp;Param1 { Val: 0, }, Param2: &amp;amp;Param2 { Val: 1, }, ... }, Field2: &amp;amp;Field2 { Param3: &amp;amp;Param3 { Val: 2, }, ... }, ... } 上述的对象创建方法有两个明显的缺点：（1）对使用者不友好，使用者在创建对象时需要知道的细节太多；（2）代码可读性很差。
针对这种对象成员较多，创建对象逻辑较为繁琐的场景，非常适合使用建造者模式来进行优化。
建造者模式的作用有如下几个：
1、封装复杂对象的创建过程，使对象使用者不感知复杂的创建逻辑。2、可以一步步按照顺序对成员进行赋值，或者创建嵌套对象，并最终完成目标对象的创建。3、对多个对象复用同样的对象创建逻辑。其中，第1和第2点比较常用，下面对建造者模式的实现也主要是针对这两点进行示例。
UML 结构 代码实现 示例 在简单的分布式应用系统（示例代码工程）中，我们定义了服务注册中心，提供服务注册、去注册、更新、 发现等功能。要实现这些功能，服务注册中心就必须保存服务的信息，我们把这些信息放在了 ServiceProfile 这个数据结构上，定义如下：
// demo/service/registry/model/service_profile.go // ServiceProfile 服务档案，其中服务ID唯一标识一个服务实例，一种服务类型可以有多个服务实例 type ServiceProfile struct { Id string // 服务ID  Type ServiceType // 服务类型  Status ServiceStatus // 服务状态  Endpoint network.</description>
    </item>
    
    <item>
      <title>【Go实现】实践GoF的23种设计模式：单例模式</title>
      <link>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/</link>
      <pubDate>Tue, 05 Apr 2022 00:00:00 +0000</pubDate>
      
      <guid>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/</guid>
      <description>上一篇：【Go实现】实践GoF的23种设计模式：SOLID原则
简单的分布式应用系统（示例代码工程）：https://github.com/ruanrunxue/Practice-Design-Pattern&amp;ndash;Go-Implementation
 简述 GoF 对单例模式（Singleton）的定义如下：
 Ensure a class only has one instance, and provide a global point of access to it.
 也即，保证一个类只有一个实例，并且为它提供一个全局访问点。
在程序设计中，有些对象通常只需要一个共享的实例，比如线程池、全局缓存、对象池等。实现共享实例最简单直接的方式就是全局变量。但是，使用全局变量会带来一些问题，比如：
 客户端程序可以创建同类实例，从而无法保证在整系统上只有一个共享实例。 难以控制对象的访问，比如想增加一个“访问次数统计”的功能就很难，可扩展性较低。 把实现细节暴露给客户端程序，加深了耦合，容易产生霰弹式修改。  对这种全局唯一的场景，更好的是使用单例模式去实现。单例模式能够限制客户端程序创建同类实例，并且可以在全局访问点上扩展或修改功能，而不影响客户端程序。
但是，并非所有的全局唯一都适用单例模式。比如下面这种场景：
 考虑需要统计一个API调用的情况，有两个指标，成功调用次数和失败调用次数。这两个指标都是全局唯一的，所以有人可能会将其建模成两个单例SuccessApiMetric和FailApiMetric。按照这个思路，随着指标数量的增多，你会发现代码里类的定义会越来越多，也越来越臃肿。这也是单例模式最常见的误用场景，更好的方法是将两个指标设计成一个对象ApiMetric下的两个实例ApiMetic success和ApiMetic fail。
 那么，如何判断一个对象是否应该被建模成单例？通常，被建模成单例的对象都有“中心点”的含义，比如线程池就是管理所有线程的中心。所以，在判断一个对象是否适合单例模式时，先思考下，是一个中心点吗？
UML结构 代码实现 根据单例模式的定义，实现的关键点有两个：
 限制调用者直接实例化该对象； 为该对象的单例提供一个全局唯一的访问方法。  对于 C++ / Java 而言，只需把对象的构造函数设计成私有的，并提供一个 static 方法去访问该对象的唯一实例即可。但 Go 语言并没有构造函数的概念，也没有 static 方法，所以需要另寻出路。
我们可以利用 Go 语言 package 的访问规则来实现，将单例对象设计成首字母小写，这样就能限定它的访问范围只在当前package下，模拟了 C++ / Java 的私有构造函数；然后，在当前 package 下实现一个首字母大写的访问函数，也就相当于 static 方法的作用了。</description>
    </item>
    
    <item>
      <title>【Go实现】实践GoF的23种设计模式：SOLID原则</title>
      <link>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8Fsolid%E5%8E%9F%E5%88%99/</link>
      <pubDate>Sun, 13 Mar 2022 00:00:00 +0000</pubDate>
      
      <guid>https://yrunz.com/p/go%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8Fsolid%E5%8E%9F%E5%88%99/</guid>
      <description>之前也有写过关于设计模式的文章《使用Go实现GoF的23种设计模式》，但是那个系列写了3篇文章就没再继续了，主要的原因是找不到合适的示例代码。考虑到，如果以类似于“鸭子是否会飞”、“烘焙的制作流程”等贴近生活的事情举例，很难在我们日常的开发中产生联系。（目前应该很少有这些逻辑的软件系统吧）
《实践GoF的23种设计模式》可以看成是《使用Go实现GoF的23种设计模式》系列的重启，吸取了上次烂尾的教训，本次在写文章之前就已经完成了23种设计模式的示例代码实现。示例代码以我们日常开发中经常碰到的一些技术/问题/场景作为切入点，示范如何运用设计模式来完成相关的实现。
 前言 从1995年GoF提出23种设计模式到现在，25年过去了，设计模式依旧是软件领域的热门话题。设计模式通常被定义为：
 设计模式（Design Pattern）是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结，使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。
 从定义上看，设计模式其实是一种经验的总结，是针对特定问题的简洁而优雅的解决方案。既然是经验总结，那么学习设计模式最直接的好处就在于可以站在巨人的肩膀上解决软件开发过程中的一些特定问题。
学习设计模式的最高境界是吃透它们本质思想，可以做到即使已经忘掉某个设计模式的名称和结构，也能在解决特定问题时信手拈来。设计模式背后的本质思想，就是我们熟知的SOLID原则。如果把设计模式类比为武侠世界里的武功招式，那么SOLID原则就是内功内力。通常来说，先把内功练好，再来学习招式，会达到事半功倍的效果。因此，在介绍设计模式之前，很有必要先介绍一下SOLID原则。
本文首先会介绍本系列文章中用到的示例代码demo的整体结构，然后开始逐一介绍SOLID原则，也即单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。
一个简单的分布式应用系统  本系列示例代码demo获取地址：https://github.com/ruanrunxue/Practice-Design-Pattern&amp;ndash;Go-Implementation
 示例代码demo工程实现了一个简单的分布式应用系统（单机版），该系统主要由以下几个模块组成：
 网络 Network，网络功能模块，模拟实现了报文转发、socket通信、http通信等功能。 数据库 Db，数据库功能模块，模拟实现了表、事务、dsl等功能。 消息队列 Mq，消息队列模块，模拟实现了基于topic的生产者/消费者的消息队列。 监控系统 Monitor，监控系统模块，模拟实现了服务日志的收集、分析、存储等功能。 边车 Sidecar，边车模块，模拟对网络报文进行拦截，实现access log上报、消息流控等功能。 服务 Service，运行服务，当前模拟实现了服务注册中心、在线商城服务集群、服务消息中介等服务。  示例代码demo工程的主要目录结构如下：
├── db # 数据库模块，定义Db、Table、TableVisitor等抽象接口和实现 ├── monitor # 监控系统模块，采用插件式的架构风格，当前实现access log日志etl功能 │ ├── config # 监控系统插件配置模块 │ ├── filter # 过滤插件的实现定义 │ ├── input # 输入插件的实现定义 │ ├── output # 输出插件的实现定义 │ ├── pipeline # Pipeline插件的实现定义，一个pipeline表示一个ETL处理流程 │ ├── plugin # 插件抽象接口的定义，比如Plugin、Config等 │ └── model # 监控系统模型对象定义 ├── mq # 消息队列模块 ├── network # 网络模块，模拟网络通信，定义了socket、packet等通用类型/接口  │ └── http # 模拟实现了http通信等服务端、客户端能力 ├── service # 服务模块，定义了服务的基本接口 │ ├── mediator # 服务消息中介，作为服务通信的中转方，实现了服务发现，消息转发的能力 │ ├── registry # 服务注册中心，提供服务注册、去注册、更新、 发现、订阅、去订阅、通知等功能 │ │ └── model # 服务注册/发现相关的模型定义 │ └── shopping # 模拟在线商城服务群的定义，包含订单服务、库存服务、支付服务、发货服务 └── sidecar # 边车模块，对socket进行拦截，提供http access log、流控功能 └── flowctrl # 流控模块，基于消息速率进行随机流控 SRP：单一职责原则 单一职责原则（The Single Responsibility Principle，SRP）应该是SOLID原则中，最容易被理解的一个，但同时也是最容易被误解的一个。很多人会把“将大函数重构成一个个职责单一的小函数”这一重构手法等价为SRP，这是不对的，小函数固然体现了职责单一，但这并不是SRP。</description>
    </item>
    
    <item>
      <title>【Java实现】实践GoF的23种设计模式：SOLID原则</title>
      <link>https://yrunz.com/p/java%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8Fsolid%E5%8E%9F%E5%88%99/</link>
      <pubDate>Sat, 26 Feb 2022 00:00:00 +0000</pubDate>
      
      <guid>https://yrunz.com/p/java%E5%AE%9E%E7%8E%B0%E5%AE%9E%E8%B7%B5gof%E7%9A%8423%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8Fsolid%E5%8E%9F%E5%88%99/</guid>
      <description>之前也有写过关于设计模式的文章《使用Go实现GoF的23种设计模式》，但是那个系列写了3篇文章就没在继续了，主要的原因是找不到合适的示例代码。考虑到，如果以类似于“鸭子是否会飞”、“烘培的制作流程”等贴近生活的事情举例，很难在我们日常的开发中产生联系。（目前应该很少有这些逻辑的软件系统吧）
《实践GoF的23种设计模式》可以看成是《使用Go实现GoF的23种设计模式》系列的重启，吸取了上次烂尾的教训，本次在写文章之前就已经完成了23种设计模式的示例代码实现。和上次不同，本次示例代码使用Java实现，以我们日常开发中经常碰到的一些技术/问题/场景作为切入点，示范如何运用设计模式来完成相关的实现。
 前言 从1995年GoF提出23种设计模式到现在，25年过去了，设计模式依旧是软件领域的热门话题。设计模式通常被定义为：
 设计模式（Design Pattern）是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结，使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。
 从定义上看，设计模式其实是一种经验的总结，是针对特定问题的简洁而优雅的解决方案。既然是经验总结，那么学习设计模式最直接的好处就在于可以站在巨人的肩膀上解决软件开发过程中的一些特定问题。
学习设计模式的最高境界是吃透它们本质思想，可以做到即使已经忘掉某个设计模式的名称和结构，也能在解决特定问题时信手拈来。设计模式背后的本质思想，就是我们熟知的SOLID原则。如果把设计模式类比为武侠世界里的武功招式，那么SOLID原则就是内功内力。通常来说，先把内功练好，再来学习招式，会达到事半功倍的效果。因此，在介绍设计模式之前，很有必要先介绍一下SOLID原则。
本文首先会介绍本系列文章中用到的示例代码demo的整体结构，然后开始逐一介绍SOLID原则，也即单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。
一个简单的分布式应用系统  本系列示例代码demo获取地址：https://github.com/ruanrunxue/Practice-Design-Pattern&amp;ndash;Java-Implementation
 示例代码demo工程实现了一个简单的分布式应用系统（单机版），该系统主要由以下几个模块组成：
 网络 Network，网络功能模块，模拟实现了报文转发、socket通信、http通信等功能。 数据库 Db，数据库功能模块，模拟实现了表、事务、dsl等功能。 消息队列 Mq，消息队列模块，模拟实现了基于topic的生产者/消费者的消息队列。 监控系统 Monitor，监控系统模块，模拟实现了服务日志的收集、分析、存储等功能。 边车 Sidecar，边车模块，模拟对网络报文进行拦截，实现access log上报、消息流控等功能。 服务 Service，运行服务，当前模拟实现了服务注册中心、在线商城服务集群、服务消息中介等服务。  示例代码demo工程的主要目录结构如下：
├── db # 数据库模块，定义Db、Table、TableVisitor等抽象接口 【@单例模式】 │ ├── cache # 数据库缓存代理，为Db新增缓存功能 【@代理模式】 │ ├── console # 数据库控制台实现，支持dsl语句查询和结果显示 【@适配器模式】 │ ├── dsl # 实现数据库dsl语句查询能力，当前只支持select语句查询 【@解释器模式】 │ ├── exception # 数据库模块相关异常定义 │ ├── iterator # 遍历表迭代器，包含按序遍历和随机遍历 【@迭代器模式】 │ └── transaction # 实现数据库的事务功能，包括执行、提交、回滚等 【@命令模式】【@备忘录模式】 ├── monitor # 监控系统模块，采用插件式的架构风格，当前实现access log日志etl功能 │ ├── config # 监控系统插件配置模块 【@抽象工厂模式】【@组合模式】 │ │ ├── json # 实现基于json格式文件的配置加载功能 │ │ └── yaml # 实现基于yaml格式文件的配置加载功能 │ ├── entity # 监控系统实体对象定义 │ ├── exception # 监控系统相关异常 │ ├── filter # Filter插件的实现定义 【@责任链模式】 │ ├── input # Input插件的实现定义 【@策略模式】 │ ├── output # Output插件的实现定义 │ ├── pipeline # Pipeline插件的实现定义，一个pipeline表示一个ETL处理流程 【@桥接模式】 │ ├── plugin # 插件抽象接口定义 │ └── schema # 监控系统相关的数据表定义  ├── mq # 消息队列模块 ├── network # 网络模块，模拟网络通信，定义了socket、packet等通用类型/接口 【@观察者模式】 │ └── http # 模拟实现了http通信等服务端、客户端能力 ├── service # 服务模块，定义了服务的基本接口 │ ├── mediator # 服务消息中介，作为服务通信的中转方，实现了服务发现，消息转发的能力 【@中介者模式】 │ ├── registry # 服务注册中心，提供服务注册、去注册、更新、 发现、订阅、去订阅、通知等功能 │ │ ├── entity # 服务注册/发现相关的实体定义 【@原型模式】【@建造者模式】 │ │ └── schema # 服务注册中心相关的数据表定义 【@访问者模式】【@享元模式】 │ └── shopping # 模拟在线商城服务群的定义，包含订单服务、库存服务、支付服务、发货服务 【@外观模式】 └── sidecar # 边车模块，对socket进行拦截，提供http access log、流控功能 【@装饰者模式】【@工厂模式】 └── flowctrl # 流控模块，基于消息速率进行随机流控 【@模板方法模式】【@状态模式】 SRP：单一职责原则 单一职责原则（The Single Responsibility Principle，SRP）应该是SOLID原则中，最容易被理解的一个，但同时也是最容易被误解的一个。很多人会把“将大函数重构成一个个职责单一的小函数”这一重构手法等价为SRP，这是不对的，小函数固然体现了职责单一，但这并不是SRP。</description>
    </item>
    
    <item>
      <title>一步步降低软件复杂度</title>
      <link>https://yrunz.com/p/%E4%B8%80%E6%AD%A5%E6%AD%A5%E9%99%8D%E4%BD%8E%E8%BD%AF%E4%BB%B6%E5%A4%8D%E6%9D%82%E5%BA%A6/</link>
      <pubDate>Sun, 08 Dec 2019 00:00:00 +0000</pubDate>
      
      <guid>https://yrunz.com/p/%E4%B8%80%E6%AD%A5%E6%AD%A5%E9%99%8D%E4%BD%8E%E8%BD%AF%E4%BB%B6%E5%A4%8D%E6%9D%82%E5%BA%A6/</guid>
      <description>前言 在进行软件开发时，我们常常会追求软件的高可维护性，高可维护性意味着当有新需求来时，系统易扩展；当出现bug时，开发人员易定位。而当我们说一个系统的可维护性太差时，往往指的是该系统太过复杂，导致给系统增加新功能时容易出现bug，而出现bug之后又难以定位。
那么，软件的复杂性又是如何定义的呢？
John Ousterhout给出的定义如下：
 Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system.
 可见，软件的复杂性是一个很泛的概念，任何使软件难以理解和难以修改的东西，都属于软件的复杂性。为此，John Ousterhout提出了一个公式来度量一个系统的复杂性： $$ C = \sum_{p}c_{p}t_{p} $$
 式中，$p$表示系统中的模块，$c_{p}$表示该模块的认知负担（Cognitive Load，即一个模块难以理解的程度），$t_{p}$表示在日常开发中在该模块花费的开发时间。
 从公式上看，一个软件的复杂性由它的各个模块的复杂性累加而成，而 模块复杂性 = 模块认知负担 * 模块开发时间，也就是模块的复杂性即和模块本身有关，也跟在该模块上花费的开发时间有关。需要注意的是，如果一个模块非常难以理解，但是后续开发过程中几乎没有涉及到它，那么它的复杂性也是很低的。
导致软件复杂的原因 导致软件复杂的原因可以细分出很多种来，而概括起来莫过于两种：依赖（dependencies） 和 隐晦（obscurity）。前者会让修改起来很费劲而且容易出现bug，比如当修改模块1时，往往也涉及到模块2、模块3、... 的改动；后者会让软件难以理解，定位一个bug，甚至是仅仅读懂一段代码都需要花费大量的时间。
软件的复杂性往往伴随着如下几种症状：
霰弹式修改（Change amplification）。当只需要修改一个功能，但又不得不对许多模块作出改动时，我们称之为霰弹式修改。这通常是因为模块之间耦合过重，相互依赖太多导致的。 比如，有一组Web页面，每个页面都是一个HTML文件，每个HTML都有一个背景属性。由于各个HTML的背景属性都是分开定义的，因此如果需要把背景颜色从橙色修改为蓝色时，就需要改动所有的HTML文件。
认知负担（Cognitive load）。当我们说一个模块隐晦、难以理解时，它就有过重的认知负担，这种情况下往往需要读者花费大量时间才能明白该模块的功能。比如，提供一个不带任何注释的calculate接口，它有2个int类型的入参和一个int类型的返回值。从该函数的签名上看，调用者根本无法得知函数的功能是什么，他只能通过花时间去阅读源码来确定函数功能后才敢去调用该函数。
int calculate(int val1, int val2); 不确定性（Unknown unknowns）。相比于前两种症状，不确定性的破坏性更大，它通常指一些在开发需求时，你必须注意的，但是又无从得知的点。它常常是因为一些隐晦的依赖导致的，会让你在开发完一个需求之后感觉心里很没谱，隐约觉得自己的代码哪里有问题，但又不清楚问题在哪，只能祈祷在测试阶段能够暴露而不要漏洞商用阶段。
如何降低软件的复杂性 对 “战术编程” Say No！ 很多程序员在进行特性开发或bug修复时，关注点往往是如何简单快速让程序跑起来，这就是典型的战术编程（Tactical programming）方法，它追求的是短期的效益——节省开发时间。战术编程最普遍的体现就是在编码之前没有进行模块设计，想到哪里就写到哪里。战术编程在系统前期可能会比较方便，一旦系统庞大起来、模块之间的耦合变重之后，添加或修改功能、修复bug都会变得寸步难行。随着系统变得越来越复杂，最后不得不对系统进行重构甚至重写。</description>
    </item>
    
    <item>
      <title>教你写好代码注释</title>
      <link>https://yrunz.com/p/%E6%95%99%E4%BD%A0%E5%86%99%E5%A5%BD%E4%BB%A3%E7%A0%81%E6%B3%A8%E9%87%8A/</link>
      <pubDate>Wed, 04 Dec 2019 00:00:00 +0000</pubDate>
      
      <guid>https://yrunz.com/p/%E6%95%99%E4%BD%A0%E5%86%99%E5%A5%BD%E4%BB%A3%E7%A0%81%E6%B3%A8%E9%87%8A/</guid>
      <description>前言 相信大家都会遇到这种情况：一周前自己写的代码，现在再拿出来看，发现读不懂了，“ 这代码是我写的？？？”。这时候，代码注释就可以发挥它的作用了——提高晦涩难懂的代码的可读性；注释可以起到隐藏代码复杂细节的作用，比如接口注释可以帮助开发者在没有阅读代码的情况下快速了解该接口的功能和用法；如果写的好，注释还可以改善系统的设计。
既然注释这么多好处，为什么我们程序员还是不愿意写注释？
“代码都写不完了，哪有时间写注释，以后再补吧“
时间不够论。这是最常见的原因，在交付速度飞快的今天，“代码写不完”是一个再常见不过的情况了，但写注释真的会导致需求延迟吗？绝不！相对于写一个接口的实现，写接口注释的时间可能只需要花费前者的5%。但不写注释，后面使用接口的人必须要多花费**50%**的时间去读懂代码！而且对于大部分程序员而言，“以后再补“大概要到2910年才能落实。
“好的代码就是最好的注释，我的代码可读性很好，没必要写注释”
好代码胜过注释论。不少程序员认为，好的代码就是最好的注释，只要代码可读性好，注释就可以省去。然而一个软件系统很多信息是无法通过代码呈现出来的，比如系统的设计思路、函数执行的预置条件等等。此外，代码可读性也不是绝对的，对于一个没有使用过Java 8 的 Lambda表达式的开发者而言，通篇的箭头“-&amp;gt;“简直就是一场噩梦。
”过期的注释容易误导人“
过期注释论。不可否认，过期的注释很容易误导读者，但是这并不能成为否认注释的借口。除非是重大的重构或重写，对注释进行大改动的情况很少出现。通常，在更改代码之后，只需花费极少的时间去更新注释，就可以避免过期注释这种情况了。
注释的分类 注释大致可以分成四类：接口注释、数据成员注释、实现注释和模块依赖注释。
接口注释 平时我们所说的接口，通常指的是一个类（包括interface、class、enum）和方法。对类而言，接口注释主要描述该类提供的功能；对方法而言，除了描述方法功能之外，方法的入参和返回值都要进行说明。当然，使用类/方法的一些预置条件和副作用等信息都需要在接口注释中提到。
/** * Returns the length of this string. * The length is equal to the number of &amp;lt;a href=&amp;#34;Character.html#unicode&amp;#34;&amp;gt;Unicode * code units&amp;lt;/a&amp;gt; in the string. * * @return the length of the sequence of characters represented by this * object. */ public int length() { return value.length; }  典型的接口注释（选自JDK 1.8中的String类）
 数据成员注释 数据成员注释和接口注释在大多数情况下都是必须的，这对于让读者快速读懂代码有很大的帮助。数据成员包括类的普通成员变量和静态成员变量，数据成员注释除了描述数据成员的本身用途之外，成员的默认值、副作用等信息都需要提及。</description>
    </item>
    
  </channel>
</rss>
