# CH12: Modular design pattern, p.336-346 ###### tags: `clean code` ## 模組化設計模式 Modular design pattern > 常用來製作單一模組的語法和結構慣例。 使用時機: - 這個概念可以表達為名詞嗎? - 這個概念是否需要建構? - 實例之間的概念會有所不同嗎? :::danger JavaScript 終究是原型(Prototype-Based)的語言,并非物件導向(OOP)語言! ::: ## Before ES6 Constructor Function > 很直白的使用 prototype 建立類 OOP 類別 ```javascript= function Book(title){ this.title = title; // ... } ``` ### 將各方法指定給其 prototype ```javascript= // way01 方法定義(相對 way02 省空間) Book.prototype.getBookName = function(){ return this.title }; // [X] Book.prototype.getBookName = () => this.title 不可以使用箭頭函式哦 // way02 物件字面值(更封裝簡潔) Book.prototype = { getBookName: function(){ /**/ }; getNumberOfPage: function(){ /**/ }; // ... } ``` ### 實現繼承 ```javascript= // Object.prototype // -> Animal.protype // -> Monkey.prototype function Animal(){} Animal.prototype = { isAnimal: true, grow(){} } ``` - 創建子物件&新增屬性方法 ```javascript= // way01 使用 ES5 Object.create() function Monkey(){} Monkey.prototype = Object.create(Animal.protoype); Monkey.prototype.isMonkey = true; Monkey.prototype.method = function(){}; // way02 使用 new 實體化 const Monkey = new Animal; Monkey.isMonkey = true; ``` - 創建子物件+新增屬性方法(一次解決) ```javascript= // 使用 Object.assign() Monkey.prototype = Object.assign(Object.create(Animal.protoype),{ isMonkey = true, screen(){/**/}, // ... }) ``` ## ES6 Class > 長得更像經典 OOP 語言,且語法更為簡潔。(但充其量也只是語法糖) ![](https://i.imgur.com/gBWIQwD.png) ```javascript= class Name{ // 必須有(且唯一)一個 constructor 方法(如果沒有定義會默認添加) constructor(forename,surname){ this.forename = forename; this.surname = surname; } sayHello(){ return `My name is ${this.forename} ${this.surname}` } } ``` ### **定義靜態方法** > 靜態方法只能由類別本身呼叫,由類別所建立的物件實體(instance)上不能被呼叫。 通常作為工具函式(utility functions)使用。 :::spoiler **static 關鍵字** [MDN](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Classes/static) ```javascript= class Triple { static triple(n) { n = n || 1; //should not be a bitwise operation return n * 3; } } class BiggerTriple extends Triple { static triple(n) { return super.triple(n) * super.triple(n); } } console.log(Triple.triple()); // 3 console.log(Triple.triple(6)); // 18 console.log(BiggerTriple.triple(3)); // 81 var tp = new Triple(); console.log(tp.triple()); ``` ::: ```javascript= class Account{ static allAccounts = []; // ... } ``` ### 定義私有欄位 Private Field > 私有欄位只能由類別本身進行存取,子類別則無權存取。 :::danger 使用私有欄位時需格外謹慎,因爲它會嚴重限制程式碼的可擴展性。 ::: ```javascript= class Rectangle{ name = "rectangle"; // 一般公有欄位 #width = 200; // 使用 # 符號定義私有欄位 #height = 100; constructor(width, height){ this.#width = width; // 私有欄位是可以在内部輕易覆蓋的 this.#height = height; } } ``` :::success **使用 _ 標記私有欄位(僞私有欄位 pseudo)** 傳統 JavaScript 沒有私有欄位的概念,通常程式設計師會選擇使用一個或多個「底線」置於私有屬性之前(__somepropertyname)。此做法為一種社會共識,并非真的能限制屬性的存取! ::: ### 擴展類別 Extends > 使用 class ... extends 可以簡單的實現擴展(繼承) ```javascript= class Animal{}; class Tiger extends Animal{}; // 使用 extends 關鍵字 ``` ### 混合類別 Mixin :::info JavaScript 沒有提供原生的混合機制,因此若要實現此目的,你可以在定義之後擴增原型,或者有效繼承自原有的 Mixin(就像他們是父類別一樣)。 ::: #### way01 將 Mixin 指定為物件,然後將他們都增加到類別的 prototype。 ```javascript= const fooMixin = {foo(){}}; const bazMixin = {baz(){}}; class MyClass {} Object.assign(MyClass.prototype, fooMixin, bazMixin); ``` :::danger BUT! 這種方法不允許覆蓋自己的 Mixin 方法 ```javascript= class MyClass { foo(){} } Object.assign(MyClass.prototype, fooMixin, bazMixin); // fooMinxin.foo 覆蓋了原本 MyClass 的 foo 方法 new MyClass().foo = fooMinxin.foo() // true ``` ::: #### way02 使用繼承(**子類別工廠 Subclass Factory**) ```javascript= const fooSubclassFactory = SuperClass => { return class extends SuperClass{ fooMethod1(){}; fooMethod2(){}; } } ``` ### 存取父類別 > 使用 class 定義的類別中的所有函數,都具有可用的 super 綁定,能提供對父類別及其屬性的存。 #### [super 關鍵字用法](https://developer.mozilla.org/zh-W/docs/Web/JavaScript/Reference/Operators/super) 1. **當作函式** | 將會呼叫父類別的建構函式(需要放在 constructor 裏面) -> `super()` 2. **當作物件** | 提供對特定方法(supper.methodName())的存取 -> super.xx ```javascript= class Tiger extends Animal { constructor(){ super() // 呼叫 Animal constructor } } ``` :::warning :bomb: **若要擴展另一個類別並額外定義新的屬性,就必須叫用 super()**。 子類別的 constructor 呼叫 super() 之前,this 是沒有指向的,會跑出Refference Error!!! ```javascript= class Tiger extends Animal { constructor(){ this.someProperty = 123; // ReferenceError! super() } } ``` :::