### 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

> 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)
---
## 下次見!

{"title":"OOP 與 Dependency Injection","contributors":"[{\"id\":\"f8142aa2-66aa-4867-821d-2f1ffff7a7ba\",\"add\":9057,\"del\":28}]","description":"拿上週所講的Express當做基底"}