亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

  免費(fèi)注冊 查看新帖 |

Chinaunix

  平臺 論壇 博客 文庫
最近訪問板塊 發(fā)新帖
查看: 979 | 回復(fù): 0
打印 上一主題 下一主題

OOP 詭異教程 [復(fù)制鏈接]

論壇徽章:
0
跳轉(zhuǎn)到指定樓層
1 [收藏(0)] [報(bào)告]
發(fā)表于 2011-12-22 08:54 |只看該作者 |倒序?yàn)g覽
本文分上、下兩篇,站在一個(gè)難以名狀的角度上研究了 JavaScript 語言中面向?qū)ο髾C(jī)制的起源、內(nèi)涵和發(fā)展,帶領(lǐng)讀者從原始森林走向高樓大廈。文章作者 lichray 是個(gè) ECMAScript 的狂熱追隨者,mozilla.org 郵件列表里的無名潛水員。
文章中使用了 Rhino 解釋器,行開頭有 "js>" 表示那是輸入,輸入下一行沒有這個(gè)標(biāo)記的表示解釋器回饋消息。
PS: 讀懂本文需要對 JavaScript 閉包和逃逸變量有較深入的了解。

一. 對象和消息
考慮一下我們平常怎么說話的。我們叫某某人做某事,用下面的句式:
forest run!
其中"!"是語氣的標(biāo)志,對于編程語言來說是沒有意義的,全部換成".":
forrest run.
不知道如果我告訴大家上面這句話就是 Smalltalk 語言中一個(gè)合法語句大家會怎么想。好了,不談這個(gè)。這樣我們就得到了一種語法,"賓"謂結(jié)構(gòu):
ObjectVerb ::
  Object Verb.
如果讓它支持多個(gè) Verb,比如
forrest run, jump, stop.
可以擴(kuò)展成這樣:
ObjectVerb ::
  Object VerbList.
VerbList ::
  Verb
  Verb , VerbList
很明顯,對于 JavaScript 來說,上面的 BNF 不可能和任何一個(gè)產(chǎn)生式匹配。問題出在哪兒?我們要幫 JavaScript 指定,誰是 Object,誰是 Verb。鑒于 Object 只有一個(gè),Verb 有多個(gè),我們可以用括號來區(qū)分它們,然后把最后那個(gè)句號去掉:
ObjectVerb ::
  Object ( VerbList )
這樣上面的那句話就變成了下面的形式:
forrest (run, jump, stop)
很像函數(shù)調(diào)用,是吧?不過還有一個(gè)問題,現(xiàn)在這些 Verb(s) 對于 JavaScript 來說是“裸詞”(Perl 語),我們可以避開再去定義這些標(biāo)識符,用字符串代替;最后再說明一下 Object 是什么:
forrest ('run', 'jump', 'stop')
那么現(xiàn)在我們第一個(gè)“模仿”自然語言的程序版本出現(xiàn)了,加上下面針對 JavaScript 的文法:
Object ::
  Identifier
Verb ::
  StringLiteral

二. 實(shí)現(xiàn)消息傳遞
有了文法,一切都好辦?吹贸鰜恚覀兿旅娴墓ぷ魇嵌x能創(chuàng)建一個(gè)新 Object 的函數(shù),函數(shù)中有一些動(dòng)作,產(chǎn)生的新 Object 是一個(gè)能處理這些消息的函數(shù)。創(chuàng)建 Forrest Gump 的函數(shù)還可以創(chuàng)建 Tom,Mike 等等;他們都是 People:
function People () {
  function run () {
    print("I'm running!")
  }
  function jump () {
    print("I'm jumping!")
  }
  function stop () {
    print("I can't stop!")
  }  
  return (function (verb) {
    switch (verb) {
      case 'run': run(); break
      case 'jump': jump() ;break
      case 'stop': stop() ;break
    }
  })
}
為了簡單起見還可以把返回的那個(gè)函數(shù)寫成這樣:
    (function (verb) {
      eval(verb)();
    }
  })
