├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── deploy.sh ├── docs ├── .vuepress │ ├── config.ts │ └── public │ │ └── img │ │ └── logo.png ├── behavioral │ ├── command.md │ ├── command_structure.png │ ├── index.md │ ├── iterator.md │ ├── iterator_structure.png │ ├── mediator.md │ ├── mediator_structure.png │ ├── memento.md │ ├── memento_middleware_structure.png │ ├── memento_strict_structure.png │ ├── memento_structure.png │ ├── observer.md │ ├── observer_structure.png │ ├── responsibility.md │ ├── responsibility_structure.png │ ├── state.md │ ├── state_structure.png │ ├── strategy.md │ ├── strategy_structure.png │ ├── template.md │ ├── template_structure.png │ ├── visitor.md │ └── visitor_structure.png ├── creational │ ├── abstract_factory.md │ ├── abstract_factory_structure.png │ ├── builder.md │ ├── builder_structure.png │ ├── factory.md │ ├── factory_structure.png │ ├── index.md │ ├── prototype.md │ ├── prototype_structure_basic.png │ ├── prototype_structure_registry.png │ ├── singleton.md │ └── singleton_structure.png ├── index.md └── structural │ ├── adapter.md │ ├── adapter_class_structure.png │ ├── adapter_object_structure.png │ ├── birdge_structure.png │ ├── bridge.md │ ├── composite.md │ ├── composite_structure.png │ ├── decorator.md │ ├── decorator_structure.png │ ├── facade.md │ ├── facade_structure.png │ ├── flyweight.md │ ├── flyweight_structure.png │ ├── index.md │ ├── proxy.md │ └── proxy_structure.png ├── jest.config.ts ├── package.json ├── pnpm-lock.yaml ├── src ├── behavioral │ ├── command │ │ ├── __tests__ │ │ │ └── index.ts │ │ └── index.ts │ ├── index.ts │ ├── iterator │ │ ├── __tests__ │ │ │ └── index.ts │ │ └── index.ts │ ├── mediator │ │ ├── __tests__ │ │ │ └── index.ts │ │ └── index.ts │ ├── memento │ │ ├── __tests__ │ │ │ └── index.ts │ │ └── index.ts │ ├── observer │ │ ├── __tests__ │ │ │ └── index.ts │ │ └── index.ts │ ├── responsibility │ │ ├── __tests__ │ │ │ └── index.ts │ │ └── index.ts │ ├── state │ │ ├── __tests__ │ │ │ └── index.ts │ │ └── index.ts │ ├── strategy │ │ ├── __tests__ │ │ │ └── index.ts │ │ └── index.ts │ ├── template │ │ ├── __tests__ │ │ │ └── index.ts │ │ └── index.ts │ └── visitor │ │ ├── __tests__ │ │ └── index.ts │ │ └── index.ts ├── creational │ ├── abstractFactory │ │ ├── __tests__ │ │ │ └── index.ts │ │ └── index.ts │ ├── builder │ │ ├── __tests__ │ │ │ └── index.ts │ │ └── index.ts │ ├── factory │ │ ├── __tests__ │ │ │ └── index.ts │ │ └── index.ts │ ├── index.ts │ ├── prototype │ │ ├── __tests__ │ │ │ └── index.ts │ │ └── index.ts │ └── singleton │ │ ├── __tests__ │ │ └── index.ts │ │ └── index.ts ├── index.ts └── structural │ ├── adapter │ ├── __tests__ │ │ └── index.ts │ └── index.ts │ ├── bridge │ ├── __tests__ │ │ └── index.ts │ └── index.ts │ ├── composite │ ├── __tests__ │ │ └── index.ts │ └── index.ts │ ├── decorator │ ├── __tests__ │ │ └── index.ts │ └── index.ts │ ├── facade │ ├── __tests__ │ │ └── index.ts │ └── index.ts │ ├── flyweight │ ├── __tests__ │ │ └── index.ts │ └── index.ts │ ├── index.ts │ └── proxy │ ├── __tests__ │ └── index.ts │ └── index.ts ├── tsconfig.test.json ├── tsdoc ├── .nojekyll ├── assets │ ├── highlight.css │ ├── main.js │ ├── search.js │ └── style.css └── index.html └── typedoc.json /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | build-and-deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout ️ 11 | uses: actions/checkout@v2.3.1 12 | 13 | - name: lock npm version 14 | uses: actions/setup-node@v3 15 | with: 16 | node-version: 16.18.0 17 | 18 | - name: Install and Build 19 | run: | 20 | npm i -g pnpm 21 | pnpm run init 22 | pnpm run docs:build 23 | env: 24 | NODE_OPTIONS: '--max_old_space_size=4096' 25 | 26 | - name: Deploy 27 | uses: JamesIves/github-pages-deploy-action@4.1.3 28 | with: 29 | BRANCH: gh-pages 30 | FOLDER: docs/.vuepress/dist 31 | ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | *.log 4 | 5 | .DS_Store 6 | .idea/ 7 | .temp/ 8 | 9 | dist/ 10 | build/ 11 | lib/ 12 | es/ 13 | coverage/ 14 | release/ 15 | 16 | node_modules/ 17 | 18 | .cache/ 19 | out/ -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "chenghuai", 4 | "iife", 5 | "vuepress" 6 | ], 7 | "git.ignoreLimitWarning": true 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 印客学院 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # encode-design-pattern 2 | 3 | > 印客学院 设计模式深入浅出 4 | 5 | ## :mountain: 能力支持 6 | 7 | ### 1. 完备的设计模式内容 8 | 9 | 提供 GOF 中全部的设计模式,包含全部内容讲解及实际代码演示 10 | 11 | ### 2. 支持 Typescript 12 | 13 | 提供完整的类型注释,帮助您从 0~1 掌握完整的代码规范 14 | 15 | ### 3. 全套的测试用例 16 | 17 | 配套完整的测试用例,帮助您掌握完整使用 18 | 19 | ## :star: 设计目的 20 | 21 | 鉴于大部分同学在开发过程中对代码的健壮性和扩展性考虑很少,同时开发过程中在重复相同的代码,其中,最大的原因就是缺乏整体考量的设计。 22 | 23 | 本项目会以设计模式为切入点,从`GOF`所提出的完整的设计模式开始,带领大家掌握设计模式之美。 24 | 25 | ## :grey_question: 什么是设计模式 26 | 27 | **设计模式**是软件设计中常见问题的典型解决方案。 它们就像能根据需求进行调整的预制蓝图, 可用于解决代码中反复出现的设计问题。 28 | 29 | 设计模式与方法或库的使用方式不同, 你很难直接在自己的程序中套用某个设计模式。 模式并不是一段特定的代码, 而是解决特定问题的一般性概念。 你可以根据模式来实现符合自己程序实际所需的解决方案。 30 | 31 | 算法更像是菜谱: 提供达成目标的明确步骤。 而模式更像是蓝图: 你可以看到最终的结果和模式的功能, 但需要自己确定实现步骤。 32 | 33 | ## :bulb: 为什要学习设计模式 34 | 35 | **设计模式**是针对软件设计中常见问题的工具箱, 其中的工具就是各种经过实践验证的解决方案。 即使你从未遇到过这些问题, 了解模式仍然非常有用, 因为它能指导你如何使用面向对象的设计原则来解决各种问题。 36 | 37 | 设计模式定义了一种让你和团队成员能够更高效沟通的通用语言。 你只需说 “哦, 这里用单例就可以了”, 所有人都会理解这条建议背后的想法。 只要知晓模式及其名称, 你就无需解释什么是单例。 38 | 39 | ## :email: 联系 40 | 41 | - **印客学院官网**: 42 | - **设计模式深入浅出** 43 | - **GitHub**: 44 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 确保脚本抛出遇到的错误 4 | set -e 5 | 6 | 7 | push_addr=`git remote get-url --push origin` 8 | commit_info=`git describe --all --always --long` 9 | dist_path=docs/.vuepress/dist 10 | push_branch=gh-pages 11 | 12 | # 生成静态文件 13 | npm run docs:build 14 | 15 | # 进入生成的文件夹 16 | cd $dist_path 17 | 18 | git init 19 | git add -A 20 | git commit -m "deploy, $commit_info" 21 | git push -f $push_addr HEAD:$push_branch 22 | 23 | cd - 24 | rm -rf $dist_path 25 | -------------------------------------------------------------------------------- /docs/.vuepress/config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig4CustomTheme, UserPlugins } from 'vuepress/config'; 2 | 3 | export default defineConfig4CustomTheme({ 4 | locales: { 5 | '/': { 6 | lang: 'zh-CN', 7 | title: '印客学院', 8 | description: '设计模式深入浅出', 9 | }, 10 | }, 11 | base: '/design-pattern/', 12 | themeConfig: { 13 | nav: [ 14 | { text: '首页', link: '/index.md' }, 15 | { 16 | text: '设计模式', 17 | items: [ 18 | { text: '创建者模式', link: '/creational/index.md' }, 19 | { text: '结构型模式', link: '/structural/index.md' }, 20 | { text: '行为模式', link: '/behavioral/index.md' }, 21 | ], 22 | }, 23 | ], 24 | sidebar: [ 25 | { 26 | title: '创建者模式', 27 | path: '/creational', 28 | children: [ 29 | { 30 | title: '工厂模式', 31 | path: '/creational/factory.md', 32 | }, 33 | { 34 | title: '抽象工厂模式', 35 | path: '/creational/abstract_factory.md', 36 | }, 37 | { 38 | title: '生成器模式', 39 | path: '/creational/builder.md', 40 | }, 41 | { 42 | title: '原型模式', 43 | path: '/creational/prototype.md', 44 | }, 45 | { 46 | title: '单例模式', 47 | path: '/creational/singleton.md', 48 | }, 49 | ], 50 | }, 51 | { 52 | title: '结构型模式', 53 | path: '/structural', 54 | children: [ 55 | { 56 | title: '适配器模式', 57 | path: '/structural/adapter.md', 58 | }, 59 | { 60 | title: '桥接模式', 61 | path: '/structural/bridge.md', 62 | }, 63 | { 64 | title: '组合模式', 65 | path: '/structural/composite.md', 66 | }, 67 | { 68 | title: '装饰模式', 69 | path: '/structural/decorator.md', 70 | }, 71 | { 72 | title: '外观模式', 73 | path: '/structural/facade.md', 74 | }, 75 | { 76 | title: '享元模式', 77 | path: '/structural/flyweight.md', 78 | }, 79 | { 80 | title: '代理模式', 81 | path: '/structural/proxy.md', 82 | }, 83 | ], 84 | }, 85 | { 86 | title: '行为模式', 87 | path: '/behavioral', 88 | children: [ 89 | { 90 | title: '责任链模式', 91 | path: '/behavioral/responsibility.md', 92 | }, 93 | { 94 | title: '命令模式', 95 | path: '/behavioral/command.md', 96 | }, 97 | { 98 | title: '迭代器模式', 99 | path: '/behavioral/iterator.md', 100 | }, 101 | { 102 | title: '中介者模式', 103 | path: '/behavioral/mediator.md', 104 | }, 105 | { 106 | title: '备忘录模式', 107 | path: '/behavioral/memento.md', 108 | }, 109 | { 110 | title: '观察者模式', 111 | path: '/behavioral/observer.md', 112 | }, 113 | { 114 | title: '状态模式', 115 | path: '/behavioral/state.md', 116 | }, 117 | { 118 | title: '策略模式', 119 | path: '/behavioral/strategy.md', 120 | }, 121 | { 122 | title: '模板方法模式', 123 | path: '/behavioral/template.md', 124 | }, 125 | { 126 | title: '访问者模式', 127 | path: '/behavioral/visitor.md', 128 | }, 129 | ], 130 | }, 131 | ], 132 | logo: '/img/logo.png', 133 | repo: 'encode-studio-fe/design-pattern', 134 | searchMaxSuggestions: 10, 135 | docsDir: 'docs', 136 | footer: { 137 | createYear: 2023, 138 | copyrightInfo: 139 | 'encode studio | github', 140 | }, 141 | 142 | extendFrontmatter: { 143 | author: { 144 | name: '澄怀', 145 | link: 'https://github.com/encode-studio-fe/design-pattern', 146 | }, 147 | }, 148 | }, 149 | 150 | head: [ 151 | ['link', { rel: 'icon', href: '/img/logo.png' }], 152 | [ 153 | 'meta', 154 | { 155 | name: 'keywords', 156 | content: '设计模式深入浅出', 157 | }, 158 | ], 159 | ], 160 | 161 | plugins: [ 162 | [ 163 | 'one-click-copy', 164 | { 165 | copySelector: ['div[class*="language-"] pre', 'div[class*="aside-code"] aside'], 166 | copyMessage: '复制成功', 167 | duration: 1000, 168 | showInMobile: false, 169 | }, 170 | ], 171 | 172 | [ 173 | 'vuepress-plugin-zooming', 174 | { 175 | selector: '.theme-vdoing-content img:not(.no-zoom)', 176 | options: { 177 | bgColor: 'rgba(0,0,0,0.6)', 178 | }, 179 | }, 180 | ], 181 | ], 182 | extraWatchFiles: ['.vuepress/config.ts'], 183 | }); 184 | -------------------------------------------------------------------------------- /docs/.vuepress/public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/.vuepress/public/img/logo.png -------------------------------------------------------------------------------- /docs/behavioral/command.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 命令模式 3 | categories: 4 | - 设计模式 5 | - 行为模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 命令模式 14 | 15 | :::tip 16 | 17 | **命令模式**是一种行为设计模式, 它可将请求转换为一个包含与请求相关的所有信息的独立对象。 该转换让你能根据不同的请求将方法参数化、 延迟请求执行或将其放入队列中, 且能实现可撤销操作。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ![command_structure](./command_structure.png) 23 | 24 | 1. 发送者 (`Sender`)——亦称 “触发者 (Invoker)”——类负责对请求进行初始化, 其中必须包含一个成员变量来存储对于命令对象的引用。 发送者触发命令, 而不向接收者直接发送请求。 注意, 发送者并不负责创建命令对象: 它通常会通过构造函数从客户端处获得预先生成的命令。 25 | 2. 命令 (`Command`) 接口通常仅声明一个执行命令的方法。 26 | 3. 具体命令 (`Concrete Commands`) 会实现各种类型的请求。 具体命令自身并不完成工作, 而是会将调用委派给一个业务逻辑对象。 但为了简化代码, 这些类可以进行合并。接收对象执行方法所需的参数可以声明为具体命令的成员变量。 你可以将命令对象设为不可变, 仅允许通过构造函数对这些成员变量进行初始化。 27 | 4. 接收者 (`Receiver`) 类包含部分业务逻辑。 几乎任何对象都可以作为接收者。 绝大部分命令只处理如何将请求传递到接收者的细节, 接收者自己会完成实际的工作。 28 | 5. 客户端 (`Client`) 会创建并配置具体命令对象。 客户端必须将包括接收者实体在内的所有请求参数传递给命令的构造函数。 此后, 生成的命令就可以与一个或多个发送者相关联了。 29 | 30 | ## 适用场景 31 | 32 | 1. 如果你需要通过操作来参数化对象, 可使用命令模式。 33 | 34 | 1. 命令模式可将特定的方法调用转化为独立对象。 这一改变也带来了许多有趣的应用: 你可以将命令作为方法的参数进行传递、 将命令保存在其他对象中, 或者在运行时切换已连接的命令等。 35 | 2. 举个例子: 你正在开发一个 GUI 组件 (例如上下文菜单), 你希望用户能够配置菜单项, 并在点击菜单项时触发操作。 36 | 37 | 2. 如果你想要将操作放入队列中、 操作的执行或者远程执行操作, 可使用命令模式。 38 | 39 | 1. 同其他对象一样, 命令也可以实现序列化 (序列化的意思是转化为字符串), 从而能方便地写入文件或数据库中。 一段时间后, 该字符串可被恢复成为最初的命令对象。 因此, 你可以延迟或计划命令的执行。 但其功能远不止如此! 使用同样的方式, 你还可以将命令放入队列、 记录命令或者通过网络发送命令。 40 | 41 | 3. 如果你想要实现操作回滚功能, 可使用命令模式。 42 | 43 | 1. 尽管有很多方法可以实现撤销和恢复功能, 但命令模式可能是其中最常用的一种。 44 | 2. 为了能够回滚操作, 你需要实现已执行操作的历史记录功能。 命令历史记录是一种包含所有已执行命令对象及其相关程序状态备份的栈结构。 45 | 3. 这种方法有两个缺点。 首先, 程序状态的保存功能并不容易实现, 因为部分状态可能是私有的。 你可以使用备忘录模式来在一定程度上解决这个问题。 46 | 4. 其次, 备份状态可能会占用大量内存。 因此, 有时你需要借助另一种实现方式: 命令无需恢复原始状态, 而是执行反向操作。 反向操作也有代价: 它可能会很难甚至是无法实现。 47 | 48 | ## 优缺点 49 | 50 | ### 优点 51 | 52 | 1. 单一职责原则。 你可以解耦触发和执行操作的类。 53 | 2. 开闭原则。 你可以在不修改已有客户端代码的情况下在程序中创建新的命令。 54 | 3. 你可以实现撤销和恢复功能。 55 | 4. 你可以实现操作的延迟执行。 56 | 5. 你可以将一组简单命令组合成一个复杂命令。 57 | 58 | ### 缺点 59 | 60 | 1. 代码可能会变得更加复杂, 因为你在发送者和接收者之间增加了一个全新的层次。 61 | 62 | ## 与其他模式的关系 63 | 64 | 1. 责任链模式、 命令模式、 中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式: 65 | 1. 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。 66 | 2. 命令在发送者和请求者之间建立单向连接。 67 | 3. 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。 68 | 4. 观察者允许接收者动态地订阅或取消接收请求。 69 | 2. 责任链的管理者可使用命令模式实现。 在这种情况下, 你可以对由请求代表的同一个上下文对象执行许多不同的操作。还有另外一种实现方式, 那就是请求自身就是一个命令对象。 在这种情况下, 你可以对由一系列不同上下文连接而成的链执行相同的操作。你可以同时使用命令和备忘录模式来实现 “撤销”。 在这种情况下, 命令用于对目标对象执行各种不同的操作, 备忘录用来保存一条命令执行前该对象的状态。 70 | 3. 命令和策略模式看上去很像, 因为两者都能通过某些行为来参数化对象。 但是, 它们的意图有非常大的不同。你可以使用命令来将任何操作转换为对象。 操作的参数将成为对象的成员变量。 你可以通过转换来延迟操作的执行、 将操作放入队列、 保存历史命令或者向远程服务发送命令等。另一方面, 策略通常可用于描述完成某件事的不同方式, 让你能够在同一个上下文类中切换算法。 71 | 4. 原型模式可用于保存命令的历史记录。 72 | 5. 你可以将访问者模式视为命令模式的加强版本, 其对象可对不同类的多种对象执行操作。 73 | 74 | ## 代码实现 75 | 76 | ```typescript 77 | interface Command { 78 | execute(): string; 79 | } 80 | 81 | class SimpleCommand implements Command { 82 | private payload: string; 83 | 84 | constructor(payload: string) { 85 | this.payload = payload; 86 | } 87 | 88 | public execute(): string { 89 | return `SimpleCommand: ${this.payload}`; 90 | } 91 | } 92 | 93 | class ComplexCommand implements Command { 94 | private receiver: Receiver; 95 | 96 | private a: string; 97 | 98 | private b: string; 99 | 100 | constructor(receiver: Receiver, a: string, b: string) { 101 | this.receiver = receiver; 102 | this.a = a; 103 | this.b = b; 104 | } 105 | 106 | public execute(): string { 107 | return this.receiver.doSomething(this.a) + ' ' + this.receiver.doSomethingElse(this.b); 108 | } 109 | } 110 | 111 | class Receiver { 112 | public doSomething(a: string): string { 113 | return a; 114 | } 115 | 116 | public doSomethingElse(b: string): string { 117 | return b; 118 | } 119 | } 120 | 121 | class Invoker { 122 | private onStart: Command; 123 | 124 | private onFinish: Command; 125 | 126 | public setOnStart(command: Command): void { 127 | this.onStart = command; 128 | } 129 | 130 | public setOnFinish(command: Command): void { 131 | this.onFinish = command; 132 | } 133 | 134 | public doSomethingImportant() { 135 | if (this.isCommand(this.onStart)) { 136 | return this.onStart.execute(); 137 | } 138 | 139 | // if (this.isCommand(this.onFinish)) { 140 | // return this.onFinish.execute(); 141 | // } 142 | } 143 | 144 | private isCommand(object): object is Command { 145 | return object.execute !== undefined; 146 | } 147 | } 148 | 149 | export { Command, SimpleCommand, ComplexCommand, Receiver, Invoker }; 150 | ``` 151 | 152 | ## 测试用例 153 | 154 | ```typescript 155 | import { Invoker, Receiver, SimpleCommand, ComplexCommand } from '../index'; 156 | 157 | describe('command pattern', () => { 158 | it('command concrete creator', () => { 159 | const invoker = new Invoker(); 160 | const receiver = new Receiver(); 161 | 162 | invoker.setOnStart(new SimpleCommand('Say Hi!')); 163 | invoker.setOnFinish(new ComplexCommand(receiver, 'Send email', 'Save report')); 164 | 165 | expect(invoker.doSomethingImportant()).toBe('SimpleCommand: Say Hi!'); 166 | // expect(invoker.doSomethingImportant()).toBe('Send email Save report'); 167 | }); 168 | }); 169 | ``` 170 | -------------------------------------------------------------------------------- /docs/behavioral/command_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/behavioral/command_structure.png -------------------------------------------------------------------------------- /docs/behavioral/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 行为模式 3 | categories: 4 | - 设计模式 5 | tags: 6 | - 设计模式 7 | author: 8 | name: 澄怀 9 | link: https://github.com/encode-studio-fe/design-pattern 10 | --- 11 | 12 | # 行为模式 13 | 14 | :::tip 15 | 行为模式负责对象间的高效沟通和职责委派。 16 | ::: 17 | 18 | [**责任链模式**](./responsibility.md): 允许将请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。 19 | 20 | [**命令模式**](./command.md): 可将请求转换为一个包含与请求相关的所有信息的独立对象。 该转换让你能根据不同的请求将方法参数化、 延迟请求执行或将其放入队列中, 且能实现可撤销操作。 21 | 22 | [**迭代器模式**](./iterator.md): 能在不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素。 23 | 24 | [**中介者模式**](./mediator.md): 减少对象之间混乱无序的依赖关系。 该模式会限制对象之间的直接交互, 迫使它们通过一个中介者对象进行合作。 25 | 26 | [**备忘录模式**](./memento.md): 允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态。 27 | 28 | [**观察者模式**](./observer.md): 允许你定义一种订阅机制, 可在对象事件发生时通知多个 “观察” 该对象的其他对象。 29 | 30 | [**状态模式**](./state.md): 能在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。 31 | 32 | [**策略模式**](./strategy.md): 能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换。 33 | 34 | [**模板方法模式**](./template.md): 在超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。 35 | 36 | [**访问者模式**](./visitor.md): 能将算法与其所作用的对象隔离开来。 37 | -------------------------------------------------------------------------------- /docs/behavioral/iterator.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 迭代器模式 3 | categories: 4 | - 设计模式 5 | - 行为模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 迭代器模式 14 | 15 | :::tip 16 | 17 | **迭代器模式**是一种行为设计模式, 让你能在不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ![iterator_structure](./iterator_structure.png) 23 | 24 | 1. 迭代器 (`Iterator`) 接口声明了遍历集合所需的操作: 获取下一个元素、 获取当前位置和重新开始迭代等。 25 | 2. 具体迭代器 (`Concrete Iterators`) 实现遍历集合的一种特定算法。 迭代器对象必须跟踪自身遍历的进度。 这使得多个迭代器可以相互独立地遍历同一集合。 26 | 3. 集合 (`Collection`) 接口声明一个或多个方法来获取与集合兼容的迭代器。 请注意, 返回方法的类型必须被声明为迭代器接口, 因此具体集合可以返回各种不同种类的迭代器。 27 | 4. 具体集合 (`Concrete Collections`) 会在客户端请求迭代器时返回一个特定的具体迭代器类实体。 你可能会琢磨, 剩下的集合代码在什么地方呢? 不用担心, 它也会在同一个类中。 只是这些细节对于实际模式来说并不重要, 所以我们将其省略了而已。 28 | 5. 客户端 (`Client`) 通过集合和迭代器的接口与两者进行交互。 这样一来客户端无需与具体类进行耦合, 允许同一客户端代码使用各种不同的集合和迭代器。客户端通常不会自行创建迭代器, 而是会从集合中获取。 但在特定情况下, 客户端可以直接创建一个迭代器 (例如当客户端需要自定义特殊迭代器时)。 29 | 30 | ## 适用场景 31 | 32 | 1. 当集合背后为复杂的数据结构, 且你希望对客户端隐藏其复杂性时 (出于使用便利性或安全性的考虑), 可以使用迭代器模式。 33 | 34 | 1. 迭代器封装了与复杂数据结构进行交互的细节, 为客户端提供多个访问集合元素的简单方法。 这种方式不仅对客户端来说非常方便, 而且能避免客户端在直接与集合交互时执行错误或有害的操作, 从而起到保护集合的作用。 35 | 36 | 2. 使用该模式可以减少程序中重复的遍历代码。 37 | 38 | 1. 重要迭代算法的代码往往体积非常庞大。 当这些代码被放置在程序业务逻辑中时, 它会让原始代码的职责模糊不清, 降低其可维护性。 因此, 将遍历代码移到特定的迭代器中可使程序代码更加精炼和简洁。 39 | 40 | 3. 如果你希望代码能够遍历不同的甚至是无法预知的数据结构, 可以使用迭代器模式。 41 | 42 | 1. 该模式为集合和迭代器提供了一些通用接口。 如果你在代码中使用了这些接口, 那么将其他实现了这些接口的集合和迭代器传递给它时, 它仍将可以正常运行。 43 | 44 | ## 优缺点 45 | 46 | ### 优点 47 | 48 | 1. 单一职责原则。 通过将体积庞大的遍历算法代码抽取为独立的类, 你可对客户端代码和集合进行整理。 49 | 2. 开闭原则。 你可实现新型的集合和迭代器并将其传递给现有代码, 无需修改现有代码。 50 | 3. 你可以并行遍历同一集合, 因为每个迭代器对象都包含其自身的遍历状态。 51 | 4. 相似的, 你可以暂停遍历并在需要时继续。 52 | 53 | ### 缺点 54 | 55 | 1. 如果你的程序只与简单的集合进行交互, 应用该模式可能会矫枉过正。 56 | 2. 对于某些特殊集合, 使用迭代器可能比直接遍历的效率低。 57 | 58 | ## 与其他模式的关系 59 | 60 | 1. 你可以使用迭代器模式来遍历组合模式树。 61 | 2. 你可以同时使用工厂方法模式和迭代器来让子类集合返回不同类型的迭代器, 并使得迭代器与集合相匹配。 62 | 3. 你可以同时使用备忘录模式和迭代器来获取当前迭代器的状态, 并且在需要的时候进行回滚。 63 | 4. 可以同时使用访问者模式和迭代器来遍历复杂数据结构, 并对其中的元素执行所需操作, 即使这些元素所属的类完全不同。 64 | 65 | ## 代码实现 66 | 67 | ```typescript 68 | interface Iterator { 69 | current(): T; 70 | next(): T; 71 | key(): number; 72 | valid(): boolean; 73 | rewind(): void; 74 | } 75 | 76 | interface Aggregator { 77 | getIterator(): Iterator; 78 | } 79 | 80 | class AlphabeticalOrderIterator implements Iterator { 81 | private collection: WordsCollection; 82 | private position: number = 0; 83 | 84 | private reverse: boolean = false; 85 | 86 | constructor(collection: WordsCollection, reverse: boolean = false) { 87 | this.collection = collection; 88 | this.reverse = reverse; 89 | 90 | if (reverse) { 91 | this.position = collection.getCount() - 1; 92 | } 93 | } 94 | 95 | public rewind() { 96 | this.position = this.reverse ? this.collection.getCount() - 1 : 0; 97 | } 98 | 99 | public current(): string { 100 | return this.collection.getItems()[this.position]; 101 | } 102 | 103 | public key(): number { 104 | return this.position; 105 | } 106 | 107 | public next() { 108 | const item = this.collection.getItems()[this.position]; 109 | this.position += this.reverse ? -1 : 1; 110 | return item; 111 | } 112 | 113 | public valid(): boolean { 114 | if (this.reverse) { 115 | return this.position >= 0; 116 | } 117 | 118 | return this.position < this.collection.getCount(); 119 | } 120 | } 121 | 122 | class WordsCollection implements Aggregator { 123 | private items: string[] = []; 124 | 125 | public getItems(): string[] { 126 | return this.items; 127 | } 128 | 129 | public getCount(): number { 130 | return this.items.length; 131 | } 132 | 133 | public addItem(item: string): void { 134 | this.items.push(item); 135 | } 136 | 137 | public getIterator(): Iterator { 138 | return new AlphabeticalOrderIterator(this); 139 | } 140 | 141 | public getReverseIterator(): Iterator { 142 | return new AlphabeticalOrderIterator(this, true); 143 | } 144 | } 145 | 146 | export { Iterator, Aggregator, AlphabeticalOrderIterator, WordsCollection }; 147 | ``` 148 | 149 | ## 测试用例 150 | 151 | ```typescript 152 | import { WordsCollection } from '../index'; 153 | 154 | describe('iterator pattern', () => { 155 | it('iterator concrete creator', () => { 156 | let str = ''; 157 | 158 | const collection = new WordsCollection(); 159 | collection.addItem('First'); 160 | collection.addItem('Second'); 161 | collection.addItem('Third'); 162 | 163 | const iterator = collection.getIterator(); 164 | 165 | while (iterator.valid()) { 166 | str += `${iterator.next()} `; 167 | } 168 | 169 | const reverseIterator = collection.getReverseIterator(); 170 | while (reverseIterator.valid()) { 171 | str += `${reverseIterator.next()} `; 172 | } 173 | expect(str).toBe('First Second Third Third Second First '); 174 | }); 175 | }); 176 | ``` 177 | -------------------------------------------------------------------------------- /docs/behavioral/iterator_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/behavioral/iterator_structure.png -------------------------------------------------------------------------------- /docs/behavioral/mediator.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 中介者模式 3 | categories: 4 | - 设计模式 5 | - 行为模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 中介者模式 14 | 15 | :::tip 16 | 17 | **中介者模式**是一种行为设计模式, 能让你减少对象之间混乱无序的依赖关系。 该模式会限制对象之间的直接交互, 迫使它们通过一个中介者对象进行合作。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ![mediator_structure](./mediator_structure.png) 23 | 24 | 1. 组件 (`Component`) 是各种包含业务逻辑的类。 每个组件都有一个指向中介者的引用, 该引用被声明为中介者接口类型。 组件不知道中介者实际所属的类, 因此你可通过将其连接到不同的中介者以使其能在其他程序中复用。 25 | 2. 中介者 (`Mediator`) 接口声明了与组件交流的方法, 但通常仅包括一个通知方法。 组件可将任意上下文 (包括自己的对象) 作为该方法的参数, 只有这样接收组件和发送者类之间才不会耦合。 26 | 3. 具体中介者 (`Concrete Mediator`) 封装了多种组件间的关系。 具体中介者通常会保存所有组件的引用并对其进行管理, 甚至有时会对其生命周期进行管理。 27 | 4. 组件并不知道其他组件的情况。 如果组件内发生了重要事件, 它只能通知中介者。 中介者收到通知后能轻易地确定发送者, 这或许已足以判断接下来需要触发的组件了。对于组件来说, 中介者看上去完全就是一个黑箱。 发送者不知道最终会由谁来处理自己的请求, 接收者也不知道最初是谁发出了请求。 28 | 29 | ## 适用场景 30 | 31 | 1. 当一些对象和其他对象紧密耦合以致难以对其进行修改时, 可使用中介者模式。 32 | 33 | 1. 该模式让你将对象间的所有关系抽取成为一个单独的类, 以使对于特定组件的修改工作独立于其他组件。 34 | 35 | 2. 当组件因过于依赖其他组件而无法在不同应用中复用时, 可使用中介者模式。 36 | 37 | 1. 应用中介者模式后, 每个组件不再知晓其他组件的情况。 尽管这些组件无法直接交流, 但它们仍可通过中介者对象进行间接交流。 如果你希望在不同应用中复用一个组件, 则需要为其提供一个新的中介者类。 38 | 39 | 3. 如果为了能在不同情景下复用一些基本行为, 导致你需要被迫创建大量组件子类时, 可使用中介者模式。 40 | 41 | 1. 由于所有组件间关系都被包含在中介者中, 因此你无需修改组件就能方便地新建中介者类以定义新的组件合作方式。 42 | 43 | ## 优缺点 44 | 45 | ### 优点 46 | 47 | 1. 单一职责原则。 你可以将多个组件间的交流抽取到同一位置, 使其更易于理解和维护。 48 | 2. 开闭原则。 你无需修改实际组件就能增加新的中介者。 49 | 3. 你可以减轻应用中多个组件间的耦合情况。 50 | 4. 你可以更方便地复用各个组件。 51 | 52 | ### 缺点 53 | 54 | 1. 一段时间后, 中介者可能会演化成为上帝对象。 55 | 56 | ## 与其他模式的关系 57 | 58 | 1. 责任链模式、 命令模式、 中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式: 59 | 1. 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。 60 | 2. 命令在发送者和请求者之间建立单向连接。 61 | 3. 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。 62 | 4. 观察者允许接收者动态地订阅或取消接收请求。 63 | 2. 外观模式和中介者的职责类似: 它们都尝试在大量紧密耦合的类中组织起合作。 64 | 1. 外观为子系统中的所有对象定义了一个简单接口, 但是它不提供任何新功能。 子系统本身不会意识到外观的存在。 子系统中的对象可以直接进行交流。 65 | 2. 中介者将系统中组件的沟通行为中心化。 各组件只知道中介者对象, 无法直接相互交流。 66 | 3. 中介者和观察者之间的区别往往很难记住。 在大部分情况下, 你可以使用其中一种模式, 而有时可以同时使用。 让我们来看看如何做到这一点。 67 | 1. 中介者的主要目标是消除一系列系统组件之间的相互依赖。 这些组件将依赖于同一个中介者对象。 观察者的目标是在对象之间建立动态的单向连接, 使得部分对象可作为其他对象的附属发挥作用。 68 | 2. 有一种流行的中介者模式实现方式依赖于观察者。 中介者对象担当发布者的角色, 其他组件则作为订阅者, 可以订阅中介者的事件或取消订阅。 当中介者以这种方式实现时, 它可能看上去与观察者非常相似。 69 | 3. 当你感到疑惑时, 记住可以采用其他方式来实现中介者。 例如, 你可永久性地将所有组件链接到同一个中介者对象。 这种实现方式和观察者并不相同, 但这仍是一种中介者模式。假设有一个程序, 其所有的组件都变成了发布者, 它们之间可以相互建立动态连接。 这样程序中就没有中心化的中介者对象, 而只有一些分布式的观察者。 70 | 71 | ## 代码实现 72 | 73 | ```typescript 74 | interface Mediator { 75 | notify(sender: object, event: string): void; 76 | } 77 | 78 | class ConcreteMediator implements Mediator { 79 | private component1: Component1; 80 | 81 | private component2: Component2; 82 | 83 | constructor(c1: Component1, c2: Component2) { 84 | this.component1 = c1; 85 | this.component1.setMediator(this); 86 | this.component2 = c2; 87 | this.component2.setMediator(this); 88 | } 89 | 90 | public notify(sender: object, event: string): void { 91 | if (event === 'A') { 92 | this.component2.doC(); 93 | } 94 | 95 | if (event === 'D') { 96 | this.component1.doB(); 97 | this.component2.doC(); 98 | } 99 | } 100 | } 101 | 102 | class BaseComponent { 103 | protected mediator: Mediator; 104 | 105 | constructor(mediator?: Mediator) { 106 | this.mediator = mediator!; 107 | } 108 | 109 | public setMediator(mediator: Mediator): void { 110 | this.mediator = mediator; 111 | } 112 | } 113 | 114 | class Component1 extends BaseComponent { 115 | public doA(): string { 116 | this.mediator.notify(this, 'A'); 117 | return 'component 1 does A '; 118 | } 119 | 120 | public doB(): string { 121 | this.mediator.notify(this, 'B'); 122 | return 'component 1 does B '; 123 | } 124 | } 125 | 126 | class Component2 extends BaseComponent { 127 | public doC(): string { 128 | this.mediator.notify(this, 'C'); 129 | return 'component 2 does C '; 130 | } 131 | 132 | public doD(): string { 133 | this.mediator.notify(this, 'D'); 134 | return 'component 2 does D '; 135 | } 136 | } 137 | 138 | export { Mediator, ConcreteMediator, BaseComponent, Component1, Component2 }; 139 | ``` 140 | 141 | ## 测试用例 142 | 143 | ```typescript 144 | import { Component1, Component2, ConcreteMediator } from '../index'; 145 | 146 | describe('mediator pattern', () => { 147 | it('mediator concrete creator', () => { 148 | const c1 = new Component1(); 149 | const c2 = new Component2(); 150 | const mediator = new ConcreteMediator(c1, c2); 151 | 152 | expect(c1.doA()).toBe('component 1 does A '); 153 | expect(c2.doD()).toBe('component 2 does D '); 154 | }); 155 | }); 156 | ``` 157 | -------------------------------------------------------------------------------- /docs/behavioral/mediator_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/behavioral/mediator_structure.png -------------------------------------------------------------------------------- /docs/behavioral/memento.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 备忘录模式 3 | categories: 4 | - 设计模式 5 | - 行为模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 备忘录模式 14 | 15 | :::tip 16 | 17 | **备忘录模式**是一种行为设计模式, 允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ### 基于嵌套类的实现 23 | 24 | ![memento_structure](./memento_structure.png) 25 | 26 | 1. 原发器 (`Originator`) 类可以生成自身状态的快照, 也可以在需要时通过快照恢复自身状态。 27 | 2. 备忘录 (`Memento`) 是原发器状态快照的值对象 (value object)。 通常做法是将备忘录设为不可变的, 并通过构造函数一次性传递数据。 28 | 3. 负责人 (`Caretaker`) 仅知道 “何时” 和 “为何” 捕捉原发器的状态, 以及何时恢复状态。负责人通过保存备忘录栈来记录原发器的历史状态。 当原发器需要回溯历史状态时, 负责人将从栈中获取最顶部的备忘录, 并将其传递给原发器的恢复 (restoration) 方法。 29 | 4. 在该实现方法中, 备忘录类将被嵌套在原发器中。 这样原发器就可访问备忘录的成员变量和方法, 即使这些方法被声明为私有。 另一方面, 负责人对于备忘录的成员变量和方法的访问权限非常有限: 它们只能在栈中保存备忘录, 而不能修改其状态。 30 | 31 | ### 基于中间接口的实现 32 | 33 | ![memento_middleware_structure](./memento_middleware_structure.png) 34 | 35 | 1. 在没有嵌套类的情况下, 你可以规定负责人仅可通过明确声明的中间接口与备忘录互动, 该接口仅声明与备忘录元数据相关的方法, 限制其对备忘录成员变量的直接访问权限。 36 | 2. 另一方面, 原发器可以直接与备忘录对象进行交互, 访问备忘录类中声明的成员变量和方法。 这种方式的缺点在于你需要将备忘录的所有成员变量声明为公有。 37 | 38 | ### 封装更加严格的实现 39 | 40 | ![memento_strict_structure](./memento_strict_structure.png) 41 | 42 | 1. 这种实现方式允许存在多种不同类型的原发器和备忘录。 每种原发器都和其相应的备忘录类进行交互。 原发器和备忘录都不会将其状态暴露给其他类。 43 | 2. 负责人此时被明确禁止修改存储在备忘录中的状态。 但负责人类将独立于原发器, 因为此时恢复方法被定义在了备忘录类中。 44 | 3. 每个备忘录将与创建了自身的原发器连接。 原发器会将自己及状态传递给备忘录的构造函数。 由于这些类之间的紧密联系, 只要原发器定义了合适的设置器 (setter), 备忘录就能恢复其状态。 45 | 46 | ## 适用场景 47 | 48 | 1. 当你需要创建对象状态快照来恢复其之前的状态时, 可以使用备忘录模式。 49 | 50 | 1. 备忘录模式允许你复制对象中的全部状态 (包括私有成员变量), 并将其独立于对象进行保存。 尽管大部分人因为 “撤销” 这个用例才记得该模式, 但其实它在处理事务 (比如需要在出现错误时回滚一个操作) 的过程中也必不可少。 51 | 52 | 2. 当直接访问对象的成员变量、 获取器或设置器将导致封装被突破时, 可以使用该模式。 53 | 54 | 1. 备忘录让对象自行负责创建其状态的快照。 任何其他对象都不能读取快照, 这有效地保障了数据的安全性。 55 | 56 | ## 优缺点 57 | 58 | ### 优点 59 | 60 | 1. 你可以在不破坏对象封装情况的前提下创建对象状态快照。 61 | 2. 你可以通过让负责人维护原发器状态历史记录来简化原发器代码。 62 | 63 | ### 缺点 64 | 65 | 1. 如果客户端过于频繁地创建备忘录, 程序将消耗大量内存。 66 | 2. 负责人必须完整跟踪原发器的生命周期, 这样才能销毁弃用的备忘录。 67 | 3. 绝大部分动态编程语言 (例如 PHP、 Python 和 JavaScript) 不能确保备忘录中的状态不被修改。 68 | 69 | ## 与其他模式的关系 70 | 71 | 1. 你可以同时使用命令模式和备忘录模式来实现 “撤销”。 在这种情况下, 命令用于对目标对象执行各种不同的操作, 备忘录用来保存一条命令执行前该对象的状态。 72 | 2. 你可以同时使用备忘录和迭代器模式来获取当前迭代器的状态, 并且在需要的时候进行回滚。 73 | 3. 有时候原型模式可以作为备忘录的一个简化版本, 其条件是你需要在历史记录中存储的对象的状态比较简单, 不需要链接其他外部资源, 或者链接可以方便地重建。 74 | 75 | ## 代码实现 76 | 77 | ```typescript 78 | class Originator { 79 | private state: string; 80 | 81 | constructor(state: string) { 82 | this.state = state; 83 | } 84 | 85 | public doSomething(): string { 86 | this.state = this.generateRandomString(30); 87 | return this.state; 88 | } 89 | 90 | private generateRandomString(length: number = 10): string { 91 | const charSet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 92 | 93 | return Array.apply(null, { length }) 94 | .map(() => charSet.charAt(Math.floor(Math.random() * charSet.length))) 95 | .join(''); 96 | } 97 | 98 | public save(): Memento { 99 | return new ConcreteMemento(this.state); 100 | } 101 | 102 | public restore(memento: Memento): string { 103 | this.state = memento.getState(); 104 | return this.state; 105 | } 106 | } 107 | 108 | interface Memento { 109 | getState(): string; 110 | 111 | getName(): string; 112 | 113 | getDate(): string; 114 | } 115 | 116 | class ConcreteMemento implements Memento { 117 | private state: string; 118 | 119 | private date: string; 120 | 121 | constructor(state: string) { 122 | this.state = state; 123 | this.date = new Date().toISOString().slice(0, 19).replace('T', ' '); 124 | } 125 | 126 | public getState(): string { 127 | return this.state; 128 | } 129 | 130 | public getName(): string { 131 | return `${this.date} / (${this.state.substr(0, 9)}...)`; 132 | } 133 | 134 | public getDate(): string { 135 | return this.date; 136 | } 137 | } 138 | 139 | class Caretaker { 140 | private mementos: Memento[] = []; 141 | 142 | private originator: Originator; 143 | 144 | constructor(originator: Originator) { 145 | this.originator = originator; 146 | } 147 | 148 | public backup(): void { 149 | this.mementos.push(this.originator.save()); 150 | } 151 | 152 | public undo(): string { 153 | if (!this.mementos.length) { 154 | return ''; 155 | } 156 | const memento = this.mementos.pop(); 157 | 158 | return this.originator.restore(memento!); 159 | } 160 | 161 | public showHistory(): void { 162 | for (const memento of this.mementos) { 163 | console.log(memento.getName()); 164 | } 165 | } 166 | } 167 | 168 | export { Originator, Memento, ConcreteMemento, Caretaker }; 169 | ``` 170 | 171 | ## 测试用例 172 | 173 | ```typescript 174 | import { Originator, Caretaker } from '../index'; 175 | 176 | describe('memento pattern', () => { 177 | it('memento concrete creator', () => { 178 | const originator = new Originator('Super-duper-super-puper-super.'); 179 | const caretaker = new Caretaker(originator); 180 | 181 | caretaker.backup(); 182 | const backUp1 = originator.doSomething(); 183 | 184 | caretaker.backup(); 185 | const backUp2 = originator.doSomething(); 186 | 187 | caretaker.backup(); 188 | 189 | caretaker.showHistory(); 190 | 191 | expect(caretaker.undo()).toBe(backUp2); 192 | 193 | expect(caretaker.undo()).toBe(backUp1); 194 | }); 195 | }); 196 | ``` 197 | -------------------------------------------------------------------------------- /docs/behavioral/memento_middleware_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/behavioral/memento_middleware_structure.png -------------------------------------------------------------------------------- /docs/behavioral/memento_strict_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/behavioral/memento_strict_structure.png -------------------------------------------------------------------------------- /docs/behavioral/memento_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/behavioral/memento_structure.png -------------------------------------------------------------------------------- /docs/behavioral/observer.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 观察者模式 3 | categories: 4 | - 设计模式 5 | - 行为模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 观察者模式 14 | 15 | :::tip 16 | 17 | **观察者模式**是一种行为设计模式, 允许你定义一种订阅机制, 可在对象事件发生时通知多个 “观察” 该对象的其他对象。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ![observer_structure](./observer_structure.png) 23 | 24 | 1. 发布者 (`Publisher`) 会向其他对象发送值得关注的事件。 事件会在发布者自身状态改变或执行特定行为后发生。 发布者中包含一个允许新订阅者加入和当前订阅者离开列表的订阅构架。 25 | 2. 当新事件发生时, 发送者会遍历订阅列表并调用每个订阅者对象的通知方法。 该方法是在订阅者接口中声明的。 26 | 3. 订阅者 (`Subscriber`) 接口声明了通知接口。 在绝大多数情况下, 该接口仅包含一个 update 更新方法。 该方法可以拥有多个参数, 使发布者能在更新时传递事件的详细信息。 27 | 4. 具体订阅者 (`Concrete Subscribers`) 可以执行一些操作来回应发布者的通知。 所有具体订阅者类都实现了同样的接口, 因此发布者不需要与具体类相耦合。 28 | 5. 订阅者通常需要一些上下文信息来正确地处理更新。 因此, 发布者通常会将一些上下文数据作为通知方法的参数进行传递。 发布者也可将自身作为参数进行传递, 使订阅者直接获取所需的数据。 29 | 6. 客户端 (`Client`) 会分别创建发布者和订阅者对象, 然后为订阅者注册发布者更新。 30 | 31 | ## 适用场景 32 | 33 | 1. 当一个对象状态的改变需要改变其他对象, 或实际对象是事先未知的或动态变化的时, 可使用观察者模式。 34 | 35 | 1. 当你使用图形用户界面类时通常会遇到一个问题。 比如, 你创建了自定义按钮类并允许客户端在按钮中注入自定义代码, 这样当用户按下按钮时就会触发这些代码。 36 | 2. 观察者模式允许任何实现了订阅者接口的对象订阅发布者对象的事件通知。 你可在按钮中添加订阅机制, 允许客户端通过自定义订阅类注入自定义代码。 37 | 38 | 2. 当应用中的一些对象必须观察其他对象时, 可使用该模式。 但仅能在有限时间内或特定情况下使用。 39 | 40 | 1. 订阅列表是动态的, 因此订阅者可随时加入或离开该列表。 41 | 42 | ## 优缺点 43 | 44 | ### 优点 45 | 46 | 1. 开闭原则。 你无需修改发布者代码就能引入新的订阅者类 (如果是发布者接口则可轻松引入发布者类)。 47 | 2. 你可以在运行时建立对象之间的联系。 48 | 49 | ### 缺点 50 | 51 | 1. 订阅者的通知顺序是随机的。 52 | 53 | ## 与其他模式的关系 54 | 55 | 1. 责任链模式、 命令模式、 中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式: 56 | 1. 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。 57 | 2. 命令在发送者和请求者之间建立单向连接。 58 | 3. 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。 59 | 4. 观察者允许接收者动态地订阅或取消接收请求。 60 | 2. 中介者和观察者之间的区别往往很难记住。 在大部分情况下, 你可以使用其中一种模式, 而有时可以同时使用。 让我们来看看如何做到这一点。中介者的主要目标是消除一系列系统组件之间的相互依赖。 这些组件将依赖于同一个中介者对象。 观察者的目标是在对象之间建立动态的单向连接, 使得部分对象可作为其他对象的附属发挥作用。有一种流行的中介者模式实现方式依赖于观察者。 中介者对象担当发布者的角色, 其他组件则作为订阅者, 可以订阅中介者的事件或取消订阅。 当中介者以这种方式实现时, 它可能看上去与观察者非常相似。 61 | 62 | ## 代码实现 63 | 64 | ```typescript 65 | interface ObserverSubject { 66 | attach(observer: Observer): void; 67 | detach(observer: Observer): void; 68 | notify(): void; 69 | } 70 | 71 | class ConcreteSubject implements ObserverSubject { 72 | public state: number; 73 | 74 | private observers: Observer[] = []; 75 | 76 | public attach(observer: Observer): string { 77 | const isExist = this.observers.includes(observer); 78 | if (isExist) { 79 | return 'Subject: Observer has been attached already.'; 80 | } 81 | 82 | this.observers.push(observer); 83 | 84 | return ''; 85 | } 86 | 87 | public detach(observer: Observer): string { 88 | const observerIndex = this.observers.indexOf(observer); 89 | if (observerIndex === -1) { 90 | return 'Subject: Nonexistent observer.'; 91 | } 92 | 93 | this.observers.splice(observerIndex, 1); 94 | return ''; 95 | } 96 | 97 | public notify(): string[] { 98 | let result = [] as string[]; 99 | for (const observer of this.observers) { 100 | result.push(observer.update(this)); 101 | } 102 | return result; 103 | } 104 | 105 | public someBusinessLogic(): string[] { 106 | return this.notify(); 107 | } 108 | } 109 | 110 | interface Observer { 111 | update(subject: ObserverSubject): string; 112 | } 113 | 114 | class ConcreteObserverA implements Observer { 115 | public update(subject: ObserverSubject): string { 116 | return 'ConcreteObserverA: Reacted to the event.'; 117 | } 118 | } 119 | 120 | class ConcreteObserverB implements Observer { 121 | public update(subject: ObserverSubject): string { 122 | return 'ConcreteObserverB: Reacted to the event.'; 123 | } 124 | } 125 | 126 | export { ObserverSubject, ConcreteSubject, Observer, ConcreteObserverA, ConcreteObserverB }; 127 | ``` 128 | 129 | ## 测试用例 130 | 131 | ```typescript 132 | import { ConcreteSubject, ConcreteObserverA, ConcreteObserverB } from '../index'; 133 | 134 | describe('observer pattern', () => { 135 | it('observer concrete creator', () => { 136 | const subject = new ConcreteSubject(); 137 | 138 | const observer1 = new ConcreteObserverA(); 139 | subject.attach(observer1); 140 | 141 | const observer2 = new ConcreteObserverB(); 142 | subject.attach(observer2); 143 | 144 | expect(subject.someBusinessLogic()).toStrictEqual([ 145 | 'ConcreteObserverA: Reacted to the event.', 146 | 'ConcreteObserverB: Reacted to the event.', 147 | ]); 148 | expect(subject.someBusinessLogic()).toStrictEqual([ 149 | 'ConcreteObserverA: Reacted to the event.', 150 | 'ConcreteObserverB: Reacted to the event.', 151 | ]); 152 | 153 | subject.detach(observer2); 154 | 155 | expect(subject.someBusinessLogic()).toStrictEqual([ 156 | 'ConcreteObserverA: Reacted to the event.', 157 | ]); 158 | }); 159 | }); 160 | ``` 161 | -------------------------------------------------------------------------------- /docs/behavioral/observer_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/behavioral/observer_structure.png -------------------------------------------------------------------------------- /docs/behavioral/responsibility.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 责任链模式 3 | categories: 4 | - 设计模式 5 | - 行为模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 责任链模式 14 | 15 | :::tip 16 | 17 | **责任链模式**是一种行为设计模式, 允许你将请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ![responsibility_structure](./responsibility_structure.png) 23 | 24 | 1. 处理者 (`Handler`) 声明了所有具体处理者的通用接口。 该接口通常仅包含单个方法用于请求处理, 但有时其还会包含一个设置链上下个处理者的方法。 25 | 2. 基础处理者 (`Base Handler`) 是一个可选的类, 你可以将所有处理者共用的样本代码放置在其中。通常情况下, 该类中定义了一个保存对于下个处理者引用的成员变量。 客户端可通过将处理者传递给上个处理者的构造函数或设定方法来创建链。 该类还可以实现默认的处理行为: 确定下个处理者存在后再将请求传递给它。 26 | 3. 具体处理者 (`Concrete Handlers`) 包含处理请求的实际代码。 每个处理者接收到请求后, 都必须决定是否进行处理, 以及是否沿着链传递请求。处理者通常是独立且不可变的, 需要通过构造函数一次性地获得所有必要地数据。 27 | 4. 客户端 (`Client`) 可根据程序逻辑一次性或者动态地生成链。 值得注意的是, 请求可发送给链上的任意一个处理者, 而非必须是第一个处理者。 28 | 29 | ## 适用场景 30 | 31 | 1. 当程序需要使用不同方式处理不同种类请求, 而且请求类型和顺序预先未知时, 可以使用责任链模式。 32 | 33 | 1. 该模式能将多个处理者连接成一条链。 接收到请求后, 它会 “询问” 每个处理者是否能够对其进行处理。 这样所有处理者都有机会来处理请求。 34 | 35 | 2. 当必须按顺序执行多个处理者时, 可以使用该模式。 36 | 37 | 1. 无论你以何种顺序将处理者连接成一条链, 所有请求都会严格按照顺序通过链上的处理者。 38 | 39 | 3. 如果所需处理者及其顺序必须在运行时进行改变, 可以使用责任链模式。 40 | 41 | 1. 如果在处理者类中有对引用成员变量的设定方法, 你将能动态地插入和移除处理者, 或者改变其顺序。 42 | 43 | ## 优缺点 44 | 45 | ### 优点 46 | 47 | 1. 你可以控制请求处理的顺序。 48 | 2. 单一职责原则。 你可对发起操作和执行操作的类进行解耦。 49 | 3. 开闭原则。 你可以在不更改现有代码的情况下在程序中新增处理者。 50 | 51 | ### 缺点 52 | 53 | 1. 部分请求可能未被处理。 54 | 55 | ## 与其他模式的关系 56 | 57 | 1. 责任链模式、 命令模式、 中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式: 58 | 1. 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。 59 | 2. 命令在发送者和请求者之间建立单向连接。 60 | 3. 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。 61 | 4. 观察者允许接收者动态地订阅或取消接收请求。 62 | 2. 责任链通常和组合模式结合使用。 在这种情况下, 叶组件接收到请求后, 可以将请求沿包含全体父组件的链一直传递至对象树的底部。 63 | 3. 责任链的管理者可使用命令模式实现。 在这种情况下, 你可以对由请求代表的同一个上下文对象执行许多不同的操作。还有另外一种实现方式, 那就是请求自身就是一个命令对象。 在这种情况下, 你可以对由一系列不同上下文连接而成的链执行相同的操作。 64 | 4. 责任链和装饰模式的类结构非常相似。 两者都依赖递归组合将需要执行的操作传递给一系列对象。 但是, 两者有几点重要的不同之处。 65 | 5. 责任链的管理者可以相互独立地执行一切操作, 还可以随时停止传递请求。 另一方面, 各种装饰可以在遵循基本接口的情况下扩展对象的行为。 此外, 装饰无法中断请求的传递。 66 | 67 | ## 代码实现 68 | 69 | ```typescript 70 | interface Handler { 71 | setNext(handler: Handler): Handler; 72 | 73 | handle(request: string): string; 74 | } 75 | 76 | abstract class AbstractHandler implements Handler { 77 | private nextHandler: Handler; 78 | 79 | public setNext(handler: Handler): Handler { 80 | this.nextHandler = handler; 81 | 82 | return handler; 83 | } 84 | 85 | public handle(request: string): string { 86 | if (this.nextHandler) { 87 | return this.nextHandler.handle(request); 88 | } 89 | 90 | return ''; 91 | } 92 | } 93 | 94 | class MonkeyHandler extends AbstractHandler { 95 | public handle(request: string): string { 96 | if (request === 'Banana') { 97 | return request; 98 | } 99 | return super.handle(request); 100 | } 101 | } 102 | 103 | class SquirrelHandler extends AbstractHandler { 104 | public handle(request: string): string { 105 | if (request === 'Nut') { 106 | return request; 107 | } 108 | return super.handle(request); 109 | } 110 | } 111 | 112 | class DogHandler extends AbstractHandler { 113 | public handle(request: string): string { 114 | if (request === 'MeatBall') { 115 | return request; 116 | } 117 | return super.handle(request); 118 | } 119 | } 120 | 121 | export { Handler, AbstractHandler, MonkeyHandler, SquirrelHandler, DogHandler }; 122 | ``` 123 | 124 | ## 测试用例 125 | 126 | ```typescript 127 | import { MonkeyHandler, SquirrelHandler, DogHandler } from '../index'; 128 | 129 | describe('responsibility pattern', () => { 130 | it('responsibility concrete creator', () => { 131 | const list = [] as string[]; 132 | const monkey = new MonkeyHandler(); 133 | const squirrel = new SquirrelHandler(); 134 | const dog = new DogHandler(); 135 | 136 | monkey.setNext(squirrel).setNext(dog); 137 | 138 | const foods = ['Nut', 'Banana', 'Cup of coffee']; 139 | 140 | for (const food of foods) { 141 | const result = monkey.handle(food); 142 | if (result) { 143 | list.push(result); 144 | } 145 | } 146 | expect(list).toStrictEqual(['Nut', 'Banana']); 147 | }); 148 | }); 149 | ``` 150 | -------------------------------------------------------------------------------- /docs/behavioral/responsibility_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/behavioral/responsibility_structure.png -------------------------------------------------------------------------------- /docs/behavioral/state.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 状态模式 3 | categories: 4 | - 设计模式 5 | - 行为模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 状态模式 14 | 15 | :::tip 16 | 17 | **状态模式**是一种行为设计模式, 让你能在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ![state_structure](./state_structure.png) 23 | 24 | 1. 上下文 (`Context`) 保存了对于一个具体状态对象的引用, 并会将所有与该状态相关的工作委派给它。 上下文通过状态接口与状态对象交互, 且会提供一个设置器用于传递新的状态对象。 25 | 2. 状态 (`State`) 接口会声明特定于状态的方法。 这些方法应能被其他所有具体状态所理解, 因为你不希望某些状态所拥有的方法永远不会被调用。 26 | 3. 具体状态 (`Concrete States`) 会自行实现特定于状态的方法。 为了避免多个状态中包含相似代码, 你可以提供一个封装有部分通用行为的中间抽象类。状态对象可存储对于上下文对象的反向引用。 状态可以通过该引用从上下文处获取所需信息, 并且能触发状态转移。 27 | 4. 上下文和具体状态都可以设置上下文的下个状态, 并可通过替换连接到上下文的状态对象来完成实际的状态转换。 28 | 29 | ## 适用场景 30 | 31 | 1. 如果对象需要根据自身当前状态进行不同行为, 同时状态的数量非常多且与状态相关的代码会频繁变更的话, 可使用状态模式。 32 | 33 | 1. 模式建议你将所有特定于状态的代码抽取到一组独立的类中。 这样一来, 你可以在独立于其他状态的情况下添加新状态或修改已有状态, 从而减少维护成本。 34 | 35 | 2. 如果某个类需要根据成员变量的当前值改变自身行为, 从而需要使用大量的条件语句时, 可使用该模式。 36 | 37 | 1. 状态模式会将这些条件语句的分支抽取到相应状态类的方法中。 同时, 你还可以清除主要类中与特定状态相关的临时成员变量和帮手方法代码。 38 | 39 | 3. 当相似状态和基于条件的状态机转换中存在许多重复代码时, 可使用状态模式。 40 | 41 | 1. 状态模式让你能够生成状态类层次结构, 通过将公用代码抽取到抽象基类中来减少重复。 42 | 43 | ## 优缺点 44 | 45 | ### 优点 46 | 47 | 1. 单一职责原则。 将与特定状态相关的代码放在单独的类中。 48 | 2. 开闭原则。 无需修改已有状态类和上下文就能引入新状态。 49 | 3. 通过消除臃肿的状态机条件语句简化上下文代码。 50 | 51 | ### 缺点 52 | 53 | 1. 如果状态机只有很少的几个状态, 或者很少发生改变, 那么应用该模式可能会显得小题大作。 54 | 55 | ## 与其他模式的关系 56 | 57 | 1. 桥接模式、 状态模式和策略模式 (在某种程度上包括适配器模式) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。 58 | 2. 状态可被视为策略的扩展。 两者都基于组合机制: 它们都通过将部分工作委派给 “帮手” 对象来改变其在不同情景下的行为。 策略使得这些对象相互之间完全独立, 它们不知道其他对象的存在。 但状态模式没有限制具体状态之间的依赖, 且允许它们自行改变在不同情景下的状态。 59 | 60 | ## 代码实现 61 | 62 | ```typescript 63 | class Context { 64 | private state: State; 65 | 66 | constructor(state: State) { 67 | this.transitionTo(state); 68 | } 69 | 70 | public transitionTo(state: State): void { 71 | this.state = state; 72 | this.state.setContext(this); 73 | } 74 | 75 | public request1(): void { 76 | this.state.handle1(); 77 | } 78 | 79 | public request2(): string { 80 | return this.state.handle2(); 81 | } 82 | } 83 | 84 | abstract class State { 85 | protected context: Context; 86 | 87 | public setContext(context: Context) { 88 | this.context = context; 89 | } 90 | 91 | public abstract handle1(): void; 92 | 93 | public abstract handle2(): string; 94 | } 95 | 96 | class ConcreteStateA extends State { 97 | public handle1(): void { 98 | this.context.transitionTo(new ConcreteStateB()); 99 | } 100 | 101 | public handle2(): string { 102 | return 'ConcreteStateA handles request2.'; 103 | } 104 | } 105 | 106 | class ConcreteStateB extends State { 107 | public handle1(): void { 108 | console.log('ConcreteStateB handles request1.'); 109 | } 110 | 111 | public handle2(): string { 112 | this.context.transitionTo(new ConcreteStateA()); 113 | return 'ConcreteStateB handles request2.'; 114 | } 115 | } 116 | export { Context, State, ConcreteStateA, ConcreteStateB }; 117 | ``` 118 | 119 | ## 测试用例 120 | 121 | ```typescript 122 | import { Context, ConcreteStateA } from '../index'; 123 | 124 | describe('state pattern', () => { 125 | it('state concrete creator', () => { 126 | const context = new Context(new ConcreteStateA()); 127 | context.request1(); 128 | expect(context.request2()).toBe('ConcreteStateB handles request2.'); 129 | }); 130 | }); 131 | ``` 132 | -------------------------------------------------------------------------------- /docs/behavioral/state_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/behavioral/state_structure.png -------------------------------------------------------------------------------- /docs/behavioral/strategy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 策略模式 3 | categories: 4 | - 设计模式 5 | - 行为模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 策略模式 14 | 15 | :::tip 16 | 17 | **策略模式**是一种行为设计模式, 它能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ![strategy_structure](./strategy_structure.png) 23 | 24 | 1. 上下文 (`Context`) 维护指向具体策略的引用, 且仅通过策略接口与该对象进行交流。 25 | 2. 策略 (`Strategy`) 接口是所有具体策略的通用接口, 它声明了一个上下文用于执行策略的方法。 26 | 3. 具体策略 (`Concrete Strategies`) 实现了上下文所用算法的各种不同变体。 27 | 4. 当上下文需要运行算法时, 它会在其已连接的策略对象上调用执行方法。 上下文不清楚其所涉及的策略类型与算法的执行方式。 28 | 5. 客户端 (`Client`) 会创建一个特定策略对象并将其传递给上下文。 上下文则会提供一个设置器以便客户端在运行时替换相关联的策略。 29 | 30 | ## 适用场景 31 | 32 | 1. 当你想使用对象中各种不同的算法变体, 并希望能在运行时切换算法时, 可使用策略模式。 33 | 34 | 1. 策略模式让你能够将对象关联至可以不同方式执行特定子任务的不同子对象, 从而以间接方式在运行时更改对象行为。 35 | 36 | 2. 当你有许多仅在执行某些行为时略有不同的相似类时, 可使用策略模式。 37 | 38 | 1. 策略模式让你能将不同行为抽取到一个独立类层次结构中, 并将原始类组合成同一个, 从而减少重复代码。 39 | 40 | 3. 如果算法在上下文的逻辑中不是特别重要, 使用该模式能将类的业务逻辑与其算法实现细节隔离开来。 41 | 42 | 1. 策略模式让你能将各种算法的代码、 内部数据和依赖关系与其他代码隔离开来。 不同客户端可通过一个简单接口执行算法, 并能在运行时进行切换。 43 | 44 | 4. 当类中使用了复杂条件运算符以在同一算法的不同变体中切换时, 可使用该模式。 45 | 46 | 1. 策略模式将所有继承自同样接口的算法抽取到独立类中, 因此不再需要条件语句。 原始对象并不实现所有算法的变体, 而是将执行工作委派给其中的一个独立算法对象。 47 | 48 | ## 优缺点 49 | 50 | ### 优点 51 | 52 | 1. 可以在运行时切换对象内的算法。 53 | 2. 你可以将算法的实现和使用算法的代码隔离开来。 54 | 3. 你可以使用组合来代替继承。 55 | 4. 开闭原则。 你无需对上下文进行修改就能够引入新的策略。 56 | 57 | ### 缺点 58 | 59 | 1. 如果你的算法极少发生改变, 那么没有任何理由引入新的类和接口。 使用该模式只会让程序过于复杂。 60 | 2. 客户端必须知晓策略间的不同——它需要选择合适的策略。 61 | 3. 许多现代编程语言支持函数类型功能, 允许你在一组匿名函数中实现不同版本的算法。 这样, 你使用这些函数的方式就和使用策略对象时完全相同, 无需借助额外的类和接口来保持代码简洁。 62 | 63 | ## 与其他模式的关系 64 | 65 | 1. 桥接模式、 状态模式和策略模式 (在某种程度上包括适配器模式) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。 66 | 2. 命令模式和策略看上去很像, 因为两者都能通过某些行为来参数化对象。 但是, 它们的意图有非常大的不同。 67 | 1. 你可以使用命令来将任何操作转换为对象。 操作的参数将成为对象的成员变量。 你可以通过转换来延迟操作的执行、 将操作放入队列、 保存历史命令或者向远程服务发送命令等。 68 | 2. 另一方面, 策略通常可用于描述完成某件事的不同方式, 让你能够在同一个上下文类中切换算法。 69 | 3. 装饰模式可让你更改对象的外表, 策略则让你能够改变其本质。 70 | 4. 模板方法模式基于继承机制: 它允许你通过扩展子类中的部分内容来改变部分算法。 策略基于组合机制: 你可以通过对相应行为提供不同的策略来改变对象的部分行为。 模板方法在类层次上运作, 因此它是静态的。 策略在对象层次上运作, 因此允许在运行时切换行为。 71 | 5. 状态可被视为策略的扩展。 两者都基于组合机制: 它们都通过将部分工作委派给 “帮手” 对象来改变其在不同情景下的行为。 策略使得这些对象相互之间完全独立, 它们不知道其他对象的存在。 但状态模式没有限制具体状态之间的依赖, 且允许它们自行改变在不同情景下的状态。 72 | 73 | ## 代码实现 74 | 75 | ```typescript 76 | class StrategyContext { 77 | private strategy: Strategy; 78 | 79 | constructor(strategy: Strategy) { 80 | this.strategy = strategy; 81 | } 82 | 83 | public setStrategy(strategy: Strategy) { 84 | this.strategy = strategy; 85 | } 86 | 87 | public doSomeBusinessLogic(): string { 88 | const result = this.strategy.doAlgorithm(['a', 'b', 'c', 'd', 'e']); 89 | return result.join(','); 90 | } 91 | } 92 | 93 | interface Strategy { 94 | doAlgorithm(data: string[]): string[]; 95 | } 96 | 97 | class ConcreteStrategyA implements Strategy { 98 | public doAlgorithm(data: string[]): string[] { 99 | return data.sort(); 100 | } 101 | } 102 | 103 | class ConcreteStrategyB implements Strategy { 104 | public doAlgorithm(data: string[]): string[] { 105 | return data.reverse(); 106 | } 107 | } 108 | 109 | export { StrategyContext, Strategy, ConcreteStrategyA, ConcreteStrategyB }; 110 | ``` 111 | 112 | ## 测试用例 113 | 114 | ```typescript 115 | import { StrategyContext, ConcreteStrategyA, ConcreteStrategyB } from '../index'; 116 | 117 | describe('strategy pattern', () => { 118 | it('strategy concrete creator', () => { 119 | const context = new StrategyContext(new ConcreteStrategyA()); 120 | expect(context.doSomeBusinessLogic()).toBe('a,b,c,d,e'); 121 | 122 | context.setStrategy(new ConcreteStrategyB()); 123 | expect(context.doSomeBusinessLogic()).toBe('e,d,c,b,a'); 124 | }); 125 | }); 126 | ``` 127 | -------------------------------------------------------------------------------- /docs/behavioral/strategy_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/behavioral/strategy_structure.png -------------------------------------------------------------------------------- /docs/behavioral/template.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 模板方法模式 3 | categories: 4 | - 设计模式 5 | - 行为模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 模板方法模式 14 | 15 | :::tip 16 | 17 | **模板方法模式**是一种行为设计模式, 它在超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ![template_structure](./template_structure.png) 23 | 24 | 1. 抽象类 (`Abstract­Class`) 会声明作为算法步骤的方法, 以及依次调用它们的实际模板方法。 算法步骤可以被声明为 抽象类型, 也可以提供一些默认实现。 25 | 2. 具体类 (`Concrete­Class`) 可以重写所有步骤, 但不能重写模板方法自身。 26 | 27 | ## 适用场景 28 | 29 | 1. 当你只希望客户端扩展某个特定算法步骤, 而不是整个算法或其结构时, 可使用模板方法模式。 30 | 31 | 1. 模板方法将整个算法转换为一系列独立的步骤, 以便子类能对其进行扩展, 同时还可让超类中所定义的结构保持完整。 32 | 33 | 2. 当多个类的算法除一些细微不同之外几乎完全一样时, 你可使用该模式。 但其后果就是, 只要算法发生变化, 你就可能需要修改所有的类。 34 | 35 | 1.在将算法转换为模板方法时, 你可将相似的实现步骤提取到超类中以去除重复代码。 子类间各不同的代码可继续保留在子类中。 36 | 37 | ## 优缺点 38 | 39 | ### 优点 40 | 41 | 1. 你可仅允许客户端重写一个大型算法中的特定部分, 使得算法其他部分修改对其所造成的影响减小。 42 | 2. 你可将重复代码提取到一个超类中。 43 | 44 | ### 缺点 45 | 46 | 1. 部分客户端可能会受到算法框架的限制。 47 | 2. 通过子类抑制默认步骤实现可能会导致违反里氏替换原则。 48 | 3. 模板方法中的步骤越多, 其维护工作就可能会越困难。 49 | 50 | ## 与其他模式的关系 51 | 52 | 1. 工厂方法模式是模板方法模式的一种特殊形式。 同时, 工厂方法可以作为一个大型模板方法中的一个步骤。 53 | 2. 模板方法基于继承机制: 它允许你通过扩展子类中的部分内容来改变部分算法。 策略模式基于组合机制: 你可以通过对相应行为提供不同的策略来改变对象的部分行为。 模板方法在类层次上运作, 因此它是静态的。 策略在对象层次上运作, 因此允许在运行时切换行为。 54 | 55 | ## 代码实现 56 | 57 | ```typescript 58 | abstract class AbstractClass { 59 | public templateMethod(): string[] { 60 | this.baseOperation1(); 61 | this.requiredOperations1(); 62 | this.baseOperation2(); 63 | this.hook1(); 64 | this.requiredOperation2(); 65 | this.baseOperation3(); 66 | this.hook2(); 67 | 68 | return [this.baseOperation1(), this.baseOperation2(), this.baseOperation3()]; 69 | } 70 | 71 | protected baseOperation1(): string { 72 | return 'baseOperation1'; 73 | } 74 | 75 | protected baseOperation2(): string { 76 | return 'baseOperation2'; 77 | } 78 | 79 | protected baseOperation3(): string { 80 | return 'baseOperation3'; 81 | } 82 | 83 | protected abstract requiredOperations1(): void; 84 | 85 | protected abstract requiredOperation2(): void; 86 | 87 | protected hook1(): void {} 88 | 89 | protected hook2(): void {} 90 | } 91 | 92 | class ConcreteClass1 extends AbstractClass { 93 | protected requiredOperations1(): string { 94 | return 'ConcreteClass1: Operation1'; 95 | } 96 | 97 | protected requiredOperation2(): string { 98 | return 'ConcreteClass1: Operation2'; 99 | } 100 | } 101 | 102 | class ConcreteClass2 extends AbstractClass { 103 | protected requiredOperations1(): string { 104 | return 'ConcreteClass2: Operation1'; 105 | } 106 | 107 | protected requiredOperation2(): string { 108 | return 'ConcreteClass2: Operation2'; 109 | } 110 | 111 | protected hook1(): string { 112 | return 'ConcreteClass2 Hook1'; 113 | } 114 | } 115 | 116 | export { AbstractClass, ConcreteClass1, ConcreteClass2 }; 117 | ``` 118 | 119 | ## 测试用例 120 | 121 | ```typescript 122 | import { ConcreteClass1 } from '../index'; 123 | 124 | describe('template pattern', () => { 125 | it('template concrete creator', () => { 126 | const concrete1 = new ConcreteClass1(); 127 | expect(concrete1.templateMethod()).toStrictEqual([ 128 | 'baseOperation1', 129 | 'baseOperation2', 130 | 'baseOperation3', 131 | ]); 132 | }); 133 | }); 134 | ``` 135 | -------------------------------------------------------------------------------- /docs/behavioral/template_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/behavioral/template_structure.png -------------------------------------------------------------------------------- /docs/behavioral/visitor.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 访问者模式 3 | categories: 4 | - 设计模式 5 | - 行为模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 访问者模式 14 | 15 | :::tip 16 | 17 | **访问者模式**是一种行为设计模式, 它能将算法与其所作用的对象隔离开来。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ![visitor_structure](./visitor_structure.png) 23 | 24 | 1. 访问者 (`Visitor`) 接口声明了一系列以对象结构的具体元素为参数的访问者方法。 如果编程语言支持重载, 这些方法的名称可以是相同的, 但是其参数一定是不同的。 25 | 2. 具体访问者 (`Concrete Visitor`) 会为不同的具体元素类实现相同行为的几个不同版本。 26 | 3. 元素 (`Element`) 接口声明了一个方法来 “接收” 访问者。 该方法必须有一个参数被声明为访问者接口类型。 27 | 4. 具体元素 (`Concrete Element`) 必须实现接收方法。 该方法的目的是根据当前元素类将其调用重定向到相应访问者的方法。 请注意, 即使元素基类实现了该方法, 所有子类都必须对其进行重写并调用访问者对象中的合适方法。 28 | 29 | ## 适用场景 30 | 31 | 1. 如果你需要对一个复杂对象结构 (例如对象树) 中的所有元素执行某些操作, 可使用访问者模式。 32 | 33 | 1. 访问者模式通过在访问者对象中为多个目标类提供相同操作的变体, 让你能在属于不同类的一组对象上执行同一操作。 34 | 35 | 2. 可使用访问者模式来清理辅助行为的业务逻辑。 36 | 37 | 1. 该模式会将所有非主要的行为抽取到一组访问者类中, 使得程序的主要类能更专注于主要的工作。 38 | 39 | 3. 当某个行为仅在类层次结构中的一些类中有意义, 而在其他类中没有意义时, 可使用该模式。 40 | 41 | 1. 你可将该行为抽取到单独的访问者类中, 只需实现接收相关类的对象作为参数的访问者方法并将其他方法留空即可。 42 | 43 | ## 优缺点 44 | 45 | ### 优点 46 | 47 | 1. 开闭原则。 你可以引入在不同类对象上执行的新行为, 且无需对这些类做出修改。 48 | 2. 单一职责原则。 可将同一行为的不同版本移到同一个类中。 49 | 3. 访问者对象可以在与各种对象交互时收集一些有用的信息。 当你想要遍历一些复杂的对象结构 (例如对象树), 并在结构中的每个对象上应用访问者时, 这些信息可能会有所帮助。 50 | 51 | ### 缺点 52 | 53 | 1. 每次在元素层次结构中添加或移除一个类时, 你都要更新所有的访问者。 54 | 2. 在访问者同某个元素进行交互时, 它们可能没有访问元素私有成员变量和方法的必要权限。 55 | 56 | ## 与其他模式的关系 57 | 58 | 1. 你可以将访问者模式视为命令模式的加强版本, 其对象可对不同类的多种对象执行操作。 59 | 2. 你可以使用访问者对整个组合模式树执行操作。 60 | 3. 可以同时使用访问者和迭代器模式来遍历复杂数据结构, 并对其中的元素执行所需操作, 即使这些元素所属的类完全不同。 61 | 62 | ## 代码实现 63 | 64 | ```typescript 65 | interface VisitorComponent { 66 | accept(visitor: Visitor): void; 67 | } 68 | 69 | class ConcreteComponentA implements VisitorComponent { 70 | public accept(visitor: Visitor): string { 71 | return visitor.visitConcreteComponentA(this); 72 | } 73 | 74 | public exclusiveMethodOfConcreteComponentA(): string { 75 | return 'A'; 76 | } 77 | } 78 | 79 | class ConcreteComponentB implements VisitorComponent { 80 | public accept(visitor: Visitor): string { 81 | return visitor.visitConcreteComponentB(this); 82 | } 83 | 84 | public specialMethodOfConcreteComponentB(): string { 85 | return 'B'; 86 | } 87 | } 88 | 89 | interface Visitor { 90 | visitConcreteComponentA(element: ConcreteComponentA): string; 91 | 92 | visitConcreteComponentB(element: ConcreteComponentB): string; 93 | } 94 | 95 | class ConcreteVisitor1 implements Visitor { 96 | public visitConcreteComponentA(element: ConcreteComponentA): string { 97 | return `${element.exclusiveMethodOfConcreteComponentA()} + ConcreteVisitor1`; 98 | } 99 | 100 | public visitConcreteComponentB(element: ConcreteComponentB): string { 101 | return `${element.specialMethodOfConcreteComponentB()} + ConcreteVisitor1`; 102 | } 103 | } 104 | 105 | class ConcreteVisitor2 implements Visitor { 106 | public visitConcreteComponentA(element: ConcreteComponentA): string { 107 | return `${element.exclusiveMethodOfConcreteComponentA()} + ConcreteVisitor2`; 108 | } 109 | 110 | public visitConcreteComponentB(element: ConcreteComponentB): string { 111 | return `${element.specialMethodOfConcreteComponentB()} + ConcreteVisitor2`; 112 | } 113 | } 114 | 115 | export { 116 | VisitorComponent, 117 | ConcreteComponentA, 118 | ConcreteComponentB, 119 | Visitor, 120 | ConcreteVisitor1, 121 | ConcreteVisitor2, 122 | }; 123 | ``` 124 | 125 | ## 测试用例 126 | 127 | ```typescript 128 | import { 129 | ConcreteVisitor1, 130 | ConcreteVisitor2, 131 | ConcreteComponentA, 132 | ConcreteComponentB, 133 | } from '../index'; 134 | 135 | describe('template pattern', () => { 136 | it('template concrete creator', () => { 137 | const componentA = new ConcreteComponentA(); 138 | const visitor1 = new ConcreteVisitor1(); 139 | 140 | expect(componentA.accept(visitor1)).toBe('A + ConcreteVisitor1'); 141 | 142 | const componentB = new ConcreteComponentB(); 143 | const visitor2 = new ConcreteVisitor2(); 144 | 145 | expect(componentB.accept(visitor2)).toBe('B + ConcreteVisitor2'); 146 | }); 147 | }); 148 | ``` 149 | -------------------------------------------------------------------------------- /docs/behavioral/visitor_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/behavioral/visitor_structure.png -------------------------------------------------------------------------------- /docs/creational/abstract_factory.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 抽象工厂模式 3 | categories: 4 | - 设计模式 5 | - 创建型模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 抽象工厂模式 14 | 15 | :::tip 16 | 17 | **抽象工厂模式**是一种创建型设计模式, 它能创建一系列相关的对象, 而无需指定其具体类。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ![abstract_factory_structure](./abstract_factory_structure.png) 23 | 24 | 1. 抽象产品 (`Abstract Product`) 为构成系列产品的一组不同但相关的产品声明接口。 25 | 2. 具体产品 (`Concrete Product`) 是抽象产品的多种不同类型实现。 所有变体 (维多利亚/现代) 都必须实现相应的抽象产品 (椅子/沙发)。 26 | 3. 抽象工厂 (`Abstract Factory`) 接口声明了一组创建各种抽象产品的方法。 27 | 4. 具体工厂 (`Concrete Factory`) 实现抽象工厂的构建方法。 每个具体工厂都对应特定产品变体, 且仅创建此种产品变体。 28 | 5. 尽管具体工厂会对具体产品进行初始化, 其构建方法签名必须返回相应的抽象产品。 这样, 使用工厂类的客户端代码就不会与工厂创建的特定产品变体耦合。 客户端 (`Client`) 只需通过抽象接口调用工厂和产品对象, 就能与任何具体工厂/产品变体交互。 29 | 30 | ## 适用场景 31 | 32 | 1. 如果代码需要与多个不同系列的相关产品交互, 但是由于无法提前获取相关信息, 或者出于对未来扩展性的考虑, 你不希望代码基于产品的具体类进行构建, 在这种情况下, 你可以使用抽象工厂。 33 | 34 | 1. 抽象工厂为你提供了一个接口, 可用于创建每个系列产品的对象。 只要代码通过该接口创建对象, 那么你就不会生成与应用程序已生成的产品类型不一致的产品。 35 | 36 | 2. 如果你有一个基于一组抽象方法的类, 且其主要功能因此变得不明确, 那么在这种情况下可以考虑使用抽象工厂模式。 37 | 38 | 1. 在设计良好的程序中, 每个类仅负责一件事。 如果一个类与多种类型产品交互, 就可以考虑将工厂方法抽取到独立的工厂类或具备完整功能的抽象工厂类中。 39 | 40 | ## 优缺点 41 | 42 | ### 优点 43 | 44 | 1. 你可以确保同一工厂生成的产品相互匹配。 45 | 2. 你可以避免客户端和具体产品代码的耦合。 46 | 3. 单一职责原则。 你可以将产品生成代码抽取到同一位置, 使得代码易于维护。 47 | 4. 开闭原则。 向应用程序中引入新产品变体时, 你无需修改客户端代码。 48 | 49 | ### 缺点 50 | 51 | 1. 由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂。 52 | 53 | ## 与其他模式的关系 54 | 55 | 1. 在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。 56 | 2. 生成器重点关注如何分步生成复杂对象。 抽象工厂专门用于生产一系列相关对象。 抽象工厂会马上返回产品, 生成器则允许你在获取产品前执行一些额外构造步骤。 57 | 3. 抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。 58 | 4. 当只需对客户端代码隐藏子系统创建对象的方式时, 你可以使用抽象工厂来代替外观模式。 59 | 5. 你可以将抽象工厂和桥接模式搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。 60 | 6. 抽象工厂、 生成器和原型都可以用单例模式来实现。 61 | 62 | ## 代码实现 63 | 64 | ```typescript 65 | interface AbstractFactory { 66 | createProductA(): AbstractProductA; 67 | 68 | createProductB(): AbstractProductB; 69 | } 70 | 71 | class ConcreteFactory1 implements AbstractFactory { 72 | public createProductA(): AbstractProductA { 73 | return new ConcreteProductA1(); 74 | } 75 | 76 | public createProductB(): AbstractProductB { 77 | return new ConcreteProductB1(); 78 | } 79 | } 80 | 81 | class ConcreteFactory2 implements AbstractFactory { 82 | public createProductA(): AbstractProductA { 83 | return new ConcreteProductA2(); 84 | } 85 | 86 | public createProductB(): AbstractProductB { 87 | return new ConcreteProductB2(); 88 | } 89 | } 90 | 91 | interface AbstractProductA { 92 | usefulFunctionA(): string; 93 | } 94 | 95 | class ConcreteProductA1 implements AbstractProductA { 96 | public usefulFunctionA(): string { 97 | return 'the product A1.'; 98 | } 99 | } 100 | 101 | class ConcreteProductA2 implements AbstractProductA { 102 | public usefulFunctionA(): string { 103 | return 'the product A2.'; 104 | } 105 | } 106 | 107 | interface AbstractProductB { 108 | usefulFunctionB(): string; 109 | 110 | anotherUsefulFunctionB(collaborator: AbstractProductA): string; 111 | } 112 | 113 | class ConcreteProductB1 implements AbstractProductB { 114 | public usefulFunctionB(): string { 115 | return 'The result of the product B1.'; 116 | } 117 | 118 | public anotherUsefulFunctionB(collaborator: AbstractProductA): string { 119 | const result = collaborator.usefulFunctionA(); 120 | return `The result of the B1 collaborating with ${result}`; 121 | } 122 | } 123 | 124 | class ConcreteProductB2 implements AbstractProductB { 125 | public usefulFunctionB(): string { 126 | return 'The result of the product B2.'; 127 | } 128 | 129 | public anotherUsefulFunctionB(collaborator: AbstractProductA): string { 130 | const result = collaborator.usefulFunctionA(); 131 | return `The result of the B2 collaborating with ${result}`; 132 | } 133 | } 134 | 135 | export { 136 | AbstractFactory, 137 | ConcreteFactory1, 138 | ConcreteFactory2, 139 | AbstractProductA, 140 | ConcreteProductA1, 141 | ConcreteProductA2, 142 | AbstractProductB, 143 | ConcreteProductB2, 144 | }; 145 | ``` 146 | 147 | ## 测试用例 148 | 149 | ```typescript 150 | import { ConcreteFactory1, ConcreteFactory2 } from '../index'; 151 | 152 | describe('abstract factory pattern', () => { 153 | it('abstract factory concrete creator 1', () => { 154 | const factory1 = new ConcreteFactory1(); 155 | 156 | const productA = factory1.createProductA(); 157 | const productB = factory1.createProductB(); 158 | 159 | expect(productB.usefulFunctionB()).toBe('The result of the product B1.'); 160 | expect(productB.anotherUsefulFunctionB(productA)).toBe( 161 | 'The result of the B1 collaborating with the product A1.' 162 | ); 163 | }); 164 | 165 | it('abstract factory concrete creator 1', () => { 166 | const factory1 = new ConcreteFactory2(); 167 | 168 | const productA = factory1.createProductA(); 169 | const productB = factory1.createProductB(); 170 | 171 | expect(productB.usefulFunctionB()).toBe('The result of the product B2.'); 172 | expect(productB.anotherUsefulFunctionB(productA)).toBe( 173 | 'The result of the B2 collaborating with the product A2.' 174 | ); 175 | }); 176 | }); 177 | ``` 178 | -------------------------------------------------------------------------------- /docs/creational/abstract_factory_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/creational/abstract_factory_structure.png -------------------------------------------------------------------------------- /docs/creational/builder.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 生成器模式 3 | categories: 4 | - 设计模式 5 | - 创建型模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 生成器模式 14 | 15 | :::tip 16 | 17 | **生成器模式**是一种创建型设计模式,允许使用相同的创建代码生成不同类型和形式的对象。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ![builder_structure](./builder_structure.png) 23 | 24 | 1. 生成器 (`Builder`) 接口声明在所有类型生成器中通用的产品构造步骤。 25 | 2. 具体生成器 (`Concrete Builders`) 提供构造过程的不同实现。 具体生成器也可以构造不遵循通用接口的产品。 26 | 3. 产品 (`Products`) 是最终生成的对象。 由不同生成器构造的产品无需属于同一类层次结构或接口。 27 | 4. 主管 (`Director`) 类定义调用构造步骤的顺序, 这样你就可以创建和复用特定的产品配置。 28 | 5. 客户端 (`Client`) 必须将某个生成器对象与主管类关联。 一般情况下, 你只需通过主管类构造函数的参数进行一次性关联即可。 此后主管类就能使用生成器对象完成后续所有的构造任务。 但在客户端将生成器对象传递给主管类制造方法时还有另一种方式。 在这种情况下, 你在使用主管类生产产品时每次都可以使用不同的生成器。 29 | 30 | ## 适用场景 31 | 32 | 1. 使用生成器模式可避免 “重叠构造函数 (`telescoping constructor`)” 的出现。 33 | 34 | 1. 假设你的构造函数中有十个可选参数, 那么调用该函数会非常不方便; 因此, 你需要重载这个构造函数, 新建几个只有较少参数的简化版。 但这些构造函数仍需调用主构造函数, 传递一些默认数值来替代省略掉的参数。生成器模式让你可以分步骤生成对象, 而且允许你仅使用必须的步骤。 应用该模式后, 你再也不需要将几十个参数塞进构造函数里了。 35 | 36 | 2. 当你希望使用代码创建不同形式的产品时, 可使用生成器模式。 37 | 38 | 1. 如果你需要创建的各种形式的产品, 它们的制造过程相似且仅有细节上的差异, 此时可使用生成器模式。 39 | 2. 基本生成器接口中定义了所有可能的制造步骤, 具体生成器将实现这些步骤来制造特定形式的产品。 同时, 主管类将负责管理制造步骤的顺序。 40 | 41 | 3. 使用生成器构造组合树或其他复杂对象。 42 | 43 | 1. 生成器模式让你能分步骤构造产品。 你可以延迟执行某些步骤而不会影响最终产品。 你甚至可以递归调用这些步骤, 这在创建对象树时非常方便。 44 | 2. 生成器在执行制造步骤时, 不能对外发布未完成的产品。 这可以避免客户端代码获取到不完整结果对象的情况。 45 | 46 | ## 优缺点 47 | 48 | ### 优点 49 | 50 | 1. 你可以分步创建对象, 暂缓创建步骤或递归运行创建步骤。 51 | 2. 生成不同形式的产品时, 你可以复用相同的制造代码。 52 | 3. 单一职责原则。 你可以将复杂构造代码从产品的业务逻辑中分离出来。 53 | 54 | ### 缺点 55 | 56 | 1. 由于该模式需要新增多个类, 因此代码整体复杂程度会有所增加。 57 | 58 | ## 与其他模式的关系 59 | 60 | 1. 在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。 61 | 2. 生成器重点关注如何分步生成复杂对象。 抽象工厂专门用于生产一系列相关对象。 抽象工厂会马上返回产品, 生成器则允许你在获取产品前执行一些额外构造步骤。 62 | 3. 你可以在创建复杂组合模式树时使用生成器, 因为这可使其构造步骤以递归的方式运行。 63 | 4. 你可以结合使用生成器和桥接模式: 主管类负责抽象工作, 各种不同的生成器负责实现工作。 64 | 5. 抽象工厂、 生成器和原型都可以用单例模式来实现。 65 | 66 | ## 代码实现 67 | 68 | ```typescript 69 | interface Builder { 70 | producePartA(): void; 71 | producePartB(): void; 72 | producePartC(): void; 73 | } 74 | 75 | class ConcreteBuilder1 implements Builder { 76 | private product: Product1; 77 | 78 | constructor() { 79 | this.reset(); 80 | } 81 | 82 | public reset(): void { 83 | this.product = new Product1(); 84 | } 85 | 86 | public producePartA(): void { 87 | this.product.parts.push('PartA1'); 88 | } 89 | 90 | public producePartB(): void { 91 | this.product.parts.push('PartB1'); 92 | } 93 | 94 | public producePartC(): void { 95 | this.product.parts.push('PartC1'); 96 | } 97 | 98 | public getProduct(): Product1 { 99 | const result = this.product; 100 | this.reset(); 101 | return result; 102 | } 103 | } 104 | 105 | class Product1 { 106 | public parts: string[] = []; 107 | 108 | public listParts(): string { 109 | return `Product parts: ${this.parts.join(', ')}`; 110 | } 111 | } 112 | 113 | class Director { 114 | private builder: Builder; 115 | 116 | public setBuilder(builder: Builder): void { 117 | this.builder = builder; 118 | } 119 | 120 | public buildMinimalViableProduct(): void { 121 | this.builder.producePartA(); 122 | } 123 | 124 | public buildFullFeaturedProduct(): void { 125 | this.builder.producePartA(); 126 | this.builder.producePartB(); 127 | this.builder.producePartC(); 128 | } 129 | } 130 | 131 | export { Builder, ConcreteBuilder1, Product1, Director }; 132 | ``` 133 | 134 | ## 测试用例 135 | 136 | ```typescript 137 | import { Director, ConcreteBuilder1 } from '../index'; 138 | 139 | describe('builder pattern', () => { 140 | it('builder concrete creator 1', () => { 141 | const director = new Director(); 142 | const builder = new ConcreteBuilder1(); 143 | 144 | director.setBuilder(builder); 145 | 146 | director.buildMinimalViableProduct(); 147 | expect(builder.getProduct().listParts()).toBe('Product parts: PartA1'); 148 | 149 | director.buildFullFeaturedProduct(); 150 | 151 | expect(builder.getProduct().listParts()).toBe('Product parts: PartA1, PartB1, PartC1'); 152 | 153 | builder.producePartA(); 154 | builder.producePartC(); 155 | expect(builder.getProduct().listParts()).toBe('Product parts: PartA1, PartC1'); 156 | }); 157 | }); 158 | ``` 159 | -------------------------------------------------------------------------------- /docs/creational/builder_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/creational/builder_structure.png -------------------------------------------------------------------------------- /docs/creational/factory.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 工厂模式 3 | categories: 4 | - 设计模式 5 | - 创建型模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 工厂模式 14 | 15 | :::tip 16 | 17 | **工厂模式**是一种创建型设计模式, 其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。 18 | ::: 19 | 20 | ## 背景 21 | 22 | 工厂方法模式建议使用特殊的工厂方法代替对于对象构造函数的直接调用 (即使用`new`运算符)。 工厂方法返回的对象通常被称作 “产品”。 23 | 24 | 乍看之下, 这种更改可能毫无意义: 我们只是改变了程序中调用构造函数的位置而已。 但是, 仔细想一下, 现在你可以在子类中重写工厂方法, 从而改变其创建产品的类型。 25 | 26 | 但有一点需要注意:仅当这些产品具有共同的基类或者接口时, 子类才能返回不同类型的产品, 同时基类中的工厂方法还应将其返回类型声明为这一共有接口。 27 | 28 | ## 结构 29 | 30 | ![factory_structure](./factory_structure.png) 31 | 32 | 1. 产品 (`Product`):将会对接口进行声明。 对于所有由创建者及其子类构建的对象, 这些接口都是通用的。 33 | 2. 具体产品 (`Concrete Products`) 是产品接口的不同实现。 34 | 3. 创建者 (`Creator`) 类声明返回产品对象的工厂方法。 该方法的返回对象类型必须与产品接口相匹配。你可以将工厂方法声明为抽象方法, 强制要求每个子类以不同方式实现该方法。 或者, 你也可以在基础工厂方法中返回默认产品类型。注意, 尽管它的名字是创建者, 但它最主要的职责并不是创建产品。 一般来说, 创建者类包含一些与产品相关的核心业务逻辑。 35 | 4. 具体创建者 (`Concrete Creators`) 将会重写基础工厂方法, 使其返回不同类型的产品。注意, 并不一定每次调用工厂方法都会创建新的实例。 工厂方法也可以返回缓存、 对象池或其他来源的已有对象。 36 | 37 | ## 适用场景 38 | 39 | 1. 当你在编写代码的过程中, 如果无法预知对象确切类别及其依赖关系时, 可使用工厂方法。 40 | 41 | 1. 工厂方法将创建产品的代码与实际使用产品的代码分离, 从而能在不影响其他代码的情况下扩展产品创建部分代码; 42 | 2. 例如, 如果需要向应用中添加一种新产品, 你只需要开发新的创建者子类, 然后重写其工厂方法即可。 43 | 44 | 2. 如果你希望用户能扩展你软件库或框架的内部组件, 可使用工厂方法。 45 | 1. 继承可能是扩展软件库或框架默认行为的最简单方法。 但是当你使用子类替代标准组件时, 框架如何辨识出该子类? 46 | 2. 解决方案是将各框架中构造组件的代码集中到单个工厂方法中, 并在继承该组件之外允许任何人对该方法进行重写。 47 | 48 | ## 优缺点 49 | 50 | ### 优点 51 | 52 | 1. 你可以避免创建者和具体产品之间的紧密耦合。 53 | 2. 单一职责原则。 你可以将产品创建代码放在程序的单一位置, 从而使得代码更容易维护。 54 | 3. 开闭原则。 无需更改现有客户端代码, 你就可以在程序中引入新的产品类型。 55 | 56 | ### 缺点 57 | 58 | 1. 应用工厂方法模式需要引入许多新的子类, 代码可能会因此变得更复杂。 最好的情况是将该模式引入创建者类的现有层次结构中。 59 | 60 | ## 与其他模式的关系 61 | 62 | 1. 在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。 63 | 2. 抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。 64 | 3. 你可以同时使用工厂方法和迭代器模式来让子类集合返回不同类型的迭代器, 并使得迭代器与集合相匹配。 65 | 4. 原型并不基于继承, 因此没有继承的缺点。 另一方面, 原型需要对被复制对象进行复杂的初始化。 工厂方法基于继承, 但是它不需要初始化步骤。 66 | 5. 工厂方法是模板方法模式的一种特殊形式。 同时, 工厂方法可以作为一个大型模板方法中的一个步骤。 67 | 68 | ## 代码实现 69 | 70 | ```typescript 71 | interface Product { 72 | operation(): string; 73 | } 74 | 75 | abstract class Creator { 76 | public abstract factoryMethod(): Product; 77 | 78 | public someOperation(): string { 79 | const product = this.factoryMethod(); 80 | 81 | return `Creator: The same creator's code has just worked with ${product.operation()}`; 82 | } 83 | } 84 | 85 | class ConcreteCreator1 extends Creator { 86 | public factoryMethod(): Product { 87 | return new ConcreteProduct1(); 88 | } 89 | } 90 | 91 | class ConcreteCreator2 extends Creator { 92 | public factoryMethod(): Product { 93 | return new ConcreteProduct2(); 94 | } 95 | } 96 | 97 | class ConcreteProduct1 implements Product { 98 | public operation(): string { 99 | return 'ConcreteProduct1'; 100 | } 101 | } 102 | 103 | class ConcreteProduct2 implements Product { 104 | public operation(): string { 105 | return 'ConcreteProduct2'; 106 | } 107 | } 108 | 109 | export { Product, Creator, ConcreteCreator1, ConcreteCreator2, ConcreteProduct1, ConcreteProduct2 }; 110 | ``` 111 | 112 | ## 测试用例 113 | 114 | ```typescript 115 | describe('factory pattern', () => { 116 | it('factory concrete creator 1', () => { 117 | const creator1 = new ConcreteCreator1(); 118 | 119 | expect(creator1.someOperation()).toBe( 120 | "Creator: The same creator's code has just worked with ConcreteProduct1" 121 | ); 122 | }); 123 | it('factory concrete creator 2', () => { 124 | const creator1 = new ConcreteCreator2(); 125 | 126 | expect(creator1.someOperation()).toBe( 127 | "Creator: The same creator's code has just worked with ConcreteProduct2" 128 | ); 129 | }); 130 | }); 131 | ``` 132 | -------------------------------------------------------------------------------- /docs/creational/factory_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/creational/factory_structure.png -------------------------------------------------------------------------------- /docs/creational/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 创建者模式 3 | categories: 4 | - 设计模式 5 | tags: 6 | - 设计模式 7 | author: 8 | name: 澄怀 9 | link: https://github.com/encode-studio-fe/design-pattern 10 | --- 11 | 12 | # 创建者模式 13 | 14 | :::tip 15 | 创建者模式提供了创建对象的机制, 能够提升已有代码的灵活性和可复用性。 16 | ::: 17 | 18 | [**工厂模式**](./factory.md): 在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。 19 | 20 | [**抽象工厂模式**](./abstract_factory.md): 创建一系列相关的对象, 而无需指定其具体类。 21 | 22 | [**生成器模式**](./builder.md): 允许使用相同的创建代码生成不同类型和形式的对象。 23 | 24 | [**原型模式**](./prototype.md): 能够复制已有对象, 而又无需使代码依赖它们所属的类。 25 | 26 | [**单例模式**](./singleton.md): 能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。 27 | -------------------------------------------------------------------------------- /docs/creational/prototype.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 原型模式 3 | categories: 4 | - 设计模式 5 | - 创建型模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 原型模式 14 | 15 | :::tip 16 | 17 | **原型模式**是一种创建型设计模式,能够复制已有对象, 而又无需使代码依赖它们所属的类。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ### 基本实现 23 | 24 | ![prototype_structure_basic](./prototype_structure_basic.png) 25 | 26 | 1. 原型 (`Prototype`) 接口将对克隆方法进行声明。 在绝大多数情况下, 其中只会有一个名为 clone 克隆的方法。 27 | 2. 具体原型 (`Concrete Prototype`) 类将实现克隆方法。 除了将原始对象的数据复制到克隆体中之外, 该方法有时还需处理克隆过程中的极端情况, 例如克隆关联对象和梳理递归依赖等等。 28 | 3. 客户端 (`Client`) 可以复制实现了原型接口的任何对象。 29 | 30 | ### 原型注册表实现 31 | 32 | ![prototype_structure_registry](./prototype_structure_registry.png) 33 | 34 | 1. 原型注册表 (`Prototype Registry`) 提供了一种访问常用原型的简单方法, 其中存储了一系列可供随时复制的预生成对象。 最简单的注册表原型是一个 名称 → 原型的哈希表。 但如果需要使用名称以外的条件进行搜索, 你可以创建更加完善的注册表版本。 35 | 36 | ## 适用场景 37 | 38 | 1. 如果你需要复制一些对象, 同时又希望代码独立于这些对象所属的具体类, 可以使用原型模式。 39 | 40 | 1. 这一点考量通常出现在代码需要处理第三方代码通过接口传递过来的对象时。 即使不考虑代码耦合的情况, 你的代码也不能依赖这些对象所属的具体类, 因为你不知道它们的具体信息。 41 | 2. 原型模式为客户端代码提供一个通用接口, 客户端代码可通过这一接口与所有实现了克隆的对象进行交互, 它也使得客户端代码与其所克隆的对象具体类独立开来。 42 | 43 | 2. 如果子类的区别仅在于其对象的初始化方式, 那么你可以使用该模式来减少子类的数量。 别人创建这些子类的目的可能是为了创建特定类型的对象。 44 | 45 | 1. 在原型模式中, 你可以使用一系列预生成的、 各种类型的对象作为原型。 46 | 2. 客户端不必根据需求对子类进行实例化, 只需找到合适的原型并对其进行克隆即可。 47 | 48 | ## 优缺点 49 | 50 | ### 优点 51 | 52 | 1. 你可以克隆对象, 而无需与它们所属的具体类相耦合。 53 | 2. 你可以克隆预生成原型, 避免反复运行初始化代码。 54 | 3. 你可以更方便地生成复杂对象。 55 | 4. 你可以用继承以外的方式来处理复杂对象的不同配置。 56 | 57 | ### 缺点 58 | 59 | 1. 克隆包含循环引用的复杂对象可能会非常麻烦。 60 | 61 | ## 与其他模式的关系 62 | 63 | 1. 在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。 64 | 2. 抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。 65 | 3. 原型可用于保存命令模式的历史记录。 66 | 4. 大量使用组合模式和装饰模式的设计通常可从对于原型的使用中获益。 你可以通过该模式来复制复杂结构, 而非从零开始重新构造。 67 | 5. 原型并不基于继承, 因此没有继承的缺点。 另一方面, 原型需要对被复制对象进行复杂的初始化。 工厂方法基于继承, 但是它不需要初始化步骤。 68 | 6. 有时候原型可以作为备忘录模式的一个简化版本, 其条件是你需要在历史记录中存储的对象的状态比较简单, 不需要链接其他外部资源, 或者链接可以方便地重建。 69 | 7. 抽象工厂、 生成器和原型都可以用单例模式来实现。 70 | 71 | ## 代码实现 72 | 73 | ```typescript 74 | class Prototype { 75 | public primitive: any; 76 | public component: object; 77 | public circularReference: ComponentWithBackReference; 78 | 79 | public clone(): this { 80 | const clone = Object.create(this); 81 | 82 | clone.component = Object.create(this.component); 83 | 84 | clone.circularReference = { 85 | ...this.circularReference, 86 | prototype: { ...this }, 87 | }; 88 | 89 | return clone; 90 | } 91 | } 92 | 93 | class ComponentWithBackReference { 94 | public prototype; 95 | 96 | constructor(prototype: Prototype) { 97 | this.prototype = prototype; 98 | } 99 | } 100 | 101 | export { Prototype, ComponentWithBackReference }; 102 | ``` 103 | 104 | ## 测试用例 105 | 106 | ```typescript 107 | import { Prototype, ComponentWithBackReference } from '../index'; 108 | 109 | describe('prototype pattern', () => { 110 | it('prototype concrete creator', () => { 111 | const p1 = new Prototype(); 112 | 113 | p1.primitive = 'chenghuai'; 114 | p1.component = new Date(); 115 | p1.circularReference = new ComponentWithBackReference(p1); 116 | 117 | const p2 = p1.clone(); 118 | 119 | expect(p1.primitive === p2.primitive).toBe(true); 120 | expect(p1.component === p2.component).toBe(false); 121 | expect(p1.circularReference === p2.circularReference).toBe(false); 122 | expect(p1.circularReference.prototype === p2.circularReference.prototype).toBe(false); 123 | }); 124 | }); 125 | ``` 126 | -------------------------------------------------------------------------------- /docs/creational/prototype_structure_basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/creational/prototype_structure_basic.png -------------------------------------------------------------------------------- /docs/creational/prototype_structure_registry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/creational/prototype_structure_registry.png -------------------------------------------------------------------------------- /docs/creational/singleton.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 单例模式 3 | categories: 4 | - 设计模式 5 | - 创建型模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 单例模式 14 | 15 | :::tip 16 | 17 | **单例模式**是一种创建型设计模式,能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ![singleton_structure](./singleton_structure.png) 23 | 24 | 1. 单例 (`Singleton`) 类声明了一个名为 get­Instance 获取实例的静态方法来返回其所属类的一个相同实例。 25 | 26 | 单例的构造函数必须对客户端 (`Client`) 代码隐藏。 调用 获取实例方法必须是获取单例对象的唯一方式。 27 | 28 | ## 适用场景 29 | 30 | 1. 如果程序中的某个类对于所有客户端只有一个可用的实例, 可以使用单例模式。 31 | 32 | 1. 单例模式禁止通过除特殊构建方法以外的任何方式来创建自身类的对象。 该方法可以创建一个新对象, 但如果该对象已经被创建, 则返回已有的对象。 33 | 34 | 2. 如果你需要更加严格地控制全局变量, 可以使用单例模式。 35 | 36 | 1. 单例模式与全局变量不同, 它保证类只存在一个实例。 除了单例类自己以外, 无法通过任何方式替换缓存的实例。 37 | 38 | 请注意, 你可以随时调整限制并设定生成单例实例的数量, 只需修改 获取实例方法, 即 getInstance 中的代码即可实现。 39 | 40 | ## 优缺点 41 | 42 | ### 优点 43 | 44 | 1. 你可以保证一个类只有一个实例。 45 | 2. 你获得了一个指向该实例的全局访问节点。 46 | 3. 仅在首次请求单例对象时对其进行初始化。 47 | 48 | ### 缺点 49 | 50 | 1. 违反了单一职责原则。 该模式同时解决了两个问题。 51 | 2. 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。 52 | 3. 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。 53 | 4. 单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要 54 | 5. 出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式。 55 | 56 | ## 与其他模式的关系 57 | 58 | 1. 如果你能将对象的所有共享状态简化为一个享元对象, 那么享元模式就和单例类似了。 但这两个模式有两个根本性的不同。 59 | 60 | 1. 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。 61 | 2. 单例对象可以是可变的。 享元对象是不可变的。 62 | 63 | 2. 抽象工厂模式、 生成器模式和原型模式都可以用单例来实现。 64 | 65 | ## 代码实现 66 | 67 | ```typescript 68 | class Singleton { 69 | private static instance: Singleton; 70 | 71 | private constructor() {} 72 | 73 | public static getInstance(): Singleton { 74 | if (!Singleton.instance) { 75 | Singleton.instance = new Singleton(); 76 | } 77 | 78 | return Singleton.instance; 79 | } 80 | 81 | public someBusinessLogic() {} 82 | } 83 | 84 | export { Singleton }; 85 | ``` 86 | 87 | ## 测试用例 88 | 89 | ```typescript 90 | import { Singleton } from '../index'; 91 | 92 | describe('singleton pattern', () => { 93 | it('singleton instance', () => { 94 | const s1 = Singleton.getInstance(); 95 | const s2 = Singleton.getInstance(); 96 | 97 | expect(s1 === s2).toBe(true); 98 | }); 99 | }); 100 | ``` 101 | -------------------------------------------------------------------------------- /docs/creational/singleton_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/creational/singleton_structure.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroText: 印客学院 4 | tagline: 设计模式深入浅出 5 | actionText: 立刻进入 → 6 | actionLink: /creational/index.md 7 | 8 | features: 9 | - title: 完备的设计模式 10 | details: 提供 GOF 中全部的设计模式,包含全部内容讲解及实际代码演示 11 | - title: 支持 Typescript 12 | details: 提供完整的类型注释,帮助您从 0~1 掌握完整的代码规范 13 | - title: 全套的测试用例 14 | details: 配套完整的测试用例,帮助您完整掌握 15 | --- 16 | 17 | ## :star: 设计目的 18 | 19 | 鉴于大部分同学在开发过程中对代码的健壮性和扩展性考虑很少,同时开发过程中在重复相同的代码,其中,最大的原因就是缺乏整体考量的设计。 20 | 21 | 本项目会以设计模式为切入点,从`GOF`所提出的完整的设计模式开始,带领大家掌握设计模式之美。 22 | 23 |
24 | 25 | ## :grey_question: 什么是设计模式 26 | 27 | **设计模式**是软件设计中常见问题的典型解决方案。 它们就像能根据需求进行调整的预制蓝图, 可用于解决代码中反复出现的设计问题。 28 | 29 | 设计模式与方法或库的使用方式不同, 你很难直接在自己的程序中套用某个设计模式。 模式并不是一段特定的代码, 而是解决特定问题的一般性概念。 你可以根据模式来实现符合自己程序实际所需的解决方案。 30 | 31 | 算法更像是菜谱: 提供达成目标的明确步骤。 而模式更像是蓝图: 你可以看到最终的结果和模式的功能, 但需要自己确定实现步骤。 32 | 33 |
34 | 35 | ## :bulb: 为什要学习设计模式 36 | 37 | **设计模式**是针对软件设计中常见问题的工具箱, 其中的工具就是各种经过实践验证的解决方案。 即使你从未遇到过这些问题, 了解模式仍然非常有用, 因为它能指导你如何使用面向对象的设计原则来解决各种问题。 38 | 39 | 设计模式定义了一种让你和团队成员能够更高效沟通的通用语言。 你只需说 “哦, 这里用单例就可以了”, 所有人都会理解这条建议背后的想法。 只要知晓模式及其名称, 你就无需解释什么是单例。 40 | 41 |
42 | 43 | ## :email: 联系 44 | 45 | - **印客学院官网**: 46 | - **GitHub**: 47 | 48 |
49 | -------------------------------------------------------------------------------- /docs/structural/adapter.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 适配器模式 3 | categories: 4 | - 设计模式 5 | - 结构型模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 适配器模式 14 | 15 | :::tip 16 | 17 | **适配器模式**是一种结构型设计模式, 它能使接口不兼容的对象能够相互合作。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ### 对象适配器 23 | 24 | ![adapter_object_structure](./adapter_object_structure.png) 25 | 26 | 1. 客户端 (`Client`) 是包含当前程序业务逻辑的类。 27 | 2. 客户端接口 (`Client Interface`) 描述了其他类与客户端代码合作时必须遵循的协议。 28 | 3. 服务 (`Service`) 中有一些功能类 (通常来自第三方或遗留系统)。 客户端与其接口不兼容, 因此无法直接调用其功能。 29 | 4. 适配器 (`Adapter`) 是一个可以同时与客户端和服务交互的类: 它在实现客户端接口的同时封装了服务对象。 适配器接受客户端通过适配器接口发起的调用, 并将其转换为适用于被封装服务对象的调用。 30 | 5. 客户端代码只需通过接口与适配器交互即可, 无需与具体的适配器类耦合。 因此, 你可以向程序中添加新类型的适配器而无需修改已有代码。 这在服务类的接口被更改或替换时很有用: 你无需修改客户端代码就可以创建新的适配器类。 31 | 32 | ### 类适配器 33 | 34 | ![adapter_class_structure](./adapter_class_structure.png) 35 | 36 | 1. 类适配器不需要封装任何对象, 因为它同时继承了客户端和服务的行为。 适配功能在重写的方法中完成。 最后生成的适配器可替代已有的客户端类进行使用。 37 | 38 | ## 适用场景 39 | 40 | 1. 当你希望使用某个类, 但是其接口与其他代码不兼容时, 可以使用适配器类。 41 | 42 | 1. 适配器模式允许你创建一个中间层类, 其可作为代码与遗留类、 第三方类或提供怪异接口的类之间的转换器。 43 | 44 | 2. 如果您需要复用这样一些类, 他们处于同一个继承体系, 并且他们又有了额外的一些共同的方法, 但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性。 45 | 46 | 1. 你可以扩展每个子类, 将缺少的功能添加到新的子类中。 但是, 你必须在所有新子类中重复添加这些代码, 这样会使得代码有坏味道。 47 | 2. 将缺失功能添加到一个适配器类中是一种优雅得多的解决方案。 然后你可以将缺少功能的对象封装在适配器中, 从而动态地获取所需功能。 如要这一点正常运作, 目标类必须要有通用接口, 适配器的成员变量应当遵循该通用接口。 这种方式同装饰模式非常相似。 48 | 49 | ## 优缺点 50 | 51 | ### 优点 52 | 53 | 1. 单一职责原则你可以将接口或数据转换代码从程序主要业务逻辑中分离。 54 | 2. 开闭原则。 只要客户端代码通过客户端接口与适配器进行交互, 你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。 55 | 56 | ### 缺点 57 | 58 | 1. 代码整体复杂度增加, 因为你需要新增一系列接口和类。 有时直接更改服务类使其与其他代码兼容会更简单。 59 | 60 | ## 与其他模式的关系 61 | 62 | 1. 桥接模式通常会于开发前期进行设计, 使你能够将程序的各个部分独立开来以便开发。 另一方面, 适配器模式通常在已有程序中使用, 让相互不兼容的类能很好地合作。 63 | 2. 适配器可以对已有对象的接口进行修改, 装饰模式则能在不改变对象接口的前提下强化对象功能。 此外, 装饰还支持递归组合, 适配器则无法实现。 64 | 3. 适配器能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰则能为对象提供加强的接口。 65 | 4. 外观模式为现有对象定义了一个新接口, 适配器则会试图运用已有的接口。 适配器通常只封装一个对象, 外观通常会作用于整个对象子系统上。 66 | 5. 桥接、 状态模式和策略模式 (在某种程度上包括适配器) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。 67 | 68 | ## 代码实现 69 | 70 | ```typescript 71 | class Target { 72 | public request(): string { 73 | return 'request'; 74 | } 75 | } 76 | 77 | class Adaptee { 78 | public specificRequest(): string { 79 | return 'specify request'; 80 | } 81 | } 82 | 83 | class Adapter extends Target { 84 | private adaptee: Adaptee; 85 | 86 | constructor(adaptee: Adaptee) { 87 | super(); 88 | this.adaptee = adaptee; 89 | } 90 | 91 | public request(): string { 92 | const result = this.adaptee.specificRequest().split(' ').reverse().join(' '); 93 | return `Adapter: (TRANSLATED) ${result}`; 94 | } 95 | } 96 | 97 | export { Target, Adaptee, Adapter }; 98 | ``` 99 | 100 | ## 测试用例 101 | 102 | ```typescript 103 | import { Adaptee, Adapter, Target } from '../index'; 104 | 105 | describe('adapter pattern', () => { 106 | it('adapter concrete', () => { 107 | const target = new Target(); 108 | const adaptee = new Adaptee(); 109 | const adapter = new Adapter(adaptee); 110 | 111 | expect(adaptee.specificRequest()).toBe('specify request'); 112 | expect(adapter.request()).toBe('Adapter: (TRANSLATED) request specify'); 113 | }); 114 | }); 115 | ``` 116 | -------------------------------------------------------------------------------- /docs/structural/adapter_class_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/structural/adapter_class_structure.png -------------------------------------------------------------------------------- /docs/structural/adapter_object_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/structural/adapter_object_structure.png -------------------------------------------------------------------------------- /docs/structural/birdge_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/structural/birdge_structure.png -------------------------------------------------------------------------------- /docs/structural/bridge.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 桥接模式 3 | categories: 4 | - 设计模式 5 | - 结构型模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 桥接模式 14 | 15 | :::tip 16 | 17 | **桥接模式**是一种结构型设计模式, 可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构, 从而能在开发时分别使用。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ![birdge_structure](./birdge_structure.png) 23 | 24 | 1. 抽象部分 (`Abstraction`) 提供高层控制逻辑, 依赖于完成底层实际工作的实现对象。 25 | 2. 实现部分 (`Implementation`) 为所有具体实现声明通用接口。 抽象部分仅能通过在这里声明的方法与实现对象交互。抽象部分可以列出和实现部分一样的方法, 但是抽象部分通常声明一些复杂行为, 这些行为依赖于多种由实现部分声明的原语操作。 26 | 3. 具体实现 (`Concrete Implementations`) 中包括特定于平台的代码。 27 | 4. 精确抽象 (`Refined Abstraction`) 提供控制逻辑的变体。 与其父类一样, 它们通过通用实现接口与不同的实现进行交互。 28 | 5. 通常情况下, 客户端 (`Client`) 仅关心如何与抽象部分合作。 但是, 客户端需要将抽象对象与一个实现对象连接起来。 29 | 30 | ## 适用场景 31 | 32 | 1. 如果你想要拆分或重组一个具有多重功能的庞杂类 (例如能与多个数据库服务器进行交互的类), 可以使用桥接模式。 33 | 34 | 1. 类的代码行数越多, 弄清其运作方式就越困难, 对其进行修改所花费的时间就越长。 一个功能上的变化可能需要在整个类范围内进行修改, 而且常常会产生错误, 甚至还会有一些严重的副作用。 35 | 2. 桥接模式可以将庞杂类拆分为几个类层次结构。 此后, 你可以修改任意一个类层次结构而不会影响到其他类层次结构。 这种方法可以简化代码的维护工作, 并将修改已有代码的风险降到最低。 36 | 37 | 2. 如果你希望在几个独立维度上扩展一个类, 可使用该模式。 38 | 39 | 1. 桥接建议将每个维度抽取为独立的类层次。 初始类将相关工作委派给属于对应类层次的对象, 无需自己完成所有工作。 40 | 41 | 3. 如果你需要在运行时切换不同实现方法, 可使用桥接模式。 42 | 43 | 1. 当然并不是说一定要实现这一点, 桥接模式可替换抽象部分中的实现对象, 具体操作就和给成员变量赋新值一样简单。 44 | 45 | ## 优缺点 46 | 47 | ### 优点 48 | 49 | 1. 你可以创建与平台无关的类和程序。 50 | 2. 客户端代码仅与高层抽象部分进行互动, 不会接触到平台的详细信息。 51 | 3. 开闭原则。 你可以新增抽象部分和实现部分, 且它们之间不会相互影响。 52 | 4. 单一职责原则。 抽象部分专注于处理高层逻辑, 实现部分处理平台细节。 53 | 54 | ### 缺点 55 | 56 | 1. 对高内聚的类使用该模式可能会让代码更加复杂。 57 | 58 | ## 与其他模式的关系 59 | 60 | 1. 桥接模式通常会于开发前期进行设计, 使你能够将程序的各个部分独立开来以便开发。 另一方面, 适配器模式通常在已有程序中使用, 让相互不兼容的类能很好地合作。 61 | 2. 桥接、 状态模式和策略模式 (在某种程度上包括适配器) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。 62 | 3. 可以将抽象工厂模式和桥接搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。 63 | 4. 可以结合使用生成器模式和桥接模式: 主管类负责抽象工作, 各种不同的生成器负责实现工作。 64 | 65 | ## 代码实现 66 | 67 | ```typescript 68 | class Abstraction { 69 | protected implementation: Implementation; 70 | 71 | constructor(implementation: Implementation) { 72 | this.implementation = implementation; 73 | } 74 | 75 | public operation(): string { 76 | const result = this.implementation.operationImplementation(); 77 | return result; 78 | } 79 | } 80 | 81 | class ExtendedAbstraction extends Abstraction { 82 | public operation(): string { 83 | const result = this.implementation.operationImplementation(); 84 | return result; 85 | } 86 | } 87 | 88 | interface Implementation { 89 | operationImplementation(): string; 90 | } 91 | 92 | class ConcreteImplementationA implements Implementation { 93 | public operationImplementation(): string { 94 | return 'ConcreteImplementationA'; 95 | } 96 | } 97 | 98 | class ConcreteImplementationB implements Implementation { 99 | public operationImplementation(): string { 100 | return 'ConcreteImplementationB'; 101 | } 102 | } 103 | 104 | export { 105 | Abstraction, 106 | ExtendedAbstraction, 107 | Implementation, 108 | ConcreteImplementationA, 109 | ConcreteImplementationB, 110 | }; 111 | ``` 112 | 113 | ## 测试用例 114 | 115 | ```typescript 116 | import { ConcreteImplementationA, ConcreteImplementationB, Abstraction } from '../index'; 117 | 118 | describe('bridge pattern', () => { 119 | it('bridge concrete A', () => { 120 | const implementationA = new ConcreteImplementationA(); 121 | const abstractionA = new Abstraction(implementationA); 122 | 123 | expect(abstractionA.operation()).toBe('ConcreteImplementationA'); 124 | }); 125 | 126 | it('bridge concrete B', () => { 127 | const implementationB = new ConcreteImplementationB(); 128 | const abstractionB = new Abstraction(implementationB); 129 | 130 | expect(abstractionB.operation()).toBe('ConcreteImplementationB'); 131 | }); 132 | }); 133 | ``` 134 | -------------------------------------------------------------------------------- /docs/structural/composite.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 组合模式 3 | categories: 4 | - 设计模式 5 | - 结构型模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 组合模式 14 | 15 | :::tip 16 | 17 | **组合模式**是一种结构型设计模式, 你可以使用它将对象组合成树状结构, 并且能像使用独立对象一样使用它们。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ![composite_structure](./composite_structure.png) 23 | 24 | 1. 组件 (`Component`) 接口描述了树中简单项目和复杂项目所共有的操作。 25 | 2. 叶节点 (`Leaf`) 是树的基本结构, 它不包含子项目。 26 | 3. 容器 (`Container`)——又名 “组合 (Composite)”——是包含叶节点或其他容器等子项目的单位。 容器不知道其子项目所属的具体类, 它只通过通用的组件接口与其子项目交互。容器接收到请求后会将工作分配给自己的子项目, 处理中间结果, 然后将最终结果返回给客户端。 27 | 4. 客户端 (`Client`) 通过组件接口与所有项目交互。 因此, 客户端能以相同方式与树状结构中的简单或复杂项目交互。 28 | 29 | ## 适用场景 30 | 31 | 1. 如果你需要实现树状对象结构, 可以使用组合模式。 32 | 33 | 1. 组合模式为你提供了两种共享公共接口的基本元素类型: 简单叶节点和复杂容器。 容器中可以包含叶节点和其他容器。 这使得你可以构建树状嵌套递归对象结构。 34 | 35 | 2. 如果你希望客户端代码以相同方式处理简单和复杂元素, 可以使用该模式。 36 | 37 | 1. 组合模式中定义的所有元素共用同一个接口。 在这一接口的帮助下, 客户端不必在意其所使用的对象的具体类。 38 | 39 | ## 优缺点 40 | 41 | ### 优点 42 | 43 | 1. 你可以利用多态和递归机制更方便地使用复杂树结构。 44 | 2. 开闭原则。 无需更改现有代码, 你就可以在应用中添加新元素, 使其成为对象树的一部分。 45 | 46 | ### 缺点 47 | 48 | 1. 对于功能差异较大的类, 提供公共接口或许会有困难。 在特定情况下, 你需要过度一般化组件接口, 使其变得令人难以理解。 49 | 50 | ## 与其他模式的关系 51 | 52 | 1. 桥接模式、 状态模式和策略模式 (在某种程度上包括适配器模式) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。 53 | 2. 你可以在创建复杂组合树时使用生成器模式, 因为这可使其构造步骤以递归的方式运行。 54 | 3. 责任链模式通常和组合模式结合使用。 在这种情况下, 叶组件接收到请求后, 可以将请求沿包含全体父组件的链一直传递至对象树的底部。 55 | 4. 你可以使用迭代器模式来遍历组合树。 56 | 5. 你可以使用访问者模式对整个组合树执行操作。 57 | 6. 你可以使用享元模式实现组合树的共享叶节点以节省内存。 58 | 7. 组合和装饰模式的结构图很相似, 因为两者都依赖递归组合来组织无限数量的对象。 59 | 8. 装饰类似于组合, 但其只有一个子组件。 此外还有一个明显不同: 装饰为被封装对象添加了额外的职责, 组合仅对其子节点的结果进行了 “求和”。 60 | 61 | ## 代码实现 62 | 63 | ```typescript 64 | abstract class CompositeComponent { 65 | protected parent!: CompositeComponent | null; 66 | 67 | public setParent(parent: CompositeComponent | null) { 68 | this.parent = parent; 69 | } 70 | 71 | public getParent(): CompositeComponent | null { 72 | return this.parent; 73 | } 74 | 75 | public add(component: CompositeComponent): void {} 76 | 77 | public remove(component: CompositeComponent): void {} 78 | 79 | public isComposite(): boolean { 80 | return false; 81 | } 82 | 83 | public abstract operation(): string; 84 | } 85 | 86 | class Leaf extends CompositeComponent { 87 | public operation(): string { 88 | return 'Leaf'; 89 | } 90 | } 91 | 92 | class Composite extends CompositeComponent { 93 | protected children: CompositeComponent[] = []; 94 | 95 | public add(component: CompositeComponent): void { 96 | this.children.push(component); 97 | component.setParent(this); 98 | } 99 | 100 | public remove(component: CompositeComponent): void { 101 | const componentIndex = this.children.indexOf(component); 102 | this.children.splice(componentIndex, 1); 103 | 104 | component.setParent(null); 105 | } 106 | 107 | public isComposite(): boolean { 108 | return true; 109 | } 110 | 111 | public operation(): string { 112 | const results: Array = []; 113 | for (const child of this.children) { 114 | results.push(child.operation()); 115 | } 116 | 117 | return `Branch(${results.join('+')})`; 118 | } 119 | } 120 | 121 | export { CompositeComponent, Leaf, Composite }; 122 | ``` 123 | 124 | ## 测试用例 125 | 126 | ```typescript 127 | import { Leaf, Composite } from '../index'; 128 | 129 | describe('composite pattern', () => { 130 | it('composite concrete 1', () => { 131 | const simple = new Leaf(); 132 | 133 | expect(simple.operation()).toBe('Leaf'); 134 | }); 135 | 136 | it('composite concrete 2', () => { 137 | const tree = new Composite(); 138 | const branch1 = new Composite(); 139 | 140 | branch1.add(new Leaf()); 141 | branch1.add(new Leaf()); 142 | 143 | const branch2 = new Composite(); 144 | branch2.add(new Leaf()); 145 | 146 | tree.add(branch1); 147 | tree.add(branch2); 148 | 149 | expect(tree.operation()).toBe('Branch(Branch(Leaf+Leaf)+Branch(Leaf))'); 150 | }); 151 | }); 152 | ``` 153 | -------------------------------------------------------------------------------- /docs/structural/composite_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/structural/composite_structure.png -------------------------------------------------------------------------------- /docs/structural/decorator.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 装饰模式 3 | categories: 4 | - 设计模式 5 | - 结构型模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 装饰模式 14 | 15 | :::tip 16 | 17 | **装饰模式**是一种结构型设计模式, 允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ![decorator_structure](./decorator_structure.png) 23 | 24 | 1. 部件 (`Component`) 声明封装器和被封装对象的公用接口。 25 | 2. 具体部件 (`Concrete Component`) 类是被封装对象所属的类。 它定义了基础行为, 但装饰类可以改变这些行为。 26 | 3. 基础装饰 (`Base Decorator`) 类拥有一个指向被封装对象的引用成员变量。 该变量的类型应当被声明为通用部件接口, 这样它就可以引用具体的部件和装饰。 装饰基类会将所有操作委派给被封装的对象。 27 | 4. 具体装饰类 (`Concrete Decorators`) 定义了可动态添加到部件的额外行为。 具体装饰类会重写装饰基类的方法, 并在调用父类方法之前或之后进行额外的行为。 28 | 5. 客户端 (`Client`) 可以使用多层装饰来封装部件, 只要它能使用通用接口与所有对象互动即可。 29 | 30 | ## 适用场景 31 | 32 | 1. 如果你希望在无需修改代码的情况下即可使用对象, 且希望在运行时为对象新增额外的行为, 可以使用装饰模式。 33 | 34 | 1. 装饰能将业务逻辑组织为层次结构, 你可为各层创建一个装饰, 在运行时将各种不同逻辑组合成对象。 由于这些对象都遵循通用接口, 客户端代码能以相同的方式使用这些对象。 35 | 36 | 2. 如果用继承来扩展对象行为的方案难以实现或者根本不可行, 你可以使用该模式。 37 | 38 | 1. 许多编程语言使用 final 最终关键字来限制对某个类的进一步扩展。 复用最终类已有行为的唯一方法是使用装饰模式: 用封装器对其进行封装。 39 | 40 | ## 优缺点 41 | 42 | ### 优点 43 | 44 | 1. 你无需创建新子类即可扩展对象的行为。 45 | 2. 你可以在运行时添加或删除对象的功能。 46 | 3. 你可以用多个装饰封装对象来组合几种行为。 47 | 4. 单一职责原则。 你可以将实现了许多不同行为的一个大类拆分为多个较小的类。 48 | 49 | ### 缺点 50 | 51 | 1. 在封装器栈中删除特定封装器比较困难。 52 | 2. 实现行为不受装饰栈顺序影响的装饰比较困难。 53 | 3. 各层的初始化配置代码看上去可能会很糟糕。 54 | 55 | ## 与其他模式的关系 56 | 57 | 1. 适配器模式可以对已有对象的接口进行修改, 装饰模式则能在不改变对象接口的前提下强化对象功能。 此外, 装饰还支持递归组合, 适配器则无法实现。 58 | 2. 适配器能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰则能为对象提供加强的接口。 59 | 3. 责任链模式和装饰模式的类结构非常相似。 两者都依赖递归组合将需要执行的操作传递给一系列对象。 但是, 两者有几点重要的不同之处。 60 | 4. 责任链的管理者可以相互独立地执行一切操作, 还可以随时停止传递请求。 另一方面, 各种装饰可以在遵循基本接口的情况下扩展对象的行为。 此外, 装饰无法中断请求的传递。 61 | 5. 组合模式和装饰的结构图很相似, 因为两者都依赖递归组合来组织无限数量的对象。 62 | 6. 装饰类似于组合, 但其只有一个子组件。 此外还有一个明显不同: 装饰为被封装对象添加了额外的职责, 组合仅对其子节点的结果进行了 “求和”。但是, 模式也可以相互合作: 你可以使用装饰来扩展组合树中特定对象的行为。 63 | 7. 大量使用组合和装饰的设计通常可从对于原型模式的使用中获益。 你可以通过该模式来复制复杂结构, 而非从零开始重新构造。 64 | 8. 装饰可让你更改对象的外表, 策略模式则让你能够改变其本质。 65 | 9. 装饰和代理有着相似的结构, 但是其意图却非常不同。 这两个模式的构建都基于组合原则, 也就是说一个对象应该将部分工作委派给另一个对象。 两者之间的不同之处在于代理通常自行管理其服务对象的生命周期, 而装饰的生成则总是由客户端进行控制。 66 | 67 | ## 代码实现 68 | 69 | ```typescript 70 | interface DecorateComponent { 71 | operation(): string; 72 | } 73 | 74 | class ConcreteComponent implements DecorateComponent { 75 | public operation(): string { 76 | return 'ConcreteComponent'; 77 | } 78 | } 79 | 80 | class Decorator implements DecorateComponent { 81 | protected component: DecorateComponent; 82 | 83 | constructor(component: DecorateComponent) { 84 | this.component = component; 85 | } 86 | 87 | public operation(): string { 88 | return this.component.operation(); 89 | } 90 | } 91 | 92 | class ConcreteDecoratorA extends Decorator { 93 | public operation(): string { 94 | return `ConcreteDecoratorA(${super.operation()})`; 95 | } 96 | } 97 | 98 | class ConcreteDecoratorB extends Decorator { 99 | public operation(): string { 100 | return `ConcreteDecoratorB(${super.operation()})`; 101 | } 102 | } 103 | 104 | export { DecorateComponent, ConcreteComponent, Decorator, ConcreteDecoratorA, ConcreteDecoratorB }; 105 | ``` 106 | 107 | ## 测试用例 108 | 109 | ```typescript 110 | import { ConcreteComponent, ConcreteDecoratorA, ConcreteDecoratorB } from '../index'; 111 | 112 | describe('decorate pattern', () => { 113 | it('simple component concrete 1', () => { 114 | const simple = new ConcreteComponent(); 115 | 116 | expect(simple.operation()).toBe('ConcreteComponent'); 117 | }); 118 | 119 | it('decorated component concrete 2', () => { 120 | const simple = new ConcreteComponent(); 121 | const decorator1 = new ConcreteDecoratorA(simple); 122 | const decorator2 = new ConcreteDecoratorB(decorator1); 123 | 124 | expect(decorator2.operation()).toBe( 125 | 'ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent))' 126 | ); 127 | }); 128 | }); 129 | ``` 130 | -------------------------------------------------------------------------------- /docs/structural/decorator_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/structural/decorator_structure.png -------------------------------------------------------------------------------- /docs/structural/facade.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 外观模式 3 | categories: 4 | - 设计模式 5 | - 结构型模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 外观模式 14 | 15 | :::tip 16 | 17 | **外观模式**是一种结构型设计模式, 能为程序库、 框架或其他复杂类提供一个简单的接口。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ![facade_structure](./facade_structure.png) 23 | 24 | 1. 外观 (`Facade`) 提供了一种访问特定子系统功能的便捷方式, 其了解如何重定向客户端请求, 知晓如何操作一切活动部件。 25 | 2. 创建附加外观 (`Additional Facade`) 类可以避免多种不相关的功能污染单一外观, 使其变成又一个复杂结构。 客户端和其他外观都可使用附加外观。 26 | 3. 复杂子系统 (`Complex Subsystem`) 由数十个不同对象构成。 如果要用这些对象完成有意义的工作, 你必须深入了解子系统的实现细节, 比如按照正确顺序初始化对象和为其提供正确格式的数据。 27 | 4. 客户端 (`Client`) 使用外观代替对子系统对象的直接调用。 28 | 29 | ## 适用场景 30 | 31 | 1. 如果你需要一个指向复杂子系统的直接接口, 且该接口的功能有限, 则可以使用外观模式。 32 | 33 | 1. 子系统通常会随着时间的推进变得越来越复杂。 即便是应用了设计模式, 通常你也会创建更多的类。 尽管在多种情形中子系统可能是更灵活或易于复用的, 但其所需的配置和样板代码数量将会增长得更快。 为了解决这个问题, 外观将会提供指向子系统中最常用功能的快捷方式, 能够满足客户端的大部分需求。 34 | 35 | 2. 如果需要将子系统组织为多层结构, 可以使用外观。 36 | 37 | 1. 创建外观来定义子系统中各层次的入口。 你可以要求子系统仅使用外观来进行交互, 以减少子系统之间的耦合。 38 | 39 | ## 优缺点 40 | 41 | ### 优点 42 | 43 | 1. 你可以让自己的代码独立于复杂子系统。 44 | 45 | ### 缺点 46 | 47 | 1. 外观可能成为与程序中所有类都耦合的上帝对象。 48 | 49 | ## 与其他模式的关系 50 | 51 | 1. 外观模式为现有对象定义了一个新接口, 适配器模式则会试图运用已有的接口。 适配器通常只封装一个对象, 外观通常会作用于整个对象子系统上。 52 | 2. 当只需对客户端代码隐藏子系统创建对象的方式时, 你可以使用抽象工厂模式来代替外观。 53 | 3. 享元模式展示了如何生成大量的小型对象, 外观则展示了如何用一个对象来代表整个子系统。 54 | 4. 外观和中介者模式的职责类似: 它们都尝试在大量紧密耦合的类中组织起合作。 55 | 1. 外观为子系统中的所有对象定义了一个简单接口, 但是它不提供任何新功能。 子系统本身不会意识到外观的存在。 子系统中的对象可以直接进行交流。 56 | 2. 中介者将系统中组件的沟通行为中心化。 各组件只知道中介者对象, 无法直接相互交流。 57 | 5. 外观类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了。 58 | 6. 外观与代理模式的相似之处在于它们都缓存了一个复杂实体并自行对其进行初始化。 代理与其服务对象遵循同一接口, 使得自己和服务对象可以互换, 在这一点上它与外观不同。 59 | 60 | ## 代码实现 61 | 62 | ```typescript 63 | class Facade { 64 | protected subsystem1: Subsystem1; 65 | 66 | protected subsystem2: Subsystem2; 67 | 68 | constructor(subsystem1?: Subsystem1, subsystem2?: Subsystem2) { 69 | this.subsystem1 = subsystem1 || new Subsystem1(); 70 | this.subsystem2 = subsystem2 || new Subsystem2(); 71 | } 72 | 73 | public operation(): string[] { 74 | let result: string[] = []; 75 | result.push(this.subsystem1.operation1()); 76 | result.push(this.subsystem2.operation1()); 77 | result.push(this.subsystem1.operationN()); 78 | result.push(this.subsystem2.operationZ()); 79 | 80 | return result; 81 | } 82 | } 83 | 84 | class Subsystem1 { 85 | public operation1(): string { 86 | return 'Subsystem1: Ready!'; 87 | } 88 | 89 | public operationN(): string { 90 | return 'Subsystem1: Go!'; 91 | } 92 | } 93 | 94 | class Subsystem2 { 95 | public operation1(): string { 96 | return 'Subsystem2: Get ready!'; 97 | } 98 | 99 | public operationZ(): string { 100 | return 'Subsystem2: Fire!'; 101 | } 102 | } 103 | 104 | export { Facade, Subsystem1, Subsystem2 }; 105 | ``` 106 | 107 | ## 测试用例 108 | 109 | ```typescript 110 | import { Subsystem1, Subsystem2, Facade } from '../index'; 111 | 112 | describe('facade pattern', () => { 113 | it('facade concrete', () => { 114 | const subsystem1 = new Subsystem1(); 115 | const subsystem2 = new Subsystem2(); 116 | const facade = new Facade(subsystem1, subsystem2); 117 | 118 | expect(facade.operation()).toStrictEqual([ 119 | 'Subsystem1: Ready!', 120 | 'Subsystem2: Get ready!', 121 | 'Subsystem1: Go!', 122 | 'Subsystem2: Fire!', 123 | ]); 124 | }); 125 | }); 126 | ``` 127 | -------------------------------------------------------------------------------- /docs/structural/facade_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/structural/facade_structure.png -------------------------------------------------------------------------------- /docs/structural/flyweight.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 享元模式 3 | categories: 4 | - 设计模式 5 | - 结构型模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 享元模式 14 | 15 | :::tip 16 | 17 | **享元模式**是一种结构型设计模式, 它摒弃了在每个对象中保存所有数据的方式, 通过共享多个对象所共有的相同状态, 让你能在有限的内存容量中载入更多对象。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ![flyweight_structure](./flyweight_structure.png) 23 | 24 | 1. 享元模式只是一种优化。 在应用该模式之前, 你要确定程序中存在与大量类似对象同时占用内存相关的内存消耗问题, 并且确保该问题无法使用其他更好的方式来解决。 25 | 2. 享元 (`Flyweight`) 类包含原始对象中部分能在多个对象中共享的状态。 同一享元对象可在许多不同情景中使用。 享元中存储的状态被称为 “内在状态”。 传递给享元方法的状态被称为 “外在状态”。 26 | 3. 情景 (`Context`) 类包含原始对象中各不相同的外在状态。 情景与享元对象组合在一起就能表示原始对象的全部状态。 27 | 4. 通常情况下, 原始对象的行为会保留在享元类中。 因此调用享元方法必须提供部分外在状态作为参数。 但你也可将行为移动到情景类中, 然后将连入的享元作为单纯的数据对象。 28 | 5. 客户端 (`Client`) 负责计算或存储享元的外在状态。 在客户端看来, 享元是一种可在运行时进行配置的模板对象, 具体的配置方式为向其方法中传入一些情景数据参数。 29 | 6. 享元工厂 (`Flyweight Factory`) 会对已有享元的缓存池进行管理。 有了工厂后, 客户端就无需直接创建享元, 它们只需调用工厂并向其传递目标享元的一些内在状态即可。 工厂会根据参数在之前已创建的享元中进行查找, 如果找到满足条件的享元就将其返回; 如果没有找到就根据参数新建享元。 30 | 31 | ## 适用场景 32 | 33 | 1. 仅在程序必须支持大量对象且没有足够的内存容量时使用享元模式。 34 | 35 | 1. 应用该模式所获的收益大小取决于使用它的方式和情景。 它在下列情况中最有效: 36 | 1. 程序需要生成数量巨大的相似对象 37 | 2. 这将耗尽目标设备的所有内存 38 | 3. 对象中包含可抽取且能在多个对象间共享的重复状态。 39 | 40 | ## 优缺点 41 | 42 | ### 优点 43 | 44 | 1. 如果程序中有很多相似对象, 那么你将可以节省大量内存。 45 | 46 | ### 缺点 47 | 48 | 1. 你可能需要牺牲执行速度来换取内存, 因为他人每次调用享元方法时都需要重新计算部分情景数据。 49 | 2. 代码会变得更加复杂。 团队中的新成员总是会问: ​ “为什么要像这样拆分一个实体的状态?”。 50 | 51 | ## 与其他模式的关系 52 | 53 | 1. 你可以使用享元模式实现组合模式树的共享叶节点以节省内存。 54 | 2. 享元展示了如何生成大量的小型对象, 外观模式则展示了如何用一个对象来代表整个子系统。 55 | 3. 如果你能将对象的所有共享状态简化为一个享元对象, 那么享元就和单例模式类似了。 但这两个模式有两个根本性的不同。 56 | 1. 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。 57 | 2. 单例对象可以是可变的。 享元对象是不可变的。 58 | 59 | ## 代码实现 60 | 61 | ```typescript 62 | class Flyweight { 63 | private sharedState: any; 64 | 65 | constructor(sharedState: any) { 66 | this.sharedState = sharedState; 67 | } 68 | 69 | public operation(uniqueState): { shared: Array; unique: Array } { 70 | const s = this.sharedState; 71 | const u = uniqueState; 72 | return { 73 | shared: s, 74 | unique: u, 75 | }; 76 | } 77 | } 78 | 79 | class FlyweightFactory { 80 | private flyweights: { [key: string]: Flyweight } = {}; 81 | 82 | constructor(initialFlyweights: string[][]) { 83 | for (const state of initialFlyweights) { 84 | this.flyweights[this.getKey(state)] = new Flyweight(state); 85 | } 86 | } 87 | 88 | private getKey(state: string[]): string { 89 | return state.join('_'); 90 | } 91 | 92 | public getFlyweight(sharedState: string[]): Flyweight { 93 | const key = this.getKey(sharedState); 94 | 95 | if (!(key in this.flyweights)) { 96 | console.log("FlyweightFactory: Can't find a flyweight, creating new one."); 97 | this.flyweights[key] = new Flyweight(sharedState); 98 | } else { 99 | console.log('FlyweightFactory: Reusing existing flyweight.'); 100 | } 101 | 102 | return this.flyweights[key]; 103 | } 104 | 105 | public listFlyweights(): void { 106 | const count = Object.keys(this.flyweights).length; 107 | console.log(`\nFlyweightFactory: I have ${count} flyweights:`); 108 | for (const key in this.flyweights) { 109 | console.log(key); 110 | } 111 | } 112 | } 113 | 114 | const factory = new FlyweightFactory([ 115 | ['chenghuai', 'Camaro2018', 'pink'], 116 | ['huaicheng', 'C300', 'black'], 117 | ['xianzao', 'C500', 'red'], 118 | ['BMW', 'M5', 'red'], 119 | ['BMW', 'X6', 'white'], 120 | ]); 121 | 122 | factory.listFlyweights(); 123 | 124 | export { Flyweight, FlyweightFactory, factory }; 125 | ``` 126 | 127 | ## 测试用例 128 | 129 | ```typescript 130 | import { factory } from '../index'; 131 | 132 | describe('flyweight pattern', () => { 133 | it('flyweight concrete', () => { 134 | const flyweight1 = factory.getFlyweight(['BMW', 'M5', 'red']); 135 | const flyweight2 = factory.getFlyweight(['BMW', 'X1', 'red']); 136 | 137 | expect(flyweight1.operation(['CL234IR', 'James'])).toStrictEqual({ 138 | shared: ['BMW', 'M5', 'red'], 139 | unique: ['CL234IR', 'James'], 140 | }); 141 | 142 | expect(flyweight2.operation(['CL234IR', 'James'])).toStrictEqual({ 143 | shared: ['BMW', 'X1', 'red'], 144 | unique: ['CL234IR', 'James'], 145 | }); 146 | }); 147 | }); 148 | ``` 149 | -------------------------------------------------------------------------------- /docs/structural/flyweight_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/structural/flyweight_structure.png -------------------------------------------------------------------------------- /docs/structural/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 结构型模式 3 | categories: 4 | - 设计模式 5 | tags: 6 | - 设计模式 7 | author: 8 | name: 澄怀 9 | link: https://github.com/encode-studio-fe/design-pattern 10 | --- 11 | 12 | # 结构型模式 13 | 14 | :::tip 15 | 结构型模式介绍如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效。 16 | ::: 17 | 18 | [**适配器模式**](./adapter.md): 是一种结构型设计模式, 它能使接口不兼容的对象能够相互合作。 19 | 20 | [**桥接模式**](./bridge.md): 可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构, 从而能在开发时分别使用。 21 | 22 | [**组合模式**](./composite.md): 将对象组合成树状结构, 并且能像使用独立对象一样使用它们。 23 | 24 | [**装饰模式**](./decorator.md): 将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。 25 | 26 | [**外观模式**](./facade.md): 为程序库、 框架或其他复杂类提供一个简单的接口。 27 | 28 | [**享元模式**](./flyweight.md): 摒弃了在每个对象中保存所有数据的方式, 通过共享多个对象所共有的相同状态, 让你能在有限的内存容量中载入更多对象。 29 | 30 | [**代理模式**](./proxy.md): 提供对象的替代品或其占位符。 代理控制着对于原对象的访问, 并允许在将请求提交给对象前后进行一些处理。 31 | -------------------------------------------------------------------------------- /docs/structural/proxy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 代理模式 3 | categories: 4 | - 设计模式 5 | - 结构型模式 6 | tags: 7 | - 设计模式 8 | author: 9 | name: 澄怀 10 | link: https://github.com/encode-studio-fe/design-pattern 11 | --- 12 | 13 | # 代理模式 14 | 15 | :::tip 16 | 17 | **代理模式**是一种结构型设计模式, 让你能够提供对象的替代品或其占位符。 代理控制着对于原对象的访问, 并允许在将请求提交给对象前后进行一些处理。 18 | ::: 19 | 20 | ## 结构 21 | 22 | ![proxy_structure](./proxy_structure.png) 23 | 24 | 1. 服务接口 (`Service Interface`) 声明了服务接口。 代理必须遵循该接口才能伪装成服务对象。 25 | 2. 服务 (`Service`) 类提供了一些实用的业务逻辑。 26 | 3. 代理 (`Proxy`) 类包含一个指向服务对象的引用成员变量。 代理完成其任务 (例如延迟初始化、 记录日志、 访问控制和缓存等) 后会将请求传递给服务对象。 27 | 4. 客户端 (`Client`) 能通过同一接口与服务或代理进行交互, 所以你可在一切需要服务对象的代码中使用代理。 28 | 29 | ## 适用场景 30 | 31 | 1. 延迟初始化 (虚拟代理)。 如果你有一个偶尔使用的重量级服务对象, 一直保持该对象运行会消耗系统资源时, 可使用代理模式。 32 | 33 | 1. 你无需在程序启动时就创建该对象, 可将对象的初始化延迟到真正有需要的时候。 34 | 35 | 2. 访问控制 (保护代理)。 如果你只希望特定客户端使用服务对象, 这里的对象可以是操作系统中非常重要的部分, 而客户端则是各种已启动的程序 (包括恶意程序), 此时可使用代理模式。 36 | 37 | 1. 代理可仅在客户端凭据满足要求时将请求传递给服务对象。 38 | 39 | 3. 本地执行远程服务 (远程代理)。 适用于服务对象位于远程服务器上的情形。 40 | 41 | 4. 在这种情形中, 代理通过网络传递客户端请求, 负责处理所有与网络相关的复杂细节。 42 | 43 | 5. 记录日志请求 (日志记录代理)。 适用于当你需要保存对于服务对象的请求历史记录时。 44 | 45 | 1. 代理可以在向服务传递请求前进行记录。 46 | 47 | 6. 缓存请求结果 (缓存代理)。 适用于需要缓存客户请求结果并对缓存生命周期进行管理时, 特别是当返回结果的体积非常大时。 48 | 49 | 1. 代理可对重复请求所需的相同结果进行缓存, 还可使用请求参数作为索引缓存的键值。 50 | 51 | 7. 智能引用。 可在没有客户端使用某个重量级对象时立即销毁该对象。 52 | 53 | 1. 代理会将所有获取了指向服务对象或其结果的客户端记录在案。 代理会时不时地遍历各个客户端, 检查它们是否仍在运行。 如果相应的客户端列表为空, 代理就会销毁该服务对象, 释放底层系统资源。 54 | 55 | ## 优缺点 56 | 57 | ### 优点 58 | 59 | 1. 你可以在客户端毫无察觉的情况下控制服务对象。 60 | 2. 如果客户端对服务对象的生命周期没有特殊要求, 你可以对生命周期进行管理。 61 | 3. 即使服务对象还未准备好或不存在, 代理也可以正常工作。 62 | 4. 开闭原则。 你可以在不对服务或客户端做出修改的情况下创建新代理。 63 | 64 | ### 缺点 65 | 66 | 1.代码可能会变得复杂, 因为需要新建许多类。 2. 服务响应可能会延迟。 67 | 68 | ## 与其他模式的关系 69 | 70 | 1. 适配器模式能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰模式则能为对象提供加强的接口。 71 | 2. 外观模式与代理的相似之处在于它们都缓存了一个复杂实体并自行对其进行初始化。 代理与其服务对象遵循同一接口, 使得自己和服务对象可以互换, 在这一点上它与外观不同。 72 | 3. 装饰和代理有着相似的结构, 但是其意图却非常不同。 这两个模式的构建都基于组合原则, 也就是说一个对象应该将部分工作委派给另一个对象。 两者之间的不同之处在于代理通常自行管理其服务对象的生命周期, 而装饰的生成则总是由客户端进行控制。 73 | 74 | ## 代码实现 75 | 76 | ```typescript 77 | interface Subject { 78 | request(): void; 79 | } 80 | 81 | class RealSubject implements Subject { 82 | public request(): string { 83 | return 'RealSubject: Handling request.'; 84 | } 85 | } 86 | 87 | class ProxyPattern implements Subject { 88 | private realSubject: RealSubject; 89 | 90 | constructor(realSubject: RealSubject) { 91 | this.realSubject = realSubject; 92 | } 93 | 94 | public request() { 95 | if (this.checkAccess()) { 96 | this.logAccess(); 97 | 98 | return this.realSubject.request(); 99 | } 100 | } 101 | 102 | private checkAccess(): boolean { 103 | console.log('Proxy: Checking access prior to firing a real request.'); 104 | 105 | return true; 106 | } 107 | 108 | private logAccess(): void { 109 | console.log('Proxy: Logging the time of request.'); 110 | } 111 | } 112 | 113 | export { Subject, RealSubject, ProxyPattern }; 114 | ``` 115 | 116 | ## 测试用例 117 | 118 | ```typescript 119 | import { RealSubject, ProxyPattern } from '../index'; 120 | 121 | describe('proxy pattern', () => { 122 | it('proxy concrete', () => { 123 | const realSubject = new RealSubject(); 124 | const proxy = new ProxyPattern(realSubject); 125 | 126 | expect(realSubject.request()).toBe('RealSubject: Handling request.'); 127 | expect(proxy.request()).toBe('RealSubject: Handling request.'); 128 | }); 129 | }); 130 | ``` 131 | -------------------------------------------------------------------------------- /docs/structural/proxy_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/design-pattern/057e1527c009fc0916c9d39dff4b3839d8c34cd0/docs/structural/proxy_structure.png -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import { Config } from '@jest/types'; 2 | 3 | export default { 4 | preset: 'ts-jest', 5 | clearMocks: true, 6 | moduleFileExtensions: ['ts', 'js', 'json'], 7 | modulePathIgnorePatterns: ['/package.json'], 8 | transform: { 9 | '^.+\\.ts?$': ['ts-jest', { tsconfig: 'tsconfig.test.json' }], 10 | }, 11 | } as Config.InitialOptions; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "encode-design-pattern", 3 | "version": "0.0.2", 4 | "description": "印客学院--设计模式深入浅出", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "type": "module", 8 | "scripts": { 9 | "preinstall": "npx only-allow pnpm", 10 | "init": "rm -rf node_modules && pnpm install", 11 | "dev": "tsup src/index.ts --format esm,cjs --watch --dts", 12 | "build": "rm -rf dist && tsup src/index.ts --format esm,cjs --dts", 13 | "docs:type": "typedoc", 14 | "docs:dev": "vuepress dev docs", 15 | "docs:build": "vuepress build docs", 16 | "deploy": "bash deploy.sh", 17 | "test": "jest" 18 | }, 19 | "files": [ 20 | "dist", 21 | "package.json", 22 | "README.md" 23 | ], 24 | "keywords": [ 25 | "encode", 26 | "design patterns", 27 | "ddd" 28 | ], 29 | "author": "chenghuai", 30 | "license": "ISC", 31 | "devDependencies": { 32 | "@jest/types": "^29.5.0", 33 | "@types/jest": "^29.0.0", 34 | "dayjs": "^1.9.7", 35 | "jest": "^29.0.0", 36 | "ts-jest": "^29.1.0", 37 | "ts-node": "^10.9.1", 38 | "tslib": "^2.5.2", 39 | "tsup": "^6.7.0", 40 | "typedoc": "^0.24.7", 41 | "typescript": "^5.0.4", 42 | "vue-template-compiler": "^2.7.14", 43 | "vuepress": "^1.9.9", 44 | "vuepress-plugin-one-click-copy": "^1.0.2", 45 | "vuepress-plugin-zooming": "^1.1.7" 46 | }, 47 | "publishConfig": { 48 | "access": "public", 49 | "registry": "https://registry.npmjs.org/" 50 | } 51 | } -------------------------------------------------------------------------------- /src/behavioral/command/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { Invoker, Receiver, SimpleCommand, ComplexCommand } from '../index'; 2 | 3 | describe('command pattern', () => { 4 | it('command concrete creator', () => { 5 | const invoker = new Invoker(); 6 | const receiver = new Receiver(); 7 | 8 | invoker.setOnStart(new SimpleCommand('Say Hi!')); 9 | invoker.setOnFinish(new ComplexCommand(receiver, 'Send email', 'Save report')); 10 | 11 | expect(invoker.doSomethingImportant()).toBe('SimpleCommand: Say Hi!'); 12 | // expect(invoker.doSomethingImportant()).toBe('Send email Save report'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/behavioral/command/index.ts: -------------------------------------------------------------------------------- 1 | interface Command { 2 | execute(): string; 3 | } 4 | 5 | class SimpleCommand implements Command { 6 | private payload: string; 7 | 8 | constructor(payload: string) { 9 | this.payload = payload; 10 | } 11 | 12 | public execute(): string { 13 | return `SimpleCommand: ${this.payload}`; 14 | } 15 | } 16 | 17 | class ComplexCommand implements Command { 18 | private receiver: Receiver; 19 | 20 | private a: string; 21 | 22 | private b: string; 23 | 24 | constructor(receiver: Receiver, a: string, b: string) { 25 | this.receiver = receiver; 26 | this.a = a; 27 | this.b = b; 28 | } 29 | 30 | public execute(): string { 31 | return this.receiver.doSomething(this.a) + ' ' + this.receiver.doSomethingElse(this.b); 32 | } 33 | } 34 | 35 | class Receiver { 36 | public doSomething(a: string): string { 37 | return a; 38 | } 39 | 40 | public doSomethingElse(b: string): string { 41 | return b; 42 | } 43 | } 44 | 45 | class Invoker { 46 | private onStart: Command; 47 | 48 | private onFinish: Command; 49 | 50 | public setOnStart(command: Command): void { 51 | this.onStart = command; 52 | } 53 | 54 | public setOnFinish(command: Command): void { 55 | this.onFinish = command; 56 | } 57 | 58 | public doSomethingImportant() { 59 | if (this.isCommand(this.onStart)) { 60 | return this.onStart.execute(); 61 | } 62 | 63 | // if (this.isCommand(this.onFinish)) { 64 | // return this.onFinish.execute(); 65 | // } 66 | } 67 | 68 | private isCommand(object): object is Command { 69 | return object.execute !== undefined; 70 | } 71 | } 72 | 73 | export { Command, SimpleCommand, ComplexCommand, Receiver, Invoker }; 74 | -------------------------------------------------------------------------------- /src/behavioral/index.ts: -------------------------------------------------------------------------------- 1 | export * from './responsibility/index'; 2 | export * from './command/index'; 3 | export * from './iterator/index'; 4 | export * from './mediator/index'; 5 | export * from './memento/index'; 6 | export * from './observer/index'; 7 | export * from './state/index'; 8 | export * from './strategy/index'; 9 | export * from './template/index'; 10 | export * from './visitor/index'; 11 | -------------------------------------------------------------------------------- /src/behavioral/iterator/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { WordsCollection } from '../index'; 2 | 3 | describe('iterator pattern', () => { 4 | it('iterator concrete creator', () => { 5 | let str = ''; 6 | 7 | const collection = new WordsCollection(); 8 | collection.addItem('First'); 9 | collection.addItem('Second'); 10 | collection.addItem('Third'); 11 | 12 | const iterator = collection.getIterator(); 13 | 14 | while (iterator.valid()) { 15 | str += `${iterator.next()} `; 16 | } 17 | 18 | const reverseIterator = collection.getReverseIterator(); 19 | while (reverseIterator.valid()) { 20 | str += `${reverseIterator.next()} `; 21 | } 22 | expect(str).toBe('First Second Third Third Second First '); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/behavioral/iterator/index.ts: -------------------------------------------------------------------------------- 1 | interface Iterator { 2 | current(): T; 3 | next(): T; 4 | key(): number; 5 | valid(): boolean; 6 | rewind(): void; 7 | } 8 | 9 | interface Aggregator { 10 | getIterator(): Iterator; 11 | } 12 | 13 | class AlphabeticalOrderIterator implements Iterator { 14 | private collection: WordsCollection; 15 | private position: number = 0; 16 | 17 | private reverse: boolean = false; 18 | 19 | constructor(collection: WordsCollection, reverse: boolean = false) { 20 | this.collection = collection; 21 | this.reverse = reverse; 22 | 23 | if (reverse) { 24 | this.position = collection.getCount() - 1; 25 | } 26 | } 27 | 28 | public rewind() { 29 | this.position = this.reverse ? this.collection.getCount() - 1 : 0; 30 | } 31 | 32 | public current(): string { 33 | return this.collection.getItems()[this.position]; 34 | } 35 | 36 | public key(): number { 37 | return this.position; 38 | } 39 | 40 | public next() { 41 | const item = this.collection.getItems()[this.position]; 42 | this.position += this.reverse ? -1 : 1; 43 | return item; 44 | } 45 | 46 | public valid(): boolean { 47 | if (this.reverse) { 48 | return this.position >= 0; 49 | } 50 | 51 | return this.position < this.collection.getCount(); 52 | } 53 | } 54 | 55 | class WordsCollection implements Aggregator { 56 | private items: string[] = []; 57 | 58 | public getItems(): string[] { 59 | return this.items; 60 | } 61 | 62 | public getCount(): number { 63 | return this.items.length; 64 | } 65 | 66 | public addItem(item: string): void { 67 | this.items.push(item); 68 | } 69 | 70 | public getIterator(): Iterator { 71 | return new AlphabeticalOrderIterator(this); 72 | } 73 | 74 | public getReverseIterator(): Iterator { 75 | return new AlphabeticalOrderIterator(this, true); 76 | } 77 | } 78 | 79 | export { Iterator, Aggregator, AlphabeticalOrderIterator, WordsCollection }; 80 | -------------------------------------------------------------------------------- /src/behavioral/mediator/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { Component1, Component2, ConcreteMediator } from '../index'; 2 | 3 | describe('mediator pattern', () => { 4 | it('mediator concrete creator', () => { 5 | const c1 = new Component1(); 6 | const c2 = new Component2(); 7 | const mediator = new ConcreteMediator(c1, c2); 8 | 9 | expect(c1.doA()).toBe('component 1 does A '); 10 | expect(c2.doD()).toBe('component 2 does D '); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/behavioral/mediator/index.ts: -------------------------------------------------------------------------------- 1 | interface Mediator { 2 | notify(sender: object, event: string): void; 3 | } 4 | 5 | class ConcreteMediator implements Mediator { 6 | private component1: Component1; 7 | 8 | private component2: Component2; 9 | 10 | constructor(c1: Component1, c2: Component2) { 11 | this.component1 = c1; 12 | this.component1.setMediator(this); 13 | this.component2 = c2; 14 | this.component2.setMediator(this); 15 | } 16 | 17 | public notify(sender: object, event: string): void { 18 | if (event === 'A') { 19 | this.component2.doC(); 20 | } 21 | 22 | if (event === 'D') { 23 | this.component1.doB(); 24 | this.component2.doC(); 25 | } 26 | } 27 | } 28 | 29 | class BaseComponent { 30 | protected mediator: Mediator; 31 | 32 | constructor(mediator?: Mediator) { 33 | this.mediator = mediator!; 34 | } 35 | 36 | public setMediator(mediator: Mediator): void { 37 | this.mediator = mediator; 38 | } 39 | } 40 | 41 | class Component1 extends BaseComponent { 42 | public doA(): string { 43 | this.mediator.notify(this, 'A'); 44 | return 'component 1 does A '; 45 | } 46 | 47 | public doB(): string { 48 | this.mediator.notify(this, 'B'); 49 | return 'component 1 does B '; 50 | } 51 | } 52 | 53 | class Component2 extends BaseComponent { 54 | public doC(): string { 55 | this.mediator.notify(this, 'C'); 56 | return 'component 2 does C '; 57 | } 58 | 59 | public doD(): string { 60 | this.mediator.notify(this, 'D'); 61 | return 'component 2 does D '; 62 | } 63 | } 64 | 65 | export { Mediator, ConcreteMediator, BaseComponent, Component1, Component2 }; 66 | -------------------------------------------------------------------------------- /src/behavioral/memento/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { Originator, Caretaker } from '../index'; 2 | 3 | describe('memento pattern', () => { 4 | it('memento concrete creator', () => { 5 | const originator = new Originator('Super-duper-super-puper-super.'); 6 | const caretaker = new Caretaker(originator); 7 | 8 | caretaker.backup(); 9 | const backUp1 = originator.doSomething(); 10 | 11 | caretaker.backup(); 12 | const backUp2 = originator.doSomething(); 13 | 14 | caretaker.backup(); 15 | 16 | caretaker.showHistory(); 17 | 18 | expect(caretaker.undo()).toBe(backUp2); 19 | 20 | expect(caretaker.undo()).toBe(backUp1); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/behavioral/memento/index.ts: -------------------------------------------------------------------------------- 1 | class Originator { 2 | private state: string; 3 | 4 | constructor(state: string) { 5 | this.state = state; 6 | } 7 | 8 | public doSomething(): string { 9 | this.state = this.generateRandomString(30); 10 | return this.state; 11 | } 12 | 13 | private generateRandomString(length: number = 10): string { 14 | const charSet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 15 | 16 | return Array.apply(null, { length }) 17 | .map(() => charSet.charAt(Math.floor(Math.random() * charSet.length))) 18 | .join(''); 19 | } 20 | 21 | public save(): Memento { 22 | return new ConcreteMemento(this.state); 23 | } 24 | 25 | public restore(memento: Memento): string { 26 | this.state = memento.getState(); 27 | return this.state; 28 | } 29 | } 30 | 31 | interface Memento { 32 | getState(): string; 33 | 34 | getName(): string; 35 | 36 | getDate(): string; 37 | } 38 | 39 | class ConcreteMemento implements Memento { 40 | private state: string; 41 | 42 | private date: string; 43 | 44 | constructor(state: string) { 45 | this.state = state; 46 | this.date = new Date().toISOString().slice(0, 19).replace('T', ' '); 47 | } 48 | 49 | public getState(): string { 50 | return this.state; 51 | } 52 | 53 | public getName(): string { 54 | return `${this.date} / (${this.state.substr(0, 9)}...)`; 55 | } 56 | 57 | public getDate(): string { 58 | return this.date; 59 | } 60 | } 61 | 62 | class Caretaker { 63 | private mementos: Memento[] = []; 64 | 65 | private originator: Originator; 66 | 67 | constructor(originator: Originator) { 68 | this.originator = originator; 69 | } 70 | 71 | public backup(): void { 72 | this.mementos.push(this.originator.save()); 73 | } 74 | 75 | public undo(): string { 76 | if (!this.mementos.length) { 77 | return ''; 78 | } 79 | const memento = this.mementos.pop(); 80 | 81 | return this.originator.restore(memento!); 82 | } 83 | 84 | public showHistory(): void { 85 | for (const memento of this.mementos) { 86 | console.log(memento.getName()); 87 | } 88 | } 89 | } 90 | 91 | export { Originator, Memento, ConcreteMemento, Caretaker }; 92 | -------------------------------------------------------------------------------- /src/behavioral/observer/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { ConcreteSubject, ConcreteObserverA, ConcreteObserverB } from '../index'; 2 | 3 | describe('observer pattern', () => { 4 | it('observer concrete creator', () => { 5 | const subject = new ConcreteSubject(); 6 | 7 | const observer1 = new ConcreteObserverA(); 8 | subject.attach(observer1); 9 | 10 | const observer2 = new ConcreteObserverB(); 11 | subject.attach(observer2); 12 | 13 | expect(subject.someBusinessLogic()).toStrictEqual([ 14 | 'ConcreteObserverA: Reacted to the event.', 15 | 'ConcreteObserverB: Reacted to the event.', 16 | ]); 17 | expect(subject.someBusinessLogic()).toStrictEqual([ 18 | 'ConcreteObserverA: Reacted to the event.', 19 | 'ConcreteObserverB: Reacted to the event.', 20 | ]); 21 | 22 | subject.detach(observer2); 23 | 24 | expect(subject.someBusinessLogic()).toStrictEqual([ 25 | 'ConcreteObserverA: Reacted to the event.', 26 | ]); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/behavioral/observer/index.ts: -------------------------------------------------------------------------------- 1 | interface ObserverSubject { 2 | attach(observer: Observer): void; 3 | detach(observer: Observer): void; 4 | notify(): void; 5 | } 6 | 7 | class ConcreteSubject implements ObserverSubject { 8 | public state: number; 9 | 10 | private observers: Observer[] = []; 11 | 12 | public attach(observer: Observer): string { 13 | const isExist = this.observers.includes(observer); 14 | if (isExist) { 15 | return 'Subject: Observer has been attached already.'; 16 | } 17 | 18 | this.observers.push(observer); 19 | 20 | return ''; 21 | } 22 | 23 | public detach(observer: Observer): string { 24 | const observerIndex = this.observers.indexOf(observer); 25 | if (observerIndex === -1) { 26 | return 'Subject: Nonexistent observer.'; 27 | } 28 | 29 | this.observers.splice(observerIndex, 1); 30 | return ''; 31 | } 32 | 33 | public notify(): string[] { 34 | let result = [] as string[]; 35 | for (const observer of this.observers) { 36 | result.push(observer.update(this)); 37 | } 38 | return result; 39 | } 40 | 41 | public someBusinessLogic(): string[] { 42 | return this.notify(); 43 | } 44 | } 45 | 46 | interface Observer { 47 | update(subject: ObserverSubject): string; 48 | } 49 | 50 | class ConcreteObserverA implements Observer { 51 | public update(subject: ObserverSubject): string { 52 | return 'ConcreteObserverA: Reacted to the event.'; 53 | } 54 | } 55 | 56 | class ConcreteObserverB implements Observer { 57 | public update(subject: ObserverSubject): string { 58 | return 'ConcreteObserverB: Reacted to the event.'; 59 | } 60 | } 61 | 62 | export { ObserverSubject, ConcreteSubject, Observer, ConcreteObserverA, ConcreteObserverB }; 63 | -------------------------------------------------------------------------------- /src/behavioral/responsibility/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { MonkeyHandler, SquirrelHandler, DogHandler } from '../index'; 2 | 3 | describe('responsibility pattern', () => { 4 | it('responsibility concrete creator', () => { 5 | const list = [] as string[]; 6 | const monkey = new MonkeyHandler(); 7 | const squirrel = new SquirrelHandler(); 8 | const dog = new DogHandler(); 9 | 10 | monkey.setNext(squirrel).setNext(dog); 11 | 12 | const foods = ['Nut', 'Banana', 'Cup of coffee']; 13 | 14 | for (const food of foods) { 15 | const result = monkey.handle(food); 16 | if (result) { 17 | list.push(result); 18 | } 19 | } 20 | expect(list).toStrictEqual(['Nut', 'Banana']); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/behavioral/responsibility/index.ts: -------------------------------------------------------------------------------- 1 | interface Handler { 2 | setNext(handler: Handler): Handler; 3 | 4 | handle(request: string): string; 5 | } 6 | 7 | abstract class AbstractHandler implements Handler { 8 | private nextHandler: Handler; 9 | 10 | public setNext(handler: Handler): Handler { 11 | this.nextHandler = handler; 12 | 13 | return handler; 14 | } 15 | 16 | public handle(request: string): string { 17 | if (this.nextHandler) { 18 | return this.nextHandler.handle(request); 19 | } 20 | 21 | return ''; 22 | } 23 | } 24 | 25 | class MonkeyHandler extends AbstractHandler { 26 | public handle(request: string): string { 27 | if (request === 'Banana') { 28 | return request 29 | } 30 | return super.handle(request); 31 | } 32 | } 33 | 34 | class SquirrelHandler extends AbstractHandler { 35 | public handle(request: string): string { 36 | if (request === 'Nut') { 37 | return request; 38 | } 39 | return super.handle(request); 40 | } 41 | } 42 | 43 | class DogHandler extends AbstractHandler { 44 | public handle(request: string): string { 45 | if (request === 'MeatBall') { 46 | return request; 47 | } 48 | return super.handle(request); 49 | } 50 | } 51 | 52 | export { Handler, AbstractHandler, MonkeyHandler, SquirrelHandler, DogHandler }; 53 | -------------------------------------------------------------------------------- /src/behavioral/state/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { Context, ConcreteStateA } from '../index'; 2 | 3 | describe('state pattern', () => { 4 | it('state concrete creator', () => { 5 | const context = new Context(new ConcreteStateA()); 6 | context.request1(); 7 | expect(context.request2()).toBe('ConcreteStateB handles request2.'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /src/behavioral/state/index.ts: -------------------------------------------------------------------------------- 1 | class Context { 2 | private state: State; 3 | 4 | constructor(state: State) { 5 | this.transitionTo(state); 6 | } 7 | 8 | public transitionTo(state: State): void { 9 | this.state = state; 10 | this.state.setContext(this); 11 | } 12 | 13 | public request1(): void { 14 | this.state.handle1(); 15 | } 16 | 17 | public request2(): string { 18 | return this.state.handle2(); 19 | } 20 | } 21 | 22 | abstract class State { 23 | protected context: Context; 24 | 25 | public setContext(context: Context) { 26 | this.context = context; 27 | } 28 | 29 | public abstract handle1(): void; 30 | 31 | public abstract handle2(): string; 32 | } 33 | 34 | class ConcreteStateA extends State { 35 | public handle1(): void { 36 | this.context.transitionTo(new ConcreteStateB()); 37 | } 38 | 39 | public handle2(): string { 40 | return 'ConcreteStateA handles request2.'; 41 | } 42 | } 43 | 44 | class ConcreteStateB extends State { 45 | public handle1(): void { 46 | console.log('ConcreteStateB handles request1.'); 47 | } 48 | 49 | public handle2(): string { 50 | this.context.transitionTo(new ConcreteStateA()); 51 | return 'ConcreteStateB handles request2.'; 52 | } 53 | } 54 | export { Context, State, ConcreteStateA, ConcreteStateB }; 55 | -------------------------------------------------------------------------------- /src/behavioral/strategy/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { StrategyContext, ConcreteStrategyA, ConcreteStrategyB } from '../index'; 2 | 3 | describe('strategy pattern', () => { 4 | it('strategy concrete creator', () => { 5 | const context = new StrategyContext(new ConcreteStrategyA()); 6 | expect(context.doSomeBusinessLogic()).toBe('a,b,c,d,e'); 7 | 8 | context.setStrategy(new ConcreteStrategyB()); 9 | expect(context.doSomeBusinessLogic()).toBe('e,d,c,b,a'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/behavioral/strategy/index.ts: -------------------------------------------------------------------------------- 1 | class StrategyContext { 2 | private strategy: Strategy; 3 | 4 | constructor(strategy: Strategy) { 5 | this.strategy = strategy; 6 | } 7 | 8 | public setStrategy(strategy: Strategy) { 9 | this.strategy = strategy; 10 | } 11 | 12 | public doSomeBusinessLogic(): string { 13 | const result = this.strategy.doAlgorithm(['a', 'b', 'c', 'd', 'e']); 14 | return result.join(','); 15 | } 16 | } 17 | 18 | interface Strategy { 19 | doAlgorithm(data: string[]): string[]; 20 | } 21 | 22 | class ConcreteStrategyA implements Strategy { 23 | public doAlgorithm(data: string[]): string[] { 24 | return data.sort(); 25 | } 26 | } 27 | 28 | class ConcreteStrategyB implements Strategy { 29 | public doAlgorithm(data: string[]): string[] { 30 | return data.reverse(); 31 | } 32 | } 33 | 34 | export { StrategyContext, Strategy, ConcreteStrategyA, ConcreteStrategyB }; 35 | -------------------------------------------------------------------------------- /src/behavioral/template/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { ConcreteClass1 } from '../index'; 2 | 3 | describe('template pattern', () => { 4 | it('template concrete creator', () => { 5 | const concrete1 = new ConcreteClass1(); 6 | expect(concrete1.templateMethod()).toStrictEqual([ 7 | 'baseOperation1', 8 | 'baseOperation2', 9 | 'baseOperation3', 10 | ]); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/behavioral/template/index.ts: -------------------------------------------------------------------------------- 1 | abstract class AbstractClass { 2 | public templateMethod(): string[] { 3 | this.baseOperation1(); 4 | this.requiredOperations1(); 5 | this.baseOperation2(); 6 | this.hook1(); 7 | this.requiredOperation2(); 8 | this.baseOperation3(); 9 | this.hook2(); 10 | 11 | return [this.baseOperation1(), this.baseOperation2(), this.baseOperation3()]; 12 | } 13 | 14 | protected baseOperation1(): string { 15 | return 'baseOperation1'; 16 | } 17 | 18 | protected baseOperation2(): string { 19 | return 'baseOperation2'; 20 | } 21 | 22 | protected baseOperation3(): string { 23 | return 'baseOperation3'; 24 | } 25 | 26 | protected abstract requiredOperations1(): void; 27 | 28 | protected abstract requiredOperation2(): void; 29 | 30 | protected hook1(): void {} 31 | 32 | protected hook2(): void {} 33 | } 34 | 35 | class ConcreteClass1 extends AbstractClass { 36 | protected requiredOperations1(): string { 37 | return 'ConcreteClass1: Operation1'; 38 | } 39 | 40 | protected requiredOperation2(): string { 41 | return 'ConcreteClass1: Operation2'; 42 | } 43 | } 44 | 45 | class ConcreteClass2 extends AbstractClass { 46 | protected requiredOperations1(): string { 47 | return 'ConcreteClass2: Operation1'; 48 | } 49 | 50 | protected requiredOperation2(): string { 51 | return 'ConcreteClass2: Operation2'; 52 | } 53 | 54 | protected hook1(): string { 55 | return 'ConcreteClass2 Hook1'; 56 | } 57 | } 58 | 59 | export { AbstractClass, ConcreteClass1, ConcreteClass2 }; 60 | -------------------------------------------------------------------------------- /src/behavioral/visitor/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ConcreteVisitor1, 3 | ConcreteVisitor2, 4 | ConcreteComponentA, 5 | ConcreteComponentB, 6 | } from '../index'; 7 | 8 | describe('template pattern', () => { 9 | it('template concrete creator', () => { 10 | const componentA = new ConcreteComponentA(); 11 | const visitor1 = new ConcreteVisitor1(); 12 | 13 | expect(componentA.accept(visitor1)).toBe('A + ConcreteVisitor1'); 14 | 15 | const componentB = new ConcreteComponentB(); 16 | const visitor2 = new ConcreteVisitor2(); 17 | 18 | expect(componentB.accept(visitor2)).toBe('B + ConcreteVisitor2'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/behavioral/visitor/index.ts: -------------------------------------------------------------------------------- 1 | interface VisitorComponent { 2 | accept(visitor: Visitor): void; 3 | } 4 | 5 | class ConcreteComponentA implements VisitorComponent { 6 | public accept(visitor: Visitor): string { 7 | return visitor.visitConcreteComponentA(this); 8 | } 9 | 10 | public exclusiveMethodOfConcreteComponentA(): string { 11 | return 'A'; 12 | } 13 | } 14 | 15 | class ConcreteComponentB implements VisitorComponent { 16 | public accept(visitor: Visitor): string { 17 | return visitor.visitConcreteComponentB(this); 18 | } 19 | 20 | public specialMethodOfConcreteComponentB(): string { 21 | return 'B'; 22 | } 23 | } 24 | 25 | interface Visitor { 26 | visitConcreteComponentA(element: ConcreteComponentA): string; 27 | 28 | visitConcreteComponentB(element: ConcreteComponentB): string; 29 | } 30 | 31 | class ConcreteVisitor1 implements Visitor { 32 | public visitConcreteComponentA(element: ConcreteComponentA): string { 33 | return `${element.exclusiveMethodOfConcreteComponentA()} + ConcreteVisitor1`; 34 | } 35 | 36 | public visitConcreteComponentB(element: ConcreteComponentB): string { 37 | return `${element.specialMethodOfConcreteComponentB()} + ConcreteVisitor1`; 38 | } 39 | } 40 | 41 | class ConcreteVisitor2 implements Visitor { 42 | public visitConcreteComponentA(element: ConcreteComponentA): string { 43 | return `${element.exclusiveMethodOfConcreteComponentA()} + ConcreteVisitor2`; 44 | } 45 | 46 | public visitConcreteComponentB(element: ConcreteComponentB): string { 47 | return `${element.specialMethodOfConcreteComponentB()} + ConcreteVisitor2`; 48 | } 49 | } 50 | 51 | export { 52 | VisitorComponent, 53 | ConcreteComponentA, 54 | ConcreteComponentB, 55 | Visitor, 56 | ConcreteVisitor1, 57 | ConcreteVisitor2, 58 | }; 59 | -------------------------------------------------------------------------------- /src/creational/abstractFactory/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { ConcreteFactory1, ConcreteFactory2 } from '../index'; 2 | 3 | describe('abstract factory pattern', () => { 4 | it('abstract factory concrete creator 1', () => { 5 | const factory1 = new ConcreteFactory1(); 6 | 7 | const productA = factory1.createProductA(); 8 | const productB = factory1.createProductB(); 9 | 10 | expect(productB.usefulFunctionB()).toBe('The result of the product B1.'); 11 | expect(productB.anotherUsefulFunctionB(productA)).toBe( 12 | 'The result of the B1 collaborating with the product A1.' 13 | ); 14 | }); 15 | 16 | it('abstract factory concrete creator 1', () => { 17 | const factory1 = new ConcreteFactory2(); 18 | 19 | const productA = factory1.createProductA(); 20 | const productB = factory1.createProductB(); 21 | 22 | expect(productB.usefulFunctionB()).toBe('The result of the product B2.'); 23 | expect(productB.anotherUsefulFunctionB(productA)).toBe( 24 | 'The result of the B2 collaborating with the product A2.' 25 | ); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/creational/abstractFactory/index.ts: -------------------------------------------------------------------------------- 1 | interface AbstractFactory { 2 | createProductA(): AbstractProductA; 3 | 4 | createProductB(): AbstractProductB; 5 | } 6 | 7 | class ConcreteFactory1 implements AbstractFactory { 8 | public createProductA(): AbstractProductA { 9 | return new ConcreteProductA1(); 10 | } 11 | 12 | public createProductB(): AbstractProductB { 13 | return new ConcreteProductB1(); 14 | } 15 | } 16 | 17 | class ConcreteFactory2 implements AbstractFactory { 18 | public createProductA(): AbstractProductA { 19 | return new ConcreteProductA2(); 20 | } 21 | 22 | public createProductB(): AbstractProductB { 23 | return new ConcreteProductB2(); 24 | } 25 | } 26 | 27 | interface AbstractProductA { 28 | usefulFunctionA(): string; 29 | } 30 | 31 | class ConcreteProductA1 implements AbstractProductA { 32 | public usefulFunctionA(): string { 33 | return 'the product A1.'; 34 | } 35 | } 36 | 37 | class ConcreteProductA2 implements AbstractProductA { 38 | public usefulFunctionA(): string { 39 | return 'the product A2.'; 40 | } 41 | } 42 | 43 | interface AbstractProductB { 44 | usefulFunctionB(): string; 45 | 46 | anotherUsefulFunctionB(collaborator: AbstractProductA): string; 47 | } 48 | 49 | class ConcreteProductB1 implements AbstractProductB { 50 | public usefulFunctionB(): string { 51 | return 'The result of the product B1.'; 52 | } 53 | 54 | public anotherUsefulFunctionB(collaborator: AbstractProductA): string { 55 | const result = collaborator.usefulFunctionA(); 56 | return `The result of the B1 collaborating with ${result}`; 57 | } 58 | } 59 | 60 | class ConcreteProductB2 implements AbstractProductB { 61 | public usefulFunctionB(): string { 62 | return 'The result of the product B2.'; 63 | } 64 | 65 | public anotherUsefulFunctionB(collaborator: AbstractProductA): string { 66 | const result = collaborator.usefulFunctionA(); 67 | return `The result of the B2 collaborating with ${result}`; 68 | } 69 | } 70 | 71 | export { 72 | AbstractFactory, 73 | ConcreteFactory1, 74 | ConcreteFactory2, 75 | AbstractProductA, 76 | ConcreteProductA1, 77 | ConcreteProductA2, 78 | AbstractProductB, 79 | ConcreteProductB2, 80 | }; 81 | -------------------------------------------------------------------------------- /src/creational/builder/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { Director, ConcreteBuilder1 } from '../index'; 2 | 3 | describe('builder pattern', () => { 4 | it('builder concrete', () => { 5 | const director = new Director(); 6 | const builder = new ConcreteBuilder1(); 7 | 8 | director.setBuilder(builder); 9 | 10 | director.buildMinimalViableProduct(); 11 | expect(builder.getProduct().listParts()).toBe('Product parts: PartA1'); 12 | 13 | director.buildFullFeaturedProduct(); 14 | 15 | expect(builder.getProduct().listParts()).toBe('Product parts: PartA1, PartB1, PartC1'); 16 | 17 | builder.producePartA(); 18 | builder.producePartC(); 19 | expect(builder.getProduct().listParts()).toBe('Product parts: PartA1, PartC1'); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/creational/builder/index.ts: -------------------------------------------------------------------------------- 1 | interface Builder { 2 | producePartA(): void; 3 | producePartB(): void; 4 | producePartC(): void; 5 | } 6 | 7 | class ConcreteBuilder1 implements Builder { 8 | private product: Product1; 9 | 10 | constructor() { 11 | this.reset(); 12 | } 13 | 14 | public reset(): void { 15 | this.product = new Product1(); 16 | } 17 | 18 | public producePartA(): void { 19 | this.product.parts.push('PartA1'); 20 | } 21 | 22 | public producePartB(): void { 23 | this.product.parts.push('PartB1'); 24 | } 25 | 26 | public producePartC(): void { 27 | this.product.parts.push('PartC1'); 28 | } 29 | 30 | public getProduct(): Product1 { 31 | const result = this.product; 32 | this.reset(); 33 | return result; 34 | } 35 | } 36 | 37 | class Product1 { 38 | public parts: string[] = []; 39 | 40 | public listParts(): string { 41 | return `Product parts: ${this.parts.join(', ')}`; 42 | } 43 | } 44 | 45 | class Director { 46 | private builder: Builder; 47 | 48 | public setBuilder(builder: Builder): void { 49 | this.builder = builder; 50 | } 51 | 52 | public buildMinimalViableProduct(): void { 53 | this.builder.producePartA(); 54 | } 55 | 56 | public buildFullFeaturedProduct(): void { 57 | this.builder.producePartA(); 58 | this.builder.producePartB(); 59 | this.builder.producePartC(); 60 | } 61 | } 62 | 63 | export { Builder, ConcreteBuilder1, Product1, Director }; 64 | -------------------------------------------------------------------------------- /src/creational/factory/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { ConcreteCreator1, ConcreteCreator2 } from '../index'; 2 | 3 | describe('factory pattern', () => { 4 | it('factory concrete creator 1', () => { 5 | const creator1 = new ConcreteCreator1(); 6 | 7 | expect(creator1.someOperation()).toBe( 8 | "Creator: The same creator's code has just worked with ConcreteProduct1" 9 | ); 10 | }); 11 | it('factory concrete creator 2', () => { 12 | const creator1 = new ConcreteCreator2(); 13 | 14 | expect(creator1.someOperation()).toBe( 15 | "Creator: The same creator's code has just worked with ConcreteProduct2" 16 | ); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/creational/factory/index.ts: -------------------------------------------------------------------------------- 1 | interface Product { 2 | operation(): string; 3 | } 4 | 5 | abstract class Creator { 6 | public abstract factoryMethod(): Product; 7 | 8 | public someOperation(): string { 9 | const product = this.factoryMethod(); 10 | 11 | return `Creator: The same creator's code has just worked with ${product.operation()}`; 12 | } 13 | } 14 | 15 | class ConcreteCreator1 extends Creator { 16 | public factoryMethod(): Product { 17 | return new ConcreteProduct1(); 18 | } 19 | } 20 | 21 | class ConcreteCreator2 extends Creator { 22 | public factoryMethod(): Product { 23 | return new ConcreteProduct2(); 24 | } 25 | } 26 | 27 | class ConcreteProduct1 implements Product { 28 | public operation(): string { 29 | return 'ConcreteProduct1'; 30 | } 31 | } 32 | 33 | class ConcreteProduct2 implements Product { 34 | public operation(): string { 35 | return 'ConcreteProduct2'; 36 | } 37 | } 38 | 39 | export { Product, Creator, ConcreteCreator1, ConcreteCreator2, ConcreteProduct1, ConcreteProduct2 }; 40 | -------------------------------------------------------------------------------- /src/creational/index.ts: -------------------------------------------------------------------------------- 1 | export * from './factory/index'; 2 | export * from './abstractFactory/index'; 3 | export * from './builder/index'; 4 | export * from './prototype/index'; 5 | export * from './singleton/index'; 6 | -------------------------------------------------------------------------------- /src/creational/prototype/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { Prototype, ComponentWithBackReference } from '../index'; 2 | 3 | describe('prototype pattern', () => { 4 | it('prototype concrete creator', () => { 5 | const p1 = new Prototype(); 6 | 7 | p1.primitive = 'chenghuai'; 8 | p1.component = new Date(); 9 | p1.circularReference = new ComponentWithBackReference(p1); 10 | 11 | const p2 = p1.clone(); 12 | 13 | expect(p1.primitive === p2.primitive).toBe(true); 14 | expect(p1.component === p2.component).toBe(false); 15 | expect(p1.circularReference === p2.circularReference).toBe(false); 16 | expect(p1.circularReference.prototype === p2.circularReference.prototype).toBe(false); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/creational/prototype/index.ts: -------------------------------------------------------------------------------- 1 | class Prototype { 2 | public primitive: any; 3 | public component: object; 4 | public circularReference: ComponentWithBackReference; 5 | 6 | public clone(): this { 7 | const clone = Object.create(this); 8 | 9 | clone.component = Object.create(this.component); 10 | 11 | clone.circularReference = { 12 | ...this.circularReference, 13 | prototype: { ...this }, 14 | }; 15 | 16 | return clone; 17 | } 18 | } 19 | 20 | class ComponentWithBackReference { 21 | public prototype; 22 | 23 | constructor(prototype: Prototype) { 24 | this.prototype = prototype; 25 | } 26 | } 27 | 28 | export { Prototype, ComponentWithBackReference }; 29 | -------------------------------------------------------------------------------- /src/creational/singleton/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { Singleton } from '../index'; 2 | 3 | describe('singleton pattern', () => { 4 | it('singleton instance', () => { 5 | const s1 = Singleton.getInstance(); 6 | const s2 = Singleton.getInstance(); 7 | 8 | expect(s1 === s2).toBe(true); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/creational/singleton/index.ts: -------------------------------------------------------------------------------- 1 | class Singleton { 2 | private static instance: Singleton; 3 | 4 | private constructor() {} 5 | 6 | public static getInstance(): Singleton { 7 | if (!Singleton.instance) { 8 | Singleton.instance = new Singleton(); 9 | } 10 | 11 | return Singleton.instance; 12 | } 13 | 14 | public someBusinessLogic() {} 15 | } 16 | 17 | export { Singleton }; 18 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './creational/index'; 2 | export * from './structural/index'; 3 | export * from './behavioral/index'; 4 | -------------------------------------------------------------------------------- /src/structural/adapter/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { Adaptee, Adapter, Target } from '../index'; 2 | 3 | describe('adapter pattern', () => { 4 | it('adapter concrete', () => { 5 | const target = new Target(); 6 | const adaptee = new Adaptee(); 7 | const adapter = new Adapter(adaptee); 8 | 9 | expect(adaptee.specificRequest()).toBe('specify request'); 10 | expect(adapter.request()).toBe('Adapter: (TRANSLATED) request specify'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/structural/adapter/index.ts: -------------------------------------------------------------------------------- 1 | class Target { 2 | public request(): string { 3 | return 'request'; 4 | } 5 | } 6 | 7 | class Adaptee { 8 | public specificRequest(): string { 9 | return 'specify request'; 10 | } 11 | } 12 | 13 | class Adapter extends Target { 14 | private adaptee: Adaptee; 15 | 16 | constructor(adaptee: Adaptee) { 17 | super(); 18 | this.adaptee = adaptee; 19 | } 20 | 21 | public request(): string { 22 | const result = this.adaptee.specificRequest().split(' ').reverse().join(' '); 23 | return `Adapter: (TRANSLATED) ${result}`; 24 | } 25 | } 26 | 27 | export { Target, Adaptee, Adapter }; 28 | -------------------------------------------------------------------------------- /src/structural/bridge/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { ConcreteImplementationA, ConcreteImplementationB, Abstraction } from '../index'; 2 | 3 | describe('bridge pattern', () => { 4 | it('bridge concrete A', () => { 5 | const implementationA = new ConcreteImplementationA(); 6 | const abstractionA = new Abstraction(implementationA); 7 | 8 | expect(abstractionA.operation()).toBe('ConcreteImplementationA'); 9 | }); 10 | 11 | it('bridge concrete B', () => { 12 | const implementationB = new ConcreteImplementationB(); 13 | const abstractionB = new Abstraction(implementationB); 14 | 15 | expect(abstractionB.operation()).toBe('ConcreteImplementationB'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/structural/bridge/index.ts: -------------------------------------------------------------------------------- 1 | class Abstraction { 2 | protected implementation: Implementation; 3 | 4 | constructor(implementation: Implementation) { 5 | this.implementation = implementation; 6 | } 7 | 8 | public operation(): string { 9 | const result = this.implementation.operationImplementation(); 10 | return result; 11 | } 12 | } 13 | 14 | class ExtendedAbstraction extends Abstraction { 15 | public operation(): string { 16 | const result = this.implementation.operationImplementation(); 17 | return result; 18 | } 19 | } 20 | 21 | interface Implementation { 22 | operationImplementation(): string; 23 | } 24 | 25 | class ConcreteImplementationA implements Implementation { 26 | public operationImplementation(): string { 27 | return 'ConcreteImplementationA'; 28 | } 29 | } 30 | 31 | class ConcreteImplementationB implements Implementation { 32 | public operationImplementation(): string { 33 | return 'ConcreteImplementationB'; 34 | } 35 | } 36 | 37 | export { 38 | Abstraction, 39 | ExtendedAbstraction, 40 | Implementation, 41 | ConcreteImplementationA, 42 | ConcreteImplementationB, 43 | }; 44 | -------------------------------------------------------------------------------- /src/structural/composite/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { Leaf, Composite } from '../index'; 2 | 3 | describe('composite pattern', () => { 4 | it('composite concrete 1', () => { 5 | const simple = new Leaf(); 6 | 7 | expect(simple.operation()).toBe('Leaf'); 8 | }); 9 | 10 | it('composite concrete 2', () => { 11 | const tree = new Composite(); 12 | const branch1 = new Composite(); 13 | 14 | branch1.add(new Leaf()); 15 | branch1.add(new Leaf()); 16 | 17 | const branch2 = new Composite(); 18 | branch2.add(new Leaf()); 19 | 20 | tree.add(branch1); 21 | tree.add(branch2); 22 | 23 | expect(tree.operation()).toBe('Branch(Branch(Leaf+Leaf)+Branch(Leaf))'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/structural/composite/index.ts: -------------------------------------------------------------------------------- 1 | abstract class CompositeComponent { 2 | protected parent!: CompositeComponent | null; 3 | 4 | public setParent(parent: CompositeComponent | null) { 5 | this.parent = parent; 6 | } 7 | 8 | public getParent(): CompositeComponent | null { 9 | return this.parent; 10 | } 11 | 12 | public add(component: CompositeComponent): void {} 13 | 14 | public remove(component: CompositeComponent): void {} 15 | 16 | public isComposite(): boolean { 17 | return false; 18 | } 19 | 20 | public abstract operation(): string; 21 | } 22 | 23 | class Leaf extends CompositeComponent { 24 | public operation(): string { 25 | return 'Leaf'; 26 | } 27 | } 28 | 29 | class Composite extends CompositeComponent { 30 | protected children: CompositeComponent[] = []; 31 | 32 | public add(component: CompositeComponent): void { 33 | this.children.push(component); 34 | component.setParent(this); 35 | } 36 | 37 | public remove(component: CompositeComponent): void { 38 | const componentIndex = this.children.indexOf(component); 39 | this.children.splice(componentIndex, 1); 40 | 41 | component.setParent(null); 42 | } 43 | 44 | public isComposite(): boolean { 45 | return true; 46 | } 47 | 48 | public operation(): string { 49 | const results: Array = []; 50 | for (const child of this.children) { 51 | results.push(child.operation()); 52 | } 53 | 54 | return `Branch(${results.join('+')})`; 55 | } 56 | } 57 | 58 | export { CompositeComponent, Leaf, Composite }; 59 | -------------------------------------------------------------------------------- /src/structural/decorator/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { ConcreteComponent, ConcreteDecoratorA, ConcreteDecoratorB } from '../index'; 2 | 3 | describe('decorate pattern', () => { 4 | it('simple component concrete 1', () => { 5 | const simple = new ConcreteComponent(); 6 | 7 | expect(simple.operation()).toBe('ConcreteComponent'); 8 | }); 9 | 10 | it('decorated component concrete 2', () => { 11 | const simple = new ConcreteComponent(); 12 | const decorator1 = new ConcreteDecoratorA(simple); 13 | const decorator2 = new ConcreteDecoratorB(decorator1); 14 | 15 | expect(decorator2.operation()).toBe( 16 | 'ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent))' 17 | ); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/structural/decorator/index.ts: -------------------------------------------------------------------------------- 1 | interface DecorateComponent { 2 | operation(): string; 3 | } 4 | 5 | class ConcreteComponent implements DecorateComponent { 6 | public operation(): string { 7 | return 'ConcreteComponent'; 8 | } 9 | } 10 | 11 | class Decorator implements DecorateComponent { 12 | protected component: DecorateComponent; 13 | 14 | constructor(component: DecorateComponent) { 15 | this.component = component; 16 | } 17 | 18 | public operation(): string { 19 | return this.component.operation(); 20 | } 21 | } 22 | 23 | class ConcreteDecoratorA extends Decorator { 24 | public operation(): string { 25 | return `ConcreteDecoratorA(${super.operation()})`; 26 | } 27 | } 28 | 29 | class ConcreteDecoratorB extends Decorator { 30 | public operation(): string { 31 | return `ConcreteDecoratorB(${super.operation()})`; 32 | } 33 | } 34 | 35 | export { DecorateComponent, ConcreteComponent, Decorator, ConcreteDecoratorA, ConcreteDecoratorB }; 36 | -------------------------------------------------------------------------------- /src/structural/facade/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { Subsystem1, Subsystem2, Facade } from '../index'; 2 | 3 | describe('facade pattern', () => { 4 | it('facade concrete', () => { 5 | const subsystem1 = new Subsystem1(); 6 | const subsystem2 = new Subsystem2(); 7 | const facade = new Facade(subsystem1, subsystem2); 8 | 9 | expect(facade.operation()).toStrictEqual([ 10 | 'Subsystem1: Ready!', 11 | 'Subsystem2: Get ready!', 12 | 'Subsystem1: Go!', 13 | 'Subsystem2: Fire!', 14 | ]); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/structural/facade/index.ts: -------------------------------------------------------------------------------- 1 | class Facade { 2 | protected subsystem1: Subsystem1; 3 | 4 | protected subsystem2: Subsystem2; 5 | 6 | constructor(subsystem1?: Subsystem1, subsystem2?: Subsystem2) { 7 | this.subsystem1 = subsystem1 || new Subsystem1(); 8 | this.subsystem2 = subsystem2 || new Subsystem2(); 9 | } 10 | 11 | public operation(): string[] { 12 | let result: string[] = []; 13 | result.push(this.subsystem1.operation1()); 14 | result.push(this.subsystem2.operation1()); 15 | result.push(this.subsystem1.operationN()); 16 | result.push(this.subsystem2.operationZ()); 17 | 18 | return result; 19 | } 20 | } 21 | 22 | class Subsystem1 { 23 | public operation1(): string { 24 | return 'Subsystem1: Ready!'; 25 | } 26 | 27 | public operationN(): string { 28 | return 'Subsystem1: Go!'; 29 | } 30 | } 31 | 32 | class Subsystem2 { 33 | public operation1(): string { 34 | return 'Subsystem2: Get ready!'; 35 | } 36 | 37 | public operationZ(): string { 38 | return 'Subsystem2: Fire!'; 39 | } 40 | } 41 | 42 | export { Facade, Subsystem1, Subsystem2 }; 43 | -------------------------------------------------------------------------------- /src/structural/flyweight/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { factory } from '../index'; 2 | 3 | describe('flyweight pattern', () => { 4 | it('flyweight concrete', () => { 5 | const flyweight1 = factory.getFlyweight(['BMW', 'M5', 'red']); 6 | const flyweight2 = factory.getFlyweight(['BMW', 'X1', 'red']); 7 | 8 | expect(flyweight1.operation(['CL234IR', 'James'])).toStrictEqual({ 9 | shared: ['BMW', 'M5', 'red'], 10 | unique: ['CL234IR', 'James'], 11 | }); 12 | 13 | expect(flyweight2.operation(['CL234IR', 'James'])).toStrictEqual({ 14 | shared: ['BMW', 'X1', 'red'], 15 | unique: ['CL234IR', 'James'], 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/structural/flyweight/index.ts: -------------------------------------------------------------------------------- 1 | class Flyweight { 2 | private sharedState: any; 3 | 4 | constructor(sharedState: any) { 5 | this.sharedState = sharedState; 6 | } 7 | 8 | public operation(uniqueState): { shared: Array; unique: Array } { 9 | const s = this.sharedState; 10 | const u = uniqueState; 11 | return { 12 | shared: s, 13 | unique: u, 14 | }; 15 | } 16 | } 17 | 18 | class FlyweightFactory { 19 | private flyweights: { [key: string]: Flyweight } = {}; 20 | 21 | constructor(initialFlyweights: string[][]) { 22 | for (const state of initialFlyweights) { 23 | this.flyweights[this.getKey(state)] = new Flyweight(state); 24 | } 25 | } 26 | 27 | private getKey(state: string[]): string { 28 | return state.join('_'); 29 | } 30 | 31 | public getFlyweight(sharedState: string[]): Flyweight { 32 | const key = this.getKey(sharedState); 33 | 34 | if (!(key in this.flyweights)) { 35 | console.log("FlyweightFactory: Can't find a flyweight, creating new one."); 36 | this.flyweights[key] = new Flyweight(sharedState); 37 | } else { 38 | console.log('FlyweightFactory: Reusing existing flyweight.'); 39 | } 40 | 41 | return this.flyweights[key]; 42 | } 43 | 44 | public listFlyweights(): void { 45 | const count = Object.keys(this.flyweights).length; 46 | console.log(`\nFlyweightFactory: I have ${count} flyweights:`); 47 | for (const key in this.flyweights) { 48 | console.log(key); 49 | } 50 | } 51 | } 52 | 53 | const factory = new FlyweightFactory([ 54 | ['chenghuai', 'Camaro2018', 'pink'], 55 | ['huaicheng', 'C300', 'black'], 56 | ['xianzao', 'C500', 'red'], 57 | ['BMW', 'M5', 'red'], 58 | ['BMW', 'X6', 'white'], 59 | ]); 60 | 61 | factory.listFlyweights(); 62 | 63 | export { Flyweight, FlyweightFactory, factory }; 64 | -------------------------------------------------------------------------------- /src/structural/index.ts: -------------------------------------------------------------------------------- 1 | export * from './adapter/index'; 2 | export * from './bridge/index'; 3 | export * from './composite/index'; 4 | export * from './decorator/index'; 5 | export * from './facade/index'; 6 | export * from './flyweight/index'; 7 | export * from './proxy/index'; 8 | -------------------------------------------------------------------------------- /src/structural/proxy/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { RealSubject, ProxyPattern } from '../index'; 2 | 3 | describe('proxy pattern', () => { 4 | it('proxy concrete', () => { 5 | const realSubject = new RealSubject(); 6 | const proxy = new ProxyPattern(realSubject); 7 | 8 | expect(realSubject.request()).toBe('RealSubject: Handling request.'); 9 | expect(proxy.request()).toBe('RealSubject: Handling request.'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/structural/proxy/index.ts: -------------------------------------------------------------------------------- 1 | interface Subject { 2 | request(): void; 3 | } 4 | 5 | class RealSubject implements Subject { 6 | public request(): string { 7 | return 'RealSubject: Handling request.'; 8 | } 9 | } 10 | 11 | class ProxyPattern implements Subject { 12 | private realSubject: RealSubject; 13 | 14 | constructor(realSubject: RealSubject) { 15 | this.realSubject = realSubject; 16 | } 17 | 18 | public request() { 19 | if (this.checkAccess()) { 20 | this.logAccess(); 21 | 22 | return this.realSubject.request(); 23 | } 24 | } 25 | 26 | private checkAccess(): boolean { 27 | console.log('Proxy: Checking access prior to firing a real request.'); 28 | 29 | return true; 30 | } 31 | 32 | private logAccess(): void { 33 | console.log('Proxy: Logging the time of request.'); 34 | } 35 | } 36 | 37 | export { Subject, RealSubject, ProxyPattern }; 38 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "compilerOptions": { 4 | "target": "ES5", 5 | "moduleResolution": "node", 6 | "esModuleInterop": true, 7 | "lib": ["ESNEXT", "dom"], 8 | "downlevelIteration": true, 9 | "sourceMap": true, 10 | "baseUrl": ".", 11 | "allowSyntheticDefaultImports": true, 12 | "skipLibCheck": true, 13 | "strictNullChecks": true, 14 | "importHelpers": true 15 | }, 16 | "include": ["src/*", "src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /tsdoc/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /tsdoc/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-code-background: #FFFFFF; 3 | --dark-code-background: #1E1E1E; 4 | } 5 | 6 | @media (prefers-color-scheme: light) { :root { 7 | --code-background: var(--light-code-background); 8 | } } 9 | 10 | @media (prefers-color-scheme: dark) { :root { 11 | --code-background: var(--dark-code-background); 12 | } } 13 | 14 | :root[data-theme='light'] { 15 | --code-background: var(--light-code-background); 16 | } 17 | 18 | :root[data-theme='dark'] { 19 | --code-background: var(--dark-code-background); 20 | } 21 | 22 | pre, code { background: var(--code-background); } 23 | -------------------------------------------------------------------------------- /tsdoc/assets/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | "use strict";(()=>{var Se=Object.create;var re=Object.defineProperty;var we=Object.getOwnPropertyDescriptor;var Te=Object.getOwnPropertyNames;var ke=Object.getPrototypeOf,Qe=Object.prototype.hasOwnProperty;var Pe=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var Ie=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Te(e))!Qe.call(t,i)&&i!==r&&re(t,i,{get:()=>e[i],enumerable:!(n=we(e,i))||n.enumerable});return t};var Ce=(t,e,r)=>(r=t!=null?Se(ke(t)):{},Ie(e||!t||!t.__esModule?re(r,"default",{value:t,enumerable:!0}):r,t));var ae=Pe((se,oe)=>{(function(){var t=function(e){var r=new t.Builder;return r.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),r.searchPipeline.add(t.stemmer),e.call(r,r),r.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(r){e.console&&console.warn&&console.warn(r)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var r=Object.create(null),n=Object.keys(e),i=0;i0){var d=t.utils.clone(r)||{};d.position=[a,u],d.index=s.length,s.push(new t.Token(n.slice(a,o),d))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,r){r in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+r),e.label=r,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var r=e.label&&e.label in this.registeredFunctions;r||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. 3 | `,e)},t.Pipeline.load=function(e){var r=new t.Pipeline;return e.forEach(function(n){var i=t.Pipeline.registeredFunctions[n];if(i)r.add(i);else throw new Error("Cannot load unregistered function: "+n)}),r},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(r){t.Pipeline.warnIfFunctionNotRegistered(r),this._stack.push(r)},this)},t.Pipeline.prototype.after=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");n=n+1,this._stack.splice(n,0,r)},t.Pipeline.prototype.before=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");this._stack.splice(n,0,r)},t.Pipeline.prototype.remove=function(e){var r=this._stack.indexOf(e);r!=-1&&this._stack.splice(r,1)},t.Pipeline.prototype.run=function(e){for(var r=this._stack.length,n=0;n1&&(oe&&(n=s),o!=e);)i=n-r,s=r+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(ol?d+=2:a==l&&(r+=n[u+1]*i[d+1],u+=2,d+=2);return r},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),r=1,n=0;r0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new t.TokenSet;s.node.edges["*"]=l}if(s.str.length==0&&(l.final=!0),i.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new t.TokenSet;s.node.edges["*"]=u}s.str.length==1&&(u.final=!0),i.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var d=s.str.charAt(0),m=s.str.charAt(1),y;m in s.node.edges?y=s.node.edges[m]:(y=new t.TokenSet,s.node.edges[m]=y),s.str.length==1&&(y.final=!0),i.push({node:y,editsRemaining:s.editsRemaining-1,str:d+s.str.slice(2)})}}}return n},t.TokenSet.fromString=function(e){for(var r=new t.TokenSet,n=r,i=0,s=e.length;i=e;r--){var n=this.uncheckedNodes[r],i=n.child.toString();i in this.minimizedNodes?n.parent.edges[n.char]=this.minimizedNodes[i]:(n.child._str=i,this.minimizedNodes[i]=n.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(r){var n=new t.QueryParser(e,r);n.parse()})},t.Index.prototype.query=function(e){for(var r=new t.Query(this.fields),n=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),l=0;l1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,r){var n=e[this._ref],i=Object.keys(this._fields);this._documents[n]=r||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,r;do e=this.next(),r=e.charCodeAt(0);while(r>47&&r<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var r=e.next();if(r==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(r.charCodeAt(0)==92){e.escapeCharacter();continue}if(r==":")return t.QueryLexer.lexField;if(r=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(r=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(r=="+"&&e.width()===1||r=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(r.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,r){this.lexer=new t.QueryLexer(e),this.query=r,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var r=e.peekLexeme();if(r!=null)switch(r.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(n+=" with value '"+r.str+"'"),new t.QueryParseError(n,r.start,r.end)}},t.QueryParser.parsePresence=function(e){var r=e.consumeLexeme();if(r!=null){switch(r.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var n="unrecognised presence operator'"+r.str+"'";throw new t.QueryParseError(n,r.start,r.end)}var i=e.peekLexeme();if(i==null){var n="expecting term or field, found nothing";throw new t.QueryParseError(n,r.start,r.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(n,i.start,i.end)}}},t.QueryParser.parseField=function(e){var r=e.consumeLexeme();if(r!=null){if(e.query.allFields.indexOf(r.str)==-1){var n=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+r.str+"', possible fields: "+n;throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.fields=[r.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,r.start,r.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var r=e.consumeLexeme();if(r!=null){e.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var n=e.peekLexeme();if(n==null){e.nextClause();return}switch(n.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+n.type+"'";throw new t.QueryParseError(i,n.start,n.end)}}},t.QueryParser.parseEditDistance=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="edit distance must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.editDistance=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="boost must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.boost=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,r){typeof define=="function"&&define.amd?define(r):typeof se=="object"?oe.exports=r():e.lunr=r()}(this,function(){return t})})()});var ne=[];function G(t,e){ne.push({selector:e,constructor:t})}var U=class{constructor(){this.alwaysVisibleMember=null;this.createComponents(document.body),this.ensureActivePageVisible(),this.ensureFocusedElementVisible(),this.listenForCodeCopies(),window.addEventListener("hashchange",()=>this.ensureFocusedElementVisible())}createComponents(e){ne.forEach(r=>{e.querySelectorAll(r.selector).forEach(n=>{n.dataset.hasInstance||(new r.constructor({el:n,app:this}),n.dataset.hasInstance=String(!0))})})}filterChanged(){this.ensureFocusedElementVisible()}ensureActivePageVisible(){let e=document.querySelector(".tsd-navigation .current"),r=e?.parentElement;for(;r&&!r.classList.contains(".tsd-navigation");)r instanceof HTMLDetailsElement&&(r.open=!0),r=r.parentElement;if(e){let n=e.getBoundingClientRect().top-document.documentElement.clientHeight/4;document.querySelector(".site-menu").scrollTop=n}}ensureFocusedElementVisible(){if(this.alwaysVisibleMember&&(this.alwaysVisibleMember.classList.remove("always-visible"),this.alwaysVisibleMember.firstElementChild.remove(),this.alwaysVisibleMember=null),!location.hash)return;let e=document.getElementById(location.hash.substring(1));if(!e)return;let r=e.parentElement;for(;r&&r.tagName!=="SECTION";)r=r.parentElement;if(r&&r.offsetParent==null){this.alwaysVisibleMember=r,r.classList.add("always-visible");let n=document.createElement("p");n.classList.add("warning"),n.textContent="This member is normally hidden due to your filter settings.",r.prepend(n)}}listenForCodeCopies(){document.querySelectorAll("pre > button").forEach(e=>{let r;e.addEventListener("click",()=>{e.previousElementSibling instanceof HTMLElement&&navigator.clipboard.writeText(e.previousElementSibling.innerText.trim()),e.textContent="Copied!",e.classList.add("visible"),clearTimeout(r),r=setTimeout(()=>{e.classList.remove("visible"),r=setTimeout(()=>{e.textContent="Copy"},100)},1e3)})})}};var ie=(t,e=100)=>{let r;return()=>{clearTimeout(r),r=setTimeout(()=>t(),e)}};var ce=Ce(ae());function de(){let t=document.getElementById("tsd-search");if(!t)return;let e=document.getElementById("tsd-search-script");t.classList.add("loading"),e&&(e.addEventListener("error",()=>{t.classList.remove("loading"),t.classList.add("failure")}),e.addEventListener("load",()=>{t.classList.remove("loading"),t.classList.add("ready")}),window.searchData&&t.classList.remove("loading"));let r=document.querySelector("#tsd-search input"),n=document.querySelector("#tsd-search .results");if(!r||!n)throw new Error("The input field or the result list wrapper was not found");let i=!1;n.addEventListener("mousedown",()=>i=!0),n.addEventListener("mouseup",()=>{i=!1,t.classList.remove("has-focus")}),r.addEventListener("focus",()=>t.classList.add("has-focus")),r.addEventListener("blur",()=>{i||(i=!1,t.classList.remove("has-focus"))});let s={base:t.dataset.base+"/"};Oe(t,n,r,s)}function Oe(t,e,r,n){r.addEventListener("input",ie(()=>{Re(t,e,r,n)},200));let i=!1;r.addEventListener("keydown",s=>{i=!0,s.key=="Enter"?Fe(e,r):s.key=="Escape"?r.blur():s.key=="ArrowUp"?ue(e,-1):s.key==="ArrowDown"?ue(e,1):i=!1}),r.addEventListener("keypress",s=>{i&&s.preventDefault()}),document.body.addEventListener("keydown",s=>{s.altKey||s.ctrlKey||s.metaKey||!r.matches(":focus")&&s.key==="/"&&(r.focus(),s.preventDefault())})}function _e(t,e){t.index||window.searchData&&(e.classList.remove("loading"),e.classList.add("ready"),t.data=window.searchData,t.index=ce.Index.load(window.searchData.index))}function Re(t,e,r,n){if(_e(n,t),!n.index||!n.data)return;e.textContent="";let i=r.value.trim(),s=i?n.index.search(`*${i}*`):[];for(let o=0;oa.score-o.score);for(let o=0,a=Math.min(10,s.length);o${le(l.parent,i)}.${u}`);let d=document.createElement("li");d.classList.value=l.classes??"";let m=document.createElement("a");m.href=n.base+l.url,m.innerHTML=u,d.append(m),e.appendChild(d)}}function ue(t,e){let r=t.querySelector(".current");if(!r)r=t.querySelector(e==1?"li:first-child":"li:last-child"),r&&r.classList.add("current");else{let n=r;if(e===1)do n=n.nextElementSibling??void 0;while(n instanceof HTMLElement&&n.offsetParent==null);else do n=n.previousElementSibling??void 0;while(n instanceof HTMLElement&&n.offsetParent==null);n&&(r.classList.remove("current"),n.classList.add("current"))}}function Fe(t,e){let r=t.querySelector(".current");if(r||(r=t.querySelector("li:first-child")),r){let n=r.querySelector("a");n&&(window.location.href=n.href),e.blur()}}function le(t,e){if(e==="")return t;let r=t.toLocaleLowerCase(),n=e.toLocaleLowerCase(),i=[],s=0,o=r.indexOf(n);for(;o!=-1;)i.push(K(t.substring(s,o)),`${K(t.substring(o,o+n.length))}`),s=o+n.length,o=r.indexOf(n,s);return i.push(K(t.substring(s))),i.join("")}var Me={"&":"&","<":"<",">":">","'":"'",'"':"""};function K(t){return t.replace(/[&<>"'"]/g,e=>Me[e])}var P=class{constructor(e){this.el=e.el,this.app=e.app}};var M="mousedown",fe="mousemove",N="mouseup",J={x:0,y:0},he=!1,ee=!1,De=!1,D=!1,pe=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);document.documentElement.classList.add(pe?"is-mobile":"not-mobile");pe&&"ontouchstart"in document.documentElement&&(De=!0,M="touchstart",fe="touchmove",N="touchend");document.addEventListener(M,t=>{ee=!0,D=!1;let e=M=="touchstart"?t.targetTouches[0]:t;J.y=e.pageY||0,J.x=e.pageX||0});document.addEventListener(fe,t=>{if(ee&&!D){let e=M=="touchstart"?t.targetTouches[0]:t,r=J.x-(e.pageX||0),n=J.y-(e.pageY||0);D=Math.sqrt(r*r+n*n)>10}});document.addEventListener(N,()=>{ee=!1});document.addEventListener("click",t=>{he&&(t.preventDefault(),t.stopImmediatePropagation(),he=!1)});var X=class extends P{constructor(r){super(r);this.className=this.el.dataset.toggle||"",this.el.addEventListener(N,n=>this.onPointerUp(n)),this.el.addEventListener("click",n=>n.preventDefault()),document.addEventListener(M,n=>this.onDocumentPointerDown(n)),document.addEventListener(N,n=>this.onDocumentPointerUp(n))}setActive(r){if(this.active==r)return;this.active=r,document.documentElement.classList.toggle("has-"+this.className,r),this.el.classList.toggle("active",r);let n=(this.active?"to-has-":"from-has-")+this.className;document.documentElement.classList.add(n),setTimeout(()=>document.documentElement.classList.remove(n),500)}onPointerUp(r){D||(this.setActive(!0),r.preventDefault())}onDocumentPointerDown(r){if(this.active){if(r.target.closest(".col-sidebar, .tsd-filter-group"))return;this.setActive(!1)}}onDocumentPointerUp(r){if(!D&&this.active&&r.target.closest(".col-sidebar")){let n=r.target.closest("a");if(n){let i=window.location.href;i.indexOf("#")!=-1&&(i=i.substring(0,i.indexOf("#"))),n.href.substring(0,i.length)==i&&setTimeout(()=>this.setActive(!1),250)}}}};var te;try{te=localStorage}catch{te={getItem(){return null},setItem(){}}}var Q=te;var me=document.head.appendChild(document.createElement("style"));me.dataset.for="filters";var Y=class extends P{constructor(r){super(r);this.key=`filter-${this.el.name}`,this.value=this.el.checked,this.el.addEventListener("change",()=>{this.setLocalStorage(this.el.checked)}),this.setLocalStorage(this.fromLocalStorage()),me.innerHTML+=`html:not(.${this.key}) .tsd-is-${this.el.name} { display: none; } 4 | `}fromLocalStorage(){let r=Q.getItem(this.key);return r?r==="true":this.el.checked}setLocalStorage(r){Q.setItem(this.key,r.toString()),this.value=r,this.handleValueChange()}handleValueChange(){this.el.checked=this.value,document.documentElement.classList.toggle(this.key,this.value),this.app.filterChanged(),document.querySelectorAll(".tsd-index-section").forEach(r=>{r.style.display="block";let n=Array.from(r.querySelectorAll(".tsd-index-link")).every(i=>i.offsetParent==null);r.style.display=n?"none":"block"})}};var Z=class extends P{constructor(r){super(r);this.summary=this.el.querySelector(".tsd-accordion-summary"),this.icon=this.summary.querySelector("svg"),this.key=`tsd-accordion-${this.summary.dataset.key??this.summary.textContent.trim().replace(/\s+/g,"-").toLowerCase()}`;let n=Q.getItem(this.key);this.el.open=n?n==="true":this.el.open,this.el.addEventListener("toggle",()=>this.update()),this.update()}update(){this.icon.style.transform=`rotate(${this.el.open?0:-90}deg)`,Q.setItem(this.key,this.el.open.toString())}};function ve(t){let e=Q.getItem("tsd-theme")||"os";t.value=e,ye(e),t.addEventListener("change",()=>{Q.setItem("tsd-theme",t.value),ye(t.value)})}function ye(t){document.documentElement.dataset.theme=t}de();G(X,"a[data-toggle]");G(Z,".tsd-index-accordion");G(Y,".tsd-filter-item input[type=checkbox]");var ge=document.getElementById("tsd-theme");ge&&ve(ge);var Ae=new U;Object.defineProperty(window,"app",{value:Ae});document.querySelectorAll("summary a").forEach(t=>{t.addEventListener("click",()=>{location.assign(t.href)})});})(); 5 | /*! Bundled license information: 6 | 7 | lunr/lunr.js: 8 | (** 9 | * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 10 | * Copyright (C) 2020 Oliver Nightingale 11 | * @license MIT 12 | *) 13 | (*! 14 | * lunr.utils 15 | * Copyright (C) 2020 Oliver Nightingale 16 | *) 17 | (*! 18 | * lunr.Set 19 | * Copyright (C) 2020 Oliver Nightingale 20 | *) 21 | (*! 22 | * lunr.tokenizer 23 | * Copyright (C) 2020 Oliver Nightingale 24 | *) 25 | (*! 26 | * lunr.Pipeline 27 | * Copyright (C) 2020 Oliver Nightingale 28 | *) 29 | (*! 30 | * lunr.Vector 31 | * Copyright (C) 2020 Oliver Nightingale 32 | *) 33 | (*! 34 | * lunr.stemmer 35 | * Copyright (C) 2020 Oliver Nightingale 36 | * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt 37 | *) 38 | (*! 39 | * lunr.stopWordFilter 40 | * Copyright (C) 2020 Oliver Nightingale 41 | *) 42 | (*! 43 | * lunr.trimmer 44 | * Copyright (C) 2020 Oliver Nightingale 45 | *) 46 | (*! 47 | * lunr.TokenSet 48 | * Copyright (C) 2020 Oliver Nightingale 49 | *) 50 | (*! 51 | * lunr.Index 52 | * Copyright (C) 2020 Oliver Nightingale 53 | *) 54 | (*! 55 | * lunr.Builder 56 | * Copyright (C) 2020 Oliver Nightingale 57 | *) 58 | */ 59 | -------------------------------------------------------------------------------- /tsdoc/assets/search.js: -------------------------------------------------------------------------------- 1 | window.searchData = JSON.parse("{\"rows\":[],\"index\":{\"version\":\"2.3.9\",\"fields\":[\"name\",\"comment\"],\"fieldVectors\":[],\"invertedIndex\":[],\"pipeline\":[]}}"); -------------------------------------------------------------------------------- /tsdoc/index.html: -------------------------------------------------------------------------------- 1 | encode-design-pattern
2 |
3 | 10 |
11 |
12 |
13 |
14 |

encode-design-pattern

15 |
16 | 29 |
32 |
33 |

Generated using TypeDoc

34 |
-------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": ["**/**(test|spec|mock).ts"], 3 | "disableSources": true, 4 | "excludeExternals": true, 5 | "readme": "none", 6 | "out": "tsdoc", 7 | "tsconfig": "tsconfig.test.json" 8 | } 9 | --------------------------------------------------------------------------------