做者简介 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