這四款SUV上個月賣的最好 只要不到15萬 是不是真的那麼好?_租車

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

日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

98-15。38萬TOp 3長安CS75售價:9。28-15。88萬TOp 4寶駿560售價:6。98-9。48萬【總結】如今國內SUV市場的火爆可謂是無人不知,而這當中自主品牌的崛起也讓我們為之欣慰,哈弗H6在競爭激烈的15萬級別SUV市場更是創下了5。3萬的銷量神話,15萬這個價格分水嶺既能選擇以上這些優秀的SUV也能選擇性能平庸的合資品牌轎車,具體怎麼選還得看你們怎麼選擇,但在看來上述的自主品牌的車輛他們在擁有不輸合資車的駕乘質感的同時價格還更有優勢,各位同學在選車是不防多留意他們一下。

9月的SUV熱銷榜單出爐了,

※超省錢租車方案

商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!

毫無疑問SUV市場依舊保持火熱。而在眾多的熱銷SUV前十名中,自主品牌脫穎而出包攬了緊湊級SUV銷量的前三名。

【總結】如今國內SUV市場的火爆可謂是無人不知,而這當中自主品牌的崛起也讓我們為之欣慰,哈弗H6在競爭激烈的15萬級別SUV市場更是創下了5.3萬的銷量神話,15萬這個價格分水嶺既能選擇以上這些優秀的SUV也能選擇性能平庸的合資品牌轎車,具體怎麼選還得看你們怎麼選擇,但在看來上述的自主品牌的車輛他們在擁有不輸合資車的駕乘質感的同時價格還更有優勢,各位同學在選車是不防多留意他們一下。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

這些十幾萬的車換個殼賣貴3萬 偏偏大家都買貴的 你呢?_包裝設計

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

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

98-18。98萬元斯柯達-明銳指導價:9。98-17。99萬元大眾-速騰指導價:13。18-21。88萬元總結科雷嘉和逍客,會選擇科雷嘉,因為它有更加高的顏值,內飾設計富含科技感,各方面的用料做工都要比逍客更好,價格可能是唯一需要考慮的地方。

孿生兄弟大家一定聽過,那麼汽車界的孿生兄弟你又知道多少呢?事先聲明一下,想要表達的不是路虎極光和陸風X7,更加不是保時捷MACAN和眾泰SR9。

要講的是真正意義上的孿生兄弟,它們的底子一樣,但是樣子不同,

※產品缺大量曝光嗎?你需要的是一流包裝設計!

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

各項的調校也不盡相同,最重要的是售價也不相同。

指導價:16.38-21.98萬元

指導價:13.98-18.98萬元

指導價:9.98-17.99萬元

指導價:13.18-21.88萬元

科雷嘉和逍客,會選擇科雷嘉,因為它有更加高的顏值,內飾設計富含科技感,各方面的用料做工都要比逍客更好,價格可能是唯一需要考慮的地方。

明銳和速騰,會選擇明銳,最大的決定因素是掀背式的尾廂,然後就是內飾的設計,速騰的內飾已經看膩了,再去看看明銳的內飾,有新鮮感。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

本年度試過最深刻的SUV竟是一台國產車_台中搬家

※台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

路虎SVR作為一輛極其暴力的性能SUV,其5。0L V8發動機能爆發出550馬力的動力,而且輸出非常直接,只要你深踩油門,車子馬上就會發出炸裂整條街的聲浪,其暢快的感覺絕對讓你上癮。吉利近年推出的車型風評都非常不錯,博瑞被稱為最美中國車,而最美中國SUV則落到了博越身上。

上一期我們為大家送上了年度印象最深刻的性能車盤點,今天就為大家送上年度印象最深刻的SUV盤點。

奔馳GLA A45 AMG是一台性能SUV,同級對手就是已經在國外上市的RS Q3。可能不少人會覺得GLA A45 AMG就是一輛加高了的A45 AMG,開起來並無太多不同。實際上底盤高度加高了之後除了通過性提高了之外,整輛GLA A45 AMG的身板還是很硬挺的,過彎的時候你還是能很明顯感覺到它性能SUV的屬性,同時超過380馬力的動力輸出也能讓你享受一下推背感,當你遇到一些非鋪裝路面時,又能直接開過去,這種逆天的設定還真是有點吸引人呢。

Model X是特斯拉首款SUV,Model X p90D在狂暴模式下百公里加速僅為3.4秒,也是把特斯拉的超強加速能力繼承了下來。除此之後,其鷹翼式車門,可電動控制開關的車門,提供五座和七座車型,

※台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

加上一個回頭率極高的外觀,Model X絕對適合一些喜歡獨特又能接受新事物的車主。

雖然車底有一個大電機,增加了不少車重,但也讓Model X的重心壓低了,行駛過程的穩定性也提高了不少,對於想偶爾激烈駕駛一下的朋友來說,Model X也是能應付得到的。

路虎SVR作為一輛極其暴力的性能SUV,其5.0L V8發動機能爆發出550馬力的動力,而且輸出非常直接,只要你深踩油門,車子馬上就會發出炸裂整條街的聲浪,其暢快的感覺絕對讓你上癮。

吉利近年推出的車型風評都非常不錯,博瑞被稱為最美中國車,而最美中國SUV則落到了博越身上。博越的設計風格越趨成熟,加上2.0L/1.8T發動機和6AT變速箱的動力單元,其匹配程度非常不錯,果然自主品牌還是得在變速箱上下功夫。

而博越的行駛質感也對得起這個價位,雖然操控上來說不會太理想,但是作為SUV,博越還是提供了不錯的舒適性,如果平常有照顧家人的需要,那麼博越是值得考慮的一款自主SUV。

好了這期盤點就到這了,請期待下一期家用車盤點。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

※台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

質信中國造 探秘吉利博越寶雞生產基地_台中搬家公司

※台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

博越整體造型融合了中國文化元素與國際時尚潮流,外觀動感精緻,內飾典雅輕奢。博越搭載了2。0L自然吸氣和1。8TD兩款發動機,與之匹配的是6擋手動和澳大利亞DSI6速手自一體變速器,實現了動力與操控的完美結合。再根據中國路況和SUV特性進行深度的調教優化,博越兼顧安全性和駕馭樂趣。

10月29日,“質信中國造 吉利博越品質之旅”活動在吉利汽車寶雞基地舉行。作為吉利汽車進入精品3.0時代的全新生產基地,寶雞基地是吉利汽車按照高標準規劃設計打造的世界一流現代化汽車工廠。寶雞基地採用了國際一流的生產設備,各車間均投入全球領先的專業機器人,建成衝壓、焊裝、塗裝、總裝全自動化的國際一流生產線,實現了全領域智能製造。寶雞基地的製造工藝不僅處於國內領先水平,同時也達到了全球一流的世界級工廠水準,成為中國汽車製造業的標杆型工廠。

圖:吉利汽車寶雞製造基地

高標準規劃建設 打造世界一流的現代化工廠

寶雞基地根據吉利集團發展戰略部署,遵循高起點規劃、高標準設計,按照世界一流現代化汽車工廠要求進行建設,也是吉利汽車響應國家“一帶一路”,“西部大開發”政策的具體體現。寶雞基地項目總投資72億元人民幣,佔地1344畝,建設用地1164畝,一次性建成包括衝壓、焊裝、塗裝、總裝的整車四大主要工藝用房,以及與之配套的聯合動力站、油化庫、試車跑道、成品車停放區、管理中心、生活配套區等各項輔助設施,形成雙班年產20萬台整車的生產規模。

圖:五序衝壓

衝壓車間,擁有兩條達到世界先進水平的全自動化生產線,A線為2400噸打頭的五序衝壓連線,用於生產外覆蓋件;B線為日本網野公司生產的四序壓機,用於生產內板件和深拉延件。衝壓車間整線全封閉設計,噪音低於85分貝;採用德國SMT獨立清洗、塗油機,可以實現最高精度5um的油膜厚度控制,達到高效清洗;兩條生產線均採用ABB直線七軸機器人搬運,最高可達14SpM,實現整線自動化;一鍵換模技術更是達到世界領先水準。

圖:車身總拼線

焊裝車間,作為引領吉利集團邁向工業4.0的首創工廠,從建設之初貫徹實施自動化、智能化、模塊化、柔性化等先進理念,力圖打造吉利的標杆工廠。全線擁有128台焊裝領域頂尖的意大利柯馬、瑞典ABB機器人,可以完成包括車身總成、地板總成、側圍總成等七大總成的全自動化生產,實現了低能耗、高強度車身以及高品質的製造目標。

圖:色漆噴塗

塗裝車間,率先在吉利集團採用水性免中塗工藝,在保證車身防腐性能和油漆外觀質量的同時,減少VOC排放50%,降低能耗30%,是目前世界塗裝領域最先進的工藝。無論是在車身底部的UBC噴塗,還是色漆、清漆的噴塗,甚至到噴塗過程中的門蓋的開啟與關閉均採用塗裝行業全球最先進的39台德國杜爾機器人來實現。

圖:車門分裝線

總裝車間,由電裝、內裝,底盤1、底盤2,外裝、複合線等6條裝配線銜接而成的主線,包括前懸、后懸、輪胎、儀錶台、前端模塊、車門、頂棚等7條分裝線以及整車檢測線組成。主線與分裝線全部採用低噪音的摩擦輸送方式,大幅度降低了車間噪音。整車檢測線採用國際先進的3D激光技術四輪定位台、多功能轉轂試驗台、前大燈檢測設備,可滿足不同車型的高品質檢測需求。

圖:OK線

除了先進的四大工藝車間,

※推薦台中搬家公司優質服務,可到府估價

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

寶雞基地的生產管控也達到了世界領先水平,採用整線生產監控、一鍵換模、光學對中等先進技術,具備全自動化生產水平。寶雞基地堅持“零缺陷”生產管理體系,採用主客觀雙向檢驗方法,通過人工與機器的雙重檢查,把控產品合格度。嚴謹的生產管控力度離不開吉利汽車“造每個人的精品車”發展理念,從技術、工藝、零部件供應和檢測等方面,全方位確保每一輛博越產品的精品品質。

