在日常編程中,我們經常需要對內存的數據進行持久化的工作,把他們保存在硬盤文件或者數據庫中。
為了避免重復, 我們通常會把這部分工作封裝在一個工具類中, 讓各個客戶端來調用。
下文的FileIO就是一個簡單的工具類(為了簡單起見,并沒有使用單例或靜態方法來實現)
Java 帝國的FileIO是一個忙碌的家伙,附近7、8個村落的人都來找他, 請他把數據存儲到硬盤里。
FileIO提供了一個簡單的接口, 大家只要告訴他文件名和要保存的字符串內容, 剩下的事就只是等待了,FileIO會完成工作,告訴大家是成功還是失敗。
public class FileIO { public boolean saveStrToFile(String fileName, String content) { try { File file = getExistsFile(fileName); writeStrToFile(content, file); return true; } catch (IOException e) { e.PRintStackTrace(); return false; } }}比如張家村的小張來請FileIO保存文件的時候是這樣的:
public class XiaoZhang { public void saveStr() { String fileName = "callback.txt"; String str = "這是一個普通的例子。"; FileIO fileIO = new FileIO(); boolean isSave = fileIO.saveStrToFile(fileName, str); System.out.println(isSave ? "保存成功" : "保存失敗"); }}通常情況下, 小張都會很快拿到返回結果, 高高興興的回家。
但這一次不知道怎么回事, 這個FileIO一直不返回結果, 把小張阻塞了長達1秒鐘!
小張說: “哥們, 怎么回事? 我這兒都等了1000毫秒了, 還沒完?
FileIO回答 : ”這不能怪我啊, 你這次的數據量實在是太大了,是誰上傳的大文件故意搗亂吧, 對了, 你殺毒沒有?“
“安全問題不用你考慮 “ 小張也有點底氣不足 :”我覺得數據量還行, 也有可能是硬盤這會兒太忙了”
總之,小張一直阻塞在那里,無法回家。
回調
阻塞的事情發生的多了,極大的影響了小張的工作, 最近這一周的工分可是落后了不少啊, 再這么下去,月底分糧的時候就要餓肚子了, 餓肚子還是小事, 自己喜歡的張二妮看到自己沒糧食,估計就找別人去了。。。
他拎了兩瓶好酒去找FileIO商量: “兄弟, 我聽說有一種異步保存的辦法, 你那邊能不能用下? 保存數據的時候起一個線程, 把主線程讓回給我,保存好了再通知我,我也不用老是等你,是吧?”
FIleIO想了想說:“這樣確實可以解決問題,但每天找我保存數據的人也很多,而且我也不知道在完成數據的寫入之后怎么通知你呢?”
小張把兩瓶好酒往前一推, “我們關系這么好,你再開個專屬我的方法唄,我在調用你的saveStrToFile方法的時候順便把我的實例給你,你搞完之后通過我的實例調用我的方法通知我就行啦。就調我的onResult()這個方法吧。這事要保密, 天知地知你知我知就行了”。
于是,FileIO為小張開了一個Vip通道:
public class FileIO { public void saveStrToFile(final String fileName, String content, final XiaoZhang xiaoZhang) { new Thread(new Runnable() { @Override public void run() { try { File file = getExistsFile(fileName); writeStrToFile(content, file); xiaoZhang.onResult(true); } catch (IOException e) { e.printStackTrace(); xiaoZhang.onResult(false); } } }).start(); }}這種方式很巧妙,小張調用FileIO的saveStrToFile(String,String,XiaoZhang)的時候,把自己的實例通過第三個參數給了FileIO,FileIO開啟子線程保存完數據之后,通過XiaoZhang給的實例回調onResult(boolean)方法。
聽起來很繞口,但總結起來就我調你的方法,你再回調我的方法。
后來,JAVA帝國給這種機制取了個名字叫回調機制,在帝國中廣為人知。
酒后泄密
由于有了FileIO的VIP通道,小張處理業務的能力大幅度提升, 工分不但在張家村獨占鰲頭, 就是算上李家村, 劉家村 等,那也是數一數二的。
小張一時風光不已,越來越多的人來向他請教秘訣,但小張卻笑而不語(這可是成功秘訣,能告訴你們嘛…)。
有一次, 李家村的小李看到了FileIO有了一個新接口(畢竟都是公開的嘛), 但是不知道怎么回事, 自己也調用不了, 類型不對啊。
小李別有用心的請小張和FileIO喝酒, 酒過三巡, 倆人終于吐露了這個秘密。
這一下子炸開了鍋, 雖然Java 帝國規定, 接口的設計一定要規范, 不能亂來, 但是大家蜂擁而至, 要求FileIO 給自己也開VIP通道。
FileIO實在是沒有辦法, 無奈之下為小李, 小王等等都開啟了VIP通道:
public class FileIO { public void saveStrToFile(String fileName, String str, XiaoZhang xiaoZhang) { ... ...//同上,省略 } public void saveStrToFile(String fileName, String str, XiaoLiu xiaoLiu) { ... ...//同上,省略 } public void saveStrToFile(String fileName, String str, XiaoWang xiaoWang) { ... ...//同上,省略 } ... ...//非常多}村長支招
隨著FileIO開啟的VIP通道越來越多,FIleIO發現自己的體積越來越膨脹,自己有大量的代碼是在處理這些VIP通道,而且處理方式都差不多,VIP通道多了也就失去其意義了。
有一次, 張家村德高望重的村長路過FileIO這里,FileIO知道村長軟件設計能力了得, 趕緊拉住就行討教。
村長果然見(lao)多(jian)識(ju)廣(hua),“小伙子,既然我們村的成員老是需要你的幫助,你就別為每個人開啟一個VIP通道,你直接弄一個我們張家村的VIP通道,
這個通道不是接受張大胖, 張二胖這樣的類, 而是接受一個ZhangClient的抽象類。這個抽象類中只有一個方法:onResult
public abstract class ZhangClient { public abstract void onResult(boolean isSave);}每次,有人去找你幫忙的時候,你也不用管具體是誰,只要他實現了ZhangClient,你就知道它有一個onResult(false)的方法,你處理完了之后直接回調它的onResult(boolean)方法就行了,是不是很簡單啊,哈哈哈哈哈~~~”
FileIO聽完老村長的話恍然大悟,這一層解決不了的事,那我們再加一層,在上一層解決唄:
public class FileIO { public void saveStrToFile(String fileName, String str, ZhangClient zClient) { new Thread(new Runnable() { @Override public void run() { try { File file = getExistsFile(fileName); writeStrToFile(str, file); zClient.onResult(true); } catch (IOException e) { e.printStackTrace(); zClient.onResult(false); } } }).start(); }}public class XiaoZhang extends ZhangClient { public void saveStr() { String fileName = "callback.txt"; String str = "這是這個抽象類回調的例子。"; FileIO fileIO = new FileIO(); fileIO.saveStrToFile(fileName, str, this); } @Override public void onResult(boolean isSave) { System.out.println(isSave ? "保存成功" : "保存失敗"); }}如上所示,FileIO表面上回調了ZhangClient 的onResult(false)方法,但實際上回調的是XiaoZhang的onResult(false)方法,因為傳進來的實例實際上是繼承了ZhangClien的小張(作者:感覺像披著羊皮的狼)。
后來,帝國將這種利用抽象類去實現回調的方式稱之為抽象類回調。
Java 巡視組
FileIO把其他通道都刪除了, 只留了一個ZhangClient通道, 現在他明白老村長的老奸巨猾了。
因為李家村的李曉華抱怨說, 我們找你保存個數據, 還得繼承一個姓Zhang的類, 實在是太扯了!
FileIO想了想, 得了, 為了避免引起眾怒, 還是改個名稱吧, 就叫FileIOClient 。
即使是這樣, 很多人還在抱怨: 我已經繼承了一個類了, 怎么可能再繼承你這個FileIOClient ? 不繼承就沒法保存數據, 還有沒有王法了! 還有,你這老是改來該去, 把我們都該累死了。
事情鬧大了, 上面派了個巡視組下來解決。
FileIO戰戰兢兢的給巡視組訴苦: ”我也實在是沒辦法啊, 你看Java也不允許多繼承, 我昨晚想起一個辦法, JAVA類都隱性繼承Object,能不能在Object里面增加一個回調的方法?“
巡視組生氣的說:”別做夢了! java.lang.Object是我們的根, 那是你加方法的地方嗎?! 你整天只知道保存數據, 難道都忘了Java帝國的接口(interface)了嗎?“
FileIO被點醒了, 既然繼承的方式搞不定,那就接口好了, 接口可以隨意實現, 想實現幾個就實現幾個。
在巡查組的監視下, FileIO很快修改了代碼:
public interface IFileIOCallback { void onResult(boolean isSave);}public class FileIO { public void saveStrToFile(String fileName, String str, IFileIOCallback callback) { new Thread(new Runnable() { @Override public void run() { try { File file = getExistsFile(fileName); writeStrToFile(str, file); callback.onResult(true); } catch (IOException e) { e.printStackTrace(); callback.onResult(false); } } }).start(); }}不幸的是, 大家的代碼也都得改一遍, 不過萬幸的是, 只需要把extends FileIOClient 改為implements IFileIOCallBack即可。public class XiaoZhang implements IFileIOCallback { public void saveStr() { String fileName = "callback.txt"; String str = "這是一個回調的例子。"; FileIO fileIO = new FileIO(); fileIO.saveStrToFile(fileName, str, this); } @Override public void onResult(boolean isSave) { System.out.println(isSave ? "保存成功" : "保存失敗"); }}后來,帝國將這種利用接口去實現回調機制的方式稱之為接口回調。
尾聲
張家村的小張有點落寞, 他原來獨有的回調方法現在已經被接口回調所替代,他獨有的優勢已經蕩然無存,風光不再。
更讓他煩心的事, 隨著FileIO接口的變化, 他的代碼也不斷地改來改去, 光是修改就耽誤了不少事兒,少掙了好多工分。
不就是一個回調嗎, 還繼承這個, 實現那個的, 這Java 搞的也太復雜了。
有小道消息說,Java帝國之外的動態語言王國有個叫Duck Typing 的東西, 實現回調的時候根本不用繼承什么東西, 也不用實現什么接口, 只要自己有一個onResult方法, 就可以被調用, 小張好奇心大起,決定去出去闖一闖。
(完)
原文地址:https://xiaoqinyu0000.github.io/Java/JavaCallback/
新聞熱點
疑難解答