那些你不經意間使用的設計模式(一) - 建立型模式

前言

前幾天我曾分享過幾張不那麼嚴謹的思惟導圖,其中便有關於設計模式的一張:javascript

在很長的一段時間裏,我只能記住某幾種設計模式,並無很好的應用。

索性咱們就以該圖爲大綱,講講那些咱們不經意間使用的設計模式 --- 建立型。html

1. 三種工廠模式:Factory Pattern

一般來講三種設計模式爲:

  • 簡單工廠模式(Simple Factory)
  • 工廠方法模式(Factory method)
  • 抽象工廠模式(Abstract factory)

其核心就是:前端

工廠起到的做用就是隱藏了建立實例的複雜度,只須要提供一個接口,簡單清晰。 --- 摘自《前端面試之道》vue

而區別則是:java

  • 簡單工廠模式,用來建立某一種產品對象的實例,用來建立單一對象。
  • 工廠方法模式,將建立實例推遲到子類中進行。
  • 抽象工廠模式,是對類的工廠抽象用來建立產品類簇,不負責建立某一類產品的實例。

其實從你會用jQuery開始,就已經在用工廠模式了:node

JavaScript設計模式與實踐--工廠模式面試

1. jQuery$(selector)

jQuery$('div')new $('div')哪一個好用?很顯然直接$()最方便 ,這是由於$()已是一個工廠方法了。算法

class jQuery {
    constructor(selector) {
        super(selector)
    }
    //  ....
}

window.$ = function(selector) {
    return new jQuery(selector)
}
複製代碼

2. ReactcreateElement()

React.createElement()方法就是一個工廠方法vue-cli

React.createElement('h1', null, 'Hello World!'),
複製代碼

3. Vue的異步組件

經過promise的方式resolve出來一個組件 後端

對應源碼:

export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
    // ...邏輯處理
    // async component
    let asyncFactory
    const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data, undefined, undefined, undefined, context,
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
    )
}

複製代碼

2. 單例模式:Singleton Pattern

單例模式是最簡單的設計模式之一。 用一句大白話來解釋就是:

實例一次後到處可用

單例模式的要點有三個:

  • 某個類只能有一個實例;
  • 它必須自行建立這個實例;
  • 它必須自行向整個系統提供這個實例。

從具體實現角度來講,就是如下三點:

  • 單例模式的類只提供私有的構造函數
  • 類定義中含有一個該類的靜態私有對象
  • 該類提供了一個靜態的公有的函數用於建立或獲取它自己的靜態私有對象。

一樣的,它也是咱們最先接觸的一種設計模式:

1. 引用第三方庫

屢次引用只會使用一個庫引用,如 jQuerylodashmoment等。

2. Vuex / Redux

全局狀態管理store

VuexRedux數據保存在單一store中,Mobx將數據保存在分散的多個store

const store = createStore(reducer)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)
複製代碼

3. Vue中第三方插件的安裝

首當其衝的就是Vuex安裝:

let Vue // bind on install