圖:四輪定位

南北工廠齊發力 產能釋放嚴把品質關

博越是吉利汽車歷時三年半時間打造的智能互聯精品SUV,集好看、好開、好智能於一身。博越整體造型融合了中國文化元素與國際時尚潮流,外觀動感精緻,內飾典雅輕奢。博越搭載了2.0L自然吸氣和1.8TD兩款發動機,與之匹配的是6擋手動和澳大利亞DSI6速手自一體變速器,實現了動力與操控的完美結合。再根據中國路況和SUV特性進行深度的調教優化,博越兼顧安全性和駕馭樂趣。

值得一提的是,博越搭載了諸多具有前瞻性的智能配置,如城市預碰撞安全系統、ACC智能自適應巡航、360°隨動3D全景影像、pM2.5空氣凈化器、“你好博越”智能語音交互系統等,在智能安全、智能駕馭、智能舒適和智能互聯方面,引領同級SUV車型,樹立了中國SUV市場新標杆。尤其是博越首創“安全識別圈”概念,從駕駛安全感到主動安全,再到被動安全,全面打造智能駕駛安全體系。

圖:媒體参觀生產線

憑藉卓越的產品性能,博越上市即熱銷。截止到10月底,博越累計銷量達7萬台;累計訂單量突破15萬台,熱銷態勢仍在繼續。在2016年度C-NCAp第三批安全碰撞結果中,博越依靠完善的被動安全系統榮獲58.2分的優異成績,刷新了2015版C-NCAp標準實施以來SUV碰撞的記錄。此外,博越榮獲2016TTA自主品牌年度自主車型和安全品質獎兩項殊榮,併入圍2017中國年度車前三強等多項大獎。連續被權威機構認證,無疑是博越精品品質的最好證明。

圖:跑道試車

在以博越為代表的精品SUV帶動下,吉利汽車2016年前三季度實現45.9萬輛的銷量成績,同比增長29%。在此基礎上,吉利汽車第二次上調全年銷量目標至70萬輛,展現出了強大的自信心。寶雞基地的投產,將承擔起博越90%的產能,達到月產1.6萬輛的規模。加上春曉工廠,博越月產能將實現2萬輛。南北工廠齊發力,將最大程度上緩解博越的產能壓力,縮短用戶的提車周期,助力博越銷量再上新台階。

吉利汽車集團副總裁、汽車銷售公司總經理林傑表示:“博越作為吉利汽車精品車發展戰略首款SUV產品,擔負着吉利汽車品牌向上,銷量突破的任務。事實也證明,在吉利汽車嚴把品質關的前提下,吉利汽車全線產品齊頭並進,SUV與轎車實現了均衡發展。在寶雞基地的順利投產下,博越的產能問題也將得到緩解,用戶的提車周期也將最大限度的縮短,但我們並不會因此放鬆對產品品質的把控。吉利汽車將堅持以市場和消費者需求為導向,不斷為消費者打造高品質、高技術、高附加值的產品,努力提升品牌影響力,引領中國汽車製造業向專業化、精品化、國際化方向轉型。”本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

※台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

Redis詳解(十三)—— Redis布隆過濾器_網頁設計公司

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

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

  本篇博客我們主要介紹如何用Redis實現布隆過濾器,但是在介紹布隆過濾器之前,我們首先介紹一下,為啥要使用布隆過濾器。

1、布隆過濾器使用場景

  比如有如下幾個需求:

  ①、原本有10億個號碼,現在又來了10萬個號碼,要快速準確判斷這10萬個號碼是否在10億個號碼庫中?

  解決辦法一:將10億個號碼存入數據庫中,進行數據庫查詢,準確性有了,但是速度會比較慢。

  解決辦法二:將10億號碼放入內存中,比如Redis緩存中,這裏我們算一下佔用內存大小:10億*8字節=8GB,通過內存查詢,準確性和速度都有了,但是大約8gb的內存空間,挺浪費內存空間的。

  ②、接觸過爬蟲的,應該有這麼一個需求,需要爬蟲的網站千千萬萬,對於一個新的網站url,我們如何判斷這個url我們是否已經爬過了?

  解決辦法還是上面的兩種,很顯然,都不太好。

  ③、同理還有垃圾郵箱的過濾。

  那麼對於類似這種,大數據量集合,如何準確快速的判斷某個數據是否在大數據量集合中,並且不佔用內存,布隆過濾器應運而生了。

2、布隆過濾器簡介

  帶着上面的幾個疑問,我們來看看到底什麼是布隆過濾器。

  布隆過濾器:一種數據結構,是由一串很長的二進制向量組成,可以將其看成一個二進制數組。既然是二進制,那麼裏面存放的不是0,就是1,但是初始默認值都是0。

  如下所示:

  

  ①、添加數據

  介紹概念的時候,我們說可以將布隆過濾器看成一個容器,那麼如何向布隆過濾器中添加一個數據呢?

  如下圖所示:當要向布隆過濾器中添加一個元素key時,我們通過多個hash函數,算出一個值,然後將這個值所在的方格置為1。

  比如,下圖hash1(key)=1,那麼在第2個格子將0變為1(數組是從0開始計數的),hash2(key)=7,那麼將第8個格子置位1,依次類推。

  

 

  ②、判斷數據是否存在?

  知道了如何向布隆過濾器中添加一個數據,那麼新來一個數據,我們如何判斷其是否存在於這個布隆過濾器中呢?

  很簡單,我們只需要將這個新的數據通過上面自定義的幾個哈希函數,分別算出各個值,然後看其對應的地方是否都是1,如果存在一個不是1的情況,那麼我們可以說,該新數據一定不存在於這個布隆過濾器中。

  反過來說,如果通過哈希函數算出來的值,對應的地方都是1,那麼我們能夠肯定的得出:這個數據一定存在於這個布隆過濾器中嗎?

  答案是否定的,因為多個不同的數據通過hash函數算出來的結果是會有重複的,所以會存在某個位置是別的數據通過hash函數置為的1。

  我們可以得到一個結論:布隆過濾器可以判斷某個數據一定不存在,但是無法判斷一定存在。

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

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

  ③、布隆過濾器優缺點

  優點:優點很明顯,二進制組成的數組,佔用內存極少,並且插入和查詢速度都足夠快。

  缺點:隨着數據的增加,誤判率會增加;還有無法判斷數據一定存在;另外還有一個重要缺點,無法刪除數據。

3、Redis實現布隆過濾器

①、bitmaps

  我們知道計算機是以二進制位作為底層存儲的基礎單位,一個字節等於8位。

  比如“big”字符串是由三個字符組成的,這三個字符對應的ASCII碼分為是98、105、103,對應的二進制存儲如下:

  

 

 

  在Redis中,Bitmaps 提供了一套命令用來操作類似上面字符串中的每一個位。

  一、設置值

setbit key offset value

  

 

   我們知道”b”的二進製表示為0110 0010,我們將第7位(從0開始)設置為1,那0110 0011 表示的就是字符“c”,所以最後的字符 “big”變成了“cig”。

  二、獲取值

gitbit key offset

  

   三、獲取位圖指定範圍值為1的個數

bitcount key [start end]

  如果不指定,那就是獲取全部值為1的個數。

  注意:start和end指定的是字節的個數,而不是位數組下標。

  

②、Redisson

  Redis 實現布隆過濾器的底層就是通過 bitmap 這種數據結構,至於如何實現,這裏就不重複造輪子了,介紹業界比較好用的一個客戶端工具——Redisson。

  Redisson 是用於在 Java 程序中操作 Redis 的庫,利用Redisson 我們可以在程序中輕鬆地使用 Redis。

  下面我們就通過 Redisson 來構造布隆過濾器。

 1 package com.ys.rediscluster.bloomfilter.redisson;
 2 
 3 import org.redisson.Redisson;
 4 import org.redisson.api.RBloomFilter;
 5 import org.redisson.api.RedissonClient;
 6 import org.redisson.config.Config;
 7 
 8 public class RedissonBloomFilter {
 9 
10     public static void main(String[] args) {
11         Config config = new Config();
12         config.useSingleServer().setAddress("redis://192.168.14.104:6379");
13         config.useSingleServer().setPassword("123");
14         //構造Redisson
15         RedissonClient redisson = Redisson.create(config);
16 
17         RBloomFilter<String> bloomFilter = redisson.getBloomFilter("phoneList");
18         //初始化布隆過濾器:預計元素為100000000L,誤差率為3%
19         bloomFilter.tryInit(100000000L,0.03);
20         //將號碼10086插入到布隆過濾器中
21         bloomFilter.add("10086");
22 
23         //判斷下面號碼是否在布隆過濾器中
24         System.out.println(bloomFilter.contains("123456"));//false
25         System.out.println(bloomFilter.contains("10086"));//true
26     }
27 }

  這是單節點的Redis實現方式,如果數據量比較大,期望的誤差率又很低,那單節點所提供的內存是無法滿足的,這時候可以使用分佈式布隆過濾器,同樣也可以用 Redisson 來實現,這裏我就不做代碼演示了,大家有興趣可以試試。

4、guava 工具

  最後提一下不用Redis如何來實現布隆過濾器。

  guava 工具包相信大家都用過,這是谷歌公司提供的,裏面也提供了布隆過濾器的實現。

 1 package com.ys.rediscluster.bloomfilter;
 2 
 3 import com.google.common.base.Charsets;
 4 import com.google.common.hash.BloomFilter;
 5 import com.google.common.hash.Funnel;
 6 import com.google.common.hash.Funnels;
 7 
 8 public class GuavaBloomFilter {
 9     public static void main(String[] args) {
10         BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8),100000,0.01);
11 
12         bloomFilter.put("10086");
13 
14         System.out.println(bloomFilter.mightContain("123456"));
15         System.out.println(bloomFilter.mightContain("10086"));
16     }
17 }

 

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

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

一本正經的聊數據結構(7):哈弗曼編碼_如何寫文案

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

什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。

前文傳送門:

「一本正經的聊數據結構(1):時間複雜度」

「一本正經的聊數據結構(2):數組與向量」

「一本正經的聊數據結構(3):棧和隊列」

