Jser 設計模式系列之面向對象 - 接口封裝與繼承

GOF在《設計模式》中說到:面向接口編程,而非面向實現編程javascript

鑑於此,這個概念可見一斑!java

JS卻不像其餘面向對象的高級語言(C#,Java,C++等)擁有內建的接口機制,以肯定一組對象和另外一組對象包含類似的的特性。所幸的是JS擁有強大的靈活性,這使得模仿接口特性又變得很是簡單。那麼究竟是接口呢?編程

 

接口概念:設計模式

接口提供了一種用以說明一個對象應該具備那些方法的手段閉包

接口,爲一些具備類似行爲的類之間(可能爲同一種類型,也可能爲不一樣類型)提供統一的方法定義,使這些類之間可以很好的實現通訊app

 

使用接口的優勢:框架

  • 自我描述性,促進代碼的重用
  • 明確一個類實現的方法,幫助其使用這個類
  • 穩定不一樣類之間的通訊

一個需求,須要多個部門協調合做的時候,接口的概念就特別重要了,每一個部門能夠循序漸進的作本身的事情,涉及到交互的時候,提供接口處理便可函數

就好像主板上的內存條,CPU,硬盤,主板提供各類接口,其餘設備直接按相應的接口插入便可this

 

javascript語言要實現接口的概念仍是有侷限性的問題的spa

  • 弱類型,具備極強的表現力的語言,那麼使用了接口後,其實就強化了類型的做用,下降了語言的靈活性了
  • 最重要的就是JS沒有對這種機制的支持,沒有強制性

 


javascript中模仿接口

經常使用的幾種手法:

  • 經過註釋
  • 經過屬性檢查模仿
  • 鴨式辨型模仿

接口自己就是一個抽象的概念,至於如何實現各有不一樣

這是個人一段項目代碼,接口是經過模塊閉包實現的,這樣的好處更能體現出封裝性

//引入編譯模塊
define('ProcessMgr', [
    'Compile'
], function(compile) {

    var flipContentProcessed,
        flipWidgetProcessed, disposeWidgetBind, objectKeys,
        converWidgetWapper, ActionMgr, getHotspot,

        //內部消息傳遞接口
        //
        //  1 構建節點樹
        //       A 構建純DOM節點
        //       B 構建content對象, 這是第二種加載模式,動態預加載
        //  2 綁定節點事件
        //  3 手動觸發事件
        //  4 自動觸發事件
        //  5 事件委託處理
        //  6 翻頁動做
        //  7 翻頁完成動做
        //  8 復位動做
        //  9 銷燬動做
        //  10 移除整個頁面結構
        //
       
ProcessMgr =
 { 
            'preCompile'     : preCompile,
            'executeCompile' : executeCompile,
            'assignTrigger'  : assignTrigger,
            'assignAutoRun'  : assignAutoRun,
            'assignSuspend'  : assignSuspend,
            'assignDispose'  : assignDispose,
            'recovery'       : recovery,
            'destroy'        : destroy,
            'removePage'     : removePage
        },

        ...........具體處理的方法...................


        return ProcessMgr; //返回對外接口
 })

 

jQuery的接口更加的直接,直接掛在在對應的原型鏈上或是靜態鏈

 


封裝

爲何要封裝?

由於咱們不關心它是如何實現的,咱們只關心如何使用

手機,電腦,咱們接觸的形形色色的東西,其實咱們一直都只是在使用它

一樣的道理,咱們放到程序設計中也是如此,封裝實現的細節 ,下降對象之間的耦合程序,保持數據的完整性與修改的約束

因此說一個設計良好的API可讓開發者賞心悅目

 

javascript實現封裝的手段

對於JS語法規則,咱們要緊緊抓住3點

  • JS函數是一等對象
  • JS是函數級的做用域,意味着函數內部的變量不能被外部訪問
  • JS是詞法性質的靜態做用域,換句話說,即使在執行期做用域仍是在定義的時候就預先分配好了

根據這3個規則咱們就能夠幹不少別的語言幹不了的事了

咱們來模擬一個完整的封裝

私有屬性和方法

var encapsulation = function(){
    //私有屬性
    var name = 'aaron'
   //私有方法
    var getName = function(){
        alert(name)
    }
}

alert(name) //

函數做用域實現變量與方法私有化,外邊天然沒法方法,固然這種徹底沒有實際意義了,咱們要配合後面的處理

 

特權屬性和方法

簡單的說就可以讓實例直接有權力訪問內部的屬性與方法,因此在設計上要修改下實現手法,

var encapsulation = function(){
    //特權
    this.name = 'aaron'
    this.getName = function(){
        alert(name)
    }
}
alert(new encapsulation().name) //aaron

弊端也顯而易見,每次new一個新的對象,特權屬性與方法都會被從新拷貝一份,也就是須要單獨佔據一塊堆內存

 

共有屬性和方法

*注意了,命名函數與函數表達式在本質上雖然沒什麼區別,可是在處理上仍是有很大不一樣

函數表達式

var encapsulation = function(){
    //靜態屬性方法
    encapsulation.name = 'aaron'
    encapsulation.getName = function(){
        alert(name)
    }
}

console.log( encapsulation.name ) //

 

命名函數

即使方法不執行,靜態屬性也能訪問到,就證實了JS有一種預解析的機制,也就是常說的函數提高了

function encapsulation(){
    //靜態屬性方法
    encapsulation.name = 'aaron'
    encapsulation.getName = function(){
        alert(name)
    }
}

console.log( encapsulation.name ) // aaron

 

共有靜態屬性和方法

這是最最經常使用的,JS 的prototype原型特性,能夠共享全部屬性與方法,固然,屬性是引用類型的時候就會存在問題了

var encapsulation = function(){
    //共享屬性方法
    encapsulation.prototype.name = 'aaron'
    encapsulation.prototype.getName = function(){
        alert(name)
    }
}

console.log(new encapsulation().name ) // aaron
console.log(new encapsulation().name ) // aaron

 

 


繼承

大型設計中,代碼確定是不少的,因此咱們須要減小重複性的代碼,儘量的弱化對象之間的耦合:

經過幾種手段

  • 類式繼承
  • 原型式繼承
  • 摻元混入

固然並不是全部的重複代碼均可以使用一種繼承方法,因此咱們通常要區分共性是否一致

我項目中使用的繼承手段

image

呵呵,眼熟吧,借鑑EXT的處理機制,有繼承與混入,本身改了點,畢竟那種分層結構,倒序調用,還有點不合適套用,下文會介紹實現

 

類式繼承

如今的框架,庫大多繼承的手段都是類式繼承,並且繼承的處理方式也基本一致,可是裏面的細節問題可能你們沒注意到,咱們一塊兒分析下

咱們看看幾個框架的繼承實現

mootools

Class

 

Backbone

extend

 

ext

extend

ext比較特殊,由於是經過注入的手法,實現繼承了類名轉化繼承混入靜態擴充

 

對比幾個框架,咱們紅線部分的相同之處沒?實現原理確是同樣的,包括EXT也同樣,只是在具體實現上增長了各自的方案,

 


設計的原理

拋開復雜的框架,咱們看看設計的底層原理是什麼,又會有什麼問題?

原型鏈做爲JS實現繼承的主要方法,其根本的思路利用原型鏈讓一個引用類型,繼承另外一個引用類型

原型與實例的關係:

構造器有一個原型對象,原型對象有一個指針指向構造器,那麼實例則是包含一個指向原型的指針

因此實例只與原型有關係

 
 
實現中的細節:
 
function SuperType(){ //父類
   this.property = true; 
} 

SuperType.prototype.getSuperValue = function(){
  return this.property; 
}; 

function SubType(){ //子類
    this.subproperty = false; 
} 

//繼承了 SuperType 
SubType.prototype = new SuperType(); //實現原型繼承,引用 

SubType.prototype.getSubValue = function (){ 
     return this.subproperty; 
}; 

var instance = new SubType(); 

alert(instance.getSuperValue());      //true
 
 
演示文稿1
 
1 繼承的本質是引用,那麼N多組實例其實都是操做的同一個引用,那麼問題來了,若是父類中有個一引用屬性,那麼一個子類操做修改了,全部的子類都會被影響
 
function SuperType(){ 
    this.colors = ["red", "blue", "green"]; 
}

function SubType(){             
} 
 
//繼承了 SuperType 
SubType.prototype = new SuperType(); 
 
var instance1 = new SubType(); 
instance1.colors.push("black"); 
alert(instance1.colors);     //"red,blue,green,black" 
 
var instance2 = new SubType(); 
alert(instance2.colors);       //"red,blue,green,black"
 
 
2 在解決原型中包含引用類型值所帶來問題的過程當中,開發人員開始使用一種叫作借用構造函數(constructor stealing)的技術(有時候也叫作僞造對象或經典繼承)
 
function SuperType(){ 
    this.colors = ["red", "blue", "green"]; 
} 
 
function SubType(){   
    //繼承了 SuperType 
    SuperType.call(this); 
} 
 
var instance1 = new SubType(); 
instance1.colors.push("black"); 
alert(instance1.colors);    //"red,blue,green,black" 
 
var instance2 = new SubType(); 
alert(instance2.colors);    //"red,blue,green"

經過使用 call()方法(或 apply()方法也能夠),咱們其實是在(將來將要)新建立的 SubType 實例的環境下調用了 SuperType 構造函數。這樣一來,就會在新 SubType 對象上執行 SuperType()函數中定義的全部對象初始化代碼。結果,SubType 的每一個實例就都會具備本身的 colors 屬性的副本了

借用構造函數的問題
若是僅僅是借用構造函數,那麼也將沒法避免構造函數模式存在的問題——方法都在構造函數中定義,所以函數複用就無從談起了。並且,在超類型的原型中定義的方法,對子類型而言也是不可見的,結果全部類型都只能使用構造函數模式。考慮到這些問題,借用構造函數的技術也是不多單獨使用的

 

經過SubType.prototype = new SuperType(); 的方式實現引用的繼承,看似很簡單,可是裏面還有幾個問題

  • 無論什麼狀況,都把父類的實例屬性與方法都複製給了子類
  • 若是子類修改了原型共享的引用屬性,倒置全部繼承的會受引向
  • 借用構造函數,那麼也將沒法避免構造函數模式存在的問題——方法都在構造函數中定義,所以函數複用就無從談起了

 

看看Backbone的如何處理

var ctor = function () {

 };
ctor.prototype = parent.prototype;

 child.prototype = new ctor();

 child.prototype.constructor = child;

可見backbone引用一箇中介ctor函數

其實就是一種代理繼承

proxy來實現繼承,這樣就避免了在classical繼承中出現的parent的constructor被使用兩次帶來的效率問題和在原型鏈中再次繼承this的屬性
function obj (o){
    var f = {}
    f.prototype = o
    return new f();
}

最後還要修正一下constructor的指針,由於是繼承ctor了

至於原型式繼承能夠參考高級程序設計3, 比較詳細了

相關文章
相關標籤/搜索