Ok。現(xiàn)在我們來試一試這個(gè)智商低于 85 的 Forrest Gump 怎么樣:
js> forrest = People()
js> forrest('run')
I'm running!
js> forrest('jump')
I'm jumping!
js> forrest('stop')
I can't stop!
事情就是這樣。我們成功地創(chuàng)造了對象,還讓他做動(dòng)作、說話。
不過,這個(gè)實(shí)現(xiàn)并不是我們上文中最后一個(gè)文法所指出的。它不支持連續(xù)發(fā)送指令。改一改。要加入順序執(zhí)行指令的辦法:
function People () {
  function run () {
    print("I'm running!")
  }
  function jump () {
    print("I'm jumping!")
  }
  function stop () {
    print("I can't stop!")
  }
  function _do_verbs_ (verblist) {
    for (var i=0; i < verblist.length; i++)
      eval(verblist[i]).call()
  }
  return (function () {
    _do_verbs_(arguments)
  })
}
這下似乎比較像樣了:
js> forrest = People()
js> forrest('jump','run','jump','stop')
I'm jumping!
I'm running!
I'm jumping!
I can't stop!

三. 利用消息傳遞處理狀態(tài)
什么是狀態(tài)?我們在進(jìn)行面向?qū)ο缶幊虝r(shí),把狀態(tài)表示為對象的一組數(shù)據(jù),我們稱之為“屬性(property)”。在我們的消息傳遞編程風(fēng)格中,可以直接把這些數(shù)據(jù)堆到產(chǎn)生對象的那個(gè)函數(shù)中去。下面給 Forrest 加入一個(gè)狀態(tài),F(xiàn)orrest 口袋里的錢。先得聲明原先有多少錢:
forrest = People(1000)
然后,我們希望可以執(zhí)行這樣的代碼,讓 forrest 支出 200 美元:
forrest('pay', 200)
但很明顯,我們無法分清 200 是 Verb 還是 'pay' 所要求的數(shù)據(jù)。我們只得簡化文法,只允許一次發(fā)送一個(gè)消息,以保全我們的腦細(xì)胞:
forrest('pay')(200)
也就是說,我們需要讓 forrest('pay') 這一表達(dá)式返回一個(gè)能改變狀態(tài)的函數(shù),而不僅僅是調(diào)用函數(shù)來顯示一句話。也就是說,如果我們想讓 Forrest 急得跳起來,我們先得跳起來:
forrest('jump')()
新時(shí)代的 Forrest 實(shí)現(xiàn)如下(省略了一點(diǎn)多余的代碼):
function People (money) {
  //var money = money
  function pay (dollars) {
    money -= dollars
  }
  function restMoney () {
    return money
  }
  function run () {
    print("I'm running!")
  }
  return (function (verb) {
    return eval(verb)
  })
}
試一下。先支出 200 美元,然后看看他還剩多少錢:
js> forrest=People(1000)
js> forrest('restMoney')()
1000
js> forrest('pay')(200)
js> forrest('restMoney')()
800
當(dāng)然,我們的 Forrest 還可以賺錢。下面這個(gè)版本比較徹底地說明了消息傳遞編程風(fēng)格的一切?梢灾苯有薷腻X之后,我們可以不需要在創(chuàng)建 Object 的時(shí)候就說明原有多少錢;當(dāng)然,使用注釋中的版本更自然:
function People (/* money */) {
  var money = 0; // var money = money ? money : 0;
  function setMoney (dollars) {
    money = dollars
  }
  function addMoney (dollars) {
    money += dollars
  }
  function pay (dollars) {
    money -= dollars
  }
  function restMoney () {
    return money
  }
  return (function (verb) {
    return eval(verb)
  })
}
試一下吧:
js> forrest = People()
js> forrest('addMoney')(1000)
js> forrest('restMoney')()
1000
js> forrest('pay')(200)
js> forrest('restMoney')()
800

上篇完。小結(jié)一下:消息傳遞的編程風(fēng)格指的是,把函數(shù) A 的執(zhí)行上下文當(dāng)作對象的數(shù)據(jù)環(huán)境,在此定義對象的動(dòng)詞(函數(shù)),然后從此上下文中返回一個(gè)可以接受、處理消息的函數(shù)(常為匿名)。用函數(shù) A 產(chǎn)生消息處理器作為對象,向此對象傳遞參數(shù)作為消息,以此執(zhí)行函數(shù) A 環(huán)境中定義的動(dòng)作,這些動(dòng)作還可能改變所在上下文中用一組數(shù)據(jù)定義的對象狀態(tài)。