export function install (_Vue) {
  if (Vue && _Vue === Vue) {
    // 若是發現 Vue 有值,就不從新建立實例了
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}
複製代碼

其它也相似,感興趣的能夠去GitHub搜索export function install (Vue)

4. 簡單實現一個單例模式:

class SingletonClass {
  constructor() {
    if (!SingletonClass.instance) {
      SingletonClass.instance = this;
    }

    return SingletonClass.instance;
  }
  // other things
}

const instance = new SingletonClass();
Object.freeze(instance);

export default instance;
複製代碼

3. 建造者模式:Builder Pattern

、建造者模式主要用於 「分步驟構建一個複雜的對象」

這在其中「分步驟」是一個穩定的算法,而複雜對象的各個部分則常常變化。

一句話:指揮者分配任務,建造者進行開發,各執其責,穩定在一個大的流程裏面去。

建造者模式概念擬物化解讀

一位女士要建造一座別墅,須要找來一位包工頭,包工頭再將具體的任務分配給工人作,作完以後再給女士使用。

1. jQuery中的建造者

jQuery中建造者模式體如今:

$( "<div class= "foo">bar</div>" );

$( "<p id="test">foo <em>bar</em></p>").appendTo("body" );

var newParagraph = $( "<p />" ).text( "Hello world" );

$( "<input />" )
      .attr({ "type": "text", "id":"sample"});
      .appendTo("#container");
複製代碼

下面是jQuery源碼內部jQuery.prototype方法的一個片斷,它將從傳遞給jQuery()選擇器的標記構建jQuery對象。

不管是否 document.createElement用於建立新元素,對元素(找到或建立)的引用都會注入到返回的對象中,所以.attr()能夠在其後當即使用其餘方法。

// HANDLE: $(html) -> $(array)
    if ( match[1] ) {
      context = context instanceof jQuery ? context[0] : context;
      doc = ( context ? context.ownerDocument || context : document );
      
      //若是傳入的是單個字符串,而且是單個標記
      //只需執行createElement並跳過其他部分
    
      ret = rsingleTag.exec( selector );

      if ( ret ) {
        if ( jQuery.isPlainObject( context ) ) {
          selector = [ document.createElement( ret[1] ) ];
          jQuery.fn.attr.call( selector, context, true );

        } else {
          selector = [ doc.createElement( ret[1] ) ];
        }

      } else {
        ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
        selector = ( ret.cacheable ? jQuery.clone(ret.fragment) 
        : ret.fragment ).childNodes;
      }

      return jQuery.merge( this, selector );
複製代碼

1. 建造者模式的理想實現

本質上,建造者模式的目標是減小構造函數所用的參數數量,並提供向對象添加靈活的行爲方法。

// 使用建造者模式以前
const person1 = new Person('Peter', 26, true, 40074986, 4, 2);

// 使用建造者模式以後
const person1 = new Person();
person1
  .name('Peter')
  .age(26)
  .member(true)
  .phone(40074986)
  .children(4)
  .cars(2);
複製代碼

2. ES6中的建造者模式

咱們來假設一個商品錄入系統的業務場景,有四個必填信息,分別是:名稱,價格,分類。 該build方法將返回最終的JavaScript對象。

/書籍建造者類
class ProductBuilder {
  constructor() {
    this.name = '';
    this.price = 0;
    this.category = '';
  }

  withName(name) {
    this.name = name
    return this
  }

  withPrice(price) {
    this.price = price
    return this
  }

  withCategory(category) {
    this.category = category
    return this
  }

  build() {
    return {
      name: this.name,
      price: this.price,
      category: this.category,
    }
  }
}

console.log(
  new ProductBuilder()
    .withName('《哈利·波特》')
    .withCategory('book')
    .withPrice('29.9')
    .build()
複製代碼

雖然只有三個屬性,咱們的構建器已經至關大,而且須要不少withers

構建器的大小隨字段數量線性增加。咱們有太多的withxxxx方法。咱們其實能夠自動建立這類withxxxx方法以簡化代碼。

3. 建造者模式簡化

class ProductBuilder {
  constructor() {
    this.name = ''
    this.price = ''
    this.category = 'other'

    // 爲每一個屬性生成`wither`
    Object.keys(this).forEach(key => {
      const witherName = `with${key.substring(0, 1).toUpperCase()}${key.substring(1)}`
      this[witherName] = value => {
        this[key] = value
        return this
      }
    })
  }

  build() {
    // 獲取今生成器的全部非函數屬性的數組
    const keysNoWithers = Object.keys(this).filter(key => typeof this[key] !== 'function')

    return keysNoWithers.reduce((returnValue, key) => {
      return {
        ...returnValue,
        [key]: this[key],
      }
    }, {})
  }
}

console.log(
  new ProductBuilder()
    .withName('《哈利波特》')
    .withCategory('book')
    .build()
)
複製代碼

咱們將全部的建造方法withxxxx在constructor調用時自動被建立,這裏咱們使用了一些ES6的新語法:Object.keys獲取對象屬性數組,...的合併對象的語法

最終咱們獲得了一種聲明式(易於理解)的方法,且能夠動態添加屬性的建造者模式。

5. 提取公用部分,實現多個建造者

當你有許多建造者時,咱們能夠輕鬆地將其廣義部分提取到一個通用的父類中,從而能夠很是輕鬆地建立新的建造者。

class BaseBuilder {
  init() {
    Object.keys(this).forEach((key) => {
      const witherName = `with${key.substring(0,1).toUpperCase()}${key.substring(1)}`;
      this[witherName] = (value) => {
        this[key] = value;
        return this;
      };
    });
  }

  build() {
    const keysNoWithers = Object.keys(this).filter((key) => (
      typeof this[key] !== 'function'
    ));

    return keysNoWithers.reduce((returnValue, key) => {
      return {
        ...returnValue,
        [key]: this[key]
      };
    }, {});
  }
}
複製代碼

而後就能夠建立多個建造者了:

class ProductBuilder extends BaseBuilder {
  constructor() {
    super();

    this.name = '《前端勸退祕訣》';
    this.price = 9.99;
    this.category = 'other';

    super.init();
  }
}
複製代碼

能夠看出,建造者模式的使用有且只適合建立極爲複雜的對象。在前端的實際業務中,在沒有這類極爲複雜的對象的建立時,仍是應該直接使用對象字面或工廠模式等方式建立對象。

4. 原型模式

prototype...再講會被砍死吧。

5. 下一篇:結構型設計模式

本來打算又雙叒憋它個一萬多字,把全部設計模式寫個遍。

可是以爲吧,這樣閱讀體驗其實並很差(主要仍是懶,想慢慢寫。)

噢對了,如今還有靠譜內推的能夠聯繫我

  • 微信:huab119
  • 郵箱:454274033@qq.com

參考文章

做者掘金文章總集

須要轉載到公衆號的喊我加下白名單就好了。

公衆號

相關文章
相關標籤/搜索