系统设计原则
- 模块化
- 高内聚(Cohesion)
- 低耦合(Coupling)
- 解耦
- 分层设计
- 单向依赖
- 服务抽象
- 可插拔设计
- 配置驱动,插件化/中间件化
什么是设计模式
维基百科定义:
在软件工程中,设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。这个术语是由埃里希·伽玛(Erich Gamma)等人在1990年代从建筑设计领域引入到计算机科学的。
设计模式并不直接用来完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案。面向对象设计模式通常以类别或对象来描述其中的关系和相互作用,但不涉及用来完成应用程序的特定类别或对象。设计模式能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免会引起麻烦的紧耦合,以增强软件设计面对并适应变化的能力。
设计模式是一套被反复使用,思想成熟,经过分类和无数实战设计经验的总结。使用设计模式是为了让系统代码可重用,可扩展,可解耦,更容易被人理解且能保证代码可靠性。设计模式使代码开发真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。只有夯实地基搭好结构,才能盖出坚壮的大楼。也是我们迈向高级开发人员必经的一步。
《Design Patterns: Elements of Reusable Object-Oriented Software》(中文名《设计模式》),提出了 23 种设计模式。大概有一半在日常编程中会经常用到。
23 种设计模式
- 抽象工厂模式(ABSTRACT FACTORY)
- 建造模式(BUILDER)
- 工厂方法模式(FACTORY METHOD)
- 原始模型模式(PROTOTYPE)
- 单例模式(SINGLETON)
- 适配器模式(ADAPTER)
- 桥梁模式(BRIDGE)
- 合成模式(COMPOSITE)
- 装饰模式(DECORATOR)
- 门面模式(FACADE)
- 享元模式(FLYWEIGHT)
- 代理模式(PROXY)
- 责任链模式(CHAIN OF RESPONSIBILITY)
- 命令模式(COMMAND)
- 解释器模式(INTERPRETER)
- 迭代子模式(ITERATOR)
- 调停者模式(MEDIATOR)
- 备忘录模式(MEMENTO)
- 观察者模式(OBSERVER)
- 状态模式(STATE)
- 策略模式(STRATEGY)
- 模板方法模式(TEMPLATE METHOD)
- 访问者模式(VISITOR)
内容繁多,为了便于初步理解,可以参见关于 23 种设计模式的有趣见解
如果将常见的设计模式根据类型做一个分类,可以这样划分:
设计模式六大原则
快速了解,可见这里
常见设计模式的应用
单例模式
实现单例模式的核心就是保证一个类仅有一个实例,那么意思就是当创建一个对象时,我们需要判断下之前有没有创建过该实例,如果创建过则返回之前创建的实例,否则新建。
应用例子:
一个页面中的模态框只有一个,每次打开与关闭的都应该是同一个,而不是重复新建
var createSingleInstance = function(fn) {
var instance = null;
return function() {
if (!instance) {
instance = fn.apply(this, arguments);
}
return instance;
}
};
var createModal = function() {
var modal = docuemnt.createElement('div');
//...
modal.style.display = 'none';
document.getElementById('container').append(modal);
return modal;
};
var modal = createSingleInstance(createModal);
工厂模式
工厂模式是由一个方法来决定到底要创建哪个类的实例, 而这些实例经常都拥有相同的接口。这种模式主要用在所实例化的类型在编译期并不能确定,而是在执行期决定的情况。说的通俗点,就像公司茶水间的饮料机,要咖啡还是牛奶取决于你按哪个按钮。
应用例子:new
关键字
观察者模式
定义了对象与其他对象之间的依赖关系,当某个对象发生改变的时候,所有依赖到这个对象的地方都会被通知。
应用例子:knockout.js 中的 ko.compute、vue 中的 computed 函数、React 的 redux 其实就是这个模式的实践。实现观察者模式的核心就是我们需要有一个变量来保存所有的依赖,一个listen函数用于向变量中添加依赖,一个trigger函数用于触发通知。
var observal = {
eventObj: {},
listen: function(key, fn) {
this.eventObj[key] = this.eventObj[key] || [];
this.eventObj[key].push(fn);
},
trigger: function(key) {
var eventList = this.eventObj[key];
if (!eventList || eventList.length < 1) {
return;
}
var length = eventList.length;
for (var i = 0; i < length; i++) {
var event = eventList[i];
event.apply(this, arguments);
}
}
};
//定义要监听的事件
observal.listen('command1', function() {
console.log('黑夜给了我夜色的眼睛~');
});
observal.listen('command1', function() {
console.log('我却用它寻找光明~');
});
observal.listen('command2', function() {
console.log('一花一世界~');
});
observal.listen('command2', function() {
console.log('一码一人生~');
});
//触发某个监听的事件
observal.trigger('command1');//黑夜给了我夜色的眼睛~ 我却用它寻找光明~
observal.trigger('command2');//一花一世界~ 一码一人生~
适配器模式
适配器模式是将一个类(对象)的接口(方法或属性)转化成客户希望的另外一个接口(方法或属性),适配器模式使得原本由于接口不兼容而不能一起工作的那些类(对象)可以一起工作。
应用例子:vue 里的 filter
代理模式
代理模式的定义是把对一个对象的访问, 交给另一个代理对象来操作。很多操作封装都可以归类为这个模式。
应用例子:众多封装 xhr 的请求库。
策略模式
策略模式的意义是定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。
应用例子:表单验证
// 把每种验证规则都用策略模式单独的封装起来。需要哪种验证的时候只需要提供这个策略的名字
nameInput.addValidata({
notNull: true,
dirtyWords: true,
maxLength: 30
})
// 而notNull,maxLength等方法只需要统一的返回true或者false,来表示是否通过了验证。
validataList = {
notNull: function( value ){
return value !== '';
},
maxLength: function( value, maxLen ){
return value.length() > maxLen;
}
}
模版方法模式
模式方法是预先定义一组算法,先把算法的不变部分抽象到父类,再将另外一些可变的步骤延迟到子类去实现。听起来有点像工厂模式( 非前面说过的简单工厂模式 ).最大的区别是,工厂模式的意图是根据子类的实现最终获得一种对象. 而模版方法模式着重于父类对子类的控制。
应用举例:所有游戏有的生命周期,登录、游戏、退出
var gameCenter = function(){
}
gameCenter.ptototype.init = function(){
this.login();
this.gameStart();
this.end();
}
gameCenter.prototype.login= function(){
//do something
}
gameCenter.prototype.gameStart= function(){
//空函数, 留给子类去重写
}
gameCenter.prototype.end= function(){
alert ( "欢迎下次再来玩" );
}
var 斗地主 = function(){
}
斗地主.prototype = gameCenter.prototype; //继承
斗地主.prototype.gameStart = function(){
//do something
}
(new 斗地主).init();
中介者模式
中介者对象可以让各个对象之间不需要显示的相互引用,从而使其耦合松散,而且可以独立的改变它们之间的交互。使用中介者模式可以把复杂的多对多关系, 变成了2个相对简单的1对多关系。
组合模式
组合模式又叫部分-整体模式,它将所有对象组合成树形结构。使得用户只需要操作最上层的接口,就可以对所有成员做相同的操作。
应用举例
// 繁琐的表单验证
if ( nameField.validata() && idCard.validata() && email.validata() && phone.validata() ){
alert ( "验证OK" );
}
// 组合模式的表单验证
form.validata = function(){
forEach( fields, function( index, field ){
if ( field.validata() === false ){
return false;
}
})
return true;
}
备忘录模式
备忘录模式在js中经常用于数据缓存. 比如一个分页控件, 从服务器获得某一页的数据后可以存入缓存。以后再翻回这一页的时候,可以直接使用缓存里的数据而无需再次请求服务器。
var Page = function(){
var page = 1,
cache = {},
data;
return function( page ){
if ( cache[ page ] ){
data = cache[ page ];
render( data );
}else{
Ajax.send( 'cgi.xx.com/xxx', function( data ){
cache[ page ] = data;
render( data );
})
}
}
}()
职责链模式
职责链模式是一个对象A向另一个对象B发起请求,如果B不处理,可以把请求转给C,如果C不处理,又可以把请求转给D。一直到有一个对象愿意处理这个请求为止。
应用举例:JS 事件冒泡
享元模式
享元模式主要用来减少程序所需的对象个数。
应用举例:Canvas 的渲染缓冲、长列表的展示(只显示视线内的元素)。
状态模式
状态模式主要可以用于这种场景
- 1、一个对象的行为取决于它的状态
- 2、一个操作中含有庞大的条件分支语句