這是最終確定的 JavaScript 基于消息傳遞編程風(fēng)格的文章“OOP 詭異教程(上)”的下篇。原來的想法是以風(fēng)格開頭,談到 JavaScript 的內(nèi)部機(jī)制,但作者 lichray 遲遲沒有動(dòng)鍵盤,認(rèn)為不如利用已有的風(fēng)格做一套機(jī)制出來,這樣可能更有意義。于是,就有了這個(gè)更加“詭異”的下篇。

這篇文章的宗旨是利用我們僅有的“賓謂”語法構(gòu)造出完整的一套面向?qū)ο髾C(jī)制,所以更多代碼在更多的時(shí)候是不應(yīng)在實(shí)際工作中使用的(也算一種元語言抽象),所以類似效率、代碼風(fēng)格之類的問題反對回帖質(zhì)疑。

四. 擴(kuò)展的實(shí)現(xiàn)
上文最后給出了一個(gè)“看上去很美”的基于消息傳遞的編程風(fēng)格,比如構(gòu)造一個(gè) People 類的代碼類似:

function People () {
  var money = 0
  function setMoney (dollars) {
    money = dollars
  }
  function pay (dollars) {
    money -= dollars
  }
  return (function (verb) {
    return eval(verb)
  })
}

有了這樣的語法我們就可以描述不少句子了。但是存在一個(gè)問題:現(xiàn)實(shí)中的 Objects 之間是存在關(guān)系的——比如,forrest 是個(gè) IQ 為 75 的傻子,傻子是 People 的一種。而我們僅僅是生搬硬套了一種語法而割裂了這種 "is-a" 關(guān)系。現(xiàn)在我們的工作,目的之一就是讓這樣一個(gè)“真切”的世界從我們已有的編程風(fēng)格的地基上拔地而起。
到底應(yīng)該怎樣做才能使 Fool 產(chǎn)生的對象都能響應(yīng) People 的消息呢?我們要給 Fool 產(chǎn)生的對象(也就是返回的那個(gè)匿名函數(shù)啦)都添加這樣一種能力:如果在 Fool 中響應(yīng)不了消息,那就反饋給 People 響應(yīng)。

function Fool (iq) {
  var IQ = iq || 0
  function init (iq) {
    IQ = iq
  }
  return (function (verb) {
    try {
      return eval(verb)
    } catch (e) {
      return People()(verb)
    }
  })
}

js> forrest = Fool()
js> forrest('init')(75)
js> forrest('IQ')
75
js> forrest('money')
0

五. 語法擴(kuò)展和代碼生成
這下代碼量增加了很多,強(qiáng)迫潛在的使用者們在創(chuàng)建每個(gè)類時(shí)都這樣寫那實(shí)在是令人抓狂。本來這篇文章應(yīng)該不提此類問題的解決,但考慮到有益于讀者理解“機(jī)制”這個(gè)抽象概念,這里給出一個(gè)可行的方案——把普通的類代碼用 Function() 函數(shù)重編譯為可用的 JavaScript 函數(shù)。也就是說,我們能給出類擴(kuò)展的代碼并指定被擴(kuò)展的類來獲取類似上文的代碼:

Fool = extend('People()', function (iq){
  var IQ = iq || 0
  function init (iq) {
    IQ = iq
  }
})

為了方便字符串操作,我們希望編譯后的代碼的參數(shù)部分(如 People())都集中出現(xiàn)在一個(gè)位置且盡可能便于定位。在函數(shù)頭添加一句

var origin = People()

當(dāng)然是可行的,這樣還能使 Fool 內(nèi)部顯式引用到其超類。但這樣還不夠漂亮。我們修改編譯后的樣例代碼為:

function () {
  return (function (origin) {
    var IQ = 0
    function init (iq) {
      IQ = iq
    }
    return (function (verb) {
      try {
        return eval(verb)
      } catch (e) {
        return origin(verb)
      }
    })
  })(People())
}

這個(gè)利用參數(shù)傳遞變量的小技巧不值得學(xué)習(xí),實(shí)際效率不高。但在這篇文章中,這樣綁定特殊變量的技術(shù)是標(biāo)準(zhǔn)方案。
那么,extend() 函數(shù)的實(shí)現(xiàn)為:

function extend (originc, code) {
  function argsArea (code) {
    // 題外話,正則表達(dá)式也有不值得使用的時(shí)候
    return code.slice(code.indexOf('(')+1, code.indexOf(')'))
  }
  function bodyCode (code) {
    // 不用 trim() 了,沒事兒找事兒
    return code.slice(code.indexOf('{')+1, code.lastIndexOf('}'))
  }
  function format (body) {
    var objc = bodyCode(function () {
      return (function (verb) {
        try {
          return eval(verb)
        } catch (e) {
        return origin(verb)
        }
      })
    }.toString())
    return 'return (function (origin) {'+body+objc+'})('+originc+')'
  }
  var $ = code.toString()
  return Function(argsArea($), format(bodyCode($)))
}

這樣前文提到過的 extend 的實(shí)例代碼就可以正常運(yùn)行了,測試代碼不再重復(fù)。

六. 機(jī)制完備化
這樣,我們的基于消息傳遞編程風(fēng)格的一套面向?qū)ο髾C(jī)制就確定下來了。機(jī)制是憲法,是語言的根本***,有了它,我們就可以通過修改代碼生成器,很快地給這套機(jī)制進(jìn)行完備化。
想法有很多,例子只舉兩個(gè)。
第一個(gè)例子:類的定義中應(yīng)該能直接引用到將產(chǎn)生的對象 self。答案只有一句話:把返回的那個(gè)作為對象的匿名函數(shù)命名為 self。
第二個(gè)例子:既然是單繼承模式,應(yīng)當(dāng)存在一個(gè)頂層類 AbsObj,使沒有指定繼承的類自動(dòng)繼承它。答案也只有一句話:在 extend 函數(shù)體第一行添加代碼:

if (arguments.length == 1) {
  code = originc
  originc = 'AbsObj()'
}

然后手工構(gòu)造設(shè)計(jì) AbsObj 類,為空也無所謂。不過當(dāng)然了,一般都會給頂層類添加一些全局性質(zhì)的消息綁定。由于是“底層操作”,基本上都需要修改 extend 函數(shù)。做了一個(gè)簡單的:

function AbsObj () {
  //檢測是否能響應(yīng)此 verb,要再用一次異常處理
  function canHandle(verb){
    try {
      // 別擔(dān)心這里的 self 會傳遞不過去
      self(verb)
    } catch (e) {
      return false
    }
    return true
  }
  function toString() {} // 這個(gè)搞起來其實(shí)很麻煩~`
  var self = function (verb) {
    return eval(verb)
  }
  return self
}

js> Obj=extend(function(){x=5})
js> o=Obj()
js> o('canHandle')('x')
true
js> o('canHandle')('y')
false

文章寫完了,小結(jié)一下。消息傳遞的編程不僅僅是一種代碼風(fēng)格,還可以成長為一種完備的機(jī)制。這種完備性遠(yuǎn)不只是這兩篇加起來不到300行的文章所能覆蓋的(例如非常徹底的“萬物皆對象”,因?yàn)橹灰悄茼憫?yīng)消息的函數(shù),連接一下 AbsObj 就是合法對象了;類,函數(shù)都可以),大家可以試著玩一玩,順便體會一下這個(gè)計(jì)算模型的透明和強(qiáng)大。
另外,熟悉函數(shù)式編程的朋友可以幫忙思考一下:這樣一個(gè)基于閉包變換的計(jì)算模型實(shí)質(zhì)上是函數(shù)式的,再配合動(dòng)態(tài)的函數(shù)式的對象級繼承(用一個(gè)匿名類代換一下)就能在純 FP 真正下實(shí)現(xiàn) OOP 了。可惜的是每一次更新操作都要重新生成對象,性能代價(jià)大了點(diǎn),不知道大家有什么好想法。

您需要登錄后才可以回帖 登錄 | 注冊

本版積分規(guī)則 發(fā)表回復(fù)

  

北京盛拓優(yōu)訊信息技術(shù)有限公司. 版權(quán)所有 京ICP備16024965號-6 北京市公安局海淀分局網(wǎng)監(jiān)中心備案編號:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年舉報(bào)專區(qū)
中國互聯(lián)網(wǎng)協(xié)會會員  聯(lián)系我們:huangweiwei@itpub.net
感謝所有關(guān)心和支持過ChinaUnix的朋友們 轉(zhuǎn)載本站內(nèi)容請注明原作者名及出處

清除 Cookies - ChinaUnix - Archiver - WAP - TOP