「一本正經的聊數據結構(4):樹」

「一本正經的聊數據結構(5):二叉樹的存儲結構與遍歷」

「一本正經的聊數據結構(6):最優二叉樹 —— 哈夫曼樹」

引言

在上一期,我們介紹了什麼是哈夫曼樹以及哈夫曼樹的構建過程,本期我們接着介紹哈夫曼樹的用途。

字符編碼壓縮

哈夫曼樹的應用很廣,哈夫曼編碼就是其在電訊通信中的應用之一。廣泛地用於數據文件壓縮的十分有效的編碼方法,其壓縮率通常在 20% ~ 90% 之間。

在電訊通信業務中,通常用二進制編碼來表示字母或其他字符,並用這樣的編碼來表示字符序列。

在計算機當中,因為計算機不是人,不能識別圖像、聲音、視頻等內容,對於計算機來講,它只能認識二進制的 0 和 1 ,在数字电子電路中,邏輯門的實現直接應用了二進制,因此現代的計算機和依賴計算機的設備里都用到二進制。

我們在計算機上看到的一切的圖像、聲音、視頻等內容,都是由二進制的方式進行存儲的。

簡單來講,我們把信息轉化為二進制的過程可以稱之為編碼,在計算機的世界里,編碼有很多種格式,比如我們常見的: ASCII 、 Unicode 、 GBK 、 GB2312 、 GB18030 、 UTF-8 、 UTF-16 等等。

編碼方式從長度上來分大致可以分為兩個大類:

  • 定長編碼:定長僅表明段與段之間長度相同,但沒說明是多長。
  • 變長編碼:變長就是段與段之間的長度不相同,同樣也不定義具體有多長。

在最初的設計中, ASCII 編碼就是採用的定長編碼的方案,使用定長一字節來表示一個字符。

舉個栗子,假如我們對 「hello」 進行編碼,使用定長編碼,為了方便,採用了十進制,主要是因為我懶,原理與二進制是一樣的。

字符 編碼
h 00
e 01
l 02
o 03

假設我們現在有個文件,內容是 00000001 ,假如定長 2 位(這裏的位指十進制的位)是唯一的編碼方案,用它去解碼,就會得到 「hhhe」 (可以對比上面的編碼, 00 代表 h ,所以前 6 個 0 轉化成 3 個 h ,後面的 01 則轉化成 e )。

但是,如果定長 2 位不是唯一的編碼方案呢?如上圖中的定長 4 位方案,如果我們誤用定長 4 位去解碼,結果就只能得到「he」( 0000 轉化為 h , 0001 轉化為 e )

隨着時代的發展,不僅老美要對他們的英文進行編碼,我們中國人也要對漢字進行編碼,而早期的 ASCII 碼的方案只有一個字節,對我們漢字而言是遠遠不夠的,所以在我們的漢字編碼方案 GB2312 中,漢字是使用兩個字節來表示的(這也是迫不得已的事,一字節壓根不夠用) 。

再多說一句,實際上我們的 GB2312 是一種變長的編碼方案,主要是為了兼容一個字節的 ASCII 碼。

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

隨着計算機在全世界的推廣,各種編碼方案都出來了,彼此之間的轉換也帶來了諸多的問題。採用某種統一的標準就勢在必行了,於是乎天上一聲霹靂, Unicode 粉墨登場!

不過 Unicode 對於只需要使用到一個字節的 ASCII 碼來講,讓他們使用 Unicode ,多少還是不是很願意的。

比如 「he」 兩個字符,用 ASCII 只需要保存成 6865 ( 16 進制),現在則成了 00680065 ,前面多的那兩個 0 (用作站位) ,基本上可以說是毫無意義,用 Unicode 編碼的文本,原來可能只有 1KB 大小,現在要變成 2KB ,體積成倍的往上漲。

最終, Unicode 編碼方案逐漸演化成了變長的 UTF-8 編碼方案,並且 UTF-8 是可以和 ASCII 碼進行兼容。

UTF-8 因為能兼容 ASCII 而受到廣泛歡迎,但在保存中文方面,要用 3 個字節,有的甚至要 4 個字節,所以在保存中文方面效率並不算太好,與此相對, GB2312 , GBK 之類用兩字節保存中文字符效率上會高,同時它們也都兼容 ASCII ,所以在中英混合的情況下還是比 UTF-8 要好,但在國際化方面及可擴展空間上則不如 UTF-8 了。

所以如果有進軍國際的想法,那麼最好還是使用 UTF-8 編碼。

哈弗曼編碼

哈弗曼編碼是一種不定長的編碼方式,是由麻省理工學院的哈夫曼博所發明,這種編碼方式實現了兩個重要目標:

  • 任何一個字符編碼,都不是其他字符編碼的前綴。

  • 信息編碼的總長度最小。

乾巴巴的,還是接着舉例子:

如果我們對 「ABACCDA」 進行編碼,假設 A, B, C, D 的編碼分別為 00, 01,10, 11。

那麼 「ABACCDA」 編碼后的結果是 「00010010101100」 (共 14 位),我們解碼的時候只需要每兩位進行拆分,就可以恢復編碼前的信息了。

那我們如果用哈弗曼編碼的方式進行編碼呢?

第一件事情是要確定每個字母的權值(出現頻率), 「ABACCDA」 這個字符串中 A, B, C, D 的權值(出現的頻率)分別為 0.43, 0.14, 0.29, 0.14 。

有了權值,我們可以構造一個哈弗曼樹了,感興趣的同學可以自己畫一下,下面這個是我畫的:

編碼的結果就顯而易見了: A:0, C:10, B:110, D:111 。

剛才那個 「ABACCDA」 編碼后的結果就是 「0110010101110」 (共 13 位)。

上面我們知道了哈夫曼編碼如何編碼,那麼我們拿到了一個經過哈弗曼編碼后的代碼,如何進行譯碼工作呢?

首先還是要知道每個字母的權重是多少,然後畫出來這個哈弗曼樹,接下來,就可以對照着這個哈弗曼樹進行譯碼工作了。

在譯碼的過程中,若編碼是 「0」 ,則向左走。若編碼是 「1」 ,則向右走,一旦到達恭弘=叶 恭弘子結點,則譯出一個字符。然後不停的重複,直到這個編碼的結束,就是我們需要的原內容了。

參考

https://www.cnblogs.com/wkfvawl/p/9783271.html

https://my.oschina.net/goldenshaw/blog/307708

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

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

銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。

重學 Java 設計模式:實戰適配器模式_網頁設計公司

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

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

作者:小傅哥
博客:https://bugstack.cn

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

一、前言

擦屁屁紙80%的面積都是保護手的!

工作到3年左右很大一部分程序員都想提升自己的技術棧,開始嘗試去閱讀一些源碼,例如Spring、Mybaits、Dubbo等,但讀着讀着發現越來越難懂,一會從這過來一會跑到那去。甚至懷疑自己技術太差,慢慢也就不願意再觸碰這部分知識。

而這主要的原因是一個框架隨着時間的發展,它的複雜程度是越來越高的,從最開始只有一個非常核心的點到最後開枝散恭弘=叶 恭弘。這就像你自己開發的業務代碼或者某個組件一樣,最開始的那部分核心代碼也許只能佔到20%,而其他大部分代碼都是為了保證核心流程能正常運行的。所以這也是你讀源碼費勁的一部分原因。

框架中用到了設計模式嗎?

框架中不僅用到設計模式還用了很多,而且有些時候根本不是一個模式的單獨使用,而是多種設計模式的綜合運用。與大部分小夥伴平時開發的CRUD可就不一樣了,如果都是if語句從上到下,也就算得不上什麼框架了。就像你到Spring的源碼中搜關鍵字Adapter,就會出現很多實現類,例如;UserCredentialsDataSourceAdapter。而這種設計模式就是我們本文要介紹的適配器模式。

適配器在生活里隨處可見

如果提到在日常生活中就很多適配器的存在你會想到什麼?在沒有看後文之前可以先思考下。

二、開發環境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程三個,可以通過關注公眾號:bugstack蟲洞棧,回復源碼下載獲取(打開獲取的鏈接,找到序號18)
工程 描述
itstack-demo-design-6-00 場景模擬工程;模擬多個MQ消息體
itstack-demo-design-6-01 使用一坨代碼實現業務需求
itstack-demo-design-6-02 通過設計模式優化改造代碼,產生對比性從而學習

三、適配器模式介紹

適配器模式的主要作用就是把原本不兼容的接口,通過適配修改做到統一。使得用戶方便使用,就像我們提到的萬能充、數據線、MAC筆記本的轉換頭、出國旅遊買個插座等等,他們都是為了適配各種不同的口,做的兼容。。

除了我們生活中出現的各種適配的場景,那麼在業務開發中呢?

在業務開發中我們會經常的需要做不同接口的兼容,尤其是中台服務,中台需要把各個業務線的各種類型服務做統一包裝,再對外提供接口進行使用。而這在我們平常的開發中也是非常常見的。

四、案例場景模擬

隨着公司的業務的不斷髮展,當基礎的系統逐步成型以後。業務運營就需要開始做用戶的拉新和促活,從而保障DAU的增速以及最終ROI轉換。

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

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

而這時候就會需要做一些營銷系統,大部分常見的都是裂變、拉客,例如;你邀請一個用戶開戶、或者邀請一個用戶下單,那麼平台就會給你返利,多邀多得。同時隨着拉新的量越來越多開始設置每月下單都會給首單獎勵,等等,各種營銷場景。

那麼這個時候做這樣一個系統就會接收各種各樣的MQ消息或者接口,如果一個個的去開發,就會耗費很大的成本,同時對於後期的拓展也有一定的難度。此時就會希望有一個系統可以配置一下就把外部的MQ接入進行,這些MQ就像上面提到的可能是一些註冊開戶消息、商品下單消息等等。

而適配器的思想方式也恰恰可以運用到這裏,並且我想強調一下,適配器不只是可以適配接口往往還可以適配一些屬性信息。

1. 場景模擬工程

