### GDSC NYUST x 資訊創客社 <br> ### JS後端與資料庫讀書會 #### OOP 與 Dependency Injection <br> #### 2023/11/22 ( Wed ) 19:00 - 21:00 #### 講師:蘇祐民 *YoMin Su* #### 本次課程影片:(⚒️製作中) <img src="" height="200px"> --- ## 課程簡介 ---- ### 本日目標 - 拿上週所講的Express當做基底 - 學習OOP的寫法 - 學習DI的概念 - 拿DI的套件進行應用 - 將上述幾個項目整合起來! - (將移動至下週課程內容) --- ## 複習一下Express ---- ### 基本架構 ```javascript= const express = require('express'); let app = express(); app.get('/', (req, res, next) => { res.status(200).send('Hello!'); }); app.listen(3000, () => { console.log("Express is running!"); }); ``` ---- ### CRUD對應的方法 | HTTP Method | Express Method | | ----------- | -------------------------- | | GET | `app.get('/', () => {})` | | POST | `app.post('/', () => {})` | | PUT | `app.put('/', () => {})` | | PATCH | `app.patch('', () => {})` | | DELETE | `app.delete('', () => {})` | ---- ### 取得請求內容 如果是JSON格式的請求: ```javascript= app.use(express.json()); ``` 如果是MultiPart格式的資料: ```javascript= const multer = require('multer') let upload = multer() app.get('/', upload.array(), () => {}); ``` 共通的部分: ```javascript= app.method('<route>', (req, res, next) => { const data = req.body; }); ``` ---- ### 取得請求路徑資訊 如果是URL中以『?』接著的參數,例如: example.com/user?sex=male ```javascript= app.get('/user', (req, res, next) => { let query = req.query; console.log(query.sex); }); ``` 如果是希望取得路由的一部分作為參數,像是: example.com/user/112233/data ```javascript= app.get('/user/:id/data', (req, res, next) => { let param = req.params; console.log(param.id); }); ``` ---- ### 註冊路由 animal.js ```javascript= const express = require('express'); let router = express.Router(); router.<method>('', () => {}); module.exports = router; ``` main.js ```javascript= const express = require('express'); const animal = require('./animal'); let app = express(); app.use('/animal', animal); app.listen(3000, () => { console.log("Express is running!"); }); ``` ---- ### 中介層 要記得互叫下一層,否則會卡住! ```javascript= let middle = (req, res, next) => { console.log('Hi!'); next(); // <== 這行很重要! } app.get('/', middle, (req, res, next) => { console.log('How are you?'); }); ``` --- ## 了解 JS 的物件導向 <br /> #### 雖然前面幾次有講過~ ---- ### 簡單介紹 JS中的class關鍵字,是在ECMAScript 6中引入的功能,在目前的生態系中,已經被廣泛使用,若你有看原始碼的習慣,會發現有非常多JS套件都已經使用到了這個功能,接下來就開始細節介紹一下該如何使用! ---- ### 基本宣告 ```javascript= class ClassName { //Content Here } ``` 以上就是一個有效的類別宣告了! > 跟Java與C#基本上如出一轍 ---- ### 定義物件屬性 ```javascript= class Person { name; age; #sex; #educationLevel; } ``` 在JS中不需要定義型別,因此就只需要列出預計要使用的到屬性名稱就好! > 若希望屬性只能自己使用,請加上# ---- ### 新增建構子 ```javascript= class Person { //Attribute here constructor(name, age, sex) { this.name = name; this.age = age; this.sex = sex; } } ``` 其他語言使用建構子時,通常都是對應類別名稱,不過在JS中,會直接使用constructor這個關鍵字~ ---- ### 建構子補充 在其他語言中,可以對建構子標記為private,使該物件無法被實例(instance)化,但在JS中不支援這樣的用法,若想要類似的效果,就得在建構子中直接丟出錯誤,使建立失敗,以下是MDN提供的範例: ```javascript= class PrivateConstructor { static #canConstructor = false; constructor() { if (!PrivateConstructor.#canConstructor) { throw new TypeError("PrivateConstructor is not constructable"); } PrivateConstructor.#canConstructor = false; // More initialization logic } static create() { PrivateConstructor.#canConstructor = true; const instance = new PrivateConstructor(); return instance; } } ``` ---- ### Getter 與 Setter 當你今天的屬性是Private,但仍舊要存取時,就會靠Getter與Setter,以下是如何定義: ```javascript= class Person { #sex; get sex() { return this.#sex; } set sex(s) { if(s !== 'M' && s !== 'F') { throw new Error('Sex not valid!'); } this.#sex = s; } } ``` ---- ### Static方法 靜態方法,讓物件在實例(instance)化前就可以被使用的關鍵字,範例來了: ```javascript= class Person { name; age; constructor(name, age) { this.name = name; this.age = age; } static create(name, age) { return new this(name, age); } } ``` ---- ### Static 補充 靜態這個標籤,不僅限於方法,屬性也同樣適用,若你不相信,可以回頭去看看前面的例子,你已經用到過了:P > static 僅針對類別本身,當變成實例後,就會無法存取 ---- ### 靠 extends 來繼承 繼承,或是說子類別,就是建立比父類更明確的子類型,看看範例: ```javascript= class Animal { name; constructor(name) { this.name = name; } speak() { console.log(this.name, 'makes a sound.'); } } class Dog extends Animal { constructor(name) { super(name) } speak() { console.log(this.name, 'woff!'); } } ``` ---- ### super 可以找爸爸 就像剛剛動物與狗的範例,可以透過super去呼叫父類的建構子,而實際影響的變數仍是自己的,透過這樣的方式,能減少重複設計的程式碼,讓子類專注在『不同的部分』 --- ## DI怎麼玩? #### Dependency Injection (依賴注入) ---- ### 解說時間 依賴注入的核心概念其實是控制反轉 (Inversion of Control, IoC),這與OOP的設計方式有很大的關係,也是不容易學習的部分,以我們使用上的目標來說,主要是為了降低方法與方法之間的耦合,使每個部分都容易替換,以此來改善維護的困難,而其帶來的額外好處則是,這使軟體測試也相對更容易進行,因此在不同的後端框架都會發現它的身影! ---- ### DI補充文件 - [菜雞新訓記: 使用 依賴注入 來解除強耦合吧](https://igouist.github.io/post/2021/11/newbie-6-dependency-injection/) - [淺入淺出 Dependency Injection](https://medium.com/wenchin-rolls-around/%E6%B7%BA%E5%85%A5%E6%B7%BA%E5%87%BA-dependency-injection-ea672ba033ca) - [為什麼要使用DI(依賴注入)?](https://blog.shiangsoft.com/netcore-dependency-injection/) - [依赖注入是什么?如何使用它?](https://www.freecodecamp.org/chinese/news/a-quick-intro-to-dependency-injection-what-it-is-and-when-to-use-it/) ---- ### 在JS中,有什麼選項? - [InversifyJS](https://inversify.io/) - 最多人使用,也是首選 - [TSyringe](https://github.com/microsoft/tsyringe) - 相對沒那麼多人,主要由微軟維護 - [typescript-ioc](https://github.com/thiagobustamante/typescript-ioc) - 目前看起來處於停更狀態 ---- ### 本次帶入的範例:InversifyJS ![](https://imgur.com/vSexQLW.png =800x) > GitHub[點我](https://github.com/inversify/inversifyjs#inversifyjs) ---- ### 先說說基本概念 在DI管理的環境下,會有一個DI容器,容器負責存放建立成實例的類別物件,而何時該注入,則是基於是否有在建構子中標記需要注入該物件,有的話,就可以在這個實例中使用到另一個實例,使功能正常運行! ---- ### 來看看範例 取自官方文件的說明,首先要引用所需要的套件: ```javascript= var inversify = require("inversify"); require("reflect-metadata"); ``` ---- ### 來宣告一下類型 ```javascript= var TYPES = { Ninja: "Ninja", // 忍者 Katana: "Katana", // 武士刀 Shuriken: "Shuriken" // 手裏劍 }; ``` 這個類型等等在注入時會使用到~ ---- ### 建立個別的物件 先是武士刀與手裏劍的類別與各自的方法 ```javascript= class Katana { hit() { return "cut!"; } } class Shuriken { throw() { return "hit!"; } } ``` ---- ### 建立個別的物件2 接下來是忍者本尊 ```javascript= class Ninja { constructor(katana, shuriken) { this._katana = katana; this._shuriken = shuriken; } fight() { return this._katana.hit(); }; sneak() { return this._shuriken.throw(); }; } ``` ---- ### 將類別標記為可注入 ```javascript= // Declare as injectable and its dependencies inversify.decorate(inversify.injectable(), Katana); inversify.decorate(inversify.injectable(), Shuriken); inversify.decorate(inversify.injectable(), Ninja); inversify.decorate(inversify.inject(TYPES.Katana), Ninja, 0); inversify.decorate(inversify.inject(TYPES.Shuriken), Ninja, 1); ``` ---- ### 建立容器與宣吿綁定 ```javascript= // Declare bindings var container = new inversify.Container(); container.bind(TYPES.Ninja).to(Ninja); container.bind(TYPES.Katana).to(Katana); container.bind(TYPES.Shuriken).to(Shuriken); ``` ---- ### 實際取得實例 ```javascript= // Resolve dependencies var ninja = container.get(TYPES.Ninja); module.exports = ninja; ``` ---- ### 完整的程式碼 ```javascript= var inversify = require("inversify"); require("reflect-metadata"); var TYPES = { Ninja: "Ninja", Katana: "Katana", Shuriken: "Shuriken" }; class Katana { hit() { return "cut!"; } } class Shuriken { throw() { return "hit!"; } } class Ninja { constructor(katana, shuriken) { this._katana = katana; this._shuriken = shuriken; } fight() { return this._katana.hit(); }; sneak() { return this._shuriken.throw(); }; } // Declare as injectable and its dependencies inversify.decorate(inversify.injectable(), Katana); inversify.decorate(inversify.injectable(), Shuriken); inversify.decorate(inversify.injectable(), Ninja); inversify.decorate(inversify.inject(TYPES.Katana), Ninja, 0); inversify.decorate(inversify.inject(TYPES.Shuriken), Ninja, 1); // Declare bindings var container = new inversify.Container(); container.bind(TYPES.Ninja).to(Ninja); container.bind(TYPES.Katana).to(Katana); container.bind(TYPES.Shuriken).to(Shuriken); // Resolve dependencies var ninja = container.get(TYPES.Ninja); module.exports = ninja; ``` ---- ### 你了解了嗎? 以上是搭配JS的基礎範例,通常在專案中會與TS一起使用,有TypeScript的型別輔助,可以處理掉更多麻煩的問題,讓程式可以更容易推斷是否有錯誤! --- ## Q & A 時間 有任何問題嗎? --- ## 學習資源 - [Classes - JavaScript | MDN](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Classes) - [JavaScript | ES6 中最容易誤會的語法糖 Class](https://medium.com/enjoy-life-enjoy-coding/javascript-es6-%E4%B8%AD%E6%9C%80%E5%AE%B9%E6%98%93%E8%AA%A4%E6%9C%83%E7%9A%84%E8%AA%9E%E6%B3%95%E7%B3%96-class-%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95-23e4a4a5e8ed) --- ## 下次見! ![sticker](https://hackmd.io/_uploads/By-liLsN6.png)
{"title":"OOP 與 Dependency Injection","contributors":"[{\"id\":\"f8142aa2-66aa-4867-821d-2f1ffff7a7ba\",\"add\":9057,\"del\":28}]","description":"拿上週所講的Express當做基底"}
    245 views
   Owned this note