分類: 3C資訊

  • 為取得專利,小米持股 Ninebot 收購電動代步車始祖 Segway

    為取得專利,小米持股 Ninebot 收購電動代步車始祖 Segway

      小米投資的電動代步車廠商 Ninebot 今天宣布與小米、順為資本、紅杉資本聯合出資收購電動代步車始祖 Segway,交易目前已全數完成。身為 Ninebot 的股東,這項收購除了意味著小米持續拓展海外市場外,對小米來說最大的收益,便是可取得 Segway 手中大量的專利。   總部位於美國的 Segway 成立於 2001 年,產品主要為雙輪的電動代步車,而 Ninebot 則是中國電動代步車品牌。Segway 在全球每年約只有 1 萬台左右的銷量,對增加 Ninebot 銷售量並沒有太大的直接幫助,據知情人士透露,先前 Segway 曾與包括法拉利、香奈兒等國際知名品牌合作,Ninebot 買下 Segway 主要是衝著其在海外的知名度,更重要的是可取得 Segway 手中握有的專利。   有趣的是,Segway 創辦人 Dean Kamen 曾在 2014 年控告 Ninebot 及其他中國廠商侵犯智慧財產權,美國國際貿易委員會於 11 月受理調查,而今調查結果尚未出爐,Segway 卻先被 Ninebot 收購,成為旗下全資子公司。   此外,Ninebot 執行長高祿峰同時也對外宣布已完成 8,000 萬美元的 A 輪融資,由小米、紅杉資本、順為資本共同出資,並表示未來將開發應用電動代步車、移動互聯網及人車互動技術的產品。     本文全文授權轉載自《科技新報》─〈〉

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※別再煩惱如何寫文案,掌握八大原則!

  • 《HelloGitHub》第 51 期

    《HelloGitHub》第 51 期

    興趣是最好的老師,HelloGitHub 就是幫你找到興趣!

    簡介

    分享 GitHub 上有趣、入門級的開源項目。

    這是一個面向編程新手熱愛編程對開源社區感興趣 人群的月刊,月刊的內容包括:各種編程語言的項目讓生活變得更美好的工具書籍、學習筆記、教程等,這些開源項目大多都是非常容易上手,而且非常 Cool。主要是希望大家能動手用起來,加入到開源社區中。

    • 會編程的可以貢獻代碼
    • 不會編程的可以反饋使用這些工具中的 Bug
    • 幫着宣傳你覺得優秀的項目
    • Star 項目⭐️

    在瀏覽、參与這些項目的過程中,你將學習到更多編程知識提高編程技巧找到編程的樂趣

    最後 HelloGitHub 這個項目就誕生了

    以下為本期內容|每個月 28 號發布最新一期|點擊查看往期內容

    C 項目

    1、goaccess:實時 Web 日誌分析工具

    2、u6a:函數式編程語言 Unlambda 的一個樸素實現,包含字節碼編譯器和解釋器。此項目可以幫助初學者理解函數式編程的思想,並提供了實現函數式編程語言解釋器的一些樸素思路。

    • 性能優異:運行性能遠高於官方實現,且優於多數現有的開源實現
    • 穩定可靠:有豐富的測試樣例支撐,可靠性高
    • 簡單樸素:代碼簡單易讀,且提供了實現思路文檔,對初學或者完全沒有學過編譯原理的新手非常友好

    C# 項目

    3、Netch:一款 Windows 平台的開源遊戲加速工具

    4、ScheduleMasterCore:一款基於 .NET Core 開發的分佈式任務調度系統。支持豐富的調度類型、靈活可控的系統參數、簡易的 UI 操作、支持多節點高可用、業務 API 集成等等特性。同時支持多樣化的部署方式,容易上手

    5、HandyControl:一套 WPF 控件庫。它幾乎重寫了所有原生樣式,同時包含 70 餘款自定義控件。支持跨平台、國際化,適用於 MVVM 架構開發,扁平化設計、支持動態更換主題和背景色。豐富的自定義控件解決了 View 設計的痛點,讓程序員更加專註於業務邏輯的開發

    C++ 項目

    6、CnC_Remastered_Collection:EA 發布的《紅警》和《泰伯利亞黎明》遊戲源代碼

    7、chinessChess:基於 Qt5 開發的中國象棋網絡對戰平台,支持單機和網絡對戰

    Go 項目

    8、grmon:Goroutine 的命令行監控工具

    9、HackChrome:Go 語言實現的從 Chrome 中獲取自動保存的用戶名密碼工具。目前僅支持 Windows Chrome 中存儲的密碼,但是很有意思還可以學習怎麼用 Go 調用 DLL 動態鏈接庫的姿勢

    10、seaweedfs:一款基於 Go 開發的部署方便、使用簡單且強大的分佈式文件系統

    11、fate:起中文名工具,去吧!算名先生

    Java 項目

    12、JApiDocs:一個無需額外註解、開箱即用的 SpringBoot 接口文檔生成工具。特性:

    • 代碼即文檔
    • 支持導出 HTML
    • 同步導出客戶端 Model 代碼
    • 等等

    13、PowerJob:基於 Akka 架構的新一代分佈式任務調度與計算框架。支持 CRON、API、固定頻率、固定延遲等調度策略,支持單機、廣播、MapReduce 等多種執行模式,支持在線任務治理與運維,提供 Shell、Python、Java 等功能豐富的任務處理器,提供工作流來編排任務解決依賴關係,使用簡單,功能強大,文檔齊全。同類產品對比:

    JavaScript 項目

    14、react-trello:任務狀態管理面板組件。實現了拖拽方式管理任務狀態,點擊即可編輯任務內容

    15、perfume.js:用於測量第一個 dom 生成的時間、用戶最早可操作時間和組件的生命周期性能的庫。示例代碼:

    perfume.start('fibonacci');
    fibonacci(400);
    perfume.end('fibonacci');
    // Perfume.js: fibonacci 0.14 ms
    

    16、Mongood:MongoDB 圖形化的管理工具。特性:

    • 基於微軟 Fluent UI,支持自動黑暗模式
    • 支持完整的 Mongo-shell 數據類型和查詢語法,利用索引實現的自動查詢和排序
    • 支持 Json 數據庫模式,既可用於 Server 也可用於 Client

    17、TimeCat:一款 JS 的網頁錄屏工具。參考了遊戲錄像的原理而實現的渲染引擎,生成的錄像文件只有傳統視頻的百分之一!還可以在錄製語音的同時自動生成字幕,導出的視頻文件可以跨端播放。目前已經開發一段時間,後續還將實現更多有意思的功能,歡迎持續關注。在線預覽

    18、react-visual-editor:基於 React 組件的可視化拖拽、搭建頁面的代碼生成工具。所見即所得,可以完美還原 UI 設計搞,並支持多款型號手機(可配置)和 PC 效果展示,模板功能可以使你分享你的頁面或者頁面中局部任何部分組件組合,減少相似頁面的重複操作。效果如下:

    19、elevator.js:一個 back to top 返回頂部的插件。如他的名字一樣,網頁在返回頂部過程中像電梯向上運行,當頁面返回到頂部時,會有電梯“到達”的提示音。叮~頁面已到達頂部

    PHP 項目

    20、code6:一款 GitHub 代碼泄露監控系統,通過定期掃描 GitHub 發現代碼泄露行為。特性:

    • 全可視化界面,操作部署簡單
    • 支持 GitHub 令牌管理及智能調度
    • 掃描結果信息豐富,支持批量操作
    • 任務配置靈活,可單獨配置任務掃描參數
    • 支持白名單模式,主動忽略白名單倉庫

    Python 項目

    21、rich:一個讓你的終端輸出變得“花里胡哨”的三方庫。我的一位前輩告訴我,不要整那些花里胡哨的主題和樣式,這是在自尋煩惱。可是臣妾做不到啊,這麼好看的終端輸出,讓我的心情都愉悅起來了。瞧那性感的語法高亮、整齊的表格、舒服的顏色、進度條等,一切都是值得的

    22、poetry:Python 虛擬環境、依賴管理工具。依賴管理工具有很多,我相上了它有三點:通過單文件 pyproject.toml 便可輕鬆的區別安裝、管理開發和正式環境、有版本鎖定可方便回滾、輸出界面簡單清爽。當然它還是個“新生兒”,嘗鮮的風險還是有的,選擇須謹慎

    23、free-python-games:真入門級的 Python 遊戲集合庫。都是簡單的小遊戲:貪吃蛇、迷宮、Pong、猜字等,運行方便、代碼簡單易懂。用遊戲開啟的你 Python 學習之旅,玩完再學源碼,其樂無窮啊。安裝運行:

    pip install freegames
    python -m freegames.snake # freegames.遊戲名
    

    24、py2sec:一款輕量級跨平台 Python “加密”、加速的腳本工具。原理是基於 Cython 將 .py 編譯成 run-time libraries 文件:.so(Linux && Mac)或 .pyd(Win),一定程度上實現了“加密”保護源代碼的功能。參數詳解如下:

    -v,  --version    显示 py2sec 版本
    -h,  --help       显示幫助菜單
    -p,  --pyth       Python 的版本,默認為你的 Python 命令綁定的 Python 版本
    -d,  --directory  Python 項目路徑(如果使用 -d 參數,將編譯整個 Python 項目)
    -f,  --file       Python文件(如果使用 -f,將編譯單個 Python 文件)
    -m,  --maintain   標記你不想編譯的文件或文件夾路徑
    -x  --nthread     編譯啟用的線程數
    -q  --quiet       靜默模式,默認 False
    -r  --release     Release 模式,清除所有中間文件,只保留加密結果文件,默認 False
    python py2sec.py -f test.py
    python py2sec.py -f example/test1.py -r
    python py2sec.py -d example/ -m test1.py,bbb/
    

    25、oxfs:一個基於 sftp 協議的 fuse 網絡文件系統,功能上類似於 sshfs。特性:

    • 引入了異步併發讀遠端文件機制,提高了文件首次讀速度。
    • 緩存持久化到本地磁盤,下次掛載時訪問更加快速。
    • 異步任務負責同步文件,避免低速的網絡讀寫阻塞上層應用。

    Swift 項目

    26、Aerial:炫酷的蘋果系統屏保項目。該屏保視頻取材自蘋果零售店 Apple TV 的專用屏保,航拍質量超棒,快換上試試吧。直接下載 Aerial.saver.zip 文件,解壓后雙擊文件“即可食用”

    其它

    27、shan-shui-inf:自動生成一副山水畫

    28、kuboard-press:一款基於 Kubernetes 的微服務管理界面。包含文檔、教程、管理界面和實戰分享

    29、vscode-rainbow-fart:一款在你編程時花式誇你的 VSCode 擴展插件。可以根據代碼關鍵字,播放貼近代碼意義的真人語音,並且有一個醒目的項目名字“彩虹屁”

    30、flink-training-course:Flink 視頻直播教程回放集合

    31、raft-zh_cn:《分佈式 Raft 一致性算法論文》中文翻譯

    32、GitHub-Chinese-Top-Charts:每周更新一次的 GitHub 中文項目排行榜

    開源書籍

    33、go-ast-book:《Go語法樹入門:開啟自製編程語言和編譯器之旅》

    機器學習

    34、Surprise:一款簡單易用基於 Python scikit 的推薦系統。如果你想用 Python 上手做一套推薦系統,那你可以試試它

    35、djl:亞馬遜開源的一款基於 Java 語言的深度學習框架。對於 Java 開發者而言,可以在 Java 中開發及應用原生的機器學習和深度學習模型,同時簡化了深度學習開發的難度。通過 DJL 提供直觀的、高級的 API,Java 開發人員可以訓練自己的模型,或者利用數據科學家用 Python 預先訓練好的模型來進行推理。如果您恰好是對學習深度學習感興趣的 Java 開發者,那麼這個項目完全對口。運行效果如下:

    36、data-science-ipython-notebooks:數據科學的 IPython 集合。包含:TensorFlow、Theano、Caffe、scikit-learn、Spark、Hadoop、MapReduce、matplotlib、pandas、SciPy 等方方面面

    最後

    如果你發現了 GitHub 上有趣的項目,歡迎在 HelloGitHub 項目提 issues 告訴我們。

    關注 HelloGitHub 公眾號獲取第一手的更新

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※教你寫出一流的銷售文案?

  • 推薦一種通過刷leetcode來增強技術功底的方法

    推薦一種通過刷leetcode來增強技術功底的方法

    背景

    如果前人認為這個一種學習提高或者檢驗能力的成功實踐。而自己目前又沒有更好的方法,那就不妨試一試。

    而不管作為面試官還是被面試者,編碼題最近越來越流行。而兩種角色都需要思考的問題是希望考察什麼能力,通過什麼題目,需要達到怎樣的程度可以說明面試者具有了這樣的能力。

    而要找到上面這些問題的答案,比較好的方式除了看一些理論性文章和接受培訓之外,自己動手刷一刷leetcode切身實踐一下不失為一個不錯的方式。而既然要花精力去做這件事情,那就需要解決一個問題:我從中可以獲得什麼提高。以下是個人的一些經驗和感受。

     

    收益

    對底層有了更深入的了解和思考

    leetcode一些常見題也是平時工作中常用的一些底層數據結構的實現方法。 

    先舉個大家使用比較多的算法:LRU(最近最少使用),在Java的實現中實現特別簡單。只是使用了LinkedHashMap這種數據結構。而如果看一些開源包里LRU的實現,發現也是這樣實現的。實際上動手實現一遍,LRU就再也不會忘了。

    再舉個數據結構的例子:字典樹又叫前綴樹。它是搜索和推薦的基礎。標準點的定義是:

    字典樹又稱單詞查找樹,Tire樹,是一種樹形結構,是一種哈希樹的變種。典型應用是用於統計,排序和保存大量的字符串(但不僅限於字符串),所以經常被搜索引擎系統用於文本詞頻統計。它的優點是:利用字符串的公共前綴來減少查詢時間,最大限度地減少無謂的字符串比較,查詢效率比哈希樹高。

    因為之前做過搜索引擎,一直也對這塊很有興趣,所以對它底層知識的補充對個人而言,感覺深度有增加。

     

      

    養成評估時空開銷的習慣

    我刷leetcode必看官方解答里估算的時間和空間複雜度。這也是作為一個架構師的必備基本能力。

    數組、哈希這些因為數據的位置不需要進行查找,只需要算數計算就可以得到,所以它們的時間複雜度是O(1)。

    鏈表如果直接在頭部或者尾部插入,因為不需要查找,所以時間複雜度也是O(1),但是定位的話因為涉及查找,按遍歷查找來算是log(n)。所以對於jdk1.7之前,hashmap底層採用的是數組+鏈表的數據結構。所以如果不算哈希衝突和擴容的話,獲取和插入數據的時間複雜度是O(1);如果出現了哈希衝突,則插入因為是頭部插入,時間複雜度還是O(1);獲取時間複雜度因為涉及先查找,所以是O(n),這個n代表衝突的數量。

    對於在有序數據中進行查找,因為可採用二分查找等優化,時間複雜度可降到log(n).

    對於遞歸調用,如果遞歸方法內進行2次調用。對於層數n來說,時間複雜度是2的n次方。舉個例子就是一個數等於前面兩個數之和。當然,如果是前面3個數之和,不進行優化的情況下時間複雜度就是3的n次方。

    對於一個n*m的二維數組等需要進行嵌套循環遍歷的,時間複雜度是O(n*m),有個特殊情況是n*m,這時候時間複雜度是n的平方。

    對於全排列的情況,時間複雜度是O(n!)。

     

    代碼簡化的方法

     

    我習慣的一種學習方法是先做題,有了一定自己的總結和思考之後,再看書學習別人的總結思考方法。對於刷leetcode相關性高,也比較受認可的書是《Cracking the Coding interview(6th)》,中文版翻譯是《程序員面試金典》。這本書對於面試官和面試者來說讀了都會有一定的收穫。

     

    我讀了這本書,對我印象最深的是介紹了兩種代碼優化的方法:BUD和BCR。

     

    BUD 

    BUD是瓶頸、不必要工作、重複工作 三個詞組首字母的縮寫。

     

    作者提出拿到一道編程題,可先嘗試用暴力解法將題目寫出來,之後找到解法的性能瓶頸,針對瓶頸進行優化,之後在去掉不必要的工作,最後去掉重複的工作。

    這個經典的編程優化方法不只可應用於編程,還可應用於整個程序的優化,也是最常規的優化方法。

     

    BCR

    BCR是Best Conceivable Runtime的縮寫,意思是想知道自己可以優化到什麼程度,先估算可達到的最優情況。

    比如:在一個無序數組中,查找兩個兩個相同的數。直覺來說如果找到這兩個數,最起碼需要將每個數都遍歷一遍,所以可達到的最優情況是O(n),無論怎麼優化,都不可能比這個更好。所以這就是優化的上限。

    這本書里還介紹了其他的優化方法如:使用額外數據結構、通過構建測試用例、根據題目的限制和提示來尋找線索,大家看這本書的時候可以了解下。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※台北網頁設計公司全省服務真心推薦

    ※想知道最厲害的網頁設計公司"嚨底家"!

    新北清潔公司,居家、辦公、裝潢細清專業服務

    ※推薦評價好的iphone維修中心

  • 【asp.net core 系列】14 .net core 中的IOC

    【asp.net core 系列】14 .net core 中的IOC

    0.前言

    通過前面幾篇,我們了解到了如何實現項目的基本架構:數據源、路由設置、加密以及身份驗證。那麼在實現的時候,我們還會遇到這樣的一個問題:當我們業務類和數據源越來越多的時候,我們無法通過普通的構造對象的方法為每個實例進行賦值。同時,傳統意義上的賦值遇到底層切換或者其他修改的時候,就需要修改大量的代碼,對改變不友好。為了改變這種現狀,我們基於面向接口編程,然後使用一些DI功能和IOC框架。

    1. IOC和DI

    先來給大家解釋幾個概念,IOC全稱Inversion of Control,翻譯過來就是控制反轉,是面向對象編程的一種設計原則,用來降低代碼之間的耦合度。所謂的控制反轉簡單來講就是將類中屬性或者其他參數的初始化交給其他方處理,而不是直接使用構造函數。

    public class Demo1
    {
    }
    
    public class Demo2
    {
    	public Demo1 demo;
    }
    

    對於以上簡單示例代碼中,在Demo2類中持有了一個Demo1的實例。如果按照之前的情況來講,我們會通過以下方法為demo賦值:

    // 方法一
    public Demo1 demo = new Demo1();
    // 方法二
    public Demo2()
    {
        demo = new Demo1();
    }
    

    這時候,如果Demo1變成下面的樣子:

    public class Demo1
    {
        public Demo1(Demo3 demo3)
        {
            // 隱藏
        }
    }
    public class Demo3
    {
    }
    

    那麼,如果Demo2 沒有持有一個Demo3的實例對象,這時候創建Demo1的時候就需要額外構造一個Demo3。如果Demo3需要持有另外一個類的對象,那麼Demo2中就需要多創建一個對象。最後就會發現這樣就陷入了一個構造“地獄”(我發明的詞,指這種為了一個對象卻得構造一大堆其他類型的對象)。

    實際上,對於Demo2並不關心Demo1的實例對象是如何獲取的,甚至都不關心它是不是Demo1的子類或者接口實現類。我在示例中使用了類,但這裏可以同步替換成Interface,替換之後,Demo2在調用Demo1的時候,還需要知道Demo1有實現類,以及實現類的信息。

    為了解決這個問題,一些高明的程序員們提出了將對象的創建這一過程交給第三方去操作,而不是調用類來創建。於是乎,上述代碼就變成了:

    public class Demo2
    {
        public Demo1 Demo {get;set;}
        public Demo2(Demo1 demo)
        {
            Demo = demo;
        }
    }
    

    似乎並沒有什麼變化?對於Demo2來說,Demo2從此不再負責Demo1的創建,這個步驟交由Demo2的調用方去創建,Demo2從此從負責維護Demo1這個對象的大麻煩中解脫了。

    但實際上構造地獄的問題還是沒有解決,只不過是通過IOC的設計將這一步后移了。這時候,那些大神們想了想,不如開發一個框架這些實體對象吧。所以就出現了很多IOC框架:AutoFac、Sping.net、Unity等。

    說到IOC就不得不提一下DI(Dependency Injection)依賴注入。所謂的依賴注入就是屬性對應實例通過構造函數或者使用屬性由第三方進行賦值。也就是最後Demo2的示例代碼中的寫法。

    早期IOC和DI是指一種技術,後來開始確定這是不同的描述。IOC描述的是一種設計模式,而DI是一種行為。

    2. 使用asp.net core的默認IOC

    在之前的ASP.NET 框架中,微軟並沒有提供默認的IOC支持。在最新的asp.net core中微軟提供了一套IOC支持,該支持在命名空間:

    Microsoft.Extensions.DependencyInjection
    

    里,在代碼中引用即可。

    主要通過以下幾組方法實現:

    public static IServiceCollection AddScoped<TService>(this IServiceCollection services) where TService : class;
    public static IServiceCollection AddSingleton<TService>(this IServiceCollection services) where TService : class;
    public static IServiceCollection AddTransient<TService>(this IServiceCollection services) where TService : class;
    

    這裏只列出了這三組方法的一種重載版本。

    這三組方法分別代表三種生命周期:

    • AddScored 表示對象的生命周期為整個Request請求
    • AddTransient 表示每次從服務容器進行請求時創建的,適合輕量級、 無狀態的服務
    • AddSingleton 表示該對象在第一次從服務容器請求后獲取,之後就不會再次初始化了

    這裏每組方法只介紹了一個版本,但實際上每個方法都有以下幾個版本:

    public static IServiceCollection AddXXX<TService>(this IServiceCollection services) where TService : class;
    public static IServiceCollection AddXXX(this IServiceCollection services, Type serviceType, Type implementationType);
    public static IServiceCollection AddXXX(this IServiceCollection services, Type serviceType, Func<IServiceProvider, object> implementationFactory);
    public static IServiceCollection AddXXX<TService, TImplementation>(this IServiceCollection services)
                where TService : class
                where TImplementation : class, TService;
    public static IServiceCollection AddXXX(this IServiceCollection services, Type serviceType);
    public static IServiceCollection AddXXX<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory) where TService : class;
    public static IServiceCollection AddXXX<TService, TImplementation>(this IServiceCollection services, Func<IServiceProvider, TImplementation> implementationFactory)
                where TService : class
                where TImplementation : class, TService;
    

    其中:implementationFactory 表示通過一個Provider實現TService/TImplementation 的工廠方法。當方法指定了泛型的時候,會自動依據泛型參數獲取要注入的類型信息,如果沒有使用泛型則必須手動傳入參數類型。

    asp.net core如果使用依賴注入的話,需要在Startup方法中設置,具體內容可以參照以下:

    public void ConfigureServices(IServiceCollection services)
    {
        //省略其他代碼
        services.AddScoped<ISysUserAuthRepository,SysUserAuthRepository>();
    }
    

    asp.net core 為DbContext提供了不同的IOC支持,AddDbContext:

    public static IServiceCollection AddDbContext<TContext>(
          this IServiceCollection serviceCollection,
          Action<DbContextOptionsBuilder> optionsAction = null,
          ServiceLifetime contextLifetime = ServiceLifetime.Scoped,
          ServiceLifetime optionsLifetime = ServiceLifetime.Scoped)
          where TContext : DbContext;
    

    使用方法如下:

    services.AddDbContext<DefaultContext>();
    

    3. AutoFac 使用

    理論上,asp.net core的IOC已經足夠好了,但是依舊原諒我的貪婪。如果有二三百個業務類需要我來設置的話,我寧願不使用IOC。因為那配置起來就是一場極其痛苦的過程。不過,可喜可賀的是AutoFac可以讓我免收這部分的困擾。

    這裏簡單介紹一下如何使用AutoFac作為IOC管理:

    cd Web  # 切換目錄到Web項目
    dotnet package add Autofac.Extensions.DependencyInjection # 添加 AutoFac的引用
    

    因為asp.net core 版本3更改了一些邏輯,AutoFac的引用方式發生了改變,現在不介紹之前版本的內容,以3為主。使用AutoFac需要先在 Program類里設置以下代碼:

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        		Host.CreateDefaultBuilder(args)
        		.UseServiceProviderFactory(new AutofacServiceProviderFactory()) // 添加這行代碼
        		.ConfigureWebHostDefaults(webBuilder =>
    			{
                    webBuilder.UseStartup<Startup>();
                });
    

    在Program類里啟用AutoFac的一個Service提供工廠類。然後在Startup類里添加如下方法:

    public void ConfigureContainer(ContainerBuilder builder)
    {
        builder.RegisterType<DefaultContext>().As<DbContext>()
                    .WithParameter("connectStr","Data Source=./demo.db")
                    .InstancePerLifetimeScope();
                
    
        builder.RegisterAssemblyTypes(Assembly.Load("Web"))
            .Where(t => t.BaseType.FullName.Contains("Filter"))
            .AsSelf();
    
        builder.RegisterAssemblyTypes(Assembly.Load("Domain"),
                        Assembly.Load("Domain.Implements"), Assembly.Load("Service"), Assembly.Load("Service.Implements"))
                    .AsSelf()
                    .AsImplementedInterfaces()
                    .InstancePerLifetimeScope()
                    .PropertiesAutowired();
    }
    
    

    修改:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews(options =>
                {
                    options.Filters.Add<UnitOfWorkFilterAttribute>();
                }).AddControllersAsServices();// 這行新增
        // 省略其他
    }
    

    4. 總結

    這一篇簡單介紹了如何在Asp.net Core中啟用IOC支持,並提供了兩種方式,可以說是各有優劣。小夥伴們根據自己需要選擇。後續會為大家詳細深入AutoFac之類IOC框架的核心秘密。

    更多內容煩請關注我的博客《高先生小屋》

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    台北網頁設計公司這麼多該如何選擇?

    ※智慧手機時代的來臨,RWD網頁設計為架站首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※回頭車貨運收費標準

  • 作為一個Java開發你用過Jib嗎

    作為一個Java開發你用過Jib嗎

    1. 前言

    JibGoogle開發的可以直接構建 Java應用的DockerOCI鏡像的類庫,以MavenGradle插件形式提供。它最騷操作的是可以在沒有Docker守護程序的情況下構建,也就是說,您不必在計算機上安裝docker守護程序!儘管Spring Boot 2.3.0.RELEASE已經推出了構建鏡像的功能,胖哥還是忍不住要試試Jib

    其實最騷的還是名字。

    2. Docker構建流程和Jib的構建流程

    沒有對比就沒有傷害。我們還是要對比一下這兩者的構建流程。

    Docker構建流程需要我們先把項目打成Jar然後編寫Dockerfile,然後使用Docker構建功能進行構建鏡像、運行容器。流程如下:

    而Jib是這樣構建的:

    作為一個Java開發者,不用再關心各種無關的命令和操作,只需要專註於Java,而且高效穩定以及可復用的增量構建。為什麼Jib能這麼快而高效?

    傳統上,將Java應用程序與應用程序Jar一起構建為單個圖像層,而Jib的構建策略將Java應用程序分為多層,以進行更細化的增量構建。更改代碼時,僅重建更改,而不重建整個應用程序。

    3. Jib構建Spring Boot應用

    接下來我將演示如何將Spring Boot 應用打成鏡像並上傳到Dockerhub倉庫。

    Maven工程為例,我們只需要在pom.xml中引入Jib Maven 插件。默認情況下Jib會把我們打好的鏡像上傳到Googlegcr.io倉庫,實際中我們會把打好的鏡像上傳到私有倉庫,所以我們要加一些個性化配置。這裏我以dockerhub倉庫為例添加一些個性化配置:

    <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>jib-maven-plugin</artifactId>
        <version>2.4.0</version>
        <configuration>
            <!-- 相當於 Dockerfile 中的 FROM -->
            <from>
                <image>amazoncorretto:8</image>
            </from>
            <to>
                <!--構建鏡像名稱,這裏我使用maven中定義的項目名稱-->
                <image>daxus/${project.name}</image>
                <!--私有倉庫的賬號密碼-->
                <auth>
                    <username>felordcn</username>
                    <password>yourpassword</password>
                </auth>
                <!--Docker 鏡像的 tag 這裏使用maven定義的版本號-->
                <tags>
                    <tag>
                        ${project.version}
                    </tag>
                </tags>
            </to>
        </configuration>
    </plugin>
    

    然後在項目根目錄執行mvn clean compile jib:build就可以了。

    其實也可以簡單引入Jib插件:

    <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>jib-maven-plugin</artifactId>
        <version>2.4.0</version>
    </plugin>
    

    只不過我們的命令會更複雜一些,需要指定一些必要的參數,例如:

    mvn clean compile jib:build \
        -Djib.to.image=myregistry/myimage:latest \
        -Djib.to.auth.username=$USERNAME \
        -Djib.to.auth.password=$PASSWORD
    

    更多的定製命令可參考官方文檔:

    https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#extended-usage

    4. 總結

    Jib使用起來非常簡單,讓開發人員以Java的風格來完成Docker鏡像的構建,能夠大大改善編程的體驗。多多關注:碼農小胖哥 獲取更多有用的編程乾貨教程。

    關注公眾號:Felordcn 獲取更多資訊

    個人博客:https://felord.cn

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • 聊聊算法–堆的構建和調整

    聊聊算法–堆的構建和調整

    先提個問題,完全二叉樹/滿二叉樹,區別?前者是指每一層都是緊湊靠左排列,最後一層可能未排滿,後者是一種特殊的完全二叉樹,

    每層都是滿的,即節點總數和深度滿足N=(2^n) -1。堆Heap,一堆蘋果,為了賣相好,越好看的越往上放,就是大頂堆;為了蘋果堆

    的穩定,質量越小越往上放,就是小頂堆;堆首先是完全二叉樹,但只確保父節點和子節點大小邏輯,不關心左右子節點的大小關係,

    通常是一個可以被看做一棵樹的數組對象,是個很常見的結構,比如BST對象,都與堆有關係,今天就說下這個重要的數據結構和應用。

     

    作者原創文章,謝絕一切轉載,違者必究!

    本文只發表在”公眾號”和”博客園”,其他均屬複製粘貼!如果覺得排版不清晰,請查看公眾號文章。 

     

    準備:

    Idea2019.03/Gradle6.0.1/Maven3.6.3/JDK11.0.4

    難度: 新手–戰士–老兵–大師

    目標:

    1.堆的構建和調整算法

    1 優先級隊列

    為理解堆的原理,先看優先級隊列,它是一種數據結構,插入或者刪除元素的時候,元素會自動排序,(優先級不是狹義的數值大小,

    但為了通俗理解,這裏以字母序為例),通常使用數組存儲,我們可以按照下圖進行轉換,序號 0 不用:

    優先級隊列的實現(Java版):

    public class PriorityQueue<Key extends Character> {
        /** 存儲元素的數組 */
        private Key[] keys;
        private int N = 0;
    
        public PriorityQueue(int capacity){
            // 下標0不用,多分配一個單位
            keys = (Key[]) new Character[capacity + 1];
        }
    
        public Key max(){
            return keys[1];
        }
    
        public void insert(Key e){
            N ++;
            keys[N] = e;
            swim(N);
        }
        public Key delMax(){
            Key max = keys[1];
            swap(1,N);
            keys[N] = null;
            N --;
            // 讓第一個元素下沉到合適的位置
            sink(1);
            return max;
        }
        /** 上浮第k個元素*/
        private void swim(int k){
            // 比父節點小,即進行交換,直到根
            while (k > 1 && less(parent(k),k)){
                swap(k,parent(k));
                k = parent(k);
            }
        }
        /** 下沉第 k 個元素*/
        private void sink(int k){
            while(k < N){
                int small = left(k);
                if (right(k) < N && less(right(k),left(k))){
                    small = right(k);
                }
                if (less(k,small)){
                    swap(k,small);
                    k = small;
                }
            }
        }
        private void swap(int i,int j){
            Key temp = keys[i];
            keys[i] = keys[j];
            keys[j] = temp;
        }
        /** 元素i和j大小比較*/
        private boolean less(int i,int j){
    //   'a' - 'b' = -1 ;
            return keys[i].compareTo(keys[j]) > 0;
        }
        /** 元素i的父節點*/
        private int parent(int i){
            return i/2;
        }
        /** 元素i的左子節點*/
        private int left(int i){
            return i * 2;
        }
        /** 元素i的右子節點*/
        private int right(int i){
            return i * 2 + 1;
        }
    }
     

    以上代碼解析:

    1 swim 上浮,對於元素k,是否需要上浮,僅需與其父節點比較,大於父節點則交換,迭代直到根節點;

    2 sink 下沉,對於元素k,是否需要下沉,需先比較其左右子節點,找出左右子節點中較小者,較小者若比父節點大,則交換,迭代直到末尾元素;

    3 insert 插入,先將元素放到數組末尾位置,再對其進行上浮操作,直到合適位置;

    4 delMax 刪除最大值,大根堆,故第一個元素最大,先將首末元素交換,再刪除末尾元素,再對首元素下沉操作,直到合適位置;

    總結:以上只是Java簡化版,java.util.PriorityQueue 是JDK原版,客官可自行研究。但設計還是非常有技巧的,值得思考一番,假設 insert 插入

    到首位,會導致數組大量元素移動。delMax 若直接刪除首位最大值,則需要進一步判斷左右子節點大小,並進行先子節點上浮再首元素下沉操作。

            有了這個堆結構,就可以進行堆排序了,將待排數全部加入此堆結構,然後依次取出,即成有序序列了!

    2 堆排序

    如要求不使用上述堆數據結構。思路(升序為例):將數組構建為一個大頂堆,首元素即為數組最大值,首尾元素交換;排除末尾元素后調整大頂堆,

    則新的首元素即為次最大值,交換首尾並再排除末尾元素;如此循環,最後的數組即為升序排列

    public class HeapSort02 {
        public static void main(String []args){
            int []arr = {2,1,8,6,4,7,3,0,9,5};
            sort(arr);
            System.out.println(Arrays.toString(arr));
        }
    
        public static void sort(int []arr){
            int len = arr.length;
            // 創建一個大頂堆
            for(int i = (int) Math.ceil(len/2 - 1); i >= 0; i--){
                //從第一個非恭弘=叶 恭弘子結點從下至上,從右至左調整結構
                adjustHeap(arr,i,len);
            }
            // 交換首尾元素,並重新調整大頂堆
            for(int j = len-1;j > 0;j--){
                swap(arr,0,j);
                adjustHeap(arr,0,j);
            }
        }
    
        /** 迭代寫法*/
        public static void adjustHeap(int []arr,int i,int length){
            int temp = arr[i];
            for (int k = 2*i + 1; k < length; k=k*2 + 1) {
            // 注意這裏的k + 1 < length
                // 如果右子節點大於左子節點,則比較對象為右子節點
                if (k + 1 < length && arr[k] < arr[k+1]){
                    k++;
                }
                if (arr[k] > temp){
                    // 不進行值交換
                    arr[i] = arr[k];
                    i = k;
                }
                else{
                    break;
                }
            }
            arr[i] = temp;
        }
    
        /** 遞歸寫法*/
        private static void adjustHeap2(int[] arr, int i, int len){
            int left = 2 * i + 1;
            int right = 2 * i + 2;
            int maxIndex = i;
            // 注意這裏的 left < len
            if (left < len && arr[left] > arr[maxIndex]){
                maxIndex = left;
            }
            if (right < len && arr[right] > arr[maxIndex]){
                maxIndex = right;
            }
            if (maxIndex != i){
                swap(arr,i,maxIndex);
                adjustHeap2(arr,maxIndex,len);
            }
        }
    
        /** 交換元素 */
        public static void swap(int []arr,int a ,int b){
            int temp=arr[a];
            arr[a] = arr[b];
            arr[b] = temp;
        }
    }
     

    以上代碼解析:

    1完全二叉樹結構中,如果根節點順序號為 0,總節點數為 N,則最末節點的父節點即為最後一個非恭弘=叶 恭弘子節點,順序號為 ceil(N/2 -1),

    2 adjustHeap2 為啥使用三個參數,不用中間的參數可以?使用三個參數,是為了進行遞歸調用,因為遞歸肯定是縮小計算規模,而這裏的形參arr和len是固定不變的;

    3 adjustHeap是非遞歸寫法,不用中間的參數可以?調用一在“構建大頂堆”處,可寫為函數體內初始化 i,並形成雙重 for 循環;調用二在“重新調整大頂堆”處,

        可見中間參數為 0,可直接去掉。故回答是可以!但需要調整寫法,且影響該方法復用,這裏直接寫為三個形參的函數更為優雅而已。

    4非遞歸寫法理解:類似插入排序思想(依次移動並找到合適的位置再插入),先將 arr[i] 取出,然後此節點和左右子樹進行比較,如子樹更大則子節點上升一層,使

        用for循環迭代到最終位置,並進行賦值;

     

    以 i=0 為例:

    5遞歸方式理解:定位目標元素的左右子樹,若子樹值更大,則進行值交換,且因為子樹發生了變化,故需要對子樹進行遞歸處理;

    3 前K個最大的數

    在N個數中找出前K個最大的數: 思路:從N個數中取出前K個數,形成一個數組[K],將該數組調整為一個小頂堆,則可知堆頂為K個數中最小值,

    然後依次將剩餘 N-K 個數與堆頂比較,若大於,則替換掉並調整堆,直到所有元素加入完畢,堆中元素即為目標集合。

    public class HeapSort {
        public static void main(String[] args) {
            int[] arr = new int[100];
            for (int i = 0; i < 100; i++) {
                arr[i] = i + 1;
            }
            // 前10個最大的數
            int k = 10;
            // 構造小頂堆
            for (int i = (int) Math.ceil(k/2 - 1); i >= 0; i--) {
                adjustHeap(arr,i,k);
            }
            // 依次比較剩餘元素
            for (int i = 10; i < arr.length; i++) {
                if (arr[i] > arr[0]){
                    swap(arr,0,i);
                    adjustHeap(arr,0,k);
                }
            }
            // 輸出結果
            for (int i = 0; i < 10; i++) {
                System.out.print(arr[i]+"-");
            }
        }
    
        /** 非迭代寫法 ,對arr[i]進行調整 */
        private static void adjustHeap(int[] arr,int i,int length){
            int temp = arr[i];
            for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
                // 因第一次循環中可能越界,故需要 k+1 < length
                if (k + 1 < length && arr[k] > arr[k + 1]){
                    k++;
                }
                if (arr[k] < temp){
                    arr[i] = arr[k];
                    i = k;
                }
                else {
                    break;
                }
            }
            arr[i] = temp;
        }
        /** 遞歸寫法 */
        private static void adjustHeap2(int[] arr,int i,int length){
            int left = i * 2 + 1;
            int right = i * 2 + 2;
            int samller = i;
            if (left < length && arr[left] > arr[samller]){
                samller = right;
            }
            if (right < length && arr[right] > arr[samller]){
                samller = right;
            }
            if (samller != i){
                swap(arr,i,samller);
                adjustHeap2(arr,samller,length);
            }
        }
    
        /** 交換元素 */
        public static void swap(int []arr,int a ,int b){
            int temp=arr[a];
            arr[a] = arr[b];
            arr[b] = temp;
        }
    }
     

    以上代碼解析:按照”初始化—構建小頂堆—比較調整—輸出結果”執行。注意for循環中,因第一次循環中未使用for語句條件判斷,可能越界,故需要 k+1 < length

    輸出結果如下:

    請看官思考,如果需求變為找出N個數中找出前K個最小的數,該如何實現? 建議動腦且動手的寫一遍!因為魔鬼在細節!

    全文完!

    我近期其他文章:

    • 1 Dubbo學習系列之十九(Apollo配置中心)
    • 2 聊聊算法——二分查找算法深度分析
    • 3 DevOps系列——Jenkins/Gitlab自動打包部署
    • 4 DevOps系列——Jenkins私服
    • 5 DevOps系列——Gitlab私服

        只寫原創,敬請關注 

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

  • 重學 Java 設計模式:實戰中介者模式「按照Mybatis原理手寫ORM框架,給JDBC方式操作數據庫增加中介者場景」

    重學 Java 設計模式:實戰中介者模式「按照Mybatis原理手寫ORM框架,給JDBC方式操作數據庫增加中介者場景」

    作者:小傅哥
    博客:https://bugstack.cn – 原創系列專題文章

    沉澱、分享、成長,讓自己和他人都能有所收穫!

    一、前言

    同齡人的差距是從什麼時候拉開的

    同樣的幼兒園、同樣的小學、一樣的書本、一樣的課堂,有人學習好、有人學習差。不只是上學,幾乎人生處處都是賽道,發令槍響起的時刻,也就把人生的差距拉開。編程開發這條路也是很長很寬,有人跑得快有人跑得慢。那麼你是否想起過,這一點點的差距到遙不可及的距離,是從哪一天開始的。摸摸肚子的肉,看看遠處的路,別人講的是故事,你想起的都是事故

    思想沒有產品高才寫出一片的ifelse

    當你承接一個需求的時候,比如;交易、訂單、營銷、保險等各類場景。如果你不熟悉這個場景下的業務模式,以及將來的拓展方向,那麼很難設計出良好可擴展的系統。再加上產品功能初建,說老闆要的急,儘快上線。作為程序員的你更沒有時間思考,整體一看現在的需求也不難,直接上手開干(一個方法兩個if語句),這樣確實滿足了當前需求。但老闆的想法多呀,產品也跟着變化快,到你這就是改改改,加加加。當然你也不客氣,回首掏就是1024個if語句!

    日積月累的技術沉澱是為了厚積薄發

    粗略的估算過,如果從上大學開始每天寫200行,一個月是6000行,一年算10個月話,就是6萬行,第三年出去實習的是時候就有20萬行的代碼量。如果你能做到這一點,找工作難?有時候很多事情就是靠時間積累出來的,想走捷徑有時候真的沒有。你的技術水平、你的業務能力、你身上的肉,都是一點點積累下來的,不要浪費看似很短的時間,一年年堅持下來,留下印刻青春的痕迹,多給自己武裝上一些能力。

    二、開發環境

    1. JDK 1.8
    2. Idea + Maven
    3. mysql 5.1.20
    4. 涉及工程一個,可以通過關注公眾號bugstack蟲洞棧,回復源碼下載獲取(打開獲取的鏈接,找到序號18)
    工程 描述
    itstack-demo-design-16-01 使用JDBC方式連接數據庫
    itstack-demo-design-16-02 手寫ORM框架操作數據庫

    三、中介者模式介紹

    中介者模式要解決的就是複雜功能應用之間的重複調用,在這中間添加一層中介者包裝服務,對外提供簡單、通用、易擴展的服務能力。

    這樣的設計模式幾乎在我們日常生活和實際業務開發中都會見到,例如;飛機降落有小姐姐在塔台喊話、無論哪個方向來的候車都從站台上下、公司的系統中有一个中台專門為你包裝所有接口和提供統一的服務等等,這些都運用了中介者模式。除此之外,你用到的一些中間件,他們包裝了底層多種數據庫的差異化,提供非常簡單的方式進行使用。

    四、案例場景模擬

    在本案例中我們通過模仿Mybatis手寫ORM框架,通過這樣操作數據庫學習中介者運用場景

    除了這樣的中間件層使用場景外,對於一些外部接口,例如N種獎品服務,可以由中台系統進行統一包裝對外提供服務能力。也是中介者模式的一種思想體現。

    在本案例中我們會把jdbc層進行包裝,讓用戶在使用數據庫服務的時候,可以和使用mybatis一樣簡單方便,通過這樣的源碼方式學習中介者模式,也方便對源碼知識的拓展學習,增強知識棧。

    五、用一坨坨代碼實現

    這是一種關於數據庫操作最初的方式

    基本上每一個學習開發的人都學習過直接使用jdbc方式連接數據庫,進行CRUD操作。以下的例子可以當做回憶。

    1. 工程結構

    itstack-demo-design-16-01
    └── src
        └── main
            └── java
                └── org.itstack.demo.design
                    └── JDBCUtil.java
    
    • 這裏的類比較簡單隻包括了一個數據庫操作類。

    2. 代碼實現

    public class JDBCUtil {
    
        private static Logger logger = LoggerFactory.getLogger(JDBCUtil.class);
    
        public static final String URL = "jdbc:mysql://127.0.0.1:3306/itstack-demo-design";
        public static final String USER = "root";
        public static final String PASSWORD = "123456";
    
        public static void main(String[] args) throws Exception {
            //1. 加載驅動程序
            Class.forName("com.mysql.jdbc.Driver");
            //2. 獲得數據庫連接
            Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
            //3. 操作數據庫
            Statement stmt = conn.createStatement();
            ResultSet resultSet = stmt.executeQuery("SELECT id, name, age, createTime, updateTime FROM user");
            //4. 如果有數據 resultSet.next() 返回true
            while (resultSet.next()) {
                logger.info("測試結果 姓名:{} 年齡:{}", resultSet.getString("name"),resultSet.getInt("age"));
            }
        }
    
    }
    
    • 以上是使用JDBC的方式進行直接操作數據庫,幾乎大家都使用過這樣的方式。

    3. 測試結果

    15:38:10.919 [main] INFO  org.itstack.demo.design.JDBCUtil - 測試結果 姓名:水水 年齡:18
    15:38:10.922 [main] INFO  org.itstack.demo.design.JDBCUtil - 測試結果 姓名:豆豆 年齡:18
    15:38:10.922 [main] INFO  org.itstack.demo.design.JDBCUtil - 測試結果 姓名:花花 年齡:19
    
    Process finished with exit code 0
    
    • 從測試結果可以看到這裏已經查詢到了數據庫中的數據。只不過如果在全部的業務開發中都這樣實現,會非常的麻煩。

    六、中介模式開發ORM框架

    `接下來就使用中介模式的思想完成模仿Mybatis的ORM框架開發~

    1. 工程結構

    itstack-demo-design-16-02
    └── src
        ├── main
        │   ├── java
        │   │   └── org.itstack.demo.design
        │   │       ├── dao
        │   │       │	├── ISchool.java
        │   │       │	└── IUserDao.java
        │   │       ├── mediator
        │   │       │	├── Configuration.java
        │   │       │	├── DefaultSqlSession.java
        │   │       │	├── DefaultSqlSessionFactory.java
        │   │       │	├── Resources.java
        │   │       │	├── SqlSession.java
        │   │       │	├── SqlSessionFactory.java
        │   │       │	├── SqlSessionFactoryBuilder.java
        │   │       │	└── SqlSessionFactoryBuilder.java
        │   │       └── po
        │   │         	├── School.java
        │   │         	└── User.java
        │   └── resources
        │       ├── mapper
        │       │   ├── School_Mapper.xml
        │       │   └── User_Mapper.xml
        │       └── mybatis-config-datasource.xml
        └── test
             └── java
                 └── org.itstack.demo.design.test
                     └── ApiTest.java
    

    中介者模式模型結構

    • 以上是對ORM框架實現的核心類,包括了;加載配置文件、對xml解析、獲取數據庫session、操作數據庫以及結果返回。
    • 左上是對數據庫的定義和處理,基本包括我們常用的方法;<T> T selectOne<T> List<T> selectList等。
    • 右側藍色部分是對數據庫配置的開啟session的工廠處理類,這裏的工廠會操作DefaultSqlSession
    • 之後是紅色地方的SqlSessionFactoryBuilder,這個類是對數據庫操作的核心類;處理工廠、解析文件、拿到session等。

    接下來我們就分別介紹各個類的功能實現過程。

    2. 代碼實現

    2.1 定義SqlSession接口

    public interface SqlSession {
    
        <T> T selectOne(String statement);
    
        <T> T selectOne(String statement, Object parameter);
    
        <T> List<T> selectList(String statement);
    
        <T> List<T> selectList(String statement, Object parameter);
    
        void close();
    }
    
    • 這裏定義了對數據庫操作的查詢接口,分為查詢一個結果和查詢多個結果,同時包括有參數和沒有參數的方法。

    2.2 SqlSession具體實現類

    public class DefaultSqlSession implements SqlSession {
    
        private Connection connection;
        private Map<String, XNode> mapperElement;
    
        public DefaultSqlSession(Connection connection, Map<String, XNode> mapperElement) {
            this.connection = connection;
            this.mapperElement = mapperElement;
        }
    
        @Override
        public <T> T selectOne(String statement) {
            try {
                XNode xNode = mapperElement.get(statement);
                PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
                ResultSet resultSet = preparedStatement.executeQuery();
                List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
                return objects.get(0);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        @Override
        public <T> List<T> selectList(String statement) {
            XNode xNode = mapperElement.get(statement);
            try {
                PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
                ResultSet resultSet = preparedStatement.executeQuery();
                return resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        // ...
    
        private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz) {
            List<T> list = new ArrayList<>();
            try {
                ResultSetMetaData metaData = resultSet.getMetaData();
                int columnCount = metaData.getColumnCount();
                // 每次遍歷行值
                while (resultSet.next()) {
                    T obj = (T) clazz.newInstance();
                    for (int i = 1; i <= columnCount; i++) {
                        Object value = resultSet.getObject(i);
                        String columnName = metaData.getColumnName(i);
                        String setMethod = "set" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1);
                        Method method;
                        if (value instanceof Timestamp) {
                            method = clazz.getMethod(setMethod, Date.class);
                        } else {
                            method = clazz.getMethod(setMethod, value.getClass());
                        }
                        method.invoke(obj, value);
                    }
                    list.add(obj);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return list;
        }
    
        @Override
        public void close() {
            if (null == connection) return;
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
    • 這裏包括了接口定義的方法實現,也就是包裝了jdbc層。
    • 通過這樣的包裝可以讓對數據庫的jdbc操作隱藏起來,外部調用的時候對入參、出參都有內部進行處理。

    2.3 定義SqlSessionFactory接口

    public interface SqlSessionFactory {
    
        SqlSession openSession();
    
    }
    
    • 開啟一個SqlSession, 這幾乎是大家在平時的使用中都需要進行操作的內容。雖然你看不見,但是當你有數據庫操作的時候都會獲取每一次執行的SqlSession

    2.4 SqlSessionFactory具體實現類

    public class DefaultSqlSessionFactory implements SqlSessionFactory {
    
        private final Configuration configuration;
    
        public DefaultSqlSessionFactory(Configuration configuration) {
            this.configuration = configuration;
        }
    
        @Override
        public SqlSession openSession() {
            return new DefaultSqlSession(configuration.connection, configuration.mapperElement);
        }
    
    }
    
    • DefaultSqlSessionFactory,是使用mybatis最常用的類,這裏我們簡單的實現了一個版本。
    • 雖然是簡單的版本,但是包括了最基本的核心思路。當開啟SqlSession時會進行返回一個DefaultSqlSession
    • 這個構造函數中向下傳遞了Configuration配置文件,在這個配置文件中包括;Connection connectionMap<String, String> dataSourceMap<String, XNode> mapperElement。如果有你閱讀過Mybatis源碼,對這個就不會陌生。

    2.5 SqlSessionFactoryBuilder實現

    public class SqlSessionFactoryBuilder {
    
        public DefaultSqlSessionFactory build(Reader reader) {
            SAXReader saxReader = new SAXReader();
            try {
                saxReader.setEntityResolver(new XMLMapperEntityResolver());
                Document document = saxReader.read(new InputSource(reader));
                Configuration configuration = parseConfiguration(document.getRootElement());
                return new DefaultSqlSessionFactory(configuration);
            } catch (DocumentException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        private Configuration parseConfiguration(Element root) {
            Configuration configuration = new Configuration();
            configuration.setDataSource(dataSource(root.selectNodes("//dataSource")));
            configuration.setConnection(connection(configuration.dataSource));
            configuration.setMapperElement(mapperElement(root.selectNodes("mappers")));
            return configuration;
        }
    
        // 獲取數據源配置信息
        private Map<String, String> dataSource(List<Element> list) {
            Map<String, String> dataSource = new HashMap<>(4);
            Element element = list.get(0);
            List content = element.content();
            for (Object o : content) {
                Element e = (Element) o;
                String name = e.attributeValue("name");
                String value = e.attributeValue("value");
                dataSource.put(name, value);
            }
            return dataSource;
        }
    
        private Connection connection(Map<String, String> dataSource) {
            try {
                Class.forName(dataSource.get("driver"));
                return DriverManager.getConnection(dataSource.get("url"), dataSource.get("username"), dataSource.get("password"));
            } catch (ClassNotFoundException | SQLException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        // 獲取SQL語句信息
        private Map<String, XNode> mapperElement(List<Element> list) {
            Map<String, XNode> map = new HashMap<>();
    
            Element element = list.get(0);
            List content = element.content();
            for (Object o : content) {
                Element e = (Element) o;
                String resource = e.attributeValue("resource");
    
                try {
                    Reader reader = Resources.getResourceAsReader(resource);
                    SAXReader saxReader = new SAXReader();
                    Document document = saxReader.read(new InputSource(reader));
                    Element root = document.getRootElement();
                    //命名空間
                    String namespace = root.attributeValue("namespace");
    
                    // SELECT
                    List<Element> selectNodes = root.selectNodes("select");
                    for (Element node : selectNodes) {
                        String id = node.attributeValue("id");
                        String parameterType = node.attributeValue("parameterType");
                        String resultType = node.attributeValue("resultType");
                        String sql = node.getText();
    
                        // ? 匹配
                        Map<Integer, String> parameter = new HashMap<>();
                        Pattern pattern = Pattern.compile("(#\\{(.*?)})");
                        Matcher matcher = pattern.matcher(sql);
                        for (int i = 1; matcher.find(); i++) {
                            String g1 = matcher.group(1);
                            String g2 = matcher.group(2);
                            parameter.put(i, g2);
                            sql = sql.replace(g1, "?");
                        }
    
                        XNode xNode = new XNode();
                        xNode.setNamespace(namespace);
                        xNode.setId(id);
                        xNode.setParameterType(parameterType);
                        xNode.setResultType(resultType);
                        xNode.setSql(sql);
                        xNode.setParameter(parameter);
    
                        map.put(namespace + "." + id, xNode);
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
    
            }
            return map;
        }
    
    }
    
    • 在這個類中包括的核心方法有;build(構建實例化元素)parseConfiguration(解析配置)dataSource(獲取數據庫配置)connection(Map<String, String> dataSource) (鏈接數據庫)mapperElement (解析sql語句)
    • 接下來我們分別介紹這樣的幾個核心方法。

    build(構建實例化元素)

    這個類主要用於創建解析xml文件的類,以及初始化SqlSession工廠類DefaultSqlSessionFactory。另外需要注意這段代碼saxReader.setEntityResolver(new XMLMapperEntityResolver());,是為了保證在不聯網的時候一樣可以解析xml,否則會需要從互聯網獲取dtd文件。

    parseConfiguration(解析配置)

    是對xml中的元素進行獲取,這裏主要獲取了;dataSourcemappers,而這兩個配置一個是我們數據庫的鏈接信息,另外一個是對數據庫操作語句的解析。

    connection(Map<String, String> dataSource) (鏈接數據庫)

    鏈接數據庫的地方和我們常見的方式是一樣的;Class.forName(dataSource.get("driver"));,但是這樣包裝以後外部是不需要知道具體的操作。同時當我們需要鏈接多套數據庫的時候,也是可以在這裏擴展。

    mapperElement (解析sql語句)

    這部分代碼塊內容相對來說比較長,但是核心的點就是為了解析xml中的sql語句配置。在我們平常的使用中基本都會配置一些sql語句,也有一些入參的佔位符。在這裏我們使用正則表達式的方式進行解析操作。

    解析完成的sql語句就有了一個名稱和sql的映射關係,當我們進行數據庫操作的時候,這個組件就可以通過映射關係獲取到對應sql語句進行操作。

    3. 測試驗證

    在測試之前需要導入sql語句到數據庫中;

    • 庫名:itstack-demo-design
    • 表名:userschool
    CREATE TABLE school ( id bigint NOT NULL AUTO_INCREMENT, name varchar(64), address varchar(256), createTime datetime, updateTime datetime, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    insert into school (id, name, address, createTime, updateTime) values (1, '北京大學', '北京市海淀區頤和園路5號', '2019-10-18 13:35:57', '2019-10-18 13:35:57');
    insert into school (id, name, address, createTime, updateTime) values (2, '南開大學', '中國天津市南開區衛津路94號', '2019-10-18 13:35:57', '2019-10-18 13:35:57');
    insert into school (id, name, address, createTime, updateTime) values (3, '同濟大學', '上海市彰武路1號同濟大廈A樓7樓7區', '2019-10-18 13:35:57', '2019-10-18 13:35:57');
    CREATE TABLE user ( id bigint(11) NOT NULL AUTO_INCREMENT, name varchar(32), age int(4), address varchar(128), entryTime datetime, remark varchar(64), createTime datetime, updateTime datetime, status int(4) DEFAULT '0', dateTime varchar(64), PRIMARY KEY (id), INDEX idx_name (name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    insert into user (id, name, age, address, entryTime, remark, createTime, updateTime, status, dateTime) values (1, '水水', 18, '吉林省榆樹市黑林鎮尹家村5組', '2019-12-22 00:00:00', '無', '2019-12-22 00:00:00', '2019-12-22 00:00:00', 0, '20200309');
    insert into user (id, name, age, address, entryTime, remark, createTime, updateTime, status, dateTime) values (2, '豆豆', 18, '遼寧省大連市清河灣司馬道407路', '2019-12-22 00:00:00', '無', '2019-12-22 00:00:00', '2019-12-22 00:00:00', 1, null);
    insert into user (id, name, age, address, entryTime, remark, createTime, updateTime, status, dateTime) values (3, '花花', 19, '遼寧省大連市清河灣司馬道407路', '2019-12-22 00:00:00', '無', '2019-12-22 00:00:00', '2019-12-22 00:00:00', 0, '20200310');
    

    3.1 創建數據庫對象類

    用戶類

    public class User {
    
        private Long id;
        private String name;
        private Integer age;
        private Date createTime;
        private Date updateTime;
        
        // ... get/set
    }
    

    學校類

    public class School {
    
        private Long id;
        private String name;
        private String address;
        private Date createTime;
        private Date updateTime;  
      
        // ... get/set
    }
    
    • 這兩個類都非常簡單,就是基本的數據庫信息。

    3.2 創建DAO包

    用戶Dao

    public interface IUserDao {
    
         User queryUserInfoById(Long id);
    
    }
    

    學校Dao

    public interface ISchoolDao {
    
        School querySchoolInfoById(Long treeId);
    
    }
    

    3.3 ORM配置文件

    鏈接配置

    <configuration>
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://127.0.0.1:3306/itstack_demo_design?useUnicode=true"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
    
        <mappers>
            <mapper resource="mapper/User_Mapper.xml"/>
            <mapper resource="mapper/School_Mapper.xml"/>
        </mappers>
    
    </configuration>
    
    • 這個配置與我們平常使用的mybatis基本是一樣的,包括了數據庫的連接池信息以及需要引入的mapper映射文件。

    操作配置(用戶)

    <mapper namespace="org.itstack.demo.design.dao.IUserDao">
    
        <select id="queryUserInfoById" parameterType="java.lang.Long" resultType="org.itstack.demo.design.po.User">
            SELECT id, name, age, createTime, updateTime
            FROM user
            where id = #{id}
        </select>
    
        <select id="queryUserList" parameterType="org.itstack.demo.design.po.User" resultType="org.itstack.demo.design.po.User">
            SELECT id, name, age, createTime, updateTime
            FROM user
            where age = #{age}
        </select>
    
    </mapper>
    

    操作配置(學校)

    <mapper namespace="org.itstack.demo.design.dao.ISchoolDao">
    
        <select id="querySchoolInfoById" resultType="org.itstack.demo.design.po.School">
            SELECT id, name, address, createTime, updateTime
            FROM school
            where id = #{id}
        </select>
    
    </mapper>
    

    3.4 單個結果查詢測試

    @Test
    public void test_queryUserInfoById() {
        String resource = "mybatis-config-datasource.xml";
        Reader reader;
        try {
            reader = Resources.getResourceAsReader(resource);
            SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
            SqlSession session = sqlMapper.openSession();
            try {
                User user = session.selectOne("org.itstack.demo.design.dao.IUserDao.queryUserInfoById", 1L);
                logger.info("測試結果:{}", JSON.toJSONString(user));
            } finally {
                session.close();
                reader.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    • 這裏的使用方式和Mybatis是一樣的,都包括了;資源加載和解析、SqlSession工廠構建、開啟SqlSession以及最後執行查詢操作selectOne

    測試結果

    16:56:51.831 [main] INFO  org.itstack.demo.design.demo.ApiTest - 測試結果:{"age":18,"createTime":1576944000000,"id":1,"name":"水水","updateTime":1576944000000}
    
    Process finished with exit code 0
    
    • 從結果上看已經滿足了我們的查詢需求。

    3.5 集合結果查詢測試

    @Test
    public void test_queryUserList() {
        String resource = "mybatis-config-datasource.xml";
        Reader reader;
        try {
            reader = Resources.getResourceAsReader(resource);
            SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
            SqlSession session = sqlMapper.openSession();
            try {
                User req = new User();
                req.setAge(18);
                List<User> userList = session.selectList("org.itstack.demo.design.dao.IUserDao.queryUserList", req);
                logger.info("測試結果:{}", JSON.toJSONString(userList));
            } finally {
                session.close();
                reader.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    • 這個測試內容與以上只是查詢方法有所不同;session.selectList,是查詢一個集合結果。

    測試結果

    16:58:13.963 [main] INFO  org.itstack.demo.design.demo.ApiTest - 測試結果:[{"age":18,"createTime":1576944000000,"id":1,"name":"水水","updateTime":1576944000000},{"age":18,"createTime":1576944000000,"id":2,"name":"豆豆","updateTime":1576944000000}]
    
    Process finished with exit code 0
    
    • 測試驗證集合的結果也是正常的,目前位置測試全部通過。

    七、總結

    • 以上通過中介者模式的設計思想我們手寫了一個ORM框架,隱去了對數據庫操作的複雜度,讓外部的調用方可以非常簡單的進行操作數據庫。這也是我們平常使用的Mybatis的原型,在我們日常的開發使用中,只需要按照配置即可非常簡單的操作數據庫。
    • 除了以上這種組件模式的開發外,還有服務接口的包裝也可以使用中介者模式來實現。比如你們公司有很多的獎品接口需要在營銷活動中對接,那麼可以把這些獎品接口統一收到中台開發一個獎品中心,對外提供服務。這樣就不需要每一個需要對接獎品的接口,都去找具體的提供者,而是找中台服務即可。
    • 在上述的實現和測試使用中可以看到,這種模式的設計滿足了;單一職責開閉原則,也就符合了迪米特原則,即越少人知道越好。外部的人只需要按照需求進行調用,不需要知道具體的是如何實現的,複雜的一面已經有組件合作服務平台處理。

    八、推薦閱讀

    • 1. 重學 Java 設計模式:實戰工廠方法模式「多種類型商品不同接口,統一發獎服務搭建場景」
    • 2. 重學 Java 設計模式:實戰原型模式「上機考試多套試,每人題目和答案亂序排列場景」
    • 3. 重學 Java 設計模式:實戰橋接模式「多支付渠道(微信、支付寶)與多支付模式(刷臉、指紋)場景」
    • 4. 重學 Java 設計模式:實戰組合模式「營銷差異化人群發券,決策樹引擎搭建場景」
    • 5. 重學 Java 設計模式:實戰外觀模式「基於SpringBoot開發門面模式中間件,統一控制接口白名單場景」
    • 6. 重學 Java 設計模式:實戰享元模式「基於Redis秒殺,提供活動與庫存信息查詢場景」

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

    新北清潔公司,居家、辦公、裝潢細清專業服務

  • 為什麼建議你使用枚舉?

    為什麼建議你使用枚舉?

    枚舉是 JDK 1.5 新增的數據類型,使用枚舉我們可以很好的描述一些特定的業務場景,比如一年中的春、夏、秋、冬,還有每周的周一到周天,還有各種顏色,以及可以用它來描述一些狀態信息,比如錯誤碼等。

    枚舉類型不止存在在 Java 語言中,在其它語言中也都能找到它的身影,例如 C# 和 Python 等,但我發現在實際的項目中使用枚舉的人很少,所以本文就來聊一聊枚舉的相關內容,好讓朋友們對枚舉有一個大概的印象,這樣在編程時起碼還能想到有“枚舉”這樣一個類型。

    本文的結構目錄如下:

    枚舉的 7 種使用方法

    很多人不使用枚舉的一個重要的原因是對枚舉不夠熟悉,那麼我們就先從枚舉的 7 種使用方法說起。

    用法一:常量

    在 JDK 1.5 之前,我們定義常量都是 public static final... ,但有了枚舉,我們就可以把這些常量定義成一個枚舉類了,實現代碼如下:

    public enum ColorEnum {  
      RED, GREEN, BLANK, YELLOW  
    } 
    

    用法二:switch

    將枚舉用在 switch 判斷中,使得代碼可讀性更高了,實現代碼如下:

    enum ColorEnum {
        GREEN, YELLOW, RED
    }
    public class ColorTest {
        ColorEnum color = ColorEnum.RED;
    
        public void change() {
            switch (color) {
                case RED:
                    color = ColorEnum.GREEN;
                    break;
                case YELLOW:
                    color = ColorEnum.RED;
                    break;
                case GREEN:
                    color = ColorEnum.YELLOW;
                    break;
            }
        }
    }
    

    用法三:枚舉中增加方法

    我們可以在枚舉中增加一些方法,讓枚舉具備更多的特性,實現代碼如下:

    public class EnumTest {
        public static void main(String[] args) {
            ErrorCodeEnum errorCode = ErrorCodeEnum.SUCCESS;
            System.out.println("狀態碼:" + errorCode.code() + 
                               " 狀態信息:" + errorCode.msg());
        }
    }
    
    enum ErrorCodeEnum {
        SUCCESS(1000, "success"),
        PARAM_ERROR(1001, "parameter error"),
        SYS_ERROR(1003, "system error"),
        NAMESPACE_NOT_FOUND(2001, "namespace not found"),
        NODE_NOT_EXIST(3002, "node not exist"),
        NODE_ALREADY_EXIST(3003, "node already exist"),
        UNKNOWN_ERROR(9999, "unknown error");
    
        private int code;
        private String msg;
    
        ErrorCodeEnum(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }
    
        public int code() {
            return code;
        }
    
        public String msg() {
            return msg;
        }
    
        public static ErrorCodeEnum getErrorCode(int code) {
            for (ErrorCodeEnum it : ErrorCodeEnum.values()) {
                if (it.code() == code) {
                    return it;
                }
            }
            return UNKNOWN_ERROR;
        }
    }
    

    以上程序的執行結果為:

    狀態碼:1000 狀態信息:success

    用法四:覆蓋枚舉方法

    我們可以覆蓋一些枚舉中的方法用於實現自己的業務,比如我們可以覆蓋 toString() 方法,實現代碼如下:

    public class EnumTest {
        public static void main(String[] args) {
            ColorEnum colorEnum = ColorEnum.RED;
            System.out.println(colorEnum.toString());
        }
    }
    
    enum ColorEnum {
        RED("紅色", 1), GREEN("綠色", 2), BLANK("白色", 3), YELLOW("黃色", 4);
        //  成員變量
        private String name;
        private int index;
    
        //  構造方法
        private ColorEnum(String name, int index) {
            this.name = name;
            this.index = index;
        }
    
        //覆蓋方法
        @Override
        public String toString() {
            return this.index + ":" + this.name;
        }
    }
    

    以上程序的執行結果為:

    1:紅色

    用法五:實現接口

    枚舉類可以用來實現接口,但不能用於繼承類,因為枚舉默認繼承了 java.lang.Enum 類,在 Java 語言中允許實現多接口,但不能繼承多個父類,實現代碼如下:

    public class EnumTest {
        public static void main(String[] args) {
            ColorEnum colorEnum = ColorEnum.RED;
            colorEnum.print();
            System.out.println("顏色:" + colorEnum.getInfo());
        }
    }
    
    interface Behaviour {
        void print();
    
        String getInfo();
    }
    
    enum ColorEnum implements Behaviour {
        RED("紅色", 1), GREEN("綠色", 2), BLANK("白色", 3), YELLOW("黃色", 4);
        private String name;
        private int index;
    
        private ColorEnum(String name, int index) {
            this.name = name;
            this.index = index;
        }
    
        @Override
        public void print() {
            System.out.println(this.index + ":" + this.name);
        }
    
        @Override
        public String getInfo() {
            return this.name;
        }
    }
    

    以上程序的執行結果為:

    1:紅色

    顏色:紅色

    用法六:在接口中組織枚舉類

    我們可以在一個接口中創建多個枚舉類,用它可以很好的實現“多態”,也就是說我們可以將擁有相同特性,但又有細微實現差別的枚舉類聚集在一個接口中,實現代碼如下:

    public class EnumTest {
        public static void main(String[] args) {
            // 賦值第一個枚舉類
            ColorInterface colorEnum = ColorInterface.ColorEnum.RED;
            System.out.println(colorEnum);
            // 賦值第二個枚舉類
            colorEnum = ColorInterface.NewColorEnum.NEW_RED;
            System.out.println(colorEnum);
        }
    }
    
    interface ColorInterface {
        enum ColorEnum implements ColorInterface {
            GREEN, YELLOW, RED
        }
        enum NewColorEnum implements ColorInterface {
            NEW_GREEN, NEW_YELLOW, NEW_RED
        }
    }
    

    以上程序的執行結果為:

    RED

    NEW_RED

    用法七:使用枚舉集合

    在 Java 語言中和枚舉類相關的,還有兩個枚舉集合類 java.util.EnumSetjava.util.EnumMap,使用它們可以實現更多的功能。

    使用 EnumSet 可以保證元素不重複,並且能獲取指定範圍內的元素,示例代碼如下:

    import java.util.ArrayList;
    import java.util.EnumSet;
    import java.util.List;
    
    public class EnumTest {
        public static void main(String[] args) {
            List<ColorEnum> list = new ArrayList<ColorEnum>();
            list.add(ColorEnum.RED);
            list.add(ColorEnum.RED);  // 重複元素
            list.add(ColorEnum.YELLOW);
            list.add(ColorEnum.GREEN);
            // 去掉重複數據
            EnumSet<ColorEnum> enumSet = EnumSet.copyOf(list);
            System.out.println("去重:" + enumSet);
    
            // 獲取指定範圍的枚舉(獲取所有的失敗狀態)
            EnumSet<ErrorCodeEnum> errorCodeEnums = EnumSet.range(ErrorCodeEnum.ERROR, ErrorCodeEnum.UNKNOWN_ERROR);
            System.out.println("所有失敗狀態:" + errorCodeEnums);
        }
    }
    
    enum ColorEnum {
        RED("紅色", 1), GREEN("綠色", 2), BLANK("白色", 3), YELLOW("黃色", 4);
        private String name;
        private int index;
    
        private ColorEnum(String name, int index) {
            this.name = name;
            this.index = index;
        }
    }
    
    enum ErrorCodeEnum {
        SUCCESS(1000, "success"),
        ERROR(2001, "parameter error"),
        SYS_ERROR(2002, "system error"),
        NAMESPACE_NOT_FOUND(2003, "namespace not found"),
        NODE_NOT_EXIST(3002, "node not exist"),
        NODE_ALREADY_EXIST(3003, "node already exist"),
        UNKNOWN_ERROR(9999, "unknown error");
    
        private int code;
        private String msg;
    
        ErrorCodeEnum(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }
    
        public int code() {
            return code;
        }
    
        public String msg() {
            return msg;
        }
    }
    

    以上程序的執行結果為:

    去重:[RED, GREEN, YELLOW]

    所有失敗狀態:[ERROR, SYS_ERROR, NAMESPACE_NOT_FOUND, NODE_NOT_EXIST, NODE_ALREADY_EXIST, UNKNOWN_ERROR]

    EnumMapHashMap 類似,不過它是一個專門為枚舉設計的 Map 集合,相比 HashMap 來說它的性能更高,因為它內部放棄使用鏈表和紅黑樹的結構,採用數組作為數據存儲的結構。

    EnumMap 基本使用示例如下:

    import java.util.EnumMap;
    
    public class EnumTest {
        public static void main(String[] args) {
            EnumMap<ColorEnum, String> enumMap = new EnumMap<>(ColorEnum.class);
            enumMap.put(ColorEnum.RED, "紅色");
            enumMap.put(ColorEnum.GREEN, "綠色");
            enumMap.put(ColorEnum.BLANK, "白色");
            enumMap.put(ColorEnum.YELLOW, "黃色");
            System.out.println(ColorEnum.RED + ":" + enumMap.get(ColorEnum.RED));
        }
    }
    
    enum ColorEnum {
        RED, GREEN, BLANK, YELLOW;
    }
    

    以上程序的執行結果為:

    RED:紅色

    使用注意事項

    阿里《Java開發手冊》對枚舉的相關規定如下,我們在使用時需要稍微注意一下。

    【強制】所有的枚舉類型字段必須要有註釋,說明每個數據項的用途。

    【參考】枚舉類名帶上 Enum 後綴,枚舉成員名稱需要全大寫,單詞間用下劃線隔開。說明:枚舉其實就是特殊的常量類,且構造方法被默認強制是私有。正例:枚舉名字為 ProcessStatusEnum 的成員名稱:SUCCESS / UNKNOWN_REASON。

    假如不使用枚舉

    在枚舉沒有誕生之前,也就是 JDK 1.5 版本之前,我們通常會使用 int 常量來表示枚舉,實現代碼如下:

    public static final int COLOR_RED = 1;
    public static final int COLOR_BLUE = 2;
    public static final int COLOR_GREEN = 3;
    

    但是使用 int 類型可能存在兩個問題:

    第一, int 類型本身並不具備安全性,假如某個程序員在定義 int 時少些了一個 final 關鍵字,那麼就會存在被其他人修改的風險,而反觀枚舉類,它“天然”就是一個常量類,不存在被修改的風險(原因詳見下半部分);

    第二,使用 int 類型的語義不夠明確,比如我們在控制台打印時如果只輸出 1…2…3 這樣的数字,我們肯定不知道它代表的是什麼含義。

    那有人就說了,那就使用常量字符唄,這總不會還不知道語義吧?實現示例代碼如下:

    public static final String COLOR_RED = "RED";
    public static final String COLOR_BLUE = "BLUE";
    public static final String COLOR_GREEN = "GREEN";
    

    但是這樣同樣存在一個問題,有些初級程序員會不按套路出牌,他們可能會直接使用字符串的值進行比較,而不是直接使用枚舉的字段,實現示例代碼如下:

    public class EnumTest {
        public static final String COLOR_RED = "RED";
        public static final String COLOR_BLUE = "BLUE";
        public static final String COLOR_GREEN = "GREEN";
        public static void main(String[] args) {
            String color = "BLUE";
            if ("BLUE".equals(color)) {
                System.out.println("藍色");
            }
        }
    }
    

    這樣當我們修改了枚舉中的值,那程序就涼涼了。

    枚舉使用場景

    枚舉的常見使用場景是單例,它的完整實現代碼如下:

    public class Singleton {
        // 枚舉類型是線程安全的,並且只會裝載一次
        private enum SingletonEnum {
            INSTANCE;
            // 聲明單例對象
            private final Singleton instance;
            // 實例化
            SingletonEnum() {
                instance = new Singleton();
            }
            private Singleton getInstance() {
                return instance;
            }
        }
        // 獲取實例(單例對象)
        public static Singleton getInstance() {
            return SingletonEnum.INSTANCE.getInstance();
        }
        private Singleton() {
        }
        // 類方法
        public void sayHi() {
            System.out.println("Hi,Java.");
        }
    }
    class SingletonTest {
        public static void main(String[] args) {
            Singleton singleton = Singleton.getInstance();
            singleton.sayHi();
        }
    }
    

    因為枚舉只會在類加載時裝載一次,所以它是線程安全的,這也是《Effective Java》作者極力推薦使用枚舉來實現單例的主要原因。

    知識擴展

    枚舉為什麼是線程安全的?

    這一點要從枚舉最終生成的字節碼說起,首先我們先來定義一個簡單的枚舉類:

    public enum ColorEnumTest {
        RED, GREEN, BLANK, YELLOW;
    }
    

    然後我們再將上面的那段代碼編譯為字節碼,具體內容如下:

    public final class ColorEnumTest extends java.lang.Enum<ColorEnumTest> {
      public static final ColorEnumTest RED;
      public static final ColorEnumTest GREEN;
      public static final ColorEnumTest BLANK;
      public static final ColorEnumTest YELLOW;
      public static ColorEnumTest[] values();
      public static ColorEnumTest valueOf(java.lang.String);
      static {};
    }
    

    從上述結果可以看出枚舉類最終會被編譯為被 final 修飾的普通類,它的所有屬性也都會被 staticfinal 關鍵字修飾,所以枚舉類在項目啟動時就會被 JVM 加載並初始化,而這個執行過程是線程安全的,所以枚舉類也是線程安全的類。

    小貼士:代碼反編譯的過程是先用 javac 命令將 java 代碼編譯字節碼(.class),再使用 javap 命令查看編譯的字節碼。

    枚舉比較小技巧

    我們在枚舉比較時使用 == 就夠了,因為枚舉類是在程序加載時就創建了(它並不是 new 出來的),並且枚舉類不允許在外部直接使用 new 關鍵字來創建枚舉實例,所以我們在使用枚舉類時本質上只有一個對象,因此在枚舉比較時使用 == 就夠了。

    並且我們在查看枚舉的 equlas() 源碼會發現,它的內部其實還是直接調用了 == 方法,源碼如下:

    public final boolean equals(Object other) {
        return this==other;
    }
    

    總結

    本文我們介紹了枚舉類的 7 種使用方法:常量、switch、枚舉中添加方法、覆蓋枚舉方法、實現接口、在接口中組織枚舉類和使用枚舉集合等,然後講了如果不使用枚舉類使用 int 類型和 String 類型存在的一些弊端:語義不夠清晰、容易被修改、存在被誤用的風險,所以我們在適合的環境下應該盡量使用枚舉類。並且我們還講了枚舉類的使用場景——單例,以及枚舉類為什麼是安全的,最後我們講了枚舉比較的小技巧,希望本文對你有幫助。

    查看 & 鳴謝

    https://www.iteye.com/blog/softbeta-1185573

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※別再煩惱如何寫文案,掌握八大原則!

  • 與特斯拉 Mddel X 比拼 奧迪 2018 年左右將推純電動車

    國外媒體美國汽車新聞網報導,奧迪將推出全新的純電動 SUV 車型與特斯拉 Model X 車型競爭,但奧迪認為插電混合動力車型才是目前新能源汽車市場最理想的車型。   奧迪將在 2018 年左右發布一款續航里程可達到 498 公里的純電動 SUV,將採用全新的造型設計理念,基於第二代 MLB 平台研發,並藉鑑新一代奧迪 Q5 車型的部分技術與設計。奧迪全新的純電動 SUV 車型將可在 20 分鐘內完成 80% 的充電,續航里程超過特斯拉 Model X 車型,對其構成不小的威脅。   不過,奧迪執行長斯泰德(Rupert Stadler)近日表示在未來的 10 至 15 年內,插電混合動力汽車將是消費者選擇新能源汽車時的首選車型。插電混合動力車型使用一台汽油引擎或柴油引擎和一台電動機聯合驅動,有一定的純電動續航里程,十分適合消費者在市區中駕駛。但是斯泰德同時表示隨著充電網絡建設的繼續,消費者未來對電動車的接受程度將增加。      

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※教你寫出一流的銷售文案?

  • 特斯拉將公布儲能電池計畫 F-貿聯可望受惠

    特斯拉傳將於 4 月 30 日公布儲能電池計畫,市場推測,該計畫將包括住宅與公共規模市場,業界傳出,本來就是特斯拉重要夥伴的 F-貿聯,有機會獲新電力線訂單。而今年在特斯拉需求持續強勁,與 Type C 等外接式擴充介面的需求爆發下,法人預期,該公司今年營運將逐季走高,全年營收看增 15%。   F-貿聯首季營收 19.04 億元,年增 10%,創下歷年同期新高。而特斯拉傳出將於下周公布最新的儲能電池計畫,據市場傳言表示,貿聯本來就是特斯拉在電動車電池模組用線與超級充電樁的線束的主力供應商,未來不排除大型公共規模用儲能電池,將與現有充電站結合。而貿聯有機會順勢切入取得新產品,並以公用市場應用的電力線為主,近日有望已小量出貨。   法人預估,F-貿聯今年除來自特斯拉的需求成長外,另在其他車用佈局方面,包括全地形車大客戶全車線束、擴大歐美客戶於倒車雷達等新產品的開發;另外針對美國官方對公營單位節能環保的需求,也配合客戶開發出得以改裝現有車為電動車的配備裝置,隨未來該商業模式成型,貢獻將逐步擴大。  

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※台北網頁設計公司全省服務真心推薦

    ※想知道最厲害的網頁設計公司"嚨底家"!

    新北清潔公司,居家、辦公、裝潢細清專業服務

    ※推薦評價好的iphone維修中心