itstack-demo-design-6-00
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── mq
                │   ├── create_account.java
                │   ├── OrderMq.java
                │   └── POPOrderDelivered.java
                └── service
                    ├── OrderServicejava
                    └── POPOrderService.java
  • 這裏模擬了三個不同類型的MQ消息,而在消息體中都有一些必要的字段,比如;用戶ID、時間、業務ID,但是每個MQ的字段屬性並不一樣。就像用戶ID在不同的MQ里也有不同的字段:uId、userId等。
  • 同時還提供了兩個不同類型的接口,一個用於查詢內部訂單訂單下單數量,一個用於查詢第三方是否首單。
  • 後面會把這些不同類型的MQ和接口做適配兼容。

2. 場景簡述

1.1 註冊開戶MQ

public class create_account {

    private String number;      // 開戶編號
    private String address;     // 開戶地
    private Date accountDate;   // 開戶時間
    private String desc;        // 開戶描述

    // ... get/set     
}

1.2 內部訂單MQ

public class OrderMq {

    private String uid;           // 用戶ID
    private String sku;           // 商品
    private String orderId;       // 訂單ID
    private Date createOrderTime; // 下單時間     

    // ... get/set      
}

1.3 第三方訂單MQ

public class POPOrderDelivered {

    private String uId;     // 用戶ID
    private String orderId; // 訂單號
    private Date orderTime; // 下單時間
    private Date sku;       // 商品
    private Date skuName;   // 商品名稱
    private BigDecimal decimal; // 金額

    // ... get/set      
}

1.4 查詢用戶內部下單數量接口

public class OrderService {

    private Logger logger = LoggerFactory.getLogger(POPOrderService.class);

    public long queryUserOrderCount(String userId){
        logger.info("自營商家,查詢用戶的訂單是否為首單:{}", userId);
        return 10L;
    }

}

1.5 查詢用戶第三方下單首單接口

public class POPOrderService {

    private Logger logger = LoggerFactory.getLogger(POPOrderService.class);

    public boolean isFirstOrder(String uId) {
        logger.info("POP商家,查詢用戶的訂單是否為首單:{}", uId);
        return true;
    }

}
  • 以上這幾項就是不同的MQ以及不同的接口的一個體現,後面我們將使用這樣的MQ消息和接口,給它們做相應的適配。

五、用一坨坨代碼實現

其實大部分時候接MQ消息都是創建一個類用於消費,通過轉換他的MQ消息屬性給自己的方法。

我們接下來也是先體現一下這種方式的實現模擬,但是這樣的實現有一個很大的問題就是,當MQ消息越來越多后,甚至幾十幾百以後,你作為中台要怎麼優化呢?

1. 工程結構

itstack-demo-design-6-01
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                └── create_accountMqService.java
                └── OrderMqService.java
                └── POPOrderDeliveredService.java
  • 目前需要接收三個MQ消息,所有就有了三個對應的類,和我們平時的代碼幾乎一樣。如果你的MQ量不多,這樣的寫法也沒什麼問題,但是隨着數量的增加,就需要考慮用一些設計模式來解決。

2. Mq接收消息實現

public class create_accountMqService {

    public void onMessage(String message) {

        create_account mq = JSON.parseObject(message, create_account.class);

        mq.getNumber();
        mq.getAccountDate();

        // ... 處理自己的業務
    }

}
  • 三組MQ的消息都是一樣模擬使用,就不一一展示了。可以獲取源碼後學習。

六、適配器模式重構代碼

接下來使用適配器模式來進行代碼優化,也算是一次很小的重構。

適配器模式要解決的主要問題就是多種差異化類型的接口做統一輸出,這在我們學習工廠方法模式中也有所提到不同種類的獎品處理,其實那也是適配器的應用。

在本文中我們還會再另外體現出一個多種MQ接收,使用MQ的場景。來把不同類型的消息做統一的處理,便於減少後續對MQ接收。

在這裏如果你之前沒要開發過接收MQ消息,可能聽上去會有些不理解這樣的場景。對此,我個人建議先了解下MQ。另外就算不了解也沒關係,不會影響對思路的體會。

再者,本文所展示的MQ兼容的核心部分,也就是處理適配不同的類型字段。而如果我們接收MQ后,在配置不同的消費類時,如果不希望一個個開發類,那麼可以使用代理類的方式進行處理。

1. 工程結構

itstack-demo-design-6-02
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── impl
                │   ├── InsideOrderService.java
                │   └── POPOrderAdapterServiceImpl.java
                ├── MQAdapter,java
                ├── OrderAdapterService,java
                └── RebateInfo,java

適配器模型結構

  • 這裏包括了兩個類型的適配;接口適配、MQ適配。之所以不只是模擬接口適配,因為很多時候大家都很常見了,所以把適配的思想換一下到MQ消息體上,增加大家多設計模式的認知。
  • 先是做MQ適配,接收各種各樣的MQ消息。當業務發展的很快,需要對下單用戶首單才給獎勵,在這樣的場景下再增加對接口的適配操作。

2. 代碼實現(MQ消息適配)

2.1 統一的MQ消息體

public class RebateInfo {

    private String userId;  // 用戶ID
    private String bizId;   // 業務ID
    private Date bizTime;   // 業務時間
    private String desc;    // 業務描述
    
    // ... get/set
}
  • MQ消息中會有多種多樣的類型屬性,雖然他們都有同樣的值提供給使用方,但是如果都這樣接入那麼當MQ消息特別多時候就會很麻煩。
  • 所以在這個案例中我們定義了通用的MQ消息體,後續把所有接入進來的消息進行統一的處理。

2.2 MQ消息體適配類

public class MQAdapter {

    public static RebateInfo filter(String strJson, Map<String, String> link) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        return filter(JSON.parseObject(strJson, Map.class), link);
    }

    public static RebateInfo filter(Map obj, Map<String, String> link) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        RebateInfo rebateInfo = new RebateInfo();
        for (String key : link.keySet()) {
            Object val = obj.get(link.get(key));
            RebateInfo.class.getMethod("set" + key.substring(0, 1).toUpperCase() + key.substring(1), String.class).invoke(rebateInfo, val.toString());
        }
        return rebateInfo;
    }

}
  • 這個類里的方法非常重要,主要用於把不同類型MQ種的各種屬性,映射成我們需要的屬性並返回。就像一個屬性中有用戶ID;uId,映射到我們需要的;userId,做統一處理。
  • 而在這個處理過程中需要把映射管理傳遞給Map<String, String> link,也就是準確的描述了,當前MQ中某個屬性名稱,映射為我們的某個屬性名稱。
  • 最終因為我們接收到的mq消息基本都是json格式,可以轉換為MAP結構。最後使用反射調用的方式給我們的類型賦值。

2.3 測試適配類

2.3.1 編寫單元測試類
@Test
public void test_MQAdapter() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    create_account create_account = new create_account();
    create_account.setNumber("100001");
    create_account.setAddress("河北省.廊坊市.廣陽區.大學里職業技術學院");
    create_account.setAccountDate(new Date());
    create_account.setDesc("在校開戶");          

    HashMap<String, String> link01 = new HashMap<String, String>();
    link01.put("userId", "number");
    link01.put("bizId", "number");
    link01.put("bizTime", "accountDate");
    link01.put("desc", "desc");
    RebateInfo rebateInfo01 = MQAdapter.filter(create_account.toString(), link01);
    System.out.println("mq.create_account(適配前)" + create_account.toString());
    System.out.println("mq.create_account(適配后)" + JSON.toJSONString(rebateInfo01));

    System.out.println("");

    OrderMq orderMq = new OrderMq();
    orderMq.setUid("100001");
    orderMq.setSku("10928092093111123");
    orderMq.setOrderId("100000890193847111");
    orderMq.setCreateOrderTime(new Date()); 

    HashMap<String, String> link02 = new HashMap<String, String>();
    link02.put("userId", "uid");
    link02.put("bizId", "orderId");
    link02.put("bizTime", "createOrderTime");
    RebateInfo rebateInfo02 = MQAdapter.filter(orderMq.toString(), link02);

    System.out.println("mq.orderMq(適配前)" + orderMq.toString());
    System.out.println("mq.orderMq(適配后)" + JSON.toJSONString(rebateInfo02));
}
  • 在這裏我們分別模擬傳入了兩個不同的MQ消息,並設置字段的映射關係。
  • 等真的業務場景開發中,就可以配這種映射配置關係交給配置文件或者數據庫後台配置,減少編碼。
2.3.2 測試結果
mq.create_account(適配前){"accountDate":1591024816000,"address":"河北省.廊坊市.廣陽區.大學里職業技術學院","desc":"在校開戶","number":"100001"}
mq.create_account(適配后){"bizId":"100001","bizTime":1591077840669,"desc":"在校開戶","userId":"100001"}

mq.orderMq(適配前){"createOrderTime":1591024816000,"orderId":"100000890193847111","sku":"10928092093111123","uid":"100001"}
mq.orderMq(適配后){"bizId":"100000890193847111","bizTime":1591077840669,"userId":"100001"}

Process finished with exit code 0
  • 從上面可以看到,同樣的字段值在做了適配前後分別有統一的字段屬性,進行處理。這樣業務開發中也就非常簡單了。
  • 另外有一個非常重要的地方,在實際業務開發中,除了反射的使用外,還可以加入代理類把映射的配置交給它。這樣就可以不需要每一個mq都手動創建類了。

3. 代碼實現(接口使用適配)

就像我們前面提到隨着業務的發展,營銷活動本身要修改,不能只是接了MQ就發獎勵。因為此時已經拉新的越來越多了,需要做一些限制。

因為增加了只有首單用戶才給獎勵,也就是你一年或者新人或者一個月的第一單才給你獎勵,而不是你之前每一次下單都給獎勵。

那麼就需要對此種方式進行限制,而此時MQ中並沒有判斷首單的屬性。只能通過接口進行查詢,而拿到的接口如下;

