前端设计模式(一)

场景
是否经常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, 播放器,组件里面的黑色背景组件等,一切一个项目里只有一个实例的对象都应使用单例模式。

思考

  1. 是不是每次用使用单例模式都需要重复写上一段代码呢?
  2. 如何改造一下,让单例模式更通用?
  1. 让工厂模式和单例模式结合起来看起来更有高级感呢?

观察者模式 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

是不是看出一点差异?
观察者模式只有【观察者】-【被观察者】两个角色。
发布/订阅模式则有【发布者】-【经纪人】-【订阅者】,三个角色。经纪人作为“中间人”不一定要通知发布者,订阅者可以主动问经纪人要数据。市面上最佳实践就是各种消息队列应用。

subscribe-pattern.jpeg

思考
浏览器中有哪些常见的观察者模式?

策略模式 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 

思考
虽然乍眼看,代码比原来的多了。
但好处:

  1. 代码更清晰了。
  2. 不同客户折扣计算之间解耦了。
  3. 实际项目中每个折扣(策略)中会增加其他校验,审核等其他操作,代码将十分优雅。扩展性,可维护性好了很多。

总结

本期讲了四种最常用的模式。
设计模式只是新手入门很好的套路,优秀的代码也不会被设计模式所限,把设计模式融会贯通到自己的代码里,才是真正理解了设计模式。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容

  • 前言上期我们学了工厂模式,单例模式,观察者模式,发布订阅模式,策略模式。让我们继续学习吧。 代理模式 Proxy ...
    JackfengGG阅读 214评论 0 0
  • 一、6种设计模式 构造函数constructor模式 构造函数模式是创建特定类型的对象的一种模式,把私有属性绑定到...
    yuhuan121阅读 361评论 0 2
  • 前言 个人推荐看一,二,三,四,八共5种模式。其他的可以学习参考使用。 一、外观模式 推荐指数:⭐️⭐️⭐️⭐️⭐...
    沐雨芝录阅读 363评论 0 6
  • 1. 面向对象三要素 ◆ 继承,子类继承父类◆ 封装,数据的权限和保密◆ 多态,同一接口不同实现 1.1 封装 1...
    zxhnext阅读 765评论 0 1
  • 首先了解设计模式的六大原则 Single Responsibility Principle 单一责任原则确保职责功...
    漆钰阅读 378评论 0 1