做者簡介 joey 螞蟻金服·數據體驗技術團隊html
咱們團隊的工做是用單頁面應用的方式實現web工具。涉及到數萬到十數萬行的前端代碼的管理,並且項目週期長達數年。前端
怎麼樣很好地管理好這種量級的前端代碼,在迭代的過程當中能保持代碼的新鮮度,對咱們來講是個挑戰。java
一個運行良好的項目,除了要有好的架構外,還須要各個功能模塊有良好的設計,學習設計模式,就是但願能有技巧地設計新功能和重構已有代碼。web
在網上看到不少說法,說學習設計模式做用不大,有些模式已通過時了,不學也能工做,學了反而容易過分設計。算法
我認爲對事物的理解是「學習——領悟——突破」的過程。不懂的時候先學習,當學到的東西和實踐經驗有差別時結合思考能夠領悟,等領悟到了其中的原理時,就能夠不拘泥於學到的內容從而根據本身的場景靈活運用了。而過分設計顯然仍是在學習和領悟之間而已。設計模式
設計模式也是這樣,《設計模式》裏列舉的23種設計模式並非所有,模式的運用上每每也不是分得那麼清楚,經常是多種模式混合使用。學習設計模式就像《倚天屠龍記》裏張無忌學習太極拳同樣,先學習招式,再打幾遍,最終忘記這些招式。23種設計模式只是招式,咱們學習的目的是爲了提升本身的設計水平,達到能結合場景信手拈來設計方案,不拘泥於招式的「大乘」境界。緩存
在學習設計模式的過程當中,我發現4人幫的原書demo代碼是C++的,而網上設計模式文章的demo可能是java的。所以結合前端的js語言特性,整理了一遍各個模式的demo,方便有志於學習設計模式的同窗們理解,共同進步。bash
假設咱們要實現一個迷宮,原始代碼以下:數據結構
function createMazeDemo() {
const maze = new Maze();
const r1 = new Room(1);
const r2 = new Room(2);
const door = new Door(r1, r2);
maze.addRoom(r1);
maze.addRoom(r2);
r1.setSide('east', new Wall());
r1.setSide('west', door);
return maze;
}
createMazeDemo();
複製代碼
咱們已經實現了一個迷宮,這時候新的需求來了,迷宮裏全部的東西都被施了魔法,但仍是要重用現有的佈局(全部構件類,如Room、Wall、Door都要換成新的類)。架構
能夠看到這樣的硬編碼方式不夠靈活,那麼如何改造createMaze方法以讓他方便地用新類型的對象建立迷宮呢?
提供一個建立一系列相關或相互依賴對象的接口,而無需指定它們具體的類。
抽象工廠模式包含以下角色:
// 迷宮的基類
class Maze {
addRoom(room: Room): void {
}
}
// 牆的基類
class Wall {
}
// 房間的基類
class Room {
constructor(id: number) {
}
setSide(direction: string, content: Room|Wall): void {
}
}
// 門的基類
class Door {
constructor(roo1: Room, room2: Room) {
}
}
// 迷宮工廠的基類,定義了生成迷宮各個構件的接口和默認實現,
// 子類能夠複寫接口的實現,返回不一樣的具體類對象。
class MazeFactory {
makeMaze(): Maze {
return new Maze();
}
makeWall(): Wall {
return new Wall();
}
makeRoom(roomId: number): Room {
return new Room(roomId);
}
makeDoor(room1: Room, room2: Room): Door {
return new Door(room1, room2);
}
}
// 經過傳入工廠對象,調用工廠的接口方法建立迷宮,
// 因爲工廠的接口都是同樣的,因此傳入不一樣的工廠對象,就能建立出不一樣系列的具體產品
function createMazeDemo(factory: MazeFactory): Maze {
const maze = factory.makeMaze();
const r1 = factory.makeRoom(1);
const r2 = factory.makeRoom(2);
const door = factory.makeDoor(r1, r2);
maze.addRoom(r1);
maze.addRoom(r2);
r1.setSide('east', factory.makeWall());
r1.setSide('west', door);
return maze;
}
// 標準系列工廠對象,工廠的每一個產品都是標準的
const standardSeries = new MazeFactory();
// 建立出標準的迷宮
createMazeDemo(standardSeries);
// 附了魔法的房間,繼承自房間的基類
class MagicRoom extends Room {
...
}
// 附了魔法的門,繼承自門的基類
class MagicDoor extends Door {
...
}
// 魔法系列的工廠,工廠的房間和門是被附了魔法的
class MagicMazeFactory extends MazeFactory {
makeRoom(roomId: number): Room {
return new MagicRoom(roomId);
}
makeDoor(room1: Room, room2: Room): Door {
return new MagicDoor(room1, room2);
}
...
}
// 魔法系列工廠對象,工廠建立出的門和房間是附了魔法的
const magicSeries = new MagicMazeFactory();
createMazeDemo(magicSeries);
複製代碼
createMazeDemo
不須要修改;createMazeDemo
傳入新的工廠對象便可;將一個複雜對象的構建與它的表示分離,使得一樣的構建過程能夠建立不一樣的表示。
建造者模式包含以下角色:
import { Maze, Wall, Room, Door } from './common';
// 迷宮建造者基類,定義了全部生成迷宮構件的接口,以及最終返回完整迷宮的接口
// 自身不建立迷宮,僅僅定義接口
class MazeBuilder {
buildMaze(): void {}
buildWall(roomId: number, direction: string): void {}
buildRoom(roomId: number): void {}
buildDoor(roomId1: number, roomId2: number): void {}
getCommonWall(roomId1: number, roomId2: number): Wall { return new Wall(); };
getMaze(): Maze|null { return null; }
}
// 建立迷宮的流程
// 相比最原始的代碼,使用建造者模式只須要聲明建造過程,而不須要知道建造過程當中用到的每一個構件的全部信息
// 好比,建造門的時候,只須要聲明要建造一扇門,而不須要關心建造門的方法內部是如何將門與房間關聯起來的
// 建造者模式只在迷宮被徹底建造完成時,才從建造者對象裏取出整個迷宮,從而能很好地反映出完整的建造過程
function createMaze(builder: MazeBuilder) {
builder.buildMaze();
builder.buildRoom(1);
builder.buildRoom(2);
builder.buildDoor(1, 2);
return builder.getMaze();
}
// 標準迷宮的建造者,繼承自建造者基類
class StandardMazeBuilder extends MazeBuilder {
currentMaze: Maze;
constructor() {
super();
this.currentMaze = new Maze();
}
getMaze(): Maze|null {
return this.currentMaze;
}
buildRoom(roomId: number): void {
if (this.currentMaze) {
const room = new Room(roomId);
this.currentMaze.addRoom(room);
room.setSide('north', new Wall());
room.setSide('south', new Wall());
room.setSide('east', new Wall());
room.setSide('west', new Wall());
}
}
buildDoor(roomId1: number, roomId2: number): void {
const r1 = this.currentMaze.getRoom(roomId1);
const r2 = this.currentMaze.getRoom(roomId2);
const door = new Door(r1, r2);
r1.setSide(this.getCommonWall(roomId1, roomId2), door);
r2.setSide(this.getCommonWall(roomId2, roomId1), door);
}
}
// 建造一個標準的迷宮
const standardBuilder = new StandardMazeBuilder();
createMaze(standardBuilder);
/**
* 建造者也能夠根本不建造具體的構件,而只是對建造過程進行計數。
*/
// 計數的數據結構聲明
interface Count {
rooms: number;
doors: number;
}
// 不建立迷宮,只記數的建造者,也繼承自建造者基類
class CountingMazeBuilder extends MazeBuilder {
doors = 0;
rooms = 0;
buildRoom(): void {
this.rooms += 1;
}
buildDoor(): void {
this.doors += 1;
}
getCounts(): Count {
return {
rooms: this.rooms,
doors: this.doors,
};
}
}
const countBuilder = new CountingMazeBuilder();
createMaze(countBuilder);
countBuilder.getCounts();
複製代碼
定義一個用於建立對象的接口,讓子類決定實例化哪一個類。工廠方法使一個類的實例化延遲到其子類。
工廠方法模式包含以下角色:
import { Maze, Wall, Room, Door } from './common';
// 迷宮遊戲類
class MazeGame {
// 建立迷宮的主方法
createMaze(): Maze {
const maze = this.makeMaze();
const r1 = this.makeRoom(1);
const r2 = this.makeRoom(2);
const door = this.makeDoor(r1, r2);
maze.addRoom(r1);
maze.addRoom(r2);
r1.setSide('north', this.makeWall());
r1.setSide('east', door);
return maze;
}
// 如下是工廠方法,經過工廠方法建立構件,而不是直接在主方法中new出具體類
// 工廠方法最重要的是定義出返回產品的接口,雖然這裏提供了默認實現,但也能夠只提供接口,讓子類來實現
makeMaze(): Maze { return new Maze(); }
makeRoom(roomId: number): Room { return new Room(roomId); }
makeWall(): Wall { return new Wall(); }
makeDoor(room1: Room, room2: Room): Door { return new Door(room1, room2); }
}
// 建立普通迷宮遊戲
const mazeGame = new MazeGame();
mazeGame.createMaze();
// 帶炸彈的牆
class BombedWall extends Wall {
...
}
// 帶炸彈的房間
class RoomWithABomb extends Room {
...
}
// 子類能夠複寫工廠方法,如下是帶炸彈的迷宮遊戲類
class BombedMazeGame extends MazeGame {
// 複寫建立牆的方法,返回一面帶炸彈的牆
makeWall(): Wall {
return new BombedWall();
}
// 複寫建立房間的方法,返回一個帶炸彈的房間
makeRoom(roomId: number): Room {
return new RoomWithABomb(roomId);
}
}
// 建立帶炸彈的迷宮遊戲
const bombedMazeGame = new BombedMazeGame();
bombedMazeGame.createMaze();
複製代碼
class Creator {
createProduct(type: string): Product {
if (type === 'normal') return new NormalProduct();
if (type === 'black) return new BlackProduct(); return new DefaultProduct(); } } // 子類能夠很容易地擴展或改變工廠方法返回的產品 class MyCreator extends Creator { createProduct(type: string): Product { // 改變產品 if (type === 'normal) return new MyNormalProduct();
// 擴展新的產品
if (type === 'white') return new WhiteProduct();
// 注意這個操做的最後一件事是調用父類的`createProduct`,這是由於子類僅對某些type的處理上與父類不一樣,對其餘的type不感興趣
return Creator.prototype.createProduct.call(this, type);
}
}
複製代碼
createMaze
方法中直接建立對象const r1 = new Room(1);
,用工廠方法const r1 = this.makeRoom(1)
,能夠在子類中複寫makeRoom
方法來實例化不一樣的房間,能更靈活地應對需求變化。用原型實例指定建立對象的種類,而且經過複用這些原型建立新的對象。
在其餘語言裏,原型模式是經過拷貝一個對象,而後修改新對象的屬性,從而減小類的定義和實例化的開銷。
但因爲js自然支持prototype,所以原型的實現方式與其餘類繼承語言有些不一樣,不須要經過對象提供clone
方法來實現模型模式。
import { Maze, Wall, Room, Door } from './common';
interface Prototype {
prototype?: any;
}
// 根據原型返回對象
function getNewInstance(prototype: Prototype, ...args: any[]): Wall|Maze|Room|Door {
const proto = Object.create(prototype);
const Kls = class {};
Kls.prototype = proto;
return new Kls(...args);
}
// 迷宮工廠,定義了生成構件的接口
class MazeFactory {
makeWall(): Wall { return new Wall(); }
makeDoor(r1: Room, r2: Room): Door { return new Door(r1, r2); }
makeRoom(id: number): Room { return new Room(id); }
makeMaze(): Maze { return new Maze(); }
}
// 原型迷宮工廠,根據初始化時傳入的原型改變返回的迷宮構件
class MazePrototypeFactory extends MazeFactory {
mazePrototype: Prototype;
wallPrototype: Prototype;
roomPrototype: Prototype;
doorPrototype: Prototype;
constructor(mazePrototype: Prototype, wallPrototype: Prototype, roomPrototype: Prototype, doorPrototype: Prototype) {
super();
this.mazePrototype = mazePrototype;
this.wallPrototype = wallPrototype;
this.roomPrototype = roomPrototype;
this.doorPrototype = doorPrototype;
}
makeMaze() {
return getNewInstance(this.mazePrototype);
}
makeRoom(id: number) {
return getNewInstance(this.roomPrototype, id);
}
makeWall() {
return getNewInstance(this.wallPrototype);
}
makeDoor(r1: Room, r2: Room): Door {
const door = getNewInstance(this.doorPrototype, r1, r2);
return door;
}
}
// 建立迷宮的過程
function createMaze(factory: MazeFactory): Maze {
const maze = factory.makeMaze();
const r1 = factory.makeRoom(1);
const r2 = factory.makeRoom(2);
const door = factory.makeDoor(r1, r2);
maze.addRoom(r1);
maze.addRoom(r2);
r1.setSide('east', factory.makeWall());
r1.setSide('west', door);
return maze;
}
// 各個迷宮構件的原型
const mazePrototype = {...};
const wallPrototype = {...};
const roomPrototype = {...};
const doorPrototype = {...};
// 生成簡單的迷宮
const simpleMazeFactory = new MazePrototypeFactory(mazePrototype, wallPrototype, roomPrototype, doorPrototype);
createMaze(simpleMazeFactory);
// 帶有炸彈的迷宮構件的原型
const bombedWallPrototype = {...};
const roomWithABombPrototype = {...};
// 生成帶有炸彈的迷宮
const bombedMazeFactory = new MazePrototypeFactory(mazePrototype, bombedWallPrototype, roomWithABombPrototype, doorPrototype);
createMaze(bombedMazeFactory);
複製代碼
new
出對象。或者類須要根據運行環境動態改變,能夠經過修改原型來產生不一樣的對象。保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
單例模式包含以下角色:
class MazeFactory {
// 將constructor設爲私有,防止經過new該類產生多個對象,破壞單例
private constructor() {}
static instance: MazeFactory;
// 若是已經有了對象,則返回緩存的對象,否則就建立一個對象並緩存,保證系統內最多隻有一個該類的對象
static getInstance(): MazeFactory {
if (!MazeFactory.instance) {
MazeFactory.instance = new MazeFactory();
}
return MazeFactory.instance;
}
}
複製代碼
class BombedMazeFactory extends MazeFactory {
...
}
class AdvancedMazeFactory {
// 將constructor設爲私有,防止經過new該類產生多個對象,破壞單例
private constructor() {}
static instance: MazeFactory;
static getInstance(type: string): MazeFactory {
if (!AdvancedMazeFactory.instance) {
if (type === 'bombed') {
AdvancedMazeFactory.instance = new BombedMazeFactory();
} else {
AdvancedMazeFactory.instance = new MazeFactory();
}
}
return AdvancedMazeFactory.instance;
}
}
複製代碼
本文只介紹了建立型模式,對後續模式感興趣的同窗能夠關注專欄或者發送簡歷至'chaofeng.lcf####alibaba-inc.com'.replace('####', '@'),歡迎有志之士加入~
原文地址:https://juejin.im/post/59fa88ac5188255a6a0d5f31