接口 描述
org.itstack.demo.design.service.OrderService.queryUserOrderCount(String userId) 出參long,查詢訂單數量
org.itstack.demo.design.service.OrderService.POPOrderService.isFirstOrder(String uId) 出參boolean,判斷是否首單
  • 兩個接口的判斷邏輯和使用方式都不同,不同的接口提供方,也有不同的出參。一個是直接判斷是否首單,另外一個需要根據訂單數量判斷。
  • 因此這裏需要使用到適配器的模式來實現,當然如果你去編寫if語句也是可以實現的,但是我們經常會提到這樣的代碼很難維護。

3.1 定義統一適配接口

public interface OrderAdapterService {

    boolean isFirst(String uId);

}
  • 後面的實現類都需要完成此接口,並把具體的邏輯包裝到指定的類中,滿足單一職責。

3.2 分別實現兩個不同的接口

內部商品接口

public class InsideOrderService implements OrderAdapterService {

    private OrderService orderService = new OrderService();

    public boolean isFirst(String uId) {
        return orderService.queryUserOrderCount(uId) <= 1;
    }

}

第三方商品接口

public class POPOrderAdapterServiceImpl implements OrderAdapterService {

    private POPOrderService popOrderService = new POPOrderService();

    public boolean isFirst(String uId) {
        return popOrderService.isFirstOrder(uId);
    }

}
  • 在這兩個接口中都實現了各自的判斷方式,尤其像是提供訂單數量的接口,需要自己判斷當前接到mq時訂單數量是否<= 1,以此判斷是否為首單。

3.3 測試適配類

3.3.1 編寫單元測試類
@Test
public void test_itfAdapter() {
    OrderAdapterService popOrderAdapterService = new POPOrderAdapterServiceImpl();
    System.out.println("判斷首單,接口適配(POP):" + popOrderAdapterService.isFirst("100001"));   

    OrderAdapterService insideOrderService = new InsideOrderService();
    System.out.println("判斷首單,接口適配(自營):" + insideOrderService.isFirst("100001"));
}
3.3.2 測試結果
23:25:47.076 [main] INFO  o.i.d.design.service.POPOrderService - POP商家,查詢用戶的訂單是否為首單:100001
判斷首單,接口適配(POP):true
23:25:47.079 [main] INFO  o.i.d.design.service.POPOrderService - 自營商家,查詢用戶的訂單是否為首單:100001
判斷首單,接口適配(自營):false

Process finished with exit code 0
  • 從測試結果上來看,此時已經的接口已經做了統一的包裝,外部使用時候就不需要關心內部的具體邏輯了。而且在調用的時候只需要傳入統一的參數即可,這樣就滿足了適配的作用。

七、總結

  • 從上文可以看到不使用適配器模式這些功能同樣可以實現,但是使用了適配器模式就可以讓代碼:乾淨整潔易於維護、減少大量重複的判斷和使用、讓代碼更加易於維護和拓展。
  • 尤其是我們對MQ這樣的多種消息體中不同屬性同類的值,進行適配再加上代理類,就可以使用簡單的配置方式接入對方提供的MQ消息,而不需要大量重複的開發。非常利於拓展。
  • 設計模式的學習學習過程可能會在一些章節中涉及到其他設計模式的體現,只不過不會重點講解,避免喧賓奪主。但在實際的使用中,往往很多設計模式是綜合使用的,並不會單一出現。

八、推薦閱讀

  • 1. 重學 Java 設計模式:實戰工廠方法模式(多種類型商品發獎場景)
  • 2. 重學 Java 設計模式:實戰抽象工廠模式(替換Redis雙集群升級場景)
  • 3. 重學 Java 設計模式:實戰建造者模式(裝修物料組合套餐選配場景)
  • 4. 重學 Java 設計模式:實戰原型模式(多套試每人題目和答案亂序場景)
  • 5. 重學 Java 設計模式:實戰單例模式(Effective Java 作者推薦枚舉單例模式)

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

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

吐血整理全網最全的單例模式_網頁設計

※網頁設計最專業,超強功能平台可客製化

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

前言

之前文章已經說過了設計模式的七大原則,即接口屏蔽原則,開閉原則,依賴倒轉原則,迪米特原則,里氏替換原則,單一職責原則,合成復用原則,不明白的,可以移至萬字總結之設計模式七大原則(https://www.cnblogs.com/chenchen0618/p/12434603.html)。從今天開始我們就要學習一些常見的設計模式,方便我們以後看源碼使用,當然,也可以指導我們平常的編碼任務。

我們常見的設計模式主要有23種,分為3種類型,咱也不全說,只寫重要的幾個把。

創建型:單例模式,工廠模式,原型模式

結構型:適配器模式,裝飾模式,代理模式

行為型:模板模式,觀察者模式,狀態模式,責任鏈模式

單例模式的概念和作用

概念

系統中只需要一個全局的實例,比如一些工具類,Converter,SqlSession等。

為什麼要用單例模式?

  • 只有一個全局的實例,減少了內存開支,特別是某個對象需要頻繁的創建和銷毀的時候,而創建和銷毀的過程由jvm執行,我們無法對其進行優化,所以單例模式的優勢就顯現出來啦。
  • 單例模式可以避免對資源的多重佔用,避免出現多線程的複雜問題。

單例模式的寫法重點

構造方法私有化

我們需要將構造方法私有化,而默認不寫的話,是公有的構造方法,外部可以顯式的調用來創建對象,我們的目的是讓外部不能創建對象。

提供獲取實例的公有方法

對外只提供一個公有的的方法,用來獲取實例,而這個實例是否是唯一的,單例的,由方法決定,外部無需關心。

單例模式的常見寫法(如下,重點)

餓漢式和懶漢式的區別

餓漢式

餓漢式,從名字上也很好理解,就是“比較餓”,迫不及待的想吃飯,實例在初始化的時候就已經建好了,不管你有沒有用到,都先建好了再說。

懶漢式

餓漢式,從名字上也很好理解,就是“比較懶”,不想吃飯,等餓的時候再吃。在初始化的時候先不建好對象,如果之後用到了,再創建對象。

1.餓漢式(靜態變量)–可以使用

A類

public class A {
    //私有的構造方法
    private A(){}
    //私有的靜態變量
    private final static A a=new A();
    //對外的公有方法
    public static A getInstance(){
        return a;
    }
}

 

測試類

public class test {
    public static void main(String[] args){
        A a1=A.getInstance();
        System.out.println(a1.hashCode());

        A a2=A.getInstance();
        System.out.println(a2.hashCode());
    }
}

 

運行結果

說明

該方法採用的靜態常量的方法來生成對應的實例,其只在類加載的時候就生成了,後續並不會再生成,所以其為單例的。

優點

在類加載的時候,就完成實例化,避免線程同步問題。

缺點

沒有達到懶加載的效果,如果從始到終都沒有用到這個實例,可能會導致內存的浪費。

2.餓漢式(靜態代碼塊)–可以使用

A類

public class A { 
    //私有的構造方法
     private A(){}
    //私有的靜態變量
     private final static A a; 
    //靜態代碼塊 
    static{ a=new A(); } 
    //對外的公有方法
    public static A getInstance(){
     return a; 
    }
}

 

測試類

public class test {
    public static void main(String[] args){
        A a1=A.getInstance();
        System.out.println(a1.hashCode());

        A a2=A.getInstance();
        System.out.println(a2.hashCode());
    }
}

 

運行結果

說明

該靜態代碼塊的餓漢式單例模式與靜態變量的餓漢式模式大同小異,只是將初始化過程移到了靜態代碼塊中。

優點缺點

與靜態變量餓漢式的優缺點類似。

3.懶漢式

A類

public class A {
    //私有的構造方法
    private A(){}
    //私有的靜態變量
    private  static A a;
    //對外的公有方法
    public static A getInstance(){
        if(a==null){
            a=new A();
        }
        return a;
    }
}

 

測試類和運行結果

同上。

優點

該方法的確做到了用到即加載,也就是當調用getInstance的時候,才判斷是否有該對象,如果不為空,則直接放回,如果為空,則新建一個對象並返回,達到了懶加載的效果。

缺點

當多線程的時候,可能會產生多個實例。比如我有兩個線程,同時調用getInstance方法,並都到了if語句,他們都新建了對象,那這裏就不是單例的啦。

4.懶漢式(線程安全,同步方法)–可以使用

public class A {
    //私有的構造方法
    private A(){}
    //私有的靜態變量
    private  static A a;
    //對外的公有方法
    public synchronized static A getInstance(){
        if(a==null){
            a=new A();
        }
        return a;
    }
}

 

測試類和運行結果

同上。

優點

通過synchronize關鍵字,解決了線程不安全的問題。如果兩個線程同時調用getInstance方法時,那就先執行一個線程,另一個等待,等第一個線程運行結束了,另一個等待的開始執行。

缺點

這種方法是解決了線程不安全的問題,卻給性能帶來了很大的問題,效率太低了,getInstance經常發生,每一次都要同步這個方法。

我們想着既然是方法同步導致了性能的問題,我們核心的代碼就是新建對象的過程,也就是new A();的過程,我們能不能只對部分代碼進行同步呢?

那就是方法5啦。

5.懶漢式(線程不安全)

A類

public class A {
    //私有的構造方法
    private A(){}
    //私有的靜態變量
    private  static A a;
    public  static A getInstance(){
        if(a==null){
            synchronized (A.class){
                a=new A();
            }
        }
        return a;
    }
} 

 

測試類和運行結果

如上。

優點

懶漢式的通用優點,用到才創建,達到懶加載的效果。

缺點

這個沒有意義,並沒有解決多線程的問題。我們可以看到如果兩個線程同時調用getInstance方法,並且都已經進入了if語句,即synchronized的位置,即便同步了,第一個線程先執行,進入synchronized同步的代碼塊,創建了對象,另一個進入等待狀態,等第一個線程執行結束,第二個線程還是會進入synchronized同步的代碼塊,創建對象。這個時候我們可以發現,對這代碼塊加了synchronized沒有任何意義,還是創建了多個對象,並不符合單例。

6.雙重檢查 –強烈推薦使用

A類

public class A {
    //私有的構造方法
    private A() {
    }

    //私有的靜態變量
    private volatile static A a;

    //對外的公有方法
    public static A getInstance() {
        if (a == null) {
            synchronized (A.class) {
                if (a == null) {
                    a = new A();
                }
            }
        }
        return a;
    }
} 

 

測試類和運行結果

同上。

優點

強烈推薦使用,這種寫法既避免了在多線程中出現線程不安全的情況,也能提高性能。

咱具體來說,如果兩個線程同時調用了getInstance方法,並且都已到達了if語句之後,synchronized語句之前,此時第一個線程進入synchronized之中,先判斷是否為空,很顯然第一次肯定為空,那麼則新建了對象。等到第二個線程進入synchronized之中,先判斷是否為空,顯然第一個已經創建了,所以即不新建對象。下次,不管是一個線程或者多個線程,在第一個if語句那就判斷出有對象了,便直接返回啦,根本進不了裏面的代碼。

缺點

就是這麼完美,沒有缺點,哈哈哈。

volatile(插曲)

咱先來看一個概念,重排序,也就是語句的執行順序會被重新安排。其主要分為三種:

1.編譯器優化的重排序:可以重新安排語句的執行順序。

2.指令級并行的重排序:現代處理器採用指令級并行技術,將多條指令重疊執行。

3.內存系統的重排序:由於處理器使用緩存和讀寫緩衝區,所以看上去可能是亂序的。

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

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

上面代碼中的a = new A();可能被被JVM分解成如下代碼:

// 可以分解為以下三個步驟
1 memory=allocate();// 分配內存 相當於c的malloc
2 ctorInstanc(memory) //初始化對象
3 s=memory //設置s指向剛分配的地址
 // 上述三個步驟可能會被重排序為 1-3-2,也就是:
1 memory=allocate();// 分配內存 相當於c的malloc
3 s=memory //設置s指向剛分配的地址
2 ctorInstanc(memory) //初始化對象

 

一旦假設發生了這樣的重排序,比如線程A在執行了步驟1和步驟3,但是步驟2還沒有執行完。這個時候線程B有進入了第一個if語句,它會判斷a不為空,即直接返回了a。其實這是一個未初始化完成的a,即會出現問題。

所以我們會將入volatile關鍵字,來禁止這樣的重排序,即可正常運行。

7.靜態內部類 –強烈推薦使用

A類

public class A {
    //私有構造函數
    private A() {
    }

    //私有的靜態內部類
    private static class B {
        //私有的靜態變量
        private static A a = new A();
    }

    //對外的公有方法
    public static A getInstance() {
        return B.a;
    }
}

 

 

優點

B在A裝載的時候並不會裝載,而是會在調用getInstance的時候裝載,這利用了JVM的裝載機制。這樣一來,優點有兩點,其一就是沒有A加載的時候,就裝載了a對象,而是在調用的時候才裝載,避免了資源的浪費。其二是多線程狀態下,沒有線程安全性的問題。

缺點

沒有缺點,太完美啦。

8.枚舉 –Java粑粑強烈推薦使用

問題1:私有構造器並不安全

如果不明白反射,可以查看我之前的文章,傳送門,萬字總結之反射(框架之魂)。

如果我們的對象是通過反射方法invoke出來,這樣新建的對象與通過調用getInstance新建的對象是不一樣的,具體咱來看代碼。

 

public class test {
    public static void main(String[] args) throws Exception {
        A a=A.getInstance();
        A b=A.getInstance();
        System.out.println("a的hash:"+a.hashCode()+",b的hash:"+b.hashCode());

        Constructor<A> constructor=A.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        A c=constructor.newInstance();
        System.out.println("a的hash:"+a.hashCode()+",c的hash:"+c.hashCode());

    }
}

 

我們來看下運行結果:

我們可以看到c的hashcode是和a,b不一樣,因為c是通過構造器反射出來的,由此可以證明私有構造器所組成的單例模式並不是十分安全的。

問題2:序列化問題

我們先將A類實現一個Serializable接口,具體代碼如下,跟之前的雙重if檢查一樣,只是多了個接口。

 

public class A implements Serializable {
    //私有的構造方法
    private A() {
    }

    //私有的靜態變量
    private volatile static A a;

    //對外的公有方法
    public static A getInstance() {
        if (a == null) {
            synchronized (A.class) {
                if (a == null) {
                    a = new A();
                }
            }
        }
        return a;
    }
} 

 

測試類:

public class test {
    public static void main(String[] args) throws Exception {
        A s = A.getInstance();

        //寫
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("學習Java的小姐姐"));
        oos.writeObject(s);
        oos.flush();
        oos.close();
        //讀
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("學習Java的小姐姐"));
        A s1 = (A)ois.readObject();
        ois.close();

        System.out.println(s+"\n"+s1);
        System.out.println("序列化前後兩個是否同一個:"+(s==s1));
    }
}

 

