NodeDesignPatten-06

Design Patten

設計模式是關於程式碼重複使用的問題

推薦書籍

Design Pattern List

  • Factory
  • Revealing constructor
  • Proxy
  • Decorator
  • Adapter
  • Strategy
  • State
  • Template
  • Middleware
  • Command

Factory

在 Javascript 中 設計通常以可用,簡單並且模組化開發為主旨

而使用 Factory 則可以在物件導向上包覆一層 Wrapper

使用上可以更加彈性

在 Factory 中可以透過 Object.create() 來建立一個物件

或是利用特定的條件來建立不同的物件

Example

factory.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const ImageJpeg = require('./imageJpeg');
const ImageGif = require('./imageGif');
const ImagePng = require('./imagePng');

function createImage(name) {
if (name.match(/\.jpe?g$/)) {
return new ImageJpeg(name);
} else if (name.match(/\.gif$/)) {
return new ImageGif(name);
} else if (name.match(/\.png$/)) {
return new ImagePng(name);
} else {
throw new Exception('Unsupported format');
}
}

const image1 = createImage('photo.jpg');
const image2 = createImage('photo.gif');
const image3 = createImage('photo.png');

console.log(image1, image2, image3);

imageJpeg.js

1
2
3
4
5
6
7
8
9
10
11
12
"use strict";

const Image = require('./image');

module.exports = class ImageJpg extends Image {
constructor(path) {
if (!path.match(/\.jpe?g$/)) {
throw new Error(`${path} is not a JPEG image`);
}
super(path);
}
};

imagePng.js

1
2
3
4
5
6
7
8
9
10
11
12
"use strict";

const Image = require('./image');

module.exports = class ImagePng extends Image {
constructor(path) {
if (!path.match(/\.png$/)) {
throw new Error(`${path} is not a PNG image`);
}
super(path);
}
};

imageGif.js

1
2
3
4
5
6
7
8
9
10
11
12
"use strict";

const Image = require('./image');

module.exports = class ImageGif extends Image {
constructor(path) {
if (!path.match(/\.gif/)) {
throw new Error(`${path} is not a GIF image`);
}
super(path);
}
};

封裝

封裝是防止某些程式碼被外部直接飲用或更改時使用

也稱為訊息隱藏

而 Javascript 常用 Closure 來實現

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function createPerson(name) {
const privateProperties = {};
const person = {
setName: name => {
if (!name) throw new Error('A person must have a name');
privateProperties.name = name;
},
getName: () => privateProperties.name
};

person.setName(name);
return person;
}

const person = createPerson('Tomas Lin');
console.log(person.getName(), person);

此範例中的person 只有透過 setName與 getName可以對person 取值與修改

1
2
3
4
5
6
7
8
9
使用 Factory 只是其中一個方式

另外也有不成文約定

function 前加上 **_** 或 **$**

但是這樣依舊可以在外部呼叫

另外也可以使用 [WeakMap](http://fitzgeraldnick.com/2014/01/13/hiding-implementation-details-with-e6-weakmaps.html)

建立一個完整的 Factory Demo

profiler.js

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
class Profiler {
constructor(label) {
this.label = label;
this.lastTime = null;
}

start() {
this.lastTime = process.hrtime();
}
end() {
const diff = process.hrtime(this.lastTime);
console.log(
`Timer "${this.label}" took ${diff[0]} seconds and ${diff[1]} nanoseconds.`
);
}
}

module.exports = function (label) {
if (process.env.NODE_ENV === 'development') {
return new Profiler(label); //[1]
} else if (process.env.NODE_ENV === 'production') {
return { //[2]
start: function () { },
end: function () { }
}
} else {
throw new Error('Must set NODE_ENV');
}
}

profilerTest.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const profiler = require('./profile');

function getRandomArray(len) {
const p = profiler(`Generating a ${len} items long array`);
p.start();
const arr = [];
for (let i = 0; i < len; i++) {
arr.push(Math.random());
}
p.end();
}

getRandomArray(1e6);
console.log('Done');
1
$ NODE_ENV=development node profilerTest
1
$ NODE_ENV=production node profilerTest

組合工廠

利用遊戲的方式來解說

通常一種遊戲會有多種角色

每種角色會擁有各自不同的基本能力

  • Runner: 可移動
  • Samurai: 可移動並且攻擊
  • Sniper: 可射擊但不可移動
  • Gunslinger: 可移動並且射擊
  • Western Samurai: 可以移動 攻擊 並射擊

希望上述可以自由地互相結合

所以不能使用 Class 或是 inheritance 來解決

Example

使用套件stampit

文章目录
  1. 1. Design Patten
    1. 1.1. Design Pattern List
      1. 1.1.1. Factory
      2. 1.1.2. Example
    2. 1.2. 封裝
      1. 1.2.1. Example
    3. 1.3. 建立一個完整的 Factory Demo
    4. 1.4. 組合工廠
    5. 1.5. Example
|