1、组合模式
定义
是用小的子对象来构建更大的 对象,而这些小的子对象本身也许是由更小 的“孙对象”构成的。
核心
可以用树形结构来表示这种“部分- 整体”的层次结构。
调用组合对象 的execute方法,程序会递归调用组合对象 下面的叶对象的execute方法
但要注意的是,组合模式不是父子关系,它是一种聚合的关系,将请求委托给 它所包含的所有叶对象。基于这种委托,就需要保证组合对象和叶对象拥有相同的 接口
此外,也要保证用一致的方式对待 列表中的每个叶对象,即叶对象属于同一类,不需要过多特殊的额外操作
实现
使用组合模式来实现扫描文件夹中的文件
// 文件夹 组合对象
function Folder(name) {
this.name = name;
this.parent = null;
this.files = [];
}
Folder.prototype = {
constructor: Folder,
add: function(file) {
file.parent = this;
this.files.push(file);
return this;
},
scan: function() {
// 委托给叶对象处理
for (var i = 0; i < this.files.length; ++i) {
this.files[i].scan();
}
},
remove: function(file) {
if (typeof file === 'undefined') {
this.files = [];
return;
}
for (var i = 0; i < this.files.length; ++i) {
if (this.files[i] === file) {
this.files.splice(i, 1);
}
}
}
};
// 文件 叶对象
function File(name) {
this.name = name;
this.parent = null;
}
File.prototype = {
constructor: File,
add: function() {
console.log('文件里面不能添加文件');
},
scan: function() {
var name = [this.name];
var parent = this.parent;
while (parent) {
name.unshift(parent.name);
parent = parent.parent;
}
console.log(name.join(' / '));
}
};
扫描结果,输出了文件所在路径:
4. 优缺点
优点
可 以方便地构造一棵树来表示对象的部分-整体 结构。在树的构造最终 完成之后,只需要通过请求树的最顶层对 象,便能对整棵树做统一一致的操作。
缺点
创建出来的对象长得都差不多,可能会使代码不好理解,创建太多的对象对性能也会有一些影响
2、 中介者模式
-
定义
所有的相关 对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可
-
核心
使网状的多对多关系变成了相对简单的一对多关系(复杂的调度处理都交给中介者)
使用中介者后:
3 、外观模式
在类似场景中,这些例子有以下特点:
1. 一个统一的外观为复杂的子系统提供一个简单的高层功能接口;
2. 原本访问者直接调用子系统内部模块导致的复杂引用关系,现在可以通过只访问这个统一的外观来避免;
var uav = {
// 电子调速器
diantiao1: {
up() {
console.log('电调1发送指令:电机1增大转速');
uav.dianji1.up();
},
down() {
console.log('电调1发送指令:电机1减小转速');
uav.dianji1.down();
}
},
diantiao2: {
up() {
console.log('电调2发送指令:电机2增大转速');
uav.dianji2.up();
},
down() {
console.log('电调2发送指令:电机2减小转速');
uav.dianji2.down();
}
},
diantiao3: {
up() {
console.log('电调3发送指令:电机3增大转速');
uav.dianji3.up();
},
down() {
console.log('电调3发送指令:电机3减小转速');
uav.dianji3.down();
}
},
diantiao4: {
up() {
console.log('电调4发送指令:电机4增大转速');
uav.dianji4.up();
},
down() {
console.log('电调4发送指令:电机4减小转速')
uav.dianji4.down()
}
},
// 电机
dianji1: {
up() { console.log('电机1增大转速') },
down() { console.log('电机1减小转速') }
},
dianji2: {
up() { console.log('电机2增大转速') },
down() { console.log('电机2减小转速') }
},
dianji3: {
up() { console.log('电机3增大转速') },
down() { console.log('电机3减小转速') }
},
dianji4: {
up() { console.log('电机4增大转速') },
down() { console.log('电机4减小转速') }
},
// 遥控器
controller: {
// 上升
up() {
uav.diantiao1.up();
uav.diantiao2.up();
uav.diantiao3.up();
uav.diantiao4.up();
},
// 前进
forward() {
uav.diantiao1.down();
uav.diantiao2.down();
uav.diantiao3.up();
uav.diantiao4.up();
},
// 下降
down() {
uav.diantiao1.down();
uav.diantiao2.down();
uav.diantiao3.down();
uav.diantiao4.down();
},
// 左转
left() {
uav.diantiao1.up();
uav.diantiao2.down();
uav.diantiao3.up();
uav.diantiao4.down();
}
}
};
// 操纵无人机
uav.controller.down(); // 发送下降指令
uav.controller.left(); // 发送左转指令
4、享元模式
定义
享元(flyweight)模式是一种用于性能优化的模式,它的目标是尽量减少共享对象的数量
核心
运用共享技术来有效支持大量细粒度的对象。
强调将对象的属性划分为内部状态(属性)与外部状态(属性)。内部状态用于对象的共享,通常不变;而外部状态则剥离开来,由具体的场景决定。
实现
在程序中使用了大量的相似对象时,可以利用享元模式来优化,减少对象的数量
举个栗子,要对某个班进行身体素质测量,仅测量身高体重来评判
非亨元模式的实现:
// 健康测量
function Fitness(name, sex, age, height, weight) {
this.name = name;
this.sex = sex;
this.age = age;
this.height = height;
this.weight = weight;
}
// 开始评判
Fitness.prototype.judge = function() {
var ret = this.name + ': ';
if (this.sex === 'male') {
ret += this.judgeMale();
} else {
ret += this.judgeFemale();
}
console.log(ret);
};
// 男性评判规则
Fitness.prototype.judgeMale = function() {
var ratio = this.height / this.weight;
return this.age > 20 ? (ratio > 3.5) : (ratio > 2.8);
};
// 女性评判规则
Fitness.prototype.judgeFemale = function() {
var ratio = this.height / this.weight;
return this.age > 20 ? (ratio > 4) : (ratio > 3);
};
var a = new Fitness('A', 'male', 18, 160, 80);
var b = new Fitness('B', 'male', 21, 180, 70);
var c = new Fitness('C', 'female', 28, 160, 80);
var d = new Fitness('D', 'male', 18, 170, 60);
var e = new Fitness('E', 'female', 18, 160, 40);
// 开始评判
a.judge(); // A: false
b.judge(); // B: false
c.judge(); // C: false
d.judge(); // D: true
e.judge(); // E: true
评判五个人就需要创建五个对象,一个班就几十个对象
可以将对象的公共部分(内部状态)抽离出来,与外部状态独立。将性别看做内部状态即可,其他属性都属于外部状态。
这么一来我们只需要维护男和女两个对象(使用factory对象),而其他变化的部分则在外部维护(使用manager对象)
// 少数共享对象(如男女), 加载外部多数数据(如给一个班级50个人的50份数据)
// 健康测量
function Fitness(sex) {
this.sex = sex;
}
// 开始评判
Fitness.prototype.judge = function(name) {
// 操作前先更新当前状态(从外部状态管理器中获取)
FitnessManager.updateFitnessData(name, this);
var ret = name + ': ';
if (this.sex === 'male') {
ret += this.judgeMale();
} else {
ret += this.judgeFemale();
}
console.log(ret);
};
// 男性评判规则
Fitness.prototype.judgeMale = function() {
var ratio = this.height / this.weight;
return this.age > 20 ? (ratio > 3.5) : (ratio > 2.8);
};
// 女性评判规则
Fitness.prototype.judgeFemale = function() {
var ratio = this.height / this.weight;
return this.age > 20 ? (ratio > 4) : (ratio > 3);
};
// 工厂,创建可共享的对象 本例子是性别 男、女
var FitnessFactory = {
objs: {},
create: function(sex) {
//单例模式
if (!this.objs[sex]) {
this.objs[sex] = new Fitness(sex);
}
return this.objs[sex];
}
};
// 管理器,管理非共享的部分
var FitnessManager = {
fitnessData: {},
// 添加一项
add: function(name, sex, age, height, weight) {
var fitness = FitnessFactory.create(sex);
// 存储变化的数据
this.fitnessData[name] = {
age: age,
height: height,
weight: weight
};
return fitness;
},
// 从存储的数据中获取,更新至当前正在使用的对象
updateFitnessData: function(name, obj) {
var fitnessData = this.fitnessData[name];
for (var item in fitnessData) {
if (fitnessData.hasOwnProperty(item)) {
obj[item] = fitnessData[item];
}
}
}
};
var a = FitnessManager.add('A', 'male', 18, 160, 80);
var b = FitnessManager.add('B', 'male', 21, 180, 70);
var c = FitnessManager.add('C', 'female', 28, 160, 80);
var d = FitnessManager.add('D', 'male', 18, 170, 60);
var e = FitnessManager.add('E', 'female', 18, 160, 40);
// 开始评判
a.judge('A'); // A: false
b.judge('B'); // B: false
c.judge('C'); // C: false
d.judge('D'); // D: true
e.judge('E'); // E: true
不过代码可能更复杂了,这个例子可能还不够充分,只是展示了享元模式如何实现,它节省了多个相似的对象,但多了一些操作。
5、状态模式
1. 定义
事物内部状态的改变往往会带来事物的行为改变。在处理的时候,将这个处理委托给当前的状态对象即可,该状态对象会负责渲染它自身的行为
2. 核心
区分事物内部的状态,把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部
3. 实现
以一个人的工作状态作为例子,在刚醒、精神、疲倦几个状态中切换着
// 工作状态
function Work(name) {
this.name = name;
this.currentState = null;
// 工作状态,保存为对应状态对象
this.wakeUpState = new WakeUpState(this);
// 精神饱满
this.energeticState = new EnergeticState(this);
// 疲倦
this.tiredState = new TiredState(this);
this.init();
}
Work.prototype.init = function() {
this.currentState = this.wakeUpState;
// 点击事件,用于触发更新状态
document.body.onclick = () => {
this.currentState.behaviour();
};
};
// 更新工作状态
Work.prototype.setState = function(state) {
this.currentState = state;
}
// 刚醒
function WakeUpState(work) {
this.work = work;
}
// 刚醒的行为
WakeUpState.prototype.behaviour = function() {
console.log(this.work.name, ':', '刚醒呢,睡个懒觉先');
// 只睡了2秒钟懒觉就精神了..
setTimeout(() => {
this.work.setState(this.work.energeticState);
}, 2 * 1000);
}
// 精神饱满
function EnergeticState(work) {
this.work = work;
}
EnergeticState.prototype.behaviour = function() {
console.log(this.work.name, ':', '超级精神的');
// 才精神1秒钟就发困了
setTimeout(() => {
this.work.setState(this.work.tiredState);
}, 1000);
};
// 疲倦
function TiredState(work) {
this.work = work;
}
TiredState.prototype.behaviour = function() {
console.log(this.work.name, ':', '怎么肥事,好困');
// 不知不觉,又变成了刚醒着的状态... 不断循环呀
setTimeout(() => {
this.work.setState(this.work.wakeUpState);
}, 1000);
};
var work = new Work('曹操');
执行work.behaviour() , 触发更新状态的操作
6、适配器模式
定义
是解决两个软件实体间的接口不兼容的问题,对不兼容的部分进行适配
核心
解决两个已有接口之间不匹配的问题
实现
比如一个简单的数据格式转换的适配器
// 渲染数据,格式限制为数组了
function renderData(data) {
data.forEach(function(item) {
console.log(item);
});
}
// 对非数组的进行转换适配
function arrayAdapter(data) {
if (typeof data !== 'object') {
return [];
}
if (Object.prototype.toString.call(data) === '[object Array]') {
return data;
}
var temp = [];
for (var item in data) {
if (data.hasOwnProperty(item)) {
temp.push(data[item]);
}
}
return temp;
}
var data = {
0: 'A',
1: 'B',
2: 'C'
};
renderData(arrayAdapter(data)); // A B C
7、装饰者模式
定义
以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。
是一种“即用即付”的方式,能够在不改变对 象自身的基础上,在程序运行期间给对象动态地 添加职责
核心
是为对象动态加入行为,经过多重包装,可以形成一条装饰链
实现
最简单的装饰者,就是重写对象的属性
var A = {
score: 10
};
A.score = '分数:' + A.score;
function Person() {}
Person.prototype.skill = function() {
console.log('数学');
};
// 装饰器,还会音乐
function MusicDecorator(person) {
this.person = person;
}
MusicDecorator.prototype.skill = function() {
this.person.skill();
console.log('音乐');
};
// 装饰器,还会跑步
function RunDecorator(person) {
this.person = person;
}
RunDecorator.prototype.skill = function() {
this.person.skill();
console.log('跑步');
};
var person = new Person();
// 装饰一下
var person1 = new MusicDecorator(person);
person1 = new RunDecorator(person1);
person.skill(); // 数学
person1.skill(); // 数学 音乐 跑步
在JS中,函数为一等对象,所以我们也可以使用更通用的装饰函数
// 装饰器,在当前函数执行前先执行另一个函数
function decoratorBefore(fn, beforeFn) {
return function() {
var ret = beforeFn.apply(this, arguments);
// 在前一个函数中判断,不需要执行当前函数
if (ret !== false) {
fn.apply(this, arguments);
}
};
}
function skill() {
console.log('数学');
}
function skillMusic() {
console.log('音乐');
}
function skillRun() {
console.log('跑步');
}
var skillDecorator = decoratorBefore(skill, skillMusic);
skillDecorator = decoratorBefore(skillDecorator, skillRun);
skillDecorator(); // 跑步 音乐 数学