我們來看下運行結果,很顯然序列化前後兩個對象並不相等。為什麼會出現這種問題呢?這個講起來,又可以寫一篇文章了。簡單來說,任何一個readObject方法,不管是顯式的還是默認的,它都會返回一個新建的實例,這個新建的實例不同於該類初始化時創建的實例。

A類

public enum A {
  a;
  public A getInstance(){
      return a;
  }
}

 

看着代碼量很少,我們將其編譯下,代碼如下:

public final class  A extends Enum< A> {      
public static final A a;
public static A[] values();
public static AvalueOf(String s);
static {}; }

 

如何解決問題1?

public class test {
    public static void main(String[] args) throws Exception {
        A a1 = A.a;
        A a2 = A.a;
        System.out.println("正常情況下,實例化兩個實例是否相同:" + (a1 == a2));

        Constructor<A> constructor = null;
        constructor = A.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        A a3 = null;
        a3 = constructor.newInstance();
        System.out.println("a1的hash:" + a1.hashCode() + ",a2的hash:" + a2.hashCode() + ",a3的hash:" + a3.hashCode());
        System.out.println("通過反射攻擊單例模式情況下,實例化兩個實例是否相同:" + (a1 == a3));
    }
}

 

運行結果:

我們看到報錯了,是在尋找構造函數的時候報錯的,即沒有無參的構造方法,那我們看下他繼承的父類ENUM有沒有構造函數,看下源碼,發現有個兩個參數String和int類型的構造方法,我們再看下是不是構造方法的問題。

我們再用父類的有參構造方法試下,代碼如下:

public class test {
    public static void main(String[] args) throws Exception {
        A a1 = A.a;
        A a2 = A.a;
        System.out.println("正常情況下,實例化兩個實例是否相同:" + (a1 == a2));
        Constructor<A> constructor = null;
        constructor = A.class.getDeclaredConstructor(String.class,int.class);//其父類的構造器
        constructor.setAccessible(true);
        A a3 = null;
        a3 = constructor.newInstance("學習Java的小姐姐",1);
        System.out.println("a1的hash:" + a1.hashCode() + ",a2的hash:" + a2.hashCode() + ",a3的hash:" + a3.hashCode());
        System.out.println("通過反射攻擊單例模式情況下,實例化兩個實例是否相同:" + (a1 == a3));
    }
}

運行結果如下:

我們發現報錯信息的位置已經換了,現在是已經有構造方法,而是在newInstance方法的時候報錯了,我們跟下源碼發現,人家已經明確寫明了如果是枚舉類型,直接拋出異常,代碼如下,所以是無法使用反射來操作枚舉類型的數據的。

如何解決問題2?

public class test {
    public static void main(String[] args) throws Exception {
        A s = A.a;

        //寫
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("學習Java的小姐姐"));
        oos.writeObject(s);
        oos.flush();
        oos.close();
        //讀
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("學習Java的小姐姐"));
        A s1 = (A)ois.readObject();
        ois.close();

        System.out.println(s+"\n"+s1);
        System.out.println("序列化前後兩個是否同一個:"+(s==s1));
    }
}

 

運行結果;

優點

避免了反射帶來的對象不一致問題和反序列問題,簡單來說,就是簡單高效沒問題。

結語

看到這裏的都是真愛的,在這裏先謝謝各位大佬啦。

單例模式是最簡單的一種設計模式,主要包括八種形式,分別是餓漢式靜態變量,餓漢式靜態代碼塊,懶漢式線程不安全,懶漢式線程安全,懶漢式線程不安全(沒啥意義),懶漢式雙重否定線程安全,內部靜態類,枚舉類型。

這幾種最優的是枚舉類型和內部靜態類,其次是懶漢式雙重否定,剩下的都差不多啦。

如果有說的不對的地方,還請各位指正,我好繼續學習去。

參考資料

一個單例模式中volatile關鍵字引發的思考

 

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

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

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

Angular 從入坑到挖坑 – 路由守衛連連看_貨運

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

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

一、Overview

Angular 入坑記錄的筆記第六篇,介紹 Angular 路由模塊中關於路由守衛的相關知識點,了解常用到的路由守衛接口,知道如何通過實現路由守衛接口來實現特定的功能需求,以及實現對於特性模塊的惰性加載

對應官方文檔地址:

  • 路由與導航

配套代碼地址:angular-practice/src/router-combat

二、Contents

  1. Angular 從入坑到棄坑 – Angular 使用入門
  2. Angular 從入坑到挖坑 – 組件食用指南
  3. Angular 從入坑到挖坑 – 表單控件概覽
  4. Angular 從入坑到挖坑 – HTTP 請求概覽
  5. Angular 從入坑到挖坑 – Router 路由使用入門指北
  6. Angular 從入坑到挖坑 – 路由守衛連連看

三、Knowledge Graph

四、Step by Step

4.1、基礎準備

重複上一篇筆記的內容,搭建一個包含路由配置的 Angualr 項目

新建四個組件,分別對應於三個實際使用到的頁面與一個設置為通配路由的 404 頁面

-- 危機中心頁面
ng g component crisis-list

-- 英雄中心頁面
ng g component hero-list

-- 英雄相親頁面
ng g component hero-detail

-- 404 頁面
ng g component page-not-found 

在 app-routing.module.ts 文件中完成對於項目路由的定義,這裏包含了對於路由的重定向、通配路由,以及通過動態路由進行參數傳遞的使用

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

// 引入組件
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { HeroListComponent } from './hero-list/hero-list.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';

