├── .gitignore ├── Builder 6.5 ├── README.md └── src │ ├── CatalogWriter.normal.ts │ ├── CatalogWriter.refactoring.ts │ ├── TagBuilder.ts │ ├── TagBuilderTest.test.ts │ ├── TagNode.ts │ └── TagNodeTest.test.ts ├── Composed 7.1 ├── README.md ├── normal │ └── List.ts └── refactoring │ └── List.ts ├── Decorator 7.3 ├── README.md └── normal │ ├── NodeReader.ts │ ├── Parser.ts │ ├── ParserUtils.ts │ ├── StringBuffer.ts │ ├── StringNode.ts │ ├── StringParser.ts │ └── Translate.ts ├── Factory 6.2 ├── README.md ├── normal │ ├── DecodingStringNode.ts │ ├── Parser.ts │ ├── StringNode.ts │ ├── StringParser.ts │ └── index.ts └── refactoring │ ├── DecodingStringNode.ts │ ├── NodeFactory.ts │ ├── Parser.ts │ ├── StringNode.ts │ ├── StringParser.ts │ └── index.ts ├── Factory 6.3 ├── README.md ├── normal │ ├── AttributeDescriptor.ts │ ├── BooleanDescriptor.ts │ ├── DefaultDescriptor.ts │ ├── NumberDescriptor.ts │ ├── StringDescriptor.ts │ └── index.ts └── refactoring │ ├── AttributeDescriptor.ts │ ├── BooleanDescriptor.ts │ ├── DefaultDescriptor.ts │ ├── NumberDescriptor.ts │ ├── StringDescriptor.ts │ └── index.ts ├── Factory 6.4 ├── README.md ├── normal │ ├── DOMBuilder.ts │ ├── DOMBuilderTest.ts │ ├── IOutputBuilder.ts │ ├── TestCase.ts │ ├── XMLBuilder.ts │ └── XMLBuilderTest.ts └── refactoring │ ├── AbstractBuilderTest.ts │ ├── DOMBuilder.ts │ ├── DOMBuilderTest.ts │ ├── IOutputBuilder.ts │ ├── TestCase.ts │ ├── XMLBuilder.ts │ └── XMLBuilderTest.ts ├── README.md ├── Singleton 6.6 ├── README.md ├── normal │ ├── Blackjack.ts │ ├── ConsoleSingleton.ts │ ├── HitStayResponse.ts │ ├── ScenarioTest.test.ts │ └── TestAlwaysHitResponse.ts └── refactoring │ ├── Blackjack.ts │ ├── HitStayResponse.ts │ ├── ScenarioTest.test.ts │ └── TestAlwaysHitResponse.ts ├── Strategy 7.2 ├── README.md ├── normal │ ├── Loan.ts │ ├── RiskFactor.ts │ └── UnusedRiskFactors.ts └── refactoring │ ├── CapitalStrategy.ts │ ├── CapitalStrategyAdvisedLine.ts │ ├── CapitalStrategyRevolver.ts │ ├── CapitalStrategyTermLoan.ts │ ├── Loan.ts │ ├── RiskFactor.ts │ └── UnusedRiskFactors.ts ├── babel.config.js ├── package-lock.json ├── package.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /Builder 6.5/README.md: -------------------------------------------------------------------------------- 1 | # 用 Builder 封装 Composite 2 | 3 | - 动机 4 | 5 | - 简化创建复杂对象的客户代码 6 | - 对客户代码和 Composite 代码解耦合 7 | - 将一个复杂对象的构建与它的表示分离,使得同样的构造过程可以创建不同的表示 8 | 9 | - 优点 10 | 11 | - 简化了构造 Composite 的客户代码 12 | - 减少了创建 Composite 的重复和易出错的本性 13 | - 在客户代码和 Composite 之间实现了松耦合 14 | - 允许对已封装的 Composite 或复杂对象创建不同的表示 15 | 16 | - 缺点 17 | 18 | - 接口可能不会很清楚地表达其意图 19 | 20 | - 做法 21 | 22 | - 前提 23 | 24 | - 已经拥有了 Compasite 的构造代码,并且希望使用 Builder 对这些代码进行封装 25 | 26 | - 1. 创建一个生成器 27 | 28 | - 生成器可以创建出一个单点 Composite 29 | 30 | - 2. 是生成器可以创建子类型 31 | 32 | - 编写多个方法使得用户可以方便地创建和布置子类型 33 | 34 | - 3. 生成器要有和 Composite 构造代码一样的功能 35 | 36 | - 4. 生成器代码要足够的简单 37 | 38 | - 5. 把 Composite 构造代码重构为使用新的生成器 39 | 40 | - 符合 Builder: Client 和 Builder: Director 关系 -------------------------------------------------------------------------------- /Builder 6.5/src/CatalogWriter.normal.ts: -------------------------------------------------------------------------------- 1 | import TagNode from "./TagNode"; 2 | 3 | class CatalogWriter { 4 | public catalogXmlFor(activity) { 5 | const activityTag = new TagNode("activity"); 6 | // …… 7 | const flavorsTag = new TagNode("flavors"); 8 | activityTag.add(flavorsTag); 9 | 10 | for (let i = 0; i < activity.getFlavorCount(); i++) { 11 | const flavorTag = new TagNode("flavor"); 12 | flavorsTag.add(flavorTag); 13 | const flavor = activity.getFlavor(i); 14 | // …… 15 | const requirementsCount = flavor.getRequirements().length; 16 | if (requirementsCount > 0) { 17 | const requirementsTag = new TagNode("requirements"); 18 | flavorTag.add(requirementsTag); 19 | for (let k = 0; k < requirementsCount; k++) { 20 | const requirement = flavor.getRequirements()[k]; 21 | const requirementTag = new TagNode("requirement"); 22 | // …… 23 | requirementsTag.add(requirementTag); 24 | } 25 | } 26 | } 27 | return activityTag.toString(); 28 | } 29 | } -------------------------------------------------------------------------------- /Builder 6.5/src/CatalogWriter.refactoring.ts: -------------------------------------------------------------------------------- 1 | import TagNode from "./TagNode"; 2 | import TagBuilder from "./TagBuilder"; 3 | 4 | class CatalogWriter { 5 | public catalogXmlFor(activity) { 6 | const builder = new TagBuilder("activity"); 7 | // …… 8 | builder.addChild("flavors"); 9 | 10 | for (let i = 0; i < activity.getFlavorCount(); i++) { 11 | builder.addToParent("flavors", "flavor"); 12 | const flavor = activity.getFlavor(i); 13 | // …… 14 | const requirementsCount = flavor.getRequirements().length; 15 | if (requirementsCount > 0) { 16 | builder.addChild("requirements"); 17 | for (let k = 0; k < requirementsCount; k++) { 18 | const requirement = flavor.getRequirements()[k]; 19 | builder.addToParent("requirements","requirement") 20 | // …… 21 | } 22 | } 23 | } 24 | return builder.toXML() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Builder 6.5/src/TagBuilder.ts: -------------------------------------------------------------------------------- 1 | import TagNode from "./TagNode"; 2 | 3 | export default class TagBuilder { 4 | private rootNode: TagNode; 5 | private currentNode: TagNode; 6 | constructor(rootTagName: string) { 7 | this.rootNode = new TagNode(rootTagName); 8 | this.currentNode = this.rootNode; 9 | } 10 | 11 | public addToParent(parentTagName: string, childTagName: string) { 12 | this.addTo(this.findParentBy(parentTagName), childTagName); 13 | } 14 | 15 | public setValue(value: string) { 16 | this.currentNode.setValue(value); 17 | } 18 | public addAttribute(attribute: string, value: string) { 19 | this.currentNode.addAttribute(attribute, value); 20 | } 21 | 22 | private findParentBy(parentTagName: string): TagNode { 23 | let parentNode = this.currentNode; 24 | while (parentNode) { 25 | if (parentNode.getName() === parentTagName) { 26 | return parentNode; 27 | } 28 | parentNode = parentNode.getParent(); 29 | } 30 | return parentNode; 31 | } 32 | 33 | public addSibling(tagName: string) { 34 | this.addTo(this.currentNode.getParent(), tagName); 35 | } 36 | public addChild(tagName: string) { 37 | this.addTo(this.currentNode, tagName); 38 | } 39 | 40 | private addTo(parentNode: TagNode, tagName: string) { 41 | this.currentNode = new TagNode(tagName); 42 | parentNode.add(this.currentNode); 43 | } 44 | public toXML() { 45 | return this.rootNode.toString(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Builder 6.5/src/TagBuilderTest.test.ts: -------------------------------------------------------------------------------- 1 | import TagBuilder from "./TagBuilder"; 2 | 3 | function assertXMLEquals(expectedXML: string, actualXML: string) { 4 | const regExr = /\s/g; 5 | const expectedXMLString = expectedXML.replace(regExr, ""); 6 | const actualXMLString = actualXML.replace(regExr, ""); 7 | expect(actualXMLString).toBe(expectedXMLString); 8 | } 9 | 10 | describe("TagBuilder", () => { 11 | test("test build one node", () => { 12 | const expectedXMl = ""; 13 | const actualXML = new TagBuilder("flavors").toXML(); 14 | expect(actualXML).toBe(expectedXMl); 15 | }); 16 | 17 | test("test build one child ", () => { 18 | const expectedXML = ` 19 | 20 | 21 | 22 | `; 23 | const tagBuilder = new TagBuilder("flavors"); 24 | tagBuilder.addChild("flavor"); 25 | const actualXML = tagBuilder.toXML(); 26 | assertXMLEquals(expectedXML, actualXML); 27 | }); 28 | 29 | test("test build children of children", () => { 30 | const expectedXML = ` 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | `; 39 | const tagBuilder = new TagBuilder("flavors"); 40 | tagBuilder.addChild("flavor"); 41 | tagBuilder.addChild("requirements"); 42 | tagBuilder.addChild("requirement"); 43 | const actualXML = tagBuilder.toXML(); 44 | assertXMLEquals(expectedXML, actualXML); 45 | }); 46 | 47 | test("test build sibling", () => { 48 | const expectedXML = ` 49 | 50 | 51 | 52 | 53 | `; 54 | 55 | const builder = new TagBuilder("flavors"); 56 | builder.addChild("flavor1"); 57 | builder.addSibling("flavor2"); 58 | const actualXML = builder.toXML(); 59 | assertXMLEquals(expectedXML, actualXML); 60 | }); 61 | 62 | test("test repeating children and grand children", () => { 63 | const expectedXML = ` 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | `; 77 | 78 | const builder = new TagBuilder("flavors"); 79 | for (let index = 0; index < 2; index++) { 80 | builder.addToParent("flavors", "flavor"); 81 | builder.addChild("requirements"); 82 | builder.addChild("requirement"); 83 | } 84 | const actualXML = builder.toXML(); 85 | assertXMLEquals(expectedXML, actualXML); 86 | }); 87 | 88 | 89 | test('test attributes and values', () => { 90 | const expectedXML = ` 91 | 92 | 93 | 94 | 1 computer for every 2 participant 95 | 96 | 97 | IDE 98 | 99 | 100 | 101 | ` 102 | 103 | 104 | const builder = new TagBuilder("flavor") 105 | builder.addAttribute("name","Test-Driven Development") 106 | builder.addChild("requirements") 107 | builder.addChild("requirement") 108 | builder.addAttribute("type","hardware") 109 | builder.setValue("1 computer for every 2 participant") 110 | builder.addToParent("requirements","requirement") 111 | builder.addAttribute("type","software") 112 | builder.setValue("IDE") 113 | 114 | const actualXML = builder.toXML(); 115 | assertXMLEquals(expectedXML, actualXML); 116 | 117 | }) 118 | }); 119 | -------------------------------------------------------------------------------- /Builder 6.5/src/TagNode.ts: -------------------------------------------------------------------------------- 1 | export default class TagNode { 2 | private tagName: string; 3 | private parent: TagNode; 4 | private children: TagNode[]; 5 | private attributes: object; 6 | private value: string; 7 | constructor(tagName: string) { 8 | this.tagName = tagName; 9 | this.children = []; 10 | this.attributes = {}; 11 | this.value = ""; 12 | } 13 | 14 | public setValue(value: string) { 15 | this.value = value; 16 | } 17 | 18 | public getParent(): any { 19 | return this.parent; 20 | } 21 | 22 | public addAttribute(attribute: string, value: string) { 23 | this.attributes[attribute] = value; 24 | } 25 | 26 | private setParent(tagNode: TagNode) { 27 | this.parent = tagNode; 28 | } 29 | 30 | public getName() { 31 | return this.tagName; 32 | } 33 | 34 | public add(tagNode: TagNode) { 35 | tagNode.setParent(this); 36 | this.children.push(tagNode); 37 | } 38 | 39 | public toString() { 40 | if (this.isToSingleTag()) { 41 | return `<${this.tagName}/>`; 42 | } 43 | return ` 44 | <${this.tagName} ${this.getAttributesStr()}> 45 | ${this.value} 46 | ${this.getChildrenTagsStr()} 47 | 48 | `; 49 | } 50 | 51 | private isToSingleTag() { 52 | return !this.hasChildren() && !this.hasValue(); 53 | } 54 | 55 | private getChildrenTagsStr() { 56 | return this.children.reduce((html, tagNode) => { 57 | html += tagNode.toString() + "\r"; 58 | return html; 59 | }, ""); 60 | } 61 | 62 | private getAttributesStr() { 63 | const attributesArr = Object.keys(this.attributes); 64 | return attributesArr.reduce((result, key) => { 65 | const value = this.attributes[key]; 66 | const attributeStr = `${key} = "${value}"`; 67 | result += attributeStr + " "; 68 | return result; 69 | }, ""); 70 | } 71 | 72 | private hasValue() { 73 | return this.value.length > 0; 74 | } 75 | private hasChildren() { 76 | return this.children.length > 0; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Builder 6.5/src/TagNodeTest.test.ts: -------------------------------------------------------------------------------- 1 | import TagNode from "./TagNode" 2 | 3 | describe('TagNodeTest', () => { 4 | 5 | 6 | test('test parents', () => { 7 | const root = new TagNode("root") 8 | expect(root.getParent()).toBeUndefined() 9 | 10 | 11 | 12 | const childNode = new TagNode('child') 13 | root.add(childNode) 14 | 15 | expect(childNode.getParent()).toEqual(root) 16 | expect(childNode.getParent().getName()).toBe("root") 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /Composed 7.1/README.md: -------------------------------------------------------------------------------- 1 | # 组合方法 2 | 3 | - 实现 4 | 5 | - 把方法的逻辑转换成几个同一细节层面上的、能够说明意图的步骤 6 | 7 | - 难点 8 | 9 | - 如何决定提取出来的方法要包含哪些代码 10 | - 当小函数过多时,需要考虑提炼成类 11 | 12 | - 优点 13 | 14 | - 清晰地描述了一个方法所实现的功能以及如何实现 15 | - 把方法分解成命名良好的、处在细节的同一层面上的行为模块,以此来简化方法 16 | 17 | - 缺点 18 | 19 | - 可能会产生过多的小方法 20 | - 可能会使调试变得困难,因为程序的逻辑分散在许多小方法中 21 | 22 | - 做法 23 | 24 | - Composed Method 都很小 25 | 26 | - 很少超过 10 行,一般都在 5 行左右 27 | 28 | - 删除重复代码和死代码 29 | 30 | - 除去明显的和微妙的代码重复 31 | - 除去没有被使用的代码,减少方法的代码量 32 | 33 | - 表达意图 34 | 35 | - 清晰地命名程序中的变量、方法和参数 36 | 37 | - 简化 38 | 39 | - 转换代码,使它尽可能简单 40 | 41 | - 使用细节的同一层面 -------------------------------------------------------------------------------- /Composed 7.1/normal/List.ts: -------------------------------------------------------------------------------- 1 | class List { 2 | private elements = []; 3 | private readOnly: boolean; 4 | private size: number; 5 | 6 | public add(element: object) { 7 | if (!this.readOnly) { 8 | const newSize = this.size + 1; 9 | 10 | if (newSize > this.elements.length) { 11 | const newElements = []; 12 | newElements.length = this.elements.length + 10; 13 | 14 | for (let i = 0; i < this.size; i++) { 15 | newElements[i] = this.elements[i]; 16 | } 17 | this.elements = newElements; 18 | } 19 | 20 | this.elements[this.size++] = element; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Composed 7.1/refactoring/List.ts: -------------------------------------------------------------------------------- 1 | class List { 2 | private elements = []; 3 | private readOnly: boolean; 4 | private size: number; 5 | private GROWTH_INCREMENT = 10; 6 | 7 | public add(element: object) { 8 | if (this.readOnly) return; 9 | 10 | if (this.atCapacity()) { 11 | this.grow(); 12 | } 13 | 14 | this.addElement(element); 15 | } 16 | public addElement(element) { 17 | this.elements[this.size++] = element; 18 | } 19 | 20 | private grow() { 21 | const newElements = []; 22 | newElements.length = this.elements.length * this.GROWTH_INCREMENT; 23 | 24 | for (let i = 0; i < this.size; i++) { 25 | newElements[i] = this.elements[i]; 26 | } 27 | this.elements = newElements; 28 | } 29 | 30 | private atCapacity() { 31 | const newSize = this.size + 1; 32 | return newSize > this.elements.length; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Decorator 7.3/README.md: -------------------------------------------------------------------------------- 1 | # 将装饰功能搬移到 Decorator 2 | 3 | - 实现 4 | 5 | - 将装饰代码搬移到 Decorator 6 | 7 | - 动机 8 | 9 | - 添加新功能时,会使原有类的核心职责或主要行为变复杂,而这些新东西只在特定情况下才执行 10 | 11 | - 优点 12 | 13 | - 把装饰功能从类中搬移去除,从而简化了类 14 | - 有效地把类的核心职责和装饰功能区分开来 15 | - 可以去除几个相关类中重复的装饰逻辑 16 | 17 | - 缺点 18 | 19 | - 改变了被装饰对象的对象类型 20 | - 会使代码变得更难理解和调试 21 | - 当 Decorator 组合产生负面影响的时候,会增加设计的复杂度 22 | 23 | - Decorator 和 Strategy 的对比 24 | 25 | - 相同点 26 | 27 | - 都可以去除与特殊情况或选择性行为相关联的条件逻辑 28 | - 都通过把这些行为从原来的类搬移到一个或多个新类中达到这一目的 29 | 30 | - 不同点 31 | 32 | - Decorator 33 | 34 | - 把自己包装在一个对象之外 35 | 36 | - Strategy 37 | 38 | - 用在一个对象当中 39 | 40 | - 选择用哪一个? 41 | 42 | - Strategy 43 | 44 | - 实例可共享 45 | - 可以随意定义自己的接口 46 | - 使用 Strategy 必须知道他们的存在、了解他们 47 | - 对包含很多数据或实现很多公共方法的类使用一个或多个 Strategy 很常见 48 | 49 | - Decorator 50 | 51 | - 不能共享实例 52 | - 接口必须与它所装饰的类的接口一致 53 | - 可以透明地为多个不同的类添加行为 54 | - 对包含很多数据或实现很多公共方法的类使用 Decorator 会变得很笨重 55 | 56 | - 做法 57 | 58 | - 确定或创建包装类型、接口或类,它声明了客户代码需要的被装饰类的公共方法 59 | - 找到为被修饰类添加装饰功能的条件逻辑,并应用用多态替换条件式重构去除这些逻辑 60 | - 步骤(2)产生了被修饰类的一个或多个子类。应用用委托替换集成重构把这些子类转换成委托类 61 | 62 | - 使每个委托类都实现包装类型 63 | - 把委托类的委托字段的类型声明为包装类型 64 | - 决定装饰代码在委托类调用委托之前还是之后执行 -------------------------------------------------------------------------------- /Decorator 7.3/normal/NodeReader.ts: -------------------------------------------------------------------------------- 1 | import Parser from "./Parser"; 2 | 3 | 4 | export default class NodeReader { 5 | private parser: Parser; 6 | constructor() { 7 | this.parser = new Parser(); 8 | } 9 | /** 10 | * getParser 11 | */ 12 | public getParser():Parser { 13 | return this.parser; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Decorator 7.3/normal/Parser.ts: -------------------------------------------------------------------------------- 1 | export default class Parser { 2 | private _shouldDecodeNodes: boolean = false; 3 | private _shouldRemoveEscapeCharacters:boolean = false; 4 | 5 | public setShouldDecoding(shouldDecodeNodes: boolean) { 6 | this._shouldDecodeNodes = shouldDecodeNodes; 7 | } 8 | 9 | public setShouldRemoveEscapeCharacters(shouldRemoveEscapeCharacters:boolean){ 10 | this._shouldRemoveEscapeCharacters = shouldRemoveEscapeCharacters; 11 | } 12 | 13 | public shouldRemoveEscapeCharacters(){ 14 | return this._shouldRemoveEscapeCharacters; 15 | } 16 | 17 | /** 18 | * shouldDecodeNodes 19 | */ 20 | public shouldDecodeNodes() { 21 | return this._shouldDecodeNodes; 22 | } 23 | 24 | /** 25 | * createParser 26 | */ 27 | public static createParser() {} 28 | 29 | public setNodeDecoding() {} 30 | 31 | /** 32 | * elements 33 | */ 34 | public elements() {} 35 | } 36 | -------------------------------------------------------------------------------- /Decorator 7.3/normal/ParserUtils.ts: -------------------------------------------------------------------------------- 1 | export default class ParserUtils { 2 | public static removeEscapeCharacters(str) { 3 | return str; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /Decorator 7.3/normal/StringBuffer.ts: -------------------------------------------------------------------------------- 1 | export default class StringBuffer { 2 | public toString() { 3 | return ""; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /Decorator 7.3/normal/StringNode.ts: -------------------------------------------------------------------------------- 1 | import Translate from "./Translate"; 2 | import StringBuffer from "./StringBuffer"; 3 | import ParserUtils from "./ParserUtils"; 4 | 5 | export default class StringNode { 6 | private shouldDecode: boolean = false; 7 | private shouldRemoveEscapeCharacters: boolean = false; 8 | private textBuffer: StringBuffer; 9 | constructor( 10 | textBuffer: StringBuffer, 11 | textBegin, 12 | textEnd, 13 | shouldDecode, 14 | shouldRemoveEscapeCharacters 15 | ) { 16 | this.textBuffer = textBuffer; 17 | this.shouldDecode = shouldDecode; 18 | this.shouldRemoveEscapeCharacters = shouldRemoveEscapeCharacters; 19 | } 20 | 21 | public toPlainTextString() { 22 | let result = this.textBuffer.toString(); 23 | 24 | if (this.shouldDecode) { 25 | result = Translate.decode(result); 26 | } 27 | 28 | if (this.shouldRemoveEscapeCharacters) { 29 | result = ParserUtils.removeEscapeCharacters(result); 30 | } 31 | return result; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Decorator 7.3/normal/StringParser.ts: -------------------------------------------------------------------------------- 1 | import StringNode from "./StringNode"; 2 | import NodeReader from "./NodeReader"; 3 | import StringBuffer from "./StringBuffer"; 4 | 5 | export default class StringParser { 6 | public find(reader: NodeReader, input, position, balance_quotes) { 7 | const textBegin = ""; 8 | const textEnd = ""; 9 | const textBuffer = new StringBuffer(); 10 | return new StringNode( 11 | textBuffer, 12 | textBegin, 13 | textEnd, 14 | reader.getParser().shouldDecodeNodes(), 15 | reader.getParser().shouldRemoveEscapeCharacters() 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Decorator 7.3/normal/Translate.ts: -------------------------------------------------------------------------------- 1 | import StringNode from "./StringNode"; 2 | 3 | export default class Translate { 4 | static decode(str): string { 5 | return str 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Factory 6.2/README.md: -------------------------------------------------------------------------------- 1 | 2 | ### 将创建知识搬移到 Factory 3 | 4 | - Factory 定义 5 | 6 | - 实现一个或多个 Creation Method 的类就称为 Factory 7 | 8 | - 动机 9 | 10 | - 当创建一个对象的知识散布在多个类中 11 | - 创建蔓延问题 12 | 13 | - 将创建的职责放在了不应该承担对象创建任务的类中 14 | 15 | - 实现 16 | 17 | - 使用一个类封装创建逻辑和客户代码的实例化/配置选项 18 | - 客户代码可以告诉 Factory 实例如何实例化/配置一个对象 19 | - 然后用同一个 Factory 实例在运行时执行实例化/配置 20 | 21 | - 优点 22 | 23 | - 合并创建逻辑和实例化/配置选项 24 | - 将客户代码与创建逻辑解耦 25 | 26 | - 缺点 27 | 28 | - 如果可以直接实例化,会使设计复杂化 29 | 30 | - 做法 31 | 32 | - 以下做法假设 Factory 将实现一个类,而不是类要实现的接口 33 | 34 | - 1. 实例化类就是一个与其他类合作实例化产品(即某个类的实例)的类 35 | 36 | - 需要用 Creation Method 来实例化产品 37 | 38 | - 2. 创建一个将成为工厂的新类,根据工厂所创建的产品给它命名 39 | 40 | - 3. 应用搬移方法重构将 Creation Method 搬移到工厂类中 41 | 42 | - 如果 Creation Method 是静态的,需要改为非静态的 43 | 44 | - 4. 将实例化类更新为实例化工厂对象,并调用工厂对象获取类的实例 45 | 46 | - 5. 在实例化中仍然使用其他类的数据和方法。 47 | 48 | - 将可用的任何东西搬移到工厂类中,这样它就能够尽可能多地处理创建工作 49 | 50 | - 扩展 51 | 52 | - 如果创建逻辑过于复杂,就应该将其改为 Abstract Factory 模式 53 | 54 | - 支持太多创建选项 55 | - 视频 56 | 57 | [重构与模式 - Factory](https://www.bilibili.com/video/BV1X7411S7ho/) -------------------------------------------------------------------------------- /Factory 6.2/normal/DecodingStringNode.ts: -------------------------------------------------------------------------------- 1 | export default class DecodingStringNode { 2 | public text: string; 3 | public type: string; 4 | constructor(text) { 5 | this.type = "decoding"; 6 | this.text = text; 7 | } 8 | } -------------------------------------------------------------------------------- /Factory 6.2/normal/Parser.ts: -------------------------------------------------------------------------------- 1 | import StringParser from "./StringParser" 2 | export default class Parser { 3 | private _stringNodeDecoding: boolean; 4 | private _removeEscapeCharacters: boolean; 5 | private _stringParser:StringParser 6 | constructor() { 7 | this._stringParser = new StringParser(this) 8 | } 9 | public shouldDecode() { 10 | return this._stringNodeDecoding; 11 | } 12 | public shouldRemoveEscapeCharacters() { 13 | return this._removeEscapeCharacters; 14 | } 15 | public setStringNodeDecoding(val) { 16 | this._stringNodeDecoding = val; 17 | } 18 | public setRemoveEscapeCharacters(val) { 19 | this._removeEscapeCharacters = val; 20 | } 21 | public parse(url) { 22 | return this._stringParser.findString(url) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Factory 6.2/normal/StringNode.ts: -------------------------------------------------------------------------------- 1 | import DecodingStringNode from "./DecodingStringNode"; 2 | 3 | export default class StringNode { 4 | public text: string; 5 | public type: string; 6 | constructor(text) { 7 | this.type = "defualt"; 8 | this.text = text; 9 | } 10 | 11 | public static createStringNode( 12 | text: string, 13 | shouldDecode: boolean, 14 | shouldRemoveEscapeCharacters: boolean 15 | ) { 16 | if (shouldDecode) { 17 | return new DecodingStringNode(text); 18 | } 19 | return new StringNode(text); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Factory 6.2/normal/StringParser.ts: -------------------------------------------------------------------------------- 1 | import Parser from "./Parser"; 2 | import StringNode from "./StringNode"; 3 | export default class StringParser { 4 | private _parser: Parser; 5 | constructor(parser) { 6 | this._parser = parser; 7 | } 8 | 9 | public findString(url) { 10 | return StringNode.createStringNode( 11 | url, 12 | this._parser.shouldDecode(), 13 | this._parser.shouldRemoveEscapeCharacters() 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Factory 6.2/normal/index.ts: -------------------------------------------------------------------------------- 1 | import Parser from "./Parser" 2 | 3 | const parser = new Parser() 4 | parser.setStringNodeDecoding(true) 5 | const node = parser.parse("cuixiaorui") 6 | console.log(node.text) 7 | console.log(node.type) 8 | 9 | -------------------------------------------------------------------------------- /Factory 6.2/refactoring/DecodingStringNode.ts: -------------------------------------------------------------------------------- 1 | export default class DecodingStringNode { 2 | public text: string; 3 | public type: string; 4 | constructor(text) { 5 | this.type = "decoding"; 6 | this.text = text; 7 | } 8 | } -------------------------------------------------------------------------------- /Factory 6.2/refactoring/NodeFactory.ts: -------------------------------------------------------------------------------- 1 | import DecodingStringNode from "./DecodingStringNode"; 2 | import StringNode from "./StringNode"; 3 | 4 | export default class NodeFactory { 5 | private _stringNodeDecoding: boolean; 6 | private _removeEscapeCharacters: boolean; 7 | 8 | public shouldDecode() { 9 | return this._stringNodeDecoding; 10 | } 11 | public shouldRemoveEscapeCharacters() { 12 | return this._removeEscapeCharacters; 13 | } 14 | public setStringNodeDecoding(val) { 15 | this._stringNodeDecoding = val; 16 | } 17 | public setRemoveEscapeCharacters(val) { 18 | this._removeEscapeCharacters = val; 19 | } 20 | 21 | public createStringNode(text: string) { 22 | if (this.shouldDecode()) { 23 | return new DecodingStringNode(text); 24 | } 25 | return new StringNode(text); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Factory 6.2/refactoring/Parser.ts: -------------------------------------------------------------------------------- 1 | import StringParser from "./StringParser" 2 | import NodeFactory from "./NodeFactory"; 3 | export default class Parser { 4 | private _stringParser:StringParser 5 | constructor() { 6 | this._stringParser = new StringParser(this) 7 | } 8 | public parse(url) { 9 | return this._stringParser.findString(url) 10 | } 11 | 12 | public getNodeFactory(){ 13 | return this._nodeFactory 14 | } 15 | 16 | private _nodeFactory: NodeFactory 17 | public setNodeFactory(option:NodeFactory){ 18 | this._nodeFactory = option; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Factory 6.2/refactoring/StringNode.ts: -------------------------------------------------------------------------------- 1 | export default class StringNode { 2 | public text: string; 3 | public type: string; 4 | constructor(text) { 5 | this.type = "defualt"; 6 | this.text = text; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Factory 6.2/refactoring/StringParser.ts: -------------------------------------------------------------------------------- 1 | import Parser from "./Parser"; 2 | export default class StringParser { 3 | private _parser: Parser; 4 | constructor(parser) { 5 | this._parser = parser; 6 | } 7 | 8 | public findString(url) { 9 | const nodeFactory = this._parser.getNodeFactory(); 10 | return nodeFactory.createStringNode(url); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Factory 6.2/refactoring/index.ts: -------------------------------------------------------------------------------- 1 | import Parser from "./Parser" 2 | import NodeFactory from "./NodeFactory" 3 | 4 | 5 | 6 | const decodeNodes = new NodeFactory() 7 | decodeNodes.setStringNodeDecoding(true) 8 | 9 | const parser = new Parser() 10 | parser.setNodeFactory(decodeNodes) 11 | const node = parser.parse("cuixiaorui") 12 | console.log(node.text) 13 | console.log(node.type) -------------------------------------------------------------------------------- /Factory 6.3/README.md: -------------------------------------------------------------------------------- 1 | ### 用 Factory 封装类 2 | 3 | - 实现 4 | 5 | - 把类的构造函数声明为非公共的,并通过 Factory 来创建它们的实例 6 | - 赋予这个 Factory 创建和返回实现了同一结构的类的实例的能力 7 | 8 | - 动机 9 | 10 | - 客户代码不需要知道这些类的存在 11 | - 这些类都实现同一个接口,并且不太会发生改变 12 | 13 | - 优点 14 | 15 | - 通过意图导向的 Creation Method 简化了不同种类实例的创建 16 | - 通过隐藏不需要公开的类减少了包结构的 “概念重量” 17 | - 帮助严格执行“面向接口编程,而不是面向实现”这一格言 18 | 19 | - 缺点 20 | 21 | - 当需要创建新种类的实例时,必须新建/更新 Creation Method 22 | - 当客户只能获得 Factory 的二进制代码而无法获得源码时,对 Factory 的指定将受到限制 23 | 24 | - 做法 25 | 26 | - 1. 找到调用类的构造函数来创建实例的一段客户代码。对构造函数调用应用提炼方法重构,生成一个公共、静态的方法。这个新方法就是一个 Creation Method。然后应用搬移方法将 Creation Method 搬移到包含所选构造函数的类的超类中 27 | - 2. 找出调用所选构造函数来创建相同实例的所有代码,将它们更新为调用 Creation Method 28 | - 3. 对可能使用类的构造函数创建的所有类型的实例重复步骤(1)和步骤(2) 29 | - 4. 把类的构造函数声明为非公共 30 | - 5. 对所有需要封装的类重构步骤(1)~步骤(4) 31 | 32 | - 视频 33 | 34 | [重构与模式 - 用 Factory 封装类](https://www.bilibili.com/video/BV197411U7bx/) 35 | -------------------------------------------------------------------------------- /Factory 6.3/normal/AttributeDescriptor.ts: -------------------------------------------------------------------------------- 1 | import DefaultDescriptor from "./DefaultDescriptor"; 2 | 3 | // 所有属性描述符的超类 4 | export default class AttributeDescriptor { 5 | private field: string; 6 | private type: string; 7 | protected constructor(field: string, type: string) { 8 | this.field = field; 9 | this.type = type; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Factory 6.3/normal/BooleanDescriptor.ts: -------------------------------------------------------------------------------- 1 | import AttributeDescriptor from "./AttributeDescriptor"; 2 | 3 | export default class BooleanDescriptor extends AttributeDescriptor { 4 | constructor(field,type) { 5 | super(field,type); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Factory 6.3/normal/DefaultDescriptor.ts: -------------------------------------------------------------------------------- 1 | import AttributeDescriptor from "./AttributeDescriptor"; 2 | 3 | export default class DefaultDescriptor extends AttributeDescriptor { 4 | constructor(field, type) { 5 | super(field, type); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Factory 6.3/normal/NumberDescriptor.ts: -------------------------------------------------------------------------------- 1 | import AttributeDescriptor from "./AttributeDescriptor"; 2 | 3 | export default class NumberDescriptor extends AttributeDescriptor { 4 | constructor(field,type) { 5 | super(field,type); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Factory 6.3/normal/StringDescriptor.ts: -------------------------------------------------------------------------------- 1 | import AttributeDescriptor from "./AttributeDescriptor"; 2 | 3 | export default class StringDescriptor extends AttributeDescriptor { 4 | constructor(field,type) { 5 | super(field,type); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Factory 6.3/normal/index.ts: -------------------------------------------------------------------------------- 1 | import NumberDescriptor from "./NumberDescriptor"; 2 | import BooleanDescriptor from "./BooleanDescriptor"; 3 | import StringDescriptor from "./StringDescriptor"; 4 | import DefaultDescriptor from "./DefaultDescriptor"; 5 | 6 | // 基于一段实现对象-关系数据库映射的代码逻辑,用来从关系型数据库中读取对象并把对象写入关系型数据库 7 | // 客户端代码 8 | function createAttributeDescriptors() { 9 | const result = []; 10 | result.push(new DefaultDescriptor("count", "user")); 11 | result.push(new DefaultDescriptor("time", "Date")); 12 | result.push(new NumberDescriptor("remoteId", "number")); 13 | result.push(new BooleanDescriptor("isPass", "boolean")); 14 | result.push(new StringDescriptor("createdBy", "string")); 15 | return result; 16 | } 17 | 18 | const descriptors = createAttributeDescriptors(); 19 | console.log(descriptors); 20 | -------------------------------------------------------------------------------- /Factory 6.3/refactoring/AttributeDescriptor.ts: -------------------------------------------------------------------------------- 1 | import NumberDescriptor from "./NumberDescriptor"; 2 | import BooleanDescriptor from "./BooleanDescriptor"; 3 | import StringDescriptor from "./StringDescriptor"; 4 | import DefaultDescriptor from "./DefaultDescriptor"; 5 | 6 | // 所有属性描述符的超类 7 | export default class AttributeDescriptor { 8 | private field: string; 9 | private type: string; 10 | protected constructor(field: string, type: string) { 11 | this.field = field; 12 | this.type = type; 13 | } 14 | 15 | public static forNumber(field): AttributeDescriptor { 16 | return new NumberDescriptor(field, "number"); 17 | } 18 | 19 | public static forBoolean(field): AttributeDescriptor { 20 | return new BooleanDescriptor(field, "boolean"); 21 | } 22 | 23 | public static forString(field): AttributeDescriptor { 24 | return new StringDescriptor(field,"string"); 25 | } 26 | 27 | public static forUser(field): AttributeDescriptor { 28 | return new DefaultDescriptor(field,"user"); 29 | } 30 | 31 | public static forDate(field): AttributeDescriptor { 32 | return new DefaultDescriptor(field,"date"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Factory 6.3/refactoring/BooleanDescriptor.ts: -------------------------------------------------------------------------------- 1 | import AttributeDescriptor from "./AttributeDescriptor"; 2 | 3 | export default class BooleanDescriptor extends AttributeDescriptor { 4 | constructor(field,type) { 5 | super(field,type); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Factory 6.3/refactoring/DefaultDescriptor.ts: -------------------------------------------------------------------------------- 1 | import AttributeDescriptor from "./AttributeDescriptor"; 2 | 3 | export default class DefaultDescriptor extends AttributeDescriptor { 4 | constructor(field, type) { 5 | super(field, type); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Factory 6.3/refactoring/NumberDescriptor.ts: -------------------------------------------------------------------------------- 1 | import AttributeDescriptor from "./AttributeDescriptor"; 2 | 3 | export default class NumberDescriptor extends AttributeDescriptor { 4 | public constructor(field,type) { 5 | super(field,type); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Factory 6.3/refactoring/StringDescriptor.ts: -------------------------------------------------------------------------------- 1 | import AttributeDescriptor from "./AttributeDescriptor"; 2 | 3 | export default class StringDescriptor extends AttributeDescriptor { 4 | constructor(field,type) { 5 | super(field,type); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Factory 6.3/refactoring/index.ts: -------------------------------------------------------------------------------- 1 | import AttributeDescriptor from "./AttributeDescriptor"; 2 | 3 | // 基于一段实现对象-关系数据库映射的代码逻辑,用来从关系型数据库中读取对象并把对象写入关系型数据库 4 | // 客户端代码 5 | function createAttributeDescriptors() { 6 | const result = []; 7 | result.push(AttributeDescriptor.forUser("count")); 8 | result.push(AttributeDescriptor.forDate("time")); 9 | result.push(AttributeDescriptor.forNumber("remoteId")); 10 | result.push(AttributeDescriptor.forBoolean("isPass")); 11 | result.push(AttributeDescriptor.forString("createdBy")); 12 | return result; 13 | } 14 | 15 | const descriptors = createAttributeDescriptors(); 16 | console.log(descriptors); 17 | -------------------------------------------------------------------------------- /Factory 6.4/README.md: -------------------------------------------------------------------------------- 1 | 2 | ### 用 Factory Method 引入多态创建 3 | 4 | - 前提条件 5 | 6 | - 类必须实现一个静态或非静态的方法来初始化并返回一个对象 7 | - 形成 Factory Method 模式 8 | 9 | - 用来标识 Factory Method 实现者可能实例化并返回的类集合的类型 10 | - 实现这一类型的类集合 11 | - 实现 Factory Method 的类,它们在本地决定实例化、初始化并返回哪些类 12 | 13 | - 动机 14 | 15 | - 一个层次中的类都相似的实现一个方法,只是对象创建的步骤不同 16 | - 当兄弟子类实现了除对象创建步骤外都很相似的方法时 17 | - 当超类和子类实现了除对象创建步骤外都很相似的方法时 18 | 19 | - 优点 20 | 21 | - 减少因创建自定义对象而产生的重复代码 22 | - 有效地表达了对象创建发生的位置,以及如何重写对象的创建 23 | - 强制 Factory Method 使用的类必须实现统一的类型 24 | 25 | - 缺点 26 | 27 | - 可能会向 Factory Method 的一些实现者传递不必要的参数 28 | 29 | - 做法 30 | 31 | - 在创建代码上应用提炼方法重构或把创建代码改为对已经存在的实例化方法的调用 32 | 33 | - 给实例化方法起一个通用的名字(createBuilder、newProduct) 34 | 35 | - 确保实例化方法的返回值类型是兄弟子类中相似方法创建的对象的通用类型 36 | 37 | - 对所有兄弟子类中的相似方法重复步骤(1) 38 | 39 | - 这回在每个兄弟子类中都生成一个实例化方法,而且这些实例化方法的签名应该是一致的 40 | 41 | - 修改兄弟子类的超类 42 | 43 | - 如果不能修改这个超类或者不希望修改它,应用提炼超类重构来产生一个集成兄弟子类原超类的新超类 44 | 45 | - 在相似方法上应用形成 Template Method 重构 46 | - 如果兄弟子类中存在其他的、能够从调用之前产生的工厂方法中获益的相似方法,重复步骤(1)~步骤(4) 47 | - 如果大多数 ConcreteCreator 中的工厂方法都包含相同的实例化代码,上移这些代码,把超类中声明的抽象方法改成具体的工厂方法,并使它执行默认的实例化行为 -------------------------------------------------------------------------------- /Factory 6.4/normal/DOMBuilder.ts: -------------------------------------------------------------------------------- 1 | import IOutputBuilder from "./IOutputBuilder"; 2 | export default class DOMBuilder implements IOutputBuilder { 3 | constructor(rootName: string) {} 4 | 5 | public addBelow(str: string) {} 6 | 7 | public addAbove(str: string) {} 8 | } 9 | -------------------------------------------------------------------------------- /Factory 6.4/normal/DOMBuilderTest.ts: -------------------------------------------------------------------------------- 1 | import TestCase from "./TestCase"; 2 | import OutputBuild from "./IOutputBuilder"; 3 | import DOMBuilder from "./DOMBuilder"; 4 | export default class DOMBuilderTest extends TestCase{ 5 | private builder: OutputBuild; 6 | public testAddAboveRoot() { 7 | 8 | this.builder = new DOMBuilder("orders"); 9 | this.builder.addBelow("order"); 10 | 11 | try { 12 | this.builder.addAbove("customer"); 13 | throw new Error("expecting java.lang.RuntimeException"); 14 | } catch (e) { 15 | console.log(e); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Factory 6.4/normal/IOutputBuilder.ts: -------------------------------------------------------------------------------- 1 | export default interface OutputBuild{ 2 | addBelow(str:string); 3 | addAbove(str:string) 4 | } -------------------------------------------------------------------------------- /Factory 6.4/normal/TestCase.ts: -------------------------------------------------------------------------------- 1 | // 模拟 jUnit 的 TestCase 2 | export default class TestCase {} -------------------------------------------------------------------------------- /Factory 6.4/normal/XMLBuilder.ts: -------------------------------------------------------------------------------- 1 | import IOutputBuilder from "./IOutputBuilder"; 2 | export default class XMLBuilder implements IOutputBuilder { 3 | constructor(rootName:string) { 4 | 5 | } 6 | public addBelow(str: string) {} 7 | 8 | public addAbove(str: string) {} 9 | } 10 | -------------------------------------------------------------------------------- /Factory 6.4/normal/XMLBuilderTest.ts: -------------------------------------------------------------------------------- 1 | import TestCase from "./TestCase"; 2 | import OutputBuild from "./IOutputBuilder"; 3 | import XMLBuilder from "./XMLBuilder"; 4 | 5 | export default class XMLBuilderTest extends TestCase { 6 | private builder: OutputBuild; 7 | public testAddAboveRoot() { 8 | 9 | this.builder = new XMLBuilder("orders"); 10 | this.builder.addBelow("order"); 11 | 12 | try { 13 | this.builder.addAbove("customer"); 14 | throw new Error("expecting java.lang.RuntimeException"); 15 | } catch (e) { 16 | console.log(e); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Factory 6.4/refactoring/AbstractBuilderTest.ts: -------------------------------------------------------------------------------- 1 | import TestCase from "./TestCase"; 2 | import IOutputBuild from "./IOutputBuilder"; 3 | 4 | export default abstract class AbstractBuilderTest extends TestCase { 5 | protected builder: IOutputBuild; 6 | 7 | protected abstract createBuilder(rootName: string): IOutputBuild; 8 | 9 | public testAddAboveRoot() { 10 | this.builder = this.createBuilder("orders"); 11 | this.builder.addBelow("order"); 12 | 13 | try { 14 | this.builder.addAbove("customer"); 15 | throw new Error("expecting java.lang.RuntimeException"); 16 | } catch (e) { 17 | console.log(e); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Factory 6.4/refactoring/DOMBuilder.ts: -------------------------------------------------------------------------------- 1 | import IOutputBuilder from "./IOutputBuilder"; 2 | export default class DOMBuilder implements IOutputBuilder { 3 | constructor(rootName: string) {} 4 | 5 | public addBelow(str: string) {} 6 | 7 | public addAbove(str: string) {} 8 | } 9 | -------------------------------------------------------------------------------- /Factory 6.4/refactoring/DOMBuilderTest.ts: -------------------------------------------------------------------------------- 1 | import DOMBuilder from "./DOMBuilder"; 2 | import AbstractBuilderTest from "./AbstractBuilderTest"; 3 | export default class DOMBuilderTest extends AbstractBuilderTest { 4 | protected createBuilder(rootName: string) { 5 | return new DOMBuilder(rootName); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Factory 6.4/refactoring/IOutputBuilder.ts: -------------------------------------------------------------------------------- 1 | export default interface OutputBuild{ 2 | addBelow(str:string); 3 | addAbove(str:string) 4 | } -------------------------------------------------------------------------------- /Factory 6.4/refactoring/TestCase.ts: -------------------------------------------------------------------------------- 1 | // 模拟 jUnit 的 TestCase 2 | export default class TestCase {} -------------------------------------------------------------------------------- /Factory 6.4/refactoring/XMLBuilder.ts: -------------------------------------------------------------------------------- 1 | import IOutputBuilder from "./IOutputBuilder"; 2 | export default class XMLBuilder implements IOutputBuilder { 3 | constructor(rootName: string) {} 4 | 5 | public addBelow(str: string) {} 6 | 7 | public addAbove(str: string) {} 8 | } 9 | -------------------------------------------------------------------------------- /Factory 6.4/refactoring/XMLBuilderTest.ts: -------------------------------------------------------------------------------- 1 | import XMLBuilder from "./XMLBuilder"; 2 | import AbstractBuilderTest from "./AbstractBuilderTest"; 3 | 4 | export default class XMLBuilderTest extends AbstractBuilderTest { 5 | createBuilder(rootName) { 6 | return new XMLBuilder(rootName); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 重构与模式 2 | 3 | ## 写作缘由 4 | 5 | ### 过度设计 6 | 7 | - 指代码的灵活性和复杂性超出所需 8 | 9 | ### 设计不足 10 | 11 | - 指所开发的软件设计不良 12 | 13 | ### 测试驱动开发和持续重构 14 | 15 | - 编程对话 16 | 17 | - 问:编写一个测试,想系统提问 18 | 19 | - 红 20 | 21 | - 答:编写代码通过这个测试,回答这一提问 22 | 23 | - 绿 24 | 25 | - 提炼: 通过合并概念、消除歧义,提炼你的回答 26 | 27 | - 重构 28 | 29 | - 反复: 提出下一个问题,继续进行对话 30 | 31 | - 优点 32 | 33 | - 保持较低的缺陷数量 34 | - 大胆地进行重构 35 | - 得到更加简单、更加优秀的代码 36 | - 编程时没有压力 37 | 38 | ### 重构与模式 39 | 40 | - 模式是重构的目的地 41 | - 重构是抵达这个目的地的道路 42 | 43 | ### 演进式设计 44 | 45 | - 学习了解优秀软件设计的演变过程比学习优秀设计本身更有价值 46 | - 测试驱动开发和持续重构是演进式设计的关键实践 47 | 48 | ## 重构 49 | 50 | ### 何谓重构 51 | 52 | - 重构 53 | 54 | - 保持行为的转换 55 | - 重构是一种对软件内部结构的改善,目的是在不改变软件的可见行为的情况下,使其更易理解,修改成本更低 56 | 57 | - 重构过程 58 | 59 | - 去除重复、简化复杂逻辑和澄清模糊的代码 60 | 61 | ### 重构的动机 62 | 63 | - 使新代码的增加更容易 64 | - 改善既有代码的设计 65 | - 对代码理解更透彻 66 | - 提高编程的趣味性 67 | 68 | ### 众目睽睽 69 | 70 | - 所有优秀的著述,都是不断修改而成的 71 | 72 | - 修改意味着重新审视 73 | 74 | - 要得到最佳的重构结果,需要多人帮助 75 | 76 | - 极限编程采用结对编程和代码集体所有的原因之一 77 | 78 | ### 可读性好的代码 79 | 80 | - 任何傻瓜都会编写计算机能理解的代码。好的程序员能够编写人能够理解的代码 81 | 82 | ### 保持清晰 83 | 84 | - 保持代码清晰类似于保持房间清洁 85 | - 方法 86 | 87 | - 持续地去除重复 88 | - 简化 89 | - 澄清代码 90 | - 不能容忍代码脏乱 91 | 92 | ### 循序渐进 93 | 94 | - 采取更小、更安全的步骤比采取更大的步骤更能快速达到目标 95 | 96 | ### 设计欠账 97 | 98 | - 指无法做如下三件事 99 | 100 | - 去除重复 101 | - 简化代码 102 | - 澄清代码的意图 103 | 104 | - 向上沟通 105 | 106 | - 使用欠账这样的金融隐喻来讨论技术问题 107 | 108 | ### 演变出新的架构 109 | 110 | - 应用需求驱动框架 111 | - 通过重构,持续改进应用程序和框架 112 | 113 | ### 复合重构 114 | 115 | - 由多个低层次重构组成的高层次重构 116 | 117 | - 所完成的许多工作都涉及代码的搬移 118 | 119 | - 测试是复合重构不可分割的一部分 120 | 121 | ### 测试驱动的重构 122 | 123 | - 应用测试驱动开发得到替换代码,然后将老代码替换为新代码 124 | 125 | - 同事保留并重新运行老代码的测试 126 | 127 | - 应用场景 128 | 129 | - 替换算法 130 | 131 | ### 复合重构的优点 132 | 133 | - 描述了重构顺序的完整计划 134 | 135 | - 更安全 136 | - 更有效 137 | - 更高效 138 | 139 | - 能够提示不明显的设计方向 140 | 141 | - 复合重构引导你从源头走到目的地 142 | 143 | - 促进对实现模式的深入思考 144 | 145 | - 思考模式的各种实现方案是大有收益的 146 | 147 | ## 模式 148 | 149 | ### 何谓模式 150 | 151 | - 三部分组成的规则 152 | 153 | - 某一坏境、一个问题以及解决问题的方案之间的关系 154 | 155 | ### 模式痴迷 156 | 157 | - 指某人对模式过于痴迷,以至于无法不在代码中使用模式 158 | 159 | ### 实现模式的方式不止一种 160 | 161 | ### 通过重构实现、趋向和去除模式 162 | 163 | - 目的是获得更好的设计,而不是实现模式 164 | 165 | ### 模式是否会使代码更加复杂 166 | 167 | - 人们对模式的熟悉程度对于他们如何看待基于模式的重构将起决定性作用 168 | 169 | ### 模式知识 170 | 171 | - 读优秀的模式图书来学习 172 | - 建立学习小组 173 | 174 | ## 代码坏味 175 | 176 | ### 常见的设计问题 177 | 178 | - 重复 179 | - 不清晰 180 | - 复杂 181 | 182 | ### 十二种坏味道 183 | 184 | - 重复代码 185 | 186 | - 形成 Template Method 187 | - 用 Factory Method 引入多态创建 188 | - 链构造函数 189 | - 用 Composite 替换一/多之分 190 | - 提取 Composite 191 | - 通过 Adapter 统一接口 192 | - 引入 Null Object 193 | 194 | - 方法过长 195 | 196 | - 组合方法 197 | - 将聚集操作搬移到 Collecting Parameter 198 | - 用 Command 替换条件调度程序 199 | - 将聚集操作搬移到 Visitor 200 | - 用 Strategy 替换条件逻辑 201 | 202 | - 条件逻辑太复杂 203 | 204 | - 用 Strategy 替换条件逻辑 205 | - 将修饰功能搬移到 Decorator 206 | - 用 State 替换状态改变条件语句 207 | - 引入 Null Object 208 | 209 | - 基本类型迷恋 210 | 211 | - 用类替换类型代码 212 | - 用 State 替换状态改变条件语句 213 | - 用 Strategy 替换条件逻辑 214 | - 用 Composite 替换隐含树 215 | - 用 Interpreter 替换隐式语言 216 | - 将装饰功能搬移到 Decorator 217 | - 用 Builder 封装 Composite 218 | 219 | - 冗赘类 220 | 221 | - 内联 Singleton 222 | 223 | - 类过大 224 | 225 | - 用 Command 替换条件调度程度 226 | - 用 State 替换状态改变条件语句 227 | - 用 Interpreter 替换隐式语言 228 | 229 | - 分支语句 230 | 231 | - 用 Command 替换条件调度程度 232 | - 将聚集操作搬移到 Visitor 233 | 234 | - 组合爆炸 235 | 236 | - 用 interpreter 替换隐式语言 237 | 238 | - 怪异解决方案 239 | 240 | - 通过 Adapter 统一接口 241 | 242 | ## 创建 243 | 244 | ### 用 Creation Method 替换构造函数 245 | ### 将创建知识搬移到 Factory 246 | ### 用 Factory 封装类 247 | ### 用 Factory Method 引入多态创建 248 | ### 用 Builder 封装 Composite 249 | ### 内联 Singleton 250 | 251 | 252 | ## 简化 253 | 254 | ### 组合方法 Composed Method -------------------------------------------------------------------------------- /Singleton 6.6/README.md: -------------------------------------------------------------------------------- 1 | # 内联 Singleton 2 | 3 | - 实现 4 | 5 | - 把 Singleton 的功能搬移到一个保存并提供对象访问入口的类中。删除 Singleton 6 | 7 | - 动机 8 | 9 | - 当可以设计或重新设计而避免使用他们的时候,SIngleton 就是不必要的 10 | 11 | - 优点 12 | 13 | - 使对象的协作变得更明显和明确 14 | - 保护了单一的实例,且不需要特殊的代码 15 | 16 | - 缺点 17 | 18 | - 当在许多层次间传递对象实例比较困难的时候,会使设计变得复杂 19 | 20 | - 做法 21 | 22 | - 吸收类 23 | 24 | - 指承担着内联 Singleton 责任的类 25 | 26 | - 在吸收类中声明 Singleton 的公共方法,使这些新方法委托到 Singleton,出去任何可能存在的 “静态”声明 27 | 28 | - 如果吸收类本身是 Singleton ,那么保留方法的“静态”声明 29 | 30 | - 把客户代码中对 Singleton 的引用修改为对吸收类的引用 31 | - 应用搬移方法重构和搬移字段重构把 Singleton 中的所有功能都搬移到吸收类中 32 | - 删除 Singleton -------------------------------------------------------------------------------- /Singleton 6.6/normal/Blackjack.ts: -------------------------------------------------------------------------------- 1 | import ConsoleSingleton from "./ConsoleSingleton"; 2 | import HitStayResponse from "./HitStayResponse"; 3 | 4 | export default class Blackjack { 5 | private player: any; 6 | private dealer: any; 7 | 8 | constructor(params) { 9 | 10 | } 11 | 12 | public play() { 13 | this.deal(); 14 | 15 | this.writeln(this.player.getHandAsString()); 16 | this.writeln(this.dealer.getHandAsStringWithFirstCardDown()); 17 | 18 | let hitStayResponse; 19 | 20 | do { 21 | this.write("H)it or S)stay:"); 22 | hitStayResponse = ConsoleSingleton.obtainHitStayResponse(); 23 | this.write(hitStayResponse.toString()); 24 | if (hitStayResponse.shouldHit()) { 25 | this.dealCardTo(this.player); 26 | this.writeln(this.dealer.getHandAsStringWithFirstCardDown()); 27 | } 28 | } while (this.canPlayerHit(hitStayResponse)); 29 | } 30 | 31 | private dealCardTo(object: any) {} 32 | private deal() {} 33 | private writeln(object: any) {} 34 | private write(str: string) {} 35 | private canPlayerHit(hitStayResponse: HitStayResponse) { 36 | return true; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Singleton 6.6/normal/ConsoleSingleton.ts: -------------------------------------------------------------------------------- 1 | import HitStayResponse from "./HitStayResponse"; 2 | 3 | export default class ConsoleSingleton { 4 | private static hitStayResponse = new HitStayResponse(); 5 | 6 | public static obtainHitStayResponse(input?) { 7 | this.hitStayResponse.readFrom(input); 8 | return this.hitStayResponse; 9 | } 10 | 11 | public static setPlayerResponse(newHitStayResponse) { 12 | this.hitStayResponse = newHitStayResponse; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Singleton 6.6/normal/HitStayResponse.ts: -------------------------------------------------------------------------------- 1 | export default class HitStayResponse { 2 | public readFrom(input) {} 3 | } 4 | -------------------------------------------------------------------------------- /Singleton 6.6/normal/ScenarioTest.test.ts: -------------------------------------------------------------------------------- 1 | import ConsoleSingleton from "./ConsoleSingleton"; 2 | import TestAlwaysHitResponse from "./TestAlwaysHitResponse"; 3 | import Blackjack from "./Blackjack"; 4 | describe("ScenarioTest", () => { 5 | test("test dealer stands when player busts", () => { 6 | ConsoleSingleton.setPlayerResponse(new TestAlwaysHitResponse()); 7 | const deck = [10, 9, 7, 2, 6]; 8 | const blackjack = new Blackjack(deck); 9 | blackjack.play(); 10 | 11 | expect("dealer wins").toBeTruthy(blackjack.didDealerWin()) 12 | expect("player loses").toBeTruthy(!blackjack.didPlayerWin()) 13 | expect(11).toEqual(blackjack.getDealerTotal()) 14 | expect(23).toEqual(blackjack.getPlayerTotal()) 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /Singleton 6.6/normal/TestAlwaysHitResponse.ts: -------------------------------------------------------------------------------- 1 | import HitStayResponse from "./HitStayResponse"; 2 | 3 | export default class TestAlwaysHitResponse extends HitStayResponse {} 4 | -------------------------------------------------------------------------------- /Singleton 6.6/refactoring/Blackjack.ts: -------------------------------------------------------------------------------- 1 | import ConsoleSingleton from "./ConsoleSingleton"; 2 | import HitStayResponse from "./HitStayResponse"; 3 | 4 | export default class Blackjack { 5 | private player: any; 6 | private dealer: any; 7 | private hitStayResponse = new HitStayResponse(); 8 | constructor(params) {} 9 | 10 | public obtainHitStayResponse(input?) { 11 | this.hitStayResponse.readFrom(input); 12 | return this.hitStayResponse; 13 | } 14 | 15 | public setPlayerResponse(newHitStayResponse) { 16 | this.hitStayResponse = newHitStayResponse; 17 | } 18 | 19 | public play() { 20 | this.deal(); 21 | 22 | this.writeln(this.player.getHandAsString()); 23 | this.writeln(this.dealer.getHandAsStringWithFirstCardDown()); 24 | 25 | let hitStayResponse; 26 | 27 | do { 28 | this.write("H)it or S)stay:"); 29 | hitStayResponse = this.obtainHitStayResponse(); 30 | this.write(hitStayResponse.toString()); 31 | if (hitStayResponse.shouldHit()) { 32 | this.dealCardTo(this.player); 33 | this.writeln(this.dealer.getHandAsStringWithFirstCardDown()); 34 | } 35 | } while (this.canPlayerHit(hitStayResponse)); 36 | } 37 | 38 | private dealCardTo(object: any) {} 39 | private deal() {} 40 | private writeln(object: any) {} 41 | private write(str: string) {} 42 | private canPlayerHit(hitStayResponse: HitStayResponse) { 43 | return true; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Singleton 6.6/refactoring/HitStayResponse.ts: -------------------------------------------------------------------------------- 1 | export default class HitStayResponse { 2 | public readFrom(input) {} 3 | } 4 | -------------------------------------------------------------------------------- /Singleton 6.6/refactoring/ScenarioTest.test.ts: -------------------------------------------------------------------------------- 1 | import ConsoleSingleton from "./ConsoleSingleton"; 2 | import TestAlwaysHitResponse from "./TestAlwaysHitResponse"; 3 | import Blackjack from "./Blackjack"; 4 | describe("ScenarioTest", () => { 5 | test("test dealer stands when player busts", () => { 6 | ConsoleSingleton.setPlayerResponse(new TestAlwaysHitResponse()); 7 | const deck = [10, 9, 7, 2, 6]; 8 | const blackjack = new Blackjack(deck); 9 | blackjack.play(); 10 | 11 | expect("dealer wins").toBeTruthy(blackjack.didDealerWin()) 12 | expect("player loses").toBeTruthy(!blackjack.didPlayerWin()) 13 | expect(11).toEqual(blackjack.getDealerTotal()) 14 | expect(23).toEqual(blackjack.getPlayerTotal()) 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /Singleton 6.6/refactoring/TestAlwaysHitResponse.ts: -------------------------------------------------------------------------------- 1 | import HitStayResponse from "./HitStayResponse"; 2 | 3 | export default class TestAlwaysHitResponse extends HitStayResponse {} 4 | -------------------------------------------------------------------------------- /Strategy 7.2/README.md: -------------------------------------------------------------------------------- 1 | # 用 strategy 替换条件逻辑 2 | 3 | - 实现 4 | 5 | - 为每个变体创建一个 Strategy 并使方法把计算委托到 Strategy 实例 6 | 7 | - 动机 8 | 9 | - 复杂的条件逻辑导致复杂度上升时 10 | 11 | - 优点 12 | 13 | - 通过减少或去除条件逻辑使算法变得清晰易懂 14 | - 通过把算法的变体搬移到类层次中简化了类 15 | - 允许在运行时用一种算法替换另一种算法 16 | 17 | - 缺点 18 | 19 | - 当应用基于继承的解决方案或”简化条件表达式“中的重构更简单时,会增加设计的复杂度 20 | - 增加了算法如何获取或接受上下文类的数据的复杂度 21 | 22 | - 做法 23 | 24 | - 确定上下文,包含很多条件逻辑的计算方法所在的类 25 | - 创建一个策略类 26 | 27 | - 在类名后添加“Strategy” 28 | 29 | - 应用搬移方法重构把计算方法搬移到策略中 30 | 31 | - 上下文类中需要先暴露这个一个计算方法,把它委托到策略类中的计算方法 32 | - 传数据 33 | 34 | - 时机 35 | 36 | - 需要传入的参数较少 37 | 38 | - 优点 39 | 40 | - 只涉及上下文类与策略类的最小耦合 41 | 42 | - 缺点 43 | 44 | - 无论具体的策略类是否需要这些数据,都必须传入到每个具体策略类中 45 | 46 | - 传上下文类 47 | 48 | - 时机 49 | 50 | - 需要传入的参数比较多时 51 | 52 | - 缺点 53 | 54 | - 破坏信息隐藏 55 | 56 | - 优点 57 | 58 | - 在上下类添加了方法,所有的策略类中都可以使用 59 | 60 | - 使用提炼参数重构让客户用策略类的一个实例装配上下文类 61 | - 在策略类的计算方法上应用多态替换条件式重构 62 | 63 | - 使用子类替换类型代码重构 64 | 65 | - 声明多个子策略类 66 | 67 | - 尽量把策略类声明为抽象类 68 | 69 | - 子类重写 -------------------------------------------------------------------------------- /Strategy 7.2/normal/Loan.ts: -------------------------------------------------------------------------------- 1 | import RiskFactor from "./RiskFactor"; 2 | import UnusedRiskFactors from "./UnusedRiskFactors"; 3 | 4 | class Loan { 5 | // 承诺金额 6 | private commitment: any; 7 | // 未偿贷款 8 | private outstanding: any; 9 | // 有效日 10 | private expiry: any; 11 | // 到期日 12 | private maturity: any; 13 | // 风险评级 14 | private riskRating: any; 15 | 16 | private today: Date; 17 | private start: Date; 18 | 19 | private readonly MILLIS_PER_DAY: number; 20 | private readonly DAYS_PER_YEAR: number; 21 | 22 | private constructor( 23 | commitment, 24 | outstanding, 25 | start, 26 | expiry, 27 | maturity, 28 | riskRating 29 | ) { 30 | this.commitment = commitment; 31 | this.outstanding = outstanding; 32 | this.start = start; 33 | this.expiry = expiry; 34 | this.maturity = maturity; 35 | this.riskRating = riskRating; 36 | } 37 | 38 | public capital() { 39 | if (this.expiry === null && this.maturity !== null) { 40 | return this.commitment * this.duration() * this.riskFactor(); 41 | } 42 | 43 | if (this.expiry !== null && this.maturity === null) { 44 | if (this.getUnusedPercentage() !== 1) { 45 | return ( 46 | this.commitment * 47 | this.getUnusedPercentage() * 48 | this.duration() * 49 | this.riskFactor() 50 | ); 51 | } else { 52 | return ( 53 | this.outstandingRiskAmount() * this.duration() * this.riskFactor() + 54 | this.unusedRiskAmount() * this.duration() * this.unusedRiskFactor() 55 | ); 56 | } 57 | } 58 | return 0; 59 | } 60 | /** 61 | * 未用额度 62 | */ 63 | getUnusedPercentage() { 64 | return 1; 65 | } 66 | 67 | public duration() { 68 | if (this.expiry === null && this.maturity !== null) { 69 | return this.weightedAverageDuration(); 70 | } else if (this.expiry !== null && this.maturity === null) { 71 | return this.yearsTo(this.expiry); 72 | } 73 | return 0; 74 | } 75 | 76 | private yearsTo(endDate: Date) { 77 | const beginDate = this.today === null ? this.start : this.today; 78 | return ( 79 | (endDate.getTime() - beginDate.getTime()) / 80 | this.MILLIS_PER_DAY / 81 | this.DAYS_PER_YEAR 82 | ); 83 | } 84 | 85 | private riskFactor() { 86 | return RiskFactor.getFactors().forRating(this.riskRating); 87 | } 88 | 89 | private unusedRiskFactor() { 90 | return UnusedRiskFactors.getFactors().forRating(this.riskRating); 91 | } 92 | 93 | private weightedAverageDuration() { 94 | return 1; 95 | } 96 | 97 | /** 98 | * 未用风险金额 99 | */ 100 | private unusedRiskAmount() { 101 | return this.commitment - this.outstanding; 102 | } 103 | /** 104 | * 未清风险金额 105 | */ 106 | private outstandingRiskAmount() { 107 | return this.outstanding; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Strategy 7.2/normal/RiskFactor.ts: -------------------------------------------------------------------------------- 1 | export default class RiskFactor { 2 | public static getFactors() { 3 | return new RiskFactor() 4 | } 5 | 6 | public forRating(obj:any){ 7 | return 1 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Strategy 7.2/normal/UnusedRiskFactors.ts: -------------------------------------------------------------------------------- 1 | export default class UnusedRiskFactors { 2 | public static getFactors() { 3 | return new UnusedRiskFactors() 4 | } 5 | 6 | public forRating(obj:any){ 7 | return 1 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Strategy 7.2/refactoring/CapitalStrategy.ts: -------------------------------------------------------------------------------- 1 | import RiskFactor from "./RiskFactor"; 2 | import UnusedRiskFactors from "./UnusedRiskFactors"; 3 | import Loan from "./Loan"; 4 | export default abstract class CapitalStrategy { 5 | private readonly MILLIS_PER_DAY: number = 86400000; 6 | private readonly DAYS_PER_YEAR: number = 365; 7 | /** 8 | * capital 9 | */ 10 | public abstract capital(loan: Loan); 11 | 12 | public duration(loan: Loan) { 13 | return this.yearsTo(loan.getExpiry(), loan); 14 | } 15 | 16 | private yearsTo(endDate: Date, loan: Loan) { 17 | const beginDate = 18 | loan.getToday() === null ? loan.getStart() : loan.getToday(); 19 | return ( 20 | (endDate.getTime() - beginDate.getTime()) / 21 | this.MILLIS_PER_DAY / 22 | this.DAYS_PER_YEAR 23 | ); 24 | } 25 | 26 | protected riskFactorFor(loan: Loan) { 27 | return RiskFactor.getFactors().forRating(loan.getRiskRating()); 28 | } 29 | 30 | protected unusedRiskFactorFor(loan: Loan) { 31 | return UnusedRiskFactors.getFactors().forRating(loan.getRiskRating()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Strategy 7.2/refactoring/CapitalStrategyAdvisedLine.ts: -------------------------------------------------------------------------------- 1 | import CapitalStrategy from "./CapitalStrategy"; 2 | import Loan from "./Loan"; 3 | 4 | export default class CapitalStrategyAdvisedLine extends CapitalStrategy { 5 | public capital(loan: Loan) { 6 | return ( 7 | loan.outstandingRiskAmount() * 8 | loan.duration() * 9 | this.riskFactorFor(loan) + 10 | loan.unusedRiskAmount() * loan.duration() * this.unusedRiskFactorFor(loan) 11 | ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Strategy 7.2/refactoring/CapitalStrategyRevolver.ts: -------------------------------------------------------------------------------- 1 | import CapitalStrategy from "./CapitalStrategy"; 2 | import Loan from "./Loan"; 3 | 4 | export default class CapitalStrategyRevolver extends CapitalStrategy { 5 | public capital(loan: Loan) { 6 | return ( 7 | loan.duration() * 8 | loan.getCommitment() * 9 | loan.getUnusedPercentage() * 10 | this.riskFactorFor(loan) 11 | ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Strategy 7.2/refactoring/CapitalStrategyTermLoan.ts: -------------------------------------------------------------------------------- 1 | import CapitalStrategy from "./CapitalStrategy"; 2 | import Loan from "./Loan"; 3 | 4 | export default class CapitalStrategyTermLoan extends CapitalStrategy { 5 | public capital(loan: Loan) { 6 | return loan.getCommitment() * loan.duration() * this.riskFactorFor(loan); 7 | } 8 | 9 | public duration(loan: Loan){ 10 | return this.weightedAverageDuration(); 11 | } 12 | 13 | private weightedAverageDuration() { 14 | return 1; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Strategy 7.2/refactoring/Loan.ts: -------------------------------------------------------------------------------- 1 | import CapitalStrategy from "./CapitalStrategy"; 2 | import CapitalStrategyTermLoan from "./CapitalStrategyTermLoan"; 3 | import CapitalStrategyAdvisedLine from "./CapitalStrategyAdvisedLine"; 4 | 5 | export default class Loan { 6 | // 承诺金额 7 | private commitment: any; 8 | // 未偿贷款 9 | private outstanding: any; 10 | // 有效日 11 | private expiry: any; 12 | // 到期日 13 | private maturity: any; 14 | // 风险评级 15 | private riskRating: any; 16 | 17 | private today: Date; 18 | private start: Date; 19 | private capitalStrategy: CapitalStrategy; 20 | 21 | public static newTermLoan(commitment, start, maturity, riskRating) { 22 | return new Loan( 23 | commitment, 24 | commitment, 25 | start, 26 | null, 27 | maturity, 28 | riskRating, 29 | new CapitalStrategyTermLoan() 30 | ); 31 | } 32 | 33 | public static newRevolver(commitment, start, expiry, riskRating) { 34 | return new Loan( 35 | commitment, 36 | 0, 37 | start, 38 | expiry, 39 | null, 40 | riskRating, 41 | new CapitalStrategyAdvisedLine() 42 | ); 43 | } 44 | 45 | public static newAdvisedLine(commitment, start, expiry, riskRating) { 46 | if (riskRating > 3) return null; 47 | 48 | const advisedLine = new Loan( 49 | commitment, 50 | 0, 51 | start, 52 | expiry, 53 | null, 54 | riskRating, 55 | new CapitalStrategyAdvisedLine() 56 | ); 57 | 58 | advisedLine.setUnusedPercentage(0.1); 59 | return advisedLine; 60 | } 61 | 62 | private constructor( 63 | commitment, 64 | outstanding, 65 | start, 66 | expiry, 67 | maturity, 68 | riskRating, 69 | capitalStrategy 70 | ) { 71 | this.commitment = commitment; 72 | this.outstanding = outstanding; 73 | this.start = start; 74 | this.expiry = expiry; 75 | this.maturity = maturity; 76 | this.riskRating = riskRating; 77 | this.capitalStrategy = capitalStrategy; 78 | } 79 | 80 | public capital() { 81 | return this.capitalStrategy.capital(this); 82 | } 83 | 84 | public duration() { 85 | return this.capitalStrategy.duration(this); 86 | } 87 | 88 | /** 89 | * 未用额度 90 | */ 91 | public getUnusedPercentage() { 92 | return 1; 93 | } 94 | 95 | public setUnusedPercentage(val) {} 96 | 97 | /** 98 | * 未用风险金额 99 | */ 100 | public unusedRiskAmount() { 101 | return this.commitment - this.outstanding; 102 | } 103 | /** 104 | * 未清风险金额 105 | */ 106 | public outstandingRiskAmount() { 107 | return this.outstanding; 108 | } 109 | 110 | /** 111 | * getExpiry 112 | * 113 | */ 114 | public getExpiry() { 115 | return this.expiry; 116 | } 117 | 118 | public getMaturity() { 119 | return this.maturity; 120 | } 121 | public getCommitment() { 122 | return this.commitment; 123 | } 124 | 125 | public getRiskRating(): any { 126 | return this.riskRating; 127 | } 128 | 129 | public getStart() { 130 | return this.start; 131 | } 132 | public getToday() { 133 | return this.today; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Strategy 7.2/refactoring/RiskFactor.ts: -------------------------------------------------------------------------------- 1 | export default class RiskFactor { 2 | public static getFactors() { 3 | return new RiskFactor() 4 | } 5 | 6 | public forRating(obj:any){ 7 | return 1 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Strategy 7.2/refactoring/UnusedRiskFactors.ts: -------------------------------------------------------------------------------- 1 | export default class UnusedRiskFactors { 2 | public static getFactors() { 3 | return new UnusedRiskFactors() 4 | } 5 | 6 | public forRating(obj:any){ 7 | return 1 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | // babel.config.js 2 | module.exports = { 3 | presets: [ 4 | ["@babel/preset-env", { targets: { node: "current" } }], 5 | "@babel/preset-typescript" 6 | ] 7 | }; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "refactoring-to-patterns", 3 | "version": "1.0.0", 4 | "description": "重构与模式", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/cuixiaorui/refactoring-to-patterns.git" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/cuixiaorui/refactoring-to-patterns/issues" 18 | }, 19 | "homepage": "https://github.com/cuixiaorui/refactoring-to-patterns#readme", 20 | "devDependencies": { 21 | "@babel/core": "^7.9.0", 22 | "@babel/preset-env": "^7.9.0", 23 | "@babel/preset-typescript": "^7.9.0", 24 | "@types/jest": "^25.1.4", 25 | "babel-jest": "^25.2.4", 26 | "jest": "^25.2.4", 27 | "ts-node": "^8.8.1", 28 | "typescript": "^3.8.3" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", //指定生成哪个模块系统代码 4 | "target": "es6", //目标代码类型 5 | "noImplicitAny": false, //在表达式和声明上有隐含的'any'类型时报错。 6 | "sourceMap": false, //用于debug 7 | "outDir": "./build", //重定向输出目录。 8 | "watch": true //在监视模式下运行编译器。会监视输出文件,在它们改变时重新编译。 9 | }, 10 | "exclude": ["node_modules"] 11 | } 12 | --------------------------------------------------------------------------------