引子
有一本講諾貝爾獎獲得者,物理學家費曼的書,叫做《發現的樂趣》,書中寫到一個費曼小時候的故事:
“我們家有《大不列顛百科全書》,我還是小孩子的時候,父親就常常讓我坐在他腿上,給我讀些《大不列顛百科全書》。比如說,我們讀關於恐龍的部分,書上可能講雷龍或其他什麼龍,書上會說:“這傢伙有 25 英尺高,腦袋寬 6 英尺。”
這時父親就停下來,說:“我們來看看這句話什麼意思。這句話的意思是:假如它站在我們家的前院裡,它是那麼高,高到足以把頭從窗戶伸進來。不過呢,它也可能遇到點麻煩,因為它的腦袋比窗戶稍微寬了些,要是它伸進頭來,會擠破窗戶。
費曼說:凡是我們讀到的東西,我們都儘量把它轉化成某種現實,從這裡我學到一個本領——凡我所讀的內容,我總設法通過某種轉換,弄明白它究竟什麼意思,它到底在說什麼。
費曼技巧
費曼技巧,或者說費曼學習法是一種以教促學的方法,一共有四步(已經知道的可以無視,直接跳過):
(1) 選擇新概念/新知識, 自己先去學習它。
(2) 假裝當一個老師,去教授別人
想象你面對一群小白,怎麼把這個概念講給他們聽,讓他們理解呢?
把你講解的思路也寫到紙上,如果實在不想寫,可以說出來。
非常重要!!!不要讓你的思路停留在大腦中,因為大腦中對於知識點之間的關聯會有些想當然的、錯誤的假設,說出來或者寫出來能找到這些“盲點”!!
(3) 如果你在教授的過程中遇到了麻煩,卡了殼,返回去學習。
重新去看書,搜相關資料,問別人,倒逼自己把這個概念搞清楚, 然後回到第二步,繼續給小白講授。
(4) 簡化你的語言。
目標是用你自己的語言,非專業的詞彙去解釋這個概念。儘量做到簡單直白,或者找到比喻來表達。
非常簡單的過程,對吧?
實戰演練
我們來用個例子來演練一下,有請碼農翻身頭號主人公張大胖出場。
張大胖正在學習Java,這一天他遇到了一個新的概念:“動態代理” (注意是學習這個概念,不是具體實現), 非常抽象,在日常程式設計中幾乎不會直接使用,理解起來有難度。
第一步,自學
張大胖看了動態代理的介紹,書上列舉出一堆煩人的程式碼來展示這個東西是怎麼使用的,比如有個介面(IHelloWorld)及其實現類(HelloWorld), 然後有個InvocationHandler的實現,最後用Proxy.newProxyInstance(....)建立一個新的類出來,這些都是什麼鬼?囉裡囉唆的。
第二步,張大胖嘗試教一下小白(當然這裡的小白至少得懂點兒Java)
張大胖:動態代理嘛,很簡單,就是給定一個介面和實現類,再加上一個InvocationHandler , 動態代理這個技術可以在執行時建立一個新的代理類出來。
小白:張老師, 新的代理類有什麼用?
張大胖:舉個例子,有個叫IHelloWorld介面及其實現類HelloWorld,它有一個叫sayHello()的方法。可以在sayHello()之前和之後,額外加一些日誌的輸出。
(在講解一個概念的時候,舉例和類比很重要,人類習慣於通過例子來學習,從具體走向抽象)
小白:那我直接寫一個新的類,比如HelloWorldEx,把日誌輸出新增到其中不就行了,為什麼還要用Proxy.newProxyInstance(......)這麼麻煩的方法?
public class HelloWorldEx implements IHelloWorld{ IHelloWorld hw; public HelloWorldEx(IHelloWorld hw){ this.hw = hw; } public void sayHello(){ Logger.startLog(); hw.sayHello(); Logger.endLog(); } }
張大胖無法回答這個問題,卡殼了!
第三步,回過頭去看書,學習。
書中也沒有解釋,唉!
仔細想一想,手動寫一個類HelloWorldEx和用Proxy.newProxyInstance來建立,區別到底是什麼?
實現的功能是相同的,但是HelloWorldEx需要事先寫好,編譯後不能改了,相當於寫死了!如果我想對Order類,Employee類,Department類,也想加點兒日誌,還得寫個OrderEx,EmployeeEx,DepartmentEx的類,太麻煩了!
而Proxy.newProxyInstance這種方法,可以在程式執行的時候為任意類動態地建立增強的類。
事先寫死的叫做靜態代理,Proxy.newProxyInstance這種方式叫做動態代理,更加靈活。
張大胖覺得這麼解釋就通了。
小白:為什麼要建立新的代理類,那個Proxy.newProxyInstance不能直接修改老的HelloWorld類嗎?
張大胖再度卡殼,上網搜尋,找到了答案,和Python,Ruby等方法不同,Java本質是一個靜態型別的語言,class一旦被裝入JVM,是不能修改,新增,刪除方法的,既然老的class不能修改,只能通過代理的方式來建立新的類了。
小白:懂了,這個技術主要用在什麼地方啊? 難道只是加個日誌?
張大胖第三次卡殼,只好再次搜尋。
原來動態代理使用得最多的是AOP,AOP中經常會以宣告的方式提出這樣的要求:
某個包下所有add開頭的方法,在執行之前都要呼叫Logger.startLog()方法,在執行之後都要呼叫Logger.endLog()方法。
或者對於所有以Service結尾的類,所有的方法執行之前都要呼叫tx.begin(),執行之後都要呼叫tx.commit(), 如果丟擲異常的話呼叫tx.rollback()。
到此為止,張大胖可以這樣來給小白講述了:
你不是用過Spring AOP嗎?AOP中經常有這樣的需求...... ,Spring想新增這些日誌和事務的功能,但是卻沒有辦法去修改使用者的類,它是框架啊,一是不知道使用者類的原始碼,二是Java不允許再修改裝載入JVM的class。
沒辦法,Spring只好在執行時找到使用者的類,然後操作位元組碼動態建立一個新類,新類會對原有的類進行增強,新增日誌,事務這些功能,注意啊,這些都是在記憶體中動態建立的。
這個技術就是Java的動態代理,不過它有個前提要求,就是使用者的類需要實現介面才行。我用一個簡單的例子給你說下,你就明白細節了......
第四步,簡化,比喻
上面的講解從文字上來說還是非常囉嗦的,用了很大篇幅來講解“為什麼”,因為理解了why ,剩下的就是細節了。
如果你徹底理解了以後,動態代理的技術細節會在大腦中會建立這麼一幅圖景:
$HelloWorld100就是那個代理類,它和HelloWorld都實現了IHelloWorld這個介面。
如果一定要用個比喻來說,它們倆就是“兄弟關係”,CgLib提供了另外一種對現有類增強的辦法,動態生成的類繼承了現有的類,兩者是“父子關係”。
小結
怎麼樣?用這種(假裝)教授別人,層層遞進、自我逼問的方法是不是很有效果?收益很大?
用這種辦法,實際上就是逼著你把大腦中的盲點和一些想當然的假設給暴露出來,效果要比單純地閱讀和記憶好得多,趕緊在學習中試一下吧!
更多精彩文章,盡在碼農翻身