const routes: Routes = [
  {
    path: 'crisis-center',
    component: CrisisListComponent,
  },
  {
    path: 'heroes',
    component: HeroListComponent,
  },
  {
    path: 'hero/:id',
    component: HeroDetailComponent,
  },
  {
    path: '',
    redirectTo: '/heroes',
    pathMatch: 'full',
  },
  {
    path: '**',
    component: PageNotFoundComponent,
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule { }

之後,在根組件中,添加 router-outlet 標籤用來聲明路由在頁面上渲染的出口

<h1>Angular Router</h1>
<nav>
  <a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a> &nbsp;&nbsp;
  <a routerLink="/heroes" routerLinkActive="active">Heroes</a>
</nav>
<router-outlet></router-outlet>

4.2、路由守衛

在 Angular 中,路由守衛主要可以解決以下的問題

  • 對於用戶訪問頁面的權限校驗(是否已經登錄?已經登錄的角色是否有權限進入?)
  • 在跳轉到組件前獲取某些必須的數據
  • 離開頁面時,提示用戶是否保存未提交的修改

Angular 路由模塊提供了如下的幾個接口用來幫助我們解決上面的問題

  • CanActivate:用來處理系統跳轉到到某個路由地址的操作(判斷是否可以進行訪問)
  • CanActivateChild:功能同 CanActivate,只不過針對的是子路由
  • CanDeactivate:用來處理從當前路由離開的情況(判斷是否存在未提交的信息)
  • CanLoad:是否允許通過延遲加載的方式加載某個模塊

在添加了路由守衛之後,通過路由守衛返回的值,從而達到我們控制路由的目的

  • true:導航將會繼續
  • false:導航將會中斷,用戶停留在當前的頁面或者是跳轉到指定的頁面
  • UrlTree:取消當前的導航,並導航到路由守衛返回的這個 UrlTree 上(一個新的路由信息)
4.2.1、CanActivate:認證授權

在實現路由守衛之前,可以通過 Angular CLI 來生成路由守衛的接口實現類,通過命令行,在 app/auth 路徑下生成一個授權守衛類,CLI 會提示我們選擇繼承的路由守衛接口,這裏選擇 CanActivate 即可

ng g guard auth/auth

在 AuthGuard 這個路由守衛類中,我們模擬了是否允許訪問一個路由地址的認證授權。首先判斷是否已經登錄,如果登錄后再判斷當前登錄人是否具有當前路由地址的訪問權限

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {

  /**
   * ctor
   * @param router 路由
   */
  constructor(private router: Router) { }

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {

    // 判斷是否有 token 信息
    let token = localStorage.getItem('auth-token') || '';
    if (token === '') {
      this.router.navigate(['/login']);
      return false;
    }

    // 判斷是否可以訪問當前連接
    let url: string = state.url;
    if (token === 'admin' && url === '/crisis-center') {
      return true;
    }

    this.router.navigate(['/login']);
    return false;
  }
}

之後我們就可以在 app-routing.module.ts 文件中引入 AuthGuard 類,針對需要保護的路由進行路由守衛的配置

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

// 引入組件
import { CrisisListComponent } from './crisis-list/crisis-list.component';

// 引入路由守衛
import { AuthGuard } from './auth/auth.guard';

const routes: Routes = [
  {
    path: 'crisis-center',
    component: CrisisListComponent,
    canActivate: [AuthGuard], // 添加針對當前路由的 canActivate 路由守衛
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule { }

4.2.2、CanActivateChild:針對子路由的認證授權

與繼承 CanActivate 接口進行路由守衛的方式相似,針對子路由的認證授權可以通過繼承 CanActivateChild 接口來實現,因為授權的邏輯很相似,這裏通過多重繼承的方式,擴展 AuthGuard 的功能,從而達到同時針對路由和子路由的路由守衛

改造下原先 canActivate 方法的實現,將認證邏輯修改為用戶的 token 信息中包含 admin 即可訪問 crisis-center 頁面,在針對子路由進行認證授權的 canActivateChild 方法中,通過判斷 token 信息是否為 admin-master 模擬完成對於子路由的訪問認證

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router, CanActivateChild } from '@angular/router';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate, CanActivateChild {

  /**
   * ctor
   * @param router 路由
   */
  constructor(private router: Router) { }

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {

    // 判斷是否有 token 信息
    let token = localStorage.getItem('auth-token') || '';
    if (token === '') {
      this.router.navigate(['/login']);
      return false;
    }

    // 判斷是否可以訪問當前連接
    let url: string = state.url;
    if (token.indexOf('admin') !== -1 && url.indexOf('/crisis-center') !== -1) {
      return true;
    }

    this.router.navigate(['/login']);
    return false;
  }

  canActivateChild(
    childRoute: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
    let token = localStorage.getItem('auth-token') || '';
    if (token === '') {
      this.router.navigate(['/login']);
      return false;
    }

    return token === 'admin-master';
  }
}

通過 Angular CLI 新增一個 crisis-detail 組件,作為 crisis-list 的子組件

ng g component crisis-detail

接下來在 crisis-list 中添加 router-outlet 標籤,用來定義子路由的渲染出口

<h2>危機中心</h2>

<ul class="crises">
  <li *ngFor="let crisis of crisisList">
    <a [routerLink]="[crisis.id]">
      <span class="badge">{{ crisis.id }}</span>{{ crisis.name }}
    </a>
  </li>
</ul>

<!-- 定義子路由的渲染出口 -->
<router-outlet></router-outlet>

在針對子路由的認證授權配置時,我們可以選擇針對每個子路由添加 canActivateChild 屬性,也可以定義一個空地址的子路由,將所有歸屬於 crisis-list 的子路由作為這個空路由的子路由,通過針對這個空路徑添加 canActivateChild 屬性,從而實現將守護規則應用到所有的子路由上

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

這裏其實相當於將原先兩級的路由模式(父:crisis-list,子:crisis-detail)改成了三級(父:crisis-list,子:’ ‘(空路徑),孫:crisis-detail)

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

// 引入組件
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { CrisisDetailComponent } from './crisis-detail/crisis-detail.component';

// 引入路由守衛
import { AuthGuard } from './auth/auth.guard';

const routes: Routes = [
  {
    path: 'crisis-center',
    component: CrisisListComponent,
    canActivate: [AuthGuard], // 添加針對當前路由的 canActivate 路由守衛
    children: [{
      path: '',
      canActivateChild: [AuthGuard], // 添加針對子路由的 canActivate 路由守衛
      children: [{
        path: 'detail',
        component: CrisisDetailComponent
      }]
    }]
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule { }

4.2.3、CanDeactivate:處理用戶未提交的修改

當進行表單填報之類的操作時,因為會涉及到一個提交的動作,當用戶沒有點擊保存按鈕就離開時,最好能暫停,對用戶進行一個友好性的提示,由用戶選擇後續的操作

創建一個路由守衛,繼承於 CanDeactivate 接口

ng g guard hero-list/guards/hero-can-deactivate

與上面的 CanActivate、CanActivateChild 路由守衛的使用方式不同,對於 CanDeactivate 守衛來說,我們需要將參數中的 unknown 替換成我們實際需要進行路由守衛的組件

import { Injectable } from '@angular/core';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class HeroCanDeactivateGuard implements CanDeactivate<unknown> {
  canDeactivate(
    component: unknown,
    currentRoute: ActivatedRouteSnapshot,
    currentState: RouterStateSnapshot,
    nextState?: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return true;
  }
  
}

例如,這裏針對的是 HeroListComponent 這個組件,因此我們需要將泛型的參數 unknown 改為 HeroListComponent,通過 component 參數,就可以獲得需要進行路由守衛的組件的相關信息

import { Injectable } from '@angular/core';
import {
  CanDeactivate,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  UrlTree,
} from '@angular/router';
import { Observable } from 'rxjs';

// 引入需要進行路由守衛的組件
import { HeroListComponent } from '../hero-list.component';

@Injectable({
  providedIn: 'root',
})
export class HeroCanDeactivateGuard
  implements CanDeactivate<HeroListComponent> {
  canDeactivate(
    component: HeroListComponent,
    currentRoute: ActivatedRouteSnapshot,
    currentState: RouterStateSnapshot,
    nextState?: RouterStateSnapshot
  ):
    | Observable<boolean | UrlTree>
    | Promise<boolean | UrlTree>
    | boolean
    | UrlTree {

    // 判斷是否修改了原始數據
    //
    const data = component.hero;
    if (data === undefined) {
      return true;
    }
    const origin = component.heroList.find(hero => hero.id === data.id);
    if (data.name === origin.name) {
      return true;
    }

    return window.confirm('內容未提交,確認離開?');
  }
}

這裏模擬判斷用戶有沒有修改原始的數據,當用戶修改了數據並移動到別的頁面時,觸發路由守衛,提示用戶是否保存后再離開當前頁面

4.3、異步路由

4.3.1、惰性加載

當應用逐漸擴大,使用現有的加載方式會造成應用在第一次訪問時就加載了全部的組件,從而導致系統首次渲染過慢。因此這裏可以使用惰性加載的方式在請求具體的模塊時才加載對應的組件

惰性加載只針對於特性模塊(NgModule),因此為了使用惰性加載這個功能點,我們需要將系統按照功能劃分,拆分出一個個獨立的模塊

首先通過 Angular CLI 創建一個危機中心模塊(crisis 模塊)

-- 查看創建模塊的相關參數
ng g module --help

-- 創建危機中心模塊(自動在 app.moudule.ts 中引入新創建的 CrisisModule、添加當前模塊的路由配置)
ng g module crisis --module app --routing

將 crisis-list、crisis-detail 組件全部移動到 crisis 模塊下面,並在 CrisisModule 中添加對於 crisis-list、crisis-detail 組件的聲明,同時將原來在 app.module.ts 中聲明的組件代碼移除

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { CrisisRoutingModule } from './crisis-routing.module';

import { FormsModule } from '@angular/forms';

// 引入模塊中使用到的組件
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { CrisisDetailComponent } from './crisis-detail/crisis-detail.component';


@NgModule({
  declarations: [
    CrisisListComponent,
    CrisisDetailComponent
  ],
  imports: [
    CommonModule,
    FormsModule,
    CrisisRoutingModule
  ]
})
export class CrisisModule { }

同樣的,將當前模塊的路由配置移動到專門的路由配置文件 crisis-routing.module.ts 中,並將 app-routing.module.ts 中相關的路由配置刪除

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

// 引入組件
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { CrisisDetailComponent } from './crisis-detail/crisis-detail.component';

// 引入路由守衛
import { AuthGuard } from '../auth/auth.guard';

const routes: Routes = [{
  path: '',
  component: CrisisListComponent,
  canActivate: [AuthGuard], // 添加針對當前路由的 canActivate 路由守衛
  children: [{
    path: '',
    canActivateChild: [AuthGuard], // 添加針對子路由的 canActivate 路由守衛
    children: [{
      path: 'detail',
      component: CrisisDetailComponent
    }]
  }]
}];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class CrisisRoutingModule { }

重新運行項目,如果你在創建模塊的命令中設置了自動引入當前模塊到 app.module.ts 文件中,大概率會遇到下面的問題

這裏的問題與配置通配路由需要放到最後的原因相似,因為腳手架在幫我們將創建的模塊導入到 app.module.ts 中時,是添加到整個數組的最後,同時因為我們已經將 crisis 模塊的路由配置移動到專門的 crisis-routing.module.ts 中了,框架在進行路由匹配時會預先匹配上 app-routing.module.ts 中設置的通配路由,從而導致無法找到實際應該對應的組件,因此這裏我們需要將 AppRoutingModule 放到聲明的最後

當問題解決后,就可以針對 crisis 模塊設置惰性加載

在配置惰性路由時,我們需要以一種類似於子路由的方式進行配置,通過路由的 loadChildren 屬性來加載對應的模塊,而不是具體的組件,修改后的 AppRoutingModule 代碼如下

import { HeroCanDeactivateGuard } from './hero-list/guards/hero-can-deactivate.guard';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  {
    path: 'crisis-center',
    loadChildren: () => import('./crisis/crisis.module').then(m => m.CrisisModule)
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes, { enableTracing: true })],
  exports: [RouterModule],
})
export class AppRoutingModule { }

當導航到這個 /crisis-center 路由時,框架會通過 loadChildren 字符串來動態加載 CrisisModule,然後把 CrisisModule 添加到當前的路由配置中,而惰性加載和重新配置工作只會發生一次,也就是在該路由首次被請求時執行,在後續請求時,該模塊和路由都是立即可用的

4.3.2、CanLoad:杜絕未通過認證授權的組件加載

在上面的代碼中,對於 CrisisModule 模塊我們已經使用 CanActivate、CanActivateChild 路由守衛來進行路由的認證授權,但是當我們並沒有權限訪問該路由的權限,卻依然點擊了鏈接時,此時框架路由仍會加載該模塊。為了杜絕這種授權未通過仍加載模塊的問題發生,這裏需要使用到 CanLoad 守衛

因為這裏的判斷邏輯與認證授權的邏輯相同,因此在 AuthGuard 中,繼承 CanLoad 接口即可,修改后的 AuthGuard 代碼如下

import { Injectable } from '@angular/core';
import {
  CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router, CanActivateChild, CanLoad, Route, UrlSegment
} from '@angular/router';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate, CanActivateChild, CanLoad {

  /**
   * ctor
   * @param router 路由
   */
  constructor(private router: Router) { }


  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {

    // 判斷是否有 token 信息
    let token = localStorage.getItem('auth-token') || '';
    if (token === '') {
      this.router.navigate(['/login']);
      return false;
    }

    // 判斷是否可以訪問當前連接
    let url: string = state.url;
    if (token.indexOf('admin') !== -1 && url.indexOf('/crisis-center') !== -1) {
      return true;
    }

    this.router.navigate(['/login']);
    return false;
  }

  canActivateChild(
    childRoute: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
    let token = localStorage.getItem('auth-token') || '';
    if (token === '') {
      this.router.navigate(['/login']);
      return false;
    }

    return token === 'admin-master';
  }

  canLoad(route: Route, segments: UrlSegment[]): boolean | Observable<boolean> | Promise<boolean> {
    let token = localStorage.getItem('auth-token') || '';
    if (token === '') {
      this.router.navigate(['/login']);
      return false;
    }

    let url = `/${route.path}`;

    if (token.indexOf('admin') !== -1 && url.indexOf('/crisis-center') !== -1) {
      return true;
    }
  }
}

同樣的,針對路由守衛的實現完成后,將需要使用到的路由守衛添加到 crisis-center 路由的 canLoad 數組中即可實現授權認證不通過時不加載模塊

import { HeroCanDeactivateGuard } from './hero-list/guards/hero-can-deactivate.guard';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  {
    path: 'crisis-center',
    loadChildren: () => import('./crisis/crisis.module').then(m => m.CrisisModule)
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes, { enableTracing: true })],
  exports: [RouterModule],
})
export class AppRoutingModule { }

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

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

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

