场景
是否经常review别人的代码看不懂?
是不是也怕自己写的代码别人看不懂?
那是因为你不会使用套路。
万物皆有套路,coding也是。
coding的套路便是设计模式(Design Patterns)
不好的代码可以使用设计模式来提升自己的代码质量。让别人一看就知道你在干什么,你写的代码意义是什么。
设计模式都是从后端代码总结出来的,当然也适合前端,虽然Javascript设计之初不是那么严谨,但可以用过我们的双手让前端代码更加“严谨”。
下面介绍几个适合前端的设计模式
工厂模式 Factory Pattern
工厂模式是一种很常用的模式。由一个工厂对象决定创建某一种产品对象类的实例,主要来创建同一类型产品对象。
讲人话:我想要一个64G,白色,iphone12。 中间怎么实现,是你工厂的事,你给我想要的就好了。
前端实现:通过一个js方法,return一个结构相同数据不同对象。
以我们后台系统(如mice2)为例子。
场景如下:
后台系统往往根据角色权限不同,显示页面不同。
也就是根据不同角色,在Vue, React等SPA应用中的路由不同,返回不同页面。
那让我们创建一个权限工厂吧。
//安全模式工厂
let PermissionFactory = function(role) {
if(this instanceof PermissionFactory) {
return new this[role]();
} else {
return new PermissionFactory(role);
}
};
//不同角色获得不同实例
PermissionFactory.prototype = {
staffs: function() {
this.name = "企业职员",
this.route = ['authorityManager', 'recommendPartner', 'goodsOperation', 'goodsManager', 'supplierManager'],
this.auth = function(){
//企业职员授权方法
}
},
supplier: function() {
this.name = "供应商",
this.route = ['supplierManager', 'goodsManager', 'assignOrder'],
this.auth = function(){
//供应商授权方法
}
},
admin: function() {
this.name = '管理员',
this.route = ['admin', 'setting'],
this.auth = function(){
//管理员授权方法
}
}
};
let staffs = PermissionFactory('staffs'); //获取企业职员权限
let supplier = PermissionFactory('supplier');//获取供应商权限
let admin = PermissionFactory('admin');//获取管理员权限
为了让代码更加抽象,我们应把所有角色行为统一起来。
//main 主流程
var role = UserFactory(role);
role.auth();
initRouter(role.route);
我们把不同角色的操作行为统一了起来。多个角色,一个流程。
这样写,我们的工厂模式就物有所值了。
思考
那如果每个实例都是大段代码,我们该如何更好的组织?
单例模式 Singleton Pattern
单例模式是保证一个类只有一个实例,并且提供一个访问它的全局访问点。
说人话:我要你一个就够了。
前端实现: if木有,返回创建一个新的。else,返回刚才创建的那个。
const single = (function(){
let unique;
function getInstance(){
return unique? unique : unique = new Construct();
}
function Construct(){
//具体实现
}
return {
getInstance
}
})();
这个实战中能解决什么问题?
你是不是经常看到很多项目里,把全局函数/属性,挂在到window上?
window.globalFunctionA
window.globalFunctionB
window.eventbus = new Vue();
甚至不写window.会有直接调用globalParamA,然别的同学看的一脸懵逼。
还有为了多平台,比如在Nodejs里使用,避免挂在在window上
2.例如axios, EventBus, 播放器,组件里面的黑色背景组件等,一切一个项目里只有一个实例的对象都应使用单例模式。
思考
- 是不是每次用使用单例模式都需要重复写上一段代码呢?
- 如何改造一下,让单例模式更通用?
- 让工厂模式和单例模式结合起来看起来更有高级感呢?
观察者模式 Observer Pattern
观察者模式是一种一对多的依赖关系。
人话: 谁关注我,谁就看得到我动态。
前端实现:定义一个Array,存放订阅者,当我有新消息时,通知订阅者。
class Observable {
constructor() {
//定义事件池
this.events = {};
}
add(name, handle) {
//增加一个事件
this.events[name] = handle;
}
notify(name, ...params) {
//通知
if (this.events[name]) {
this.events[name].apply(this, params);
return;
}
throw new Error('not find!');
}
}
这是极简的观察者模式。
解决什么问题呢?
当Vue, React项目中,跨组件通信时,有时候会非常不方便。
比如Car项目里是由一个父容器,包含所有的异步路由子容器的结构。总不能把所有通信方法都写在父容器上吧?
所以一般会引用第三方的event库,那你有没有想过实现方式呢?
那说到观察者模式,必然会说到发布/订阅模式。
让我们看看两种模式的全貌吧。
观察者模式(Observer pattern)代码示例
function ObserverList(){
this.observerList = [];
}
ObserverList.prototype.add = function( obj ){
return this.observerList.push( obj );
};
ObserverList.prototype.count = function(){
return this.observerList.length;
};
ObserverList.prototype.get = function( index ){
if( index > -1 && index < this.observerList.length ){
return this.observerList[ index ];
}
};
ObserverList.prototype.indexOf = function( obj, startIndex ){
var i = startIndex;
while( i < this.observerList.length ){
if( this.observerList[i] === obj ){
return i;
}
i++;
}
return -1;
};
ObserverList.prototype.removeAt = function( index ){
this.observerList.splice( index, 1 );
};
function Subject(){
this.observers = new ObserverList();
}
Subject.prototype.addObserver = function( observer ){
this.observers.add( observer );
};
Subject.prototype.removeObserver = function( observer ){
this.observers.removeAt( this.observers.indexOf( observer, 0 ) );
};
Subject.prototype.notify = function( context ){
var observerCount = this.observers.count();
for(var i=0; i < observerCount; i++){
this.observers.get(i).update( context );
}
};
function Observer(){
this.update = function(){
// ...
};
}
发布订阅模式(Publish subscribe pattern)代码示例
var pubsub = {};
(function(myObject) {
var topics = {};
var subUid = -1;
myObject.publish = function( topic, args ) {
if ( !topics[topic] ) {
return false;
}
var subscribers = topics[topic],
len = subscribers ? subscribers.length : 0;
while (len--) {
subscribers[len].func( topic, args );
}
return this;
};
myObject.subscribe = function( topic, func ) {
if (!topics[topic]) {
topics[topic] = [];
}
var token = ( ++subUid ).toString();
topics[topic].push({
token: token,
func: func
});
return token;
};
myObject.unsubscribe = function( token ) {
for ( var m in topics ) {
if ( topics[m] ) {
for ( var i = 0, j = topics[m].length; i < j; i++ ) {
if ( topics[m][i].token === token ) {
topics[m].splice( i, 1 );
return token;
}
}
}
}
return this;
};
}( pubsub ));
来源:《Learning JavaScript Design Patterns》 by Addy Osmani
是不是看出一点差异?
观察者模式只有【观察者】-【被观察者】两个角色。
发布/订阅模式则有【发布者】-【经纪人】-【订阅者】,三个角色。经纪人作为“中间人”不一定要通知发布者,订阅者可以主动问经纪人要数据。市面上最佳实践就是各种消息队列应用。
思考
浏览器中有哪些常见的观察者模式?
策略模式 Strategy Pattern
策略模式指的是定义一些列的算法,把他们一个个封装起来,目的就是将算法的使用与算法的实现分离开来,避免多重判断条件,更具有扩展性。
人话: 把很多判断分成小的方法增加扩展性。
前端实现: 利用prototyp进行扩展。
在机票,火车票,酒店等情景下,有普通客户,Vip客户,白金客户折扣等等。
如果我们正常写就会有如下代码。
function Price(personType, price) {
if (personType == 'vip') {
//vip5折
return price * 0.5;
}
else if (personType == 'superVip'){
//白金3折
return price * 0.3;
} else {
//其他都全价
return price;
}
}
这样写对不对呢? 对
这样写好不好? 不好。
原因:当我们继续增加各种折扣,满减等等销售手段计算,持续增加ifelse判断。代码将很难维护。
使用策略模式,代码改进如下
//普通客户
const price = function() {
return this.price;
}
// vip客户
const vipPrice = function() {
return this.price * .5;
}
// 白金客户
const superVipPrice = function() {
return this.price * .3;
}
// 收银台
function Cashier() {
this.name = '';
this.strategy = null;
this.price = 0;
}
Cashier.prototype.set = function(name, strategy, price) {
this.name = name;
this.strategy = strategy;
this.price = price;
}
Cashier.prototype.getResult = function() {
console.log(this.name + ' 的结账价为: ' + this.strategy.call(this));
}
var cashier = new Cashier();
cashier.set ('普通客户', price, 200);
cashier.getResult(); // 普通客户 的结账价为: 200
cashier.set ('vip客户', vipPrice, 200);
cashier.getResult(); // vip客户 的结账价为: 100
cashier.set ('老客户', superVipPrice, 200);
cashier.getResult(); // 白金客户 的结账价为: 60
思考
虽然乍眼看,代码比原来的多了。
但好处:
- 代码更清晰了。
- 不同客户折扣计算之间解耦了。
- 实际项目中每个折扣(策略)中会增加其他校验,审核等其他操作,代码将十分优雅。扩展性,可维护性好了很多。
总结
本期讲了四种最常用的模式。
设计模式只是新手入门很好的套路,优秀的代码也不会被设计模式所限,把设计模式融会贯通到自己的代码里,才是真正理解了设计模式。