從聚合支付業務的設計來聊聊策略模式_網頁設計公司

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

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

六月福利

2020年6月公眾號碼農小胖哥原創文章轉發第一名將送全新《Spring Boot實戰》實體書一本,該書是學習熱門框架 Spring Boot的經典之作。你不再需要依靠運氣,而是勤奮。截止統計日期2020年6月30日,統計數據以官方公眾號工具為準,運營人員不參加活動,本次活動圖書由掘金社區贊助。

1. 前言

前幾天講了設計模式中的命令模式,今天來看看另一個模式。移動支付目前在國內已經是非常普及了,連樓下早餐攤的七十多歲大媽也使用支付寶和微信支付賣雞蛋餅。如果讓你做一個App你肯定要考慮多個渠道支付,以保證獲客渠道。如果讓你來接入多種支付渠道你會怎麼設計?

2. 通常寫法

一般下面這種寫法很容易被創造出來:

    public boolean pay(BigDecimal amount){
        
        boolean ret =false;
        if (alipay){
            //todo 支付寶的邏輯
        }else if (wechatpay){
            //todo 微信支付的邏輯
        }else if (ooxx){
           // …… 
        }
        return ret;
    }

如果集成了四五種支付,這個代碼就沒法看了少說幾千行,而且改動某個支付的邏輯很容易改了其它支付的邏輯。因此需要合理的設計來避免這種風險。

3. 策略模式

大部分的支付可以簡化為這個流程:

中間的發起支付前邏輯和支付后處理邏輯是客戶端的自定義業務邏輯,向支付服務器發送的請求只會攜帶對應支付服務器特定要求的參數調用不同的支付SDK。所以我們分別建立對應支付方式的策略來隔離區分它們,降低它們的耦合度。當準備支付時我們只需要選擇對應的策略就可以了。

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

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

這就用到了設計模式中的策略模式:

結合上面的類圖,我們就來結合著需求來聊聊策略模式中的主要幾個角色。

  • Strategy接口。這個接口用來聲明每一種方式的獨立執行策略,用來封裝具體策略的特有算法邏輯。
  • ConcreteStrategy是Strategy的實現類。實現了不同策略的算法邏輯。比如每種支付的調用細節等等。
  • Context上下文。它通過策略接口來引用了具體的策略並使用具體的策略來執行邏輯,同時所有策略的共性也可以在該類中進行統一處理。在聚合支付需求中我們傳入一個策略,先執行支付前的邏輯,然後使用策略,策略執行完畢后,再執行後置的共性邏輯。
  • Client客戶端。創建策略對象並傳遞給上下文Context,然後由上下文運行具體的策略。

結合業務邏輯是這樣的:請求到達客戶端,客戶端根據請求中包含的支付渠道來構建對應的策略對象並把它交給上下文對象去執行支付流程。

然後我們就可以分別為支付寶、微信支付、銀聯支付構造三個策略對象 AliPayStrategy、WechatPayStrategy、UnionPayStrategy ,我們來模擬一下執行策略:

public class Client {

    public static void main(String[] args) {
        // 獲取請求中的支付渠道標識
        String code = "p01";
        PayStrategy payStrategy = null;
        PayRequest request = null;
        
        if (PayType.ALI.getCode().equals(code)) {
            //組裝為支付寶支付策略
            payStrategy = new AliPayStrategy();
            // 構造支付寶請求參數
            request = new AliPayRequest();
        }
        if (PayType.WECHAT.getCode().equals(code)) {
            //組裝為微信支付策略
            payStrategy = new AliPayStrategy();
            // 構造微信支付請求參數
            request = new WechatPayRequest();
        }

        if (PayType.UNION.getCode().equals(code)) {
            //組裝為銀聯支付策略
            payStrategy = new UnionPayStrategy();
            // 構造銀聯支付請求參數
            request = new UnionPayRequest();
        }

        if (Objects.nonNull(payStrategy)) {
            PayContext payContext = new PayContext();
            payContext.setPayStrategy(payStrategy);
            payContext.pay(request);
        }
    }
}

我們拿到請求中的支付標識,然後初始化對應的支付策略,封裝指定的請求參數,交給上下文對象PayContext 來執行請求。如果你再增加什麼ApplePay之類的去實現ApplePayStrategy就行了。

4. 優缺點

策略模式並不都帶來正面的作用。

4.1 優點

  • 我們將算法的實現和算法的使用進行了隔離,算法實現只關心算法邏輯,使用算法只關心什麼條件下使用什麼算法。
  • 開閉原則,無需修改上下文對象,想擴展只需要引入新的策略。
  • 運行時根據條件動態的去切換算法。
  • 適應個性的同時也可以兼容共性。

4.2 缺點

  • 客戶端必須明確知道激活策略的條件。
  • 引入太多的策略類。
  • 可被一些函數式接口所代替。偽代碼Pay.request(data).strategy(data->{ })。

5. 總結

策略模式也是很常見而且有着廣泛使用場景的設計模式。今天我們從聚合支付來學習了策略模式,對它的優缺點也進行了一個分析。隨着函數式編程的普及,策略模式開始被逐漸的代替,但是它依然值得我們去學習。感謝你的閱讀,多多關注:碼農小胖哥,更多編程乾貨奉上。

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

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

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

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。