買車算什麼?信不信帶它出門蘭博基尼都給我讓路_網頁設計公司

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

所以擁有日內瓦印記的腕錶也是收藏家和鑒賞家的最愛,這意味着工藝達到了瑞士手錶行業的頂級水準。羅傑杜彼年輕,但卻擁有眾多技藝精湛的工匠。在製表領域,能夠獨立製造極其複雜的擺輪遊絲的製表廠可謂屈指可數,但羅傑杜彼能做到真正的自造遊絲自造機芯。

有句話大家應該都聽過:窮玩車,富玩表。但在上個世紀七八十年代,

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

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

這句話中的車指的是三轉一響中的自行車,表指的是西洋表。當時自行車儘管難得,不過無論精密還是精美程度都遠遠不及機械錶,價格自然也天上地下,所以有了這句戲言。

但如今汽車與手錶同為工藝極其複雜的高精度机械,而如此精密的机械作用放在一塊手錶里,難度簡直是汽車的千倍以上。所以,當叫獸看到一塊手錶原來能賣到這個價時:

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

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

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

抗疫不忘環保 梅克爾重申「歐盟綠色協議」_網頁設計公司

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

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

摘錄自2020年4月29日聯合報報導

德國總理梅克爾4月28日在「第十一屆彼得斯堡氣候對話」所召開的視頻會議上說,盡管世界各國都面臨到疫情對經濟的沖擊,仍然必須「密切關注氣候保護問題」。

梅克爾也重申了她對歐盟「歐盟綠色協議」(European Green Deal、歐洲綠色政綱)的支持。該協議致力於在2030年前將歐盟整體的碳排放量降低到1990年水平的50%以下。目前會員國之間同意減少40%的碳排放量。

梅克爾說德國將會繼續逐步汰換掉燃煤的發電廠,並且持續投資永續能源。她也支持藉由對二氧化碳定價的方式來降低碳排放。

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

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

 

 

污染治理
國際新聞
德國
武漢肺炎
疫情
歐盟
救經濟靠綠色振興

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

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

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

回收福島核災污染土壤 環境省首試種蔬菜_網頁設計公司

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

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

摘錄自2020年8月9日東網報導

日本共同社上周六(8日)披露環境省的未公開文件內容,指當局決定使用三一一福島核災後去污清理出的土壤,首次用以種植蔬菜等可食用農作物,就將核污土壤循環再用成農地展開實驗。惟此舉料將引起居民憂慮,觸發反對情緒。

龍谷大學環境經濟學教授大島堅一請求公開的環境省文件顯示,該省已改變以往方針,計劃直接在核污土壤上種植番茄、青瓜、椰菜、粟米等農作物,並將有關提案寫入環境省使用核污土壤的「基本草案」和「指南」草案。

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

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

文件顯示,該省在1月召開的專家會議上,表明有意被列為「返家困難區域」的飯館村種植食用作物,所以希望進行核污土壤種植試驗,證明有關土壤在未覆蓋無污染泥土的情況下也沒有污染問題。當局指,此舉旨在取得科學上的知識和經驗,又強調已在閉門會議上獲得村幹部和地方代表的同意。

污染治理
國際新聞
日本
福島核災
核能

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

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

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

30萬選大SUV要7座還是要配置真糾結_網頁設計公司

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

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

而考慮到漢蘭達有七座的設計,它的內部空間更加靈活,而且它的車尾造型要比冠道更加的平直,所以在尾箱空間上漢蘭達有車不小的優勢。動力冠道碾壓漢蘭達如果說前面三個部分都是半斤八兩。那麼在動力系統上的差距可真不小了,兩款車型都使用2。

本田冠道不久前剛剛上市。這一款被本田寄予厚望的中型SUV還沒有上市便火了一把。由於保密工作做得十分好,所以冠道的價格直到發布前都沒有消息,而就在冠道發布之後。其26.98到32.98萬的售價倍受吐槽。用網友的一句話來說。昂科威毫無壓力,銳界鬆了口氣,漢蘭達決定繼續加價,樓蘭覺得自己不再孤單。那麼在這個看似偏高的定價背後。冠道真的賣的貴嗎?

我們挑選漢蘭達和本田冠道進行對比。漢蘭達也可以說是冠道最大的競爭對手。我們都挑選29萬左右的版本。

冠道 2017款 370TURBO 四驅尊享版

指導價:29.28萬 下文簡稱冠道 文中圖片為370TURBO 四驅至尊版

漢蘭達 2015款 2.0T 四驅豪華版 7座

指導價:29.48萬 下文簡稱漢蘭達

他們都使用的2.0T的發動機。並且都使用了四驅系統。兩款車型究竟哪一款更加具有性價比呢?

外觀

半斤八兩各具特色

從外觀上來說,其實兩款車是見仁見智的,在長度上漢蘭達要稍微的長那麼一點點。但是在寬度上冠道更加有優勢。所以在外觀的尺寸尚兩款車型算是打成一個平手。(冠道尺寸4816*1942*1669mm,漢蘭達尺寸4855*1925*1720)

而冠道不到一米七的高度則更加具有運動感。從外觀設計的風格來看,漢蘭達更加的圓潤,而冠道則更加的犀利剛硬。這也比較貼合兩款車型的設計風格。

內飾

冠道更精緻

內飾上來說漢蘭達的內飾是屬於比較實用的。巨大的儲物格,較大的車內按鈕這些設計都是偏向於實用性而寬敞的車內空間優秀的人機工程學設計。都是漢蘭達的優勢所在。簡單的車廂設計,並含着深厚的設計功底這也是豐田一直以來的特點。

冠道的內飾和它的外觀一樣,都是偏向於硬朗的風格。值得注意的是冠道的中控大屏是可以電動調節角度的非常貼心,非常人性化的一個設計。冠道的內飾更有設計感,

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

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

一改之前本田比較偏向於廉價的設計感覺。冠道的內飾十分值得好評,相比以往的本田來說精緻度和設計感都提升了不少。

空間

漢蘭達更加優秀

這兩款車型漢蘭達使用的是七座設計,而冠道是五座車給因此我們只對比兩款車型的前兩排空間。在前兩排的空間表現上漢蘭達和冠道真的是半斤八兩,不分伯仲,兩款車型都是十分注重車內空間的車型,它們非常大的車身尺寸也決定了他們的空間表現註定不會差。

而考慮到漢蘭達有七座的設計,它的內部空間更加靈活,而且它的車尾造型要比冠道更加的平直,所以在尾箱空間上漢蘭達有車不小的優勢。

動力

冠道碾壓漢蘭達

如果說前面三個部分都是半斤八兩。那麼在動力系統上的差距可真不小了,兩款車型都使用2.0T的渦輪增壓發動機。但是冠道的2.0T最大功率272馬力(漢蘭達為220馬力),比漢蘭達的2.0T整整大了52馬力。最大扭矩也高出了20牛米(冠道370牛米,漢蘭達為350牛米)。而且冠道的車身重量比漢蘭達要輕了204公斤。所以在最終的加速表現上兩款車型差異巨大。冠道的官方百公里加速時間為8秒,而漢蘭達的實測百公里加速時間為9.61秒。而在剎車距離上冠道也在38.6米左右。漢蘭達實測則需要41.38米。在加速和急剎車性能上的差距可是實實在在存在的。

這裏額外說一句冠道標稱使用93號汽油而漢蘭達需要97號汽油。但是漢蘭達的油箱容積為72升,冠道為55升。綜合他倆的平均油耗來看漢蘭達在續航能力上的優勢也不小。

冠道使用9AT變速箱而漢蘭達使用6AT的變速箱。好壞沒有體驗不作評價,但是冠道的9AT顯然能夠給他帶來更加好的高速靜謐性和燃油經濟性。

配置

冠道碾壓漢蘭達

在配置上兩款車型差異也十分大,而結論是冠道的配置要比漢蘭達好很多。

冠道比漢蘭達多出了

电子手剎

胎壓監測

自動駐車

可變轉向比

電動後備箱

后倒車雷達

方向盤換擋

GpS導航系統

定位互動服務

12喇叭音響

全LED大燈

轉向輔助燈

車內氛圍燈

后視鏡加熱

內后視鏡自動防炫目

和小編十分推薦的併線輔助系統

相比冠道漢蘭達則多出了

膝部氣囊

全尺寸備胎

陡坡緩降

中央差速器鎖止功能

以及腰部支撐調節

第二排靠背角度調節

第二排移動

和第三排座椅

CD支持Mp3/WMA

單碟CD系統

相比較來說冠道配置要實在很多。

總結

冠道性價比更加高

冠道和漢蘭達在外觀內飾以及空間方面勉強打成平手。而在動力方面冠道的優勢十分明顯在配置上冠道也要更加的好。所以相比漢蘭達冠道賣的貴嗎?在小編看來冠道賣的一點都不貴。那麼,為什麼冠道賣到這個價錢呢?

其實這隻是本田戰略的一部分而已。首先我們可以看到26萬多起價的本田冠道它的基礎配置就已經十分高了。所以這也不排除未來會推出更加低配車型的可能用以下探價格。冠道除了那個十分讓人心潮澎湃的動力之外,寬敞的空間優秀的隔音和底盤表現也是許多人關心的,因此對於那些開車節奏比較緩慢。不那麼注重動力的人和更加關心舒適與質感的人來說在明年三月份冠道還將推出一個1.5T版本供大家選擇,價格在22萬左右,因此如果你覺得2.0T的冠道貴不妨再等等。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

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

7座良心國貨SUV 選頂配也不到7萬真值_網頁設計公司

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

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

5L的自然吸氣發動機,最大馬力113ps,峰值扭矩150N。m,從賬面參數上基本上可以得知這台SUV僅僅只能應付城市的道路,不過在自主品牌家用車陣營裏面,幻速S3L採用的前置后驅的驅動方式,倒是一定程度上可以彌補發動機功率不大的短板。

北汽幻速S3,在上市之時就引起很多消費者的關注,並不大的車身,但是卻有着7座的布局,而且較低的售價更是使得眾多預算不多,用車需求又較大的朋友對其表現出濃厚的興趣。如今在S3的基礎上加長了軸距的S3L也已經陸續到店銷售,車價依然吸引,頂配不足7萬元的設定,它會是你心中的那道菜么?

外觀:更協調的設計

幻速S3L的前臉可以說是印象較為深刻的造型。整體用X造型勾勒的前中網讓人不免想到了雷克薩斯的“紡錘形”進氣格柵,抄襲與否咱且不論,甭管怎麼說這套全新的前臉設計比老款的幻速S3來得要更加精神。

車身側面的協調感也更加和諧,老款的幻速S3側面看上去就像是一個麵包車把車頭給拉長了一些,愣是充SUV在賣呢,儘管S3L的平台並沒有更換,但是車身整體輪廓更加柔和流暢,個人認為幻速S3L的顏值相較於S3來說,還是提升了不少。

內飾:更符合年輕人的胃口

幻速S3L的內飾設計採用了撞色的概念,視覺衝擊力很強,鮮艷的配色方案顯得更加的絢麗與時尚。整體的線條勾勒方式趨向平直,

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

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

其實在售價不高的車型上用更平直簡潔的線條來設計中控台,會更有利於彰顯車內的檔次感。

動力:熟悉的配方

幻速S3L的動力總成沒有變化,依然搭載的是一台1.5L的自然吸氣發動機,最大馬力113ps,峰值扭矩150N.m,從賬面參數上基本上可以得知這台SUV僅僅只能應付城市的道路,不過在自主品牌家用車陣營裏面,幻速S3L採用的前置后驅的驅動方式,倒是一定程度上可以彌補發動機功率不大的短板。

編輯總結:幻速S3L其實還是一台從微面平台換殼而生的SUV車型,從前置后驅的驅動方式,較高的車身重心,前麥弗遜獨立懸架后多連桿非獨立懸架等地方我們還是可以看出它與商用微面之間的關係。

但是回歸汽車產品本身,幻速S3L的外觀和內飾優化做得其實算是比較用心的類型,更加符合城市SUV特性的細節處設計,已經可以看出幻速S3L逐漸的向家庭用途乘用車方向進行妥協。更加年輕化運動化的外觀、更加時尚、舒適感更進一步的內飾布局,這些都可以將幻速S3L歸類於一台合格的城市型家用SUV的範疇。

從定價上看,幻速S3L可以直接往頂配車型考慮,畢竟一共只有兩款車型,6.68萬-6.98萬的差價僅僅只在三千元。幻速S3L幻速S3L全系配備車身穩定系統,第三排的乘坐空間可以應付短途乘坐多人的需求,最重要的是,它的頂配售價不足七萬,幻速S3L在外觀、內飾、空間、配置等方面所下的功夫算是比較用心,而作為消費者的我們,要考慮的就是我們對於北汽幻速這個品牌的認同度,是否達到一個讓我們放心掏腰包購買它的高度了。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

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

光便宜還不夠 這些10幾萬的家用車還能賺些買菜錢_網頁設計公司

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

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

如果這些政策真的落實這類打車軟件很有可能成為最新的相親軟件就像網上說過的一個梗本地戶口、價格不便宜的新車、還有本地牌照滿足這些條件估計都有車有房比普通的交友平台更牛逼。下班也要出去做網約司機了。說了這麼多,究竟什麼車才適合跑滴滴。

我們沒車一族的出行方式基本就是靠走、或者踩個自行車、乘坐公共交通工具、部分地區短途還能坐摩托車、土豪一點就去坐計程車。從Uber進入中國以及和滴滴出行一起相互大肆燒錢開始,我們終於找到了一個舒服、價格便宜的出行方式,也讓有車一族找到了一個月入破萬的工作。

還記得那時全民皆滴滴的時代

人們出行叫網約車

不僅隨傳隨到、關鍵是便宜

再也不用碰到那些讓人厭惡的出租車!

對於很多駕駛員來說

終於擁有一個月入破萬的工作

還記得網約車剛興起的時候

做幾個月新買的車就回本了

每天做幾單就有巨額獎勵

也讓不少人辭掉了原本的工作

紛紛投入了網約車的行列

作為一個開放的平台,各位車主只需要身份證、駕駛證、行駛證就能完成註冊,投入到偉大的網約車行列,也正是因為手續如此簡單,也造就了網約車行列出現一些混亂的狀況,也存在一些比較極端的負面新聞。

下面我們來扒一扒你打滴滴遇過的奇葩事情…

王小姐:我曾經叫了一輛滴滴,然後來的竟然是一檯面包車!麵包車!而且還有很大一股異味!

楊先生:叫了滴滴,看着App,盯着地圖上的小汽車離自己越來越近了,然後趕時間的我馬上就能上車了….後來司機開過了,結果司機越開越遠,最後又兜了很大一個圈子轉回來!!!我勒個去!

好日子總會到頭的

網約車車存在已經嚴重影響到計程車行業

為了保障利益

終極BOSS受不了壓力要出手了!

《網約車徵求意見稿》

已經從北上廣深這幾個一線城市出台

用最簡單的話術總結這個意見稿

車、牌、人三方面都作出明確規定!

在尺寸上

北、深、上三大城市軸距要求≥2.7米

新能源車軸距≥2.65米

廣州長寬高分別大於4.6米、1.7米、1.42米

在排量上

北、上、深≥2.0L或1.8T;上海沒要求

在年限上

廣州要求1年內准新車

深圳要求2年內車型

北京、上海並沒要求

總結下來最嚴格的還是廣州

另外網約車要求是本地牌照

北京需要搖號

上海:拍牌均價88359元(10月)

廣州:拍牌均價19614元(9月)

深圳:拍牌均價39729元(9月)

在駕駛員方面

北、上兩城需要本地戶口

深圳需要有居住證

廣州並沒要求(這是一個包容的城市)

看起來要滿足這一些條件真不容易…

如果這些政策真的落實

這類打車軟件很有可能成為

最新的相親軟件

就像網上說過的一個梗

本地戶口、價格不便宜的新車、還有本地牌照

滿足這些條件估計都有車有房

比普通的交友平台更牛逼!

下班也要出去做網約司機了!

說了這麼多,究竟什麼車才適合跑滴滴?

要跑滴滴首先要滿足以下幾個條件!

中級車以上(價格還不能太高)

動力在2.0L或1.8T以上

車子省油、保養便宜、而且耐用

最後當然要空間大

1日系选手

凱美瑞

車身尺寸:4850*1825*1480mm

軸距:2775mm

符合車型:全系

售價:18.48-32.98萬

點評:在看來,滴滴的法規上,日系車佔優!先趴一趴日系車的優點,空間大、省油、保養便宜、而且還耐操,用來做網約車還真是合適,而這一次所說的豐田凱美瑞全系車型均已滿足各項條件,這裏更推薦選擇雙擎車型,雖然是2.5L的排量但是油耗才5-7L左右的表現就已經能獨領風騷,更不用說2.0L車型本身的表現了。照這麼說雅閣也是個不錯的選擇囖?其實不然,針對網約車來說,凱美瑞的後排舒適度是在雅閣之上的,

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

自己搭車也會選擇凱美瑞。

2德系选手

帕薩特

車身尺寸:4872*1834*1484mm

軸距:2803mm

符合車型:1.8T以上車型

售價:22.29-33.29萬

點評:帕薩特可以說是在德系品牌中能夠達標而且價格是最便宜的一個車型,在空間、動力、排量、尺寸方面均已經滿足要求,雖然帕薩特有一個比較大的優惠幅度,但是1.8T以上的車型辦下來也要20多萬,專門拿來做網約車就真的比較浪費了,而且德系車保養並不便宜,另外還存在一定程度的燒機油情況,拿來做網約車,未免投入太大了。

3美系选手

邁銳寶XL

車身尺寸:4923*1854*1477mm

軸距:2829mm

符合車型:2.5L版本

售價:21.99-24.99萬

點評:外觀帥氣、空間大、配置高是邁銳寶XL主要賣點之一,只是用來做網約車就必須選擇2.5L的版本,在油耗方面會偏高,而且後期投入比較大,除了這一款車型,你也可以選擇2.0L的君威或者2.0T的蒙迪歐,但是百公里油耗隨便都能超過10L的車型來說,拿來做網約車加油加到怕啊。

4國產选手

博瑞

車身尺寸:4956*1861*1513mm

軸距:2850mm

符合車型:全系

售價:11.98-22.98萬

點評:實際上博瑞也是為數不多能夠參与到網約車序列的車型之一,首先就是國產的中型車並不多,其中就博瑞賣得不錯,雖然博瑞的油耗也是百公里油耗超過10L的貨,但關鍵是車型起售價比其他車型便宜,雖然博瑞的小毛病還是有不少,但三大件的大問題還算得上過關,拿來做網約車也是可以的。

總結:

就如所說,這一次網約車的規定就像給日系車量身定做那般,做網約車最關鍵的是什麼?認為關鍵就是要省油!另外就是要售價便宜的,所以真的要狠下心來做網約車,跟偏向於選擇凱美瑞這一類的日系中型車。本站聲明:網站內容來源於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語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

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

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

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

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

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

一、前言

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

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

而這主要的原因是一個框架隨着時間的發展,它的複雜程度是越來越高的,從最開始只有一個非常核心的點到最後開枝散恭弘=叶 恭弘。這就像你自己開發的業務代碼或者某個組件一樣,最開始的那部分核心代碼也許只能佔到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 作者推薦枚舉單例模式)

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

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

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

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

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

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

六月福利

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

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

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

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語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

LeetCode 73,為什麼第一反應想到的解法很有可能是個坑?_網頁設計公司

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

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

本文始發於個人公眾號:TechFlow,原創不易,求個關注

今天是LeetCode第42篇文章,我們來看看LeetCode第73題矩陣置零,set matrix zeroes。

這題的難度是Medium,通過率在43%左右,從通過率上可以看出這題的難度並不大。但是這題的解法不少,從易到難,有很多種方法。而且解法和它的推導過程都挺有意思,我們一起來看下。

題意

首先我們來看題意,這題的題意很簡單,給定一個二維數組。要求我們對這個數組當中的元素做如下修改,如果數組的i行j列為0,那麼將同行和同列的元素全部置為0。要求我們直接在原數組上進行修改,而不是返回一個新的數組。

言下之意,要求我們實現一個in-place的方法,而避免額外開闢新的內存。

樣例

Input: 
[
  [0,1,2,0],
  [3,4,5,2],
  [1,3,1,5]
]
Output: 
[
  [0,0,0,0],
  [0,4,5,0],
  [0,3,1,0]
]

近在眼前的解法原來是坑

這題的題意非常簡單,解法也非常明顯,以至於很多人拿到它都會當做模擬題來解決。即遍歷一下數組,如果找到0,那麼將它所在的行和列賦值為0,然後繼續遍歷。

這段邏輯並不難寫,我們很容易寫出來:

class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """  Do not return anything, modify matrix in-place instead.  """
        n = len(matrix)
        if n == 0:
            return
        
        m = len(matrix[0])
        for i in range(n):
            for j in range(m):
                # 當我們找到為0的位置之後,將所在的行和列置為0
                if matrix[i][j] == 0:
                    for k in range(m):
                        matrix[i][k] = 0
                    for k in range(n):
                        matrix[k][j] = 0

但是很遺憾的是,這樣的做法是錯誤的,實際上連樣例都無法通過。通不過樣例的原因也很簡單, 因為0具有傳遞性。

舉個簡單的例子,假設第0行當中有一個0,那麼最後的結果一定是第0行全部被置為0。但問題是我們是在遍歷到0的時候來進行的set操作,這樣會將第0行的其他元素也置為0。這樣當我們遍歷到後面的位置之後,會繼續傳遞,從而將一些不該置為0的地方也置為0了。

舉個簡單的例子,比如:第0行是1 0 0 1。顯然由於第0行存在0,所以操作之後的結果一定是全為0。但問題是matrix[0][3]這個位置原本並不為0,但是如果我們在發現matrix[0][1]為0的時候,將它置為0的話,那麼當我們後面遍歷到matrix[0][3]得到0的時候,會無法判斷究竟是這個位置原本就是0,還是前面出現了0導致這一行全部變成了0。這兩者的操作是不同的。

眼看着目標就在眼前,好像一伸手就碰得到,但是偏偏好像這一步就是咫尺天涯,怎麼也碰不到。這種感覺想想都很難受,我想,當你試着用這種方法去解這道題然後發現不行的時候,一定會有這樣的感覺。並且你會發現好像也沒有什麼很好的辦法來優化。

這種情況在正式的算法比賽當中經常遇到,所以專業的競賽选手有了經驗(吃過虧)之後,想出思路的第一時間就會立即轉向思考,這樣做是不是會有什麼坑,或者是考慮不到的情況。嚴謹一點的同學還會構思幾組不同的測試數據進行測試,或者是腦海中模擬算法的運算。

剛不過去只能繞

以前我年輕的時候總是不信邪,有時候明知道這個方法並不好,或者是存在反例,但是仍會堅持想要通過自己的努力想出一個方案來解決它,而不是更換方法。

我不知道有多少人有同樣的想法,但是一般來說頭鐵的毛病最後總是會被治好的。這題算是一個不錯的例子,如果你堅持使用模擬的方法來做這道題,只有一種方案就是再創建一個同樣大小的數組來作為緩存。當我們遇到0的時候,我們不直接修改原數組中的結果,而是修改緩存,將同行和同列緩存數組中的元素置為0,最後再將緩存數組與原數組合併。

但是顯然這不是一種好的方法,因為題目要求in-place的目的就是為了節約空間,我們另外創建了一個同樣大小的數組顯然違背了題目的本意

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

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

所以頭鐵到最後還是得認清現狀,這個方法不適合這道題,需要更換解法。如果是在比賽當中得出的這個結論,那麼很有可能獎牌已經和你沒什麼關係了。堅持和固執本身也許沒有太大的區別, 可能只是出現的場景不一樣。

進階解法

回到這道題本身,我們已經證明了模擬的思路是行不通的,除了一邊遍歷一邊操作可能帶來的混亂之外,還有一個點是這樣的複雜度很高。因為如果原數據當中如果本身0就很多的話,那麼我們會需要不停地操作,極端情況下,如果所有元素都是0,那麼我們每一個位置都需要操作一下行列,整體的複雜度會達到

既然如此,還有什麼好的辦法嗎?

當然是有的,其實也挺明顯的,因為對於一個出現的0來說它會影響的範圍是固定的,就是所在的行和列,那我們是不是記錄下會全部置為0的行和列,最後再遍歷一遍數據,看下當前元素是不是出在置為0的範圍當中就可以了。這種方法需要我們再創建兩個數組,用來存儲行和列是否被置為0。

這個解法也很直觀,想到了代碼應該不難寫:

class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """  Do not return anything, modify matrix in-place instead.  """
        n = len(matrix)
        if n == 0:
            return
        
        m = len(matrix[0])
        rows = [0 for _ in range(n)]
        cols = [0 for _ in range(m)]
        
        # 記錄置為0的行和列
        for i in range(n):
            for j in range(m):
                if matrix[i][j] == 0:
                    rows[i], cols[j] = 1, 1
                    
        # 如果所在行或者列置為0,那麼當前位置為0
        for i in range(n):
            for j in range(m):
                if rows[i] or cols[j]:
                    matrix[i][j] = 0
                    

終極解法

上面的做法雖然通過之後的戰績不太光彩,沒能戰勝90%以上的提交,但是能夠通過,而且算法沒有數量級的差距,也算是可以的。如果讓我來做,我可能就想到這種方法為止了。但是題目當中明確說了,還有空間複雜度為O(1)的算法,逼得我進一步思考了一下。

一般來說我們都是優化時間複雜度,很少會優化空間複雜度。相比於優化時間,優化空間有時候更加困難。因為有些時候我們可以空間換時間,可以預處理,可以離線計算……方法相對比較多。但優化空間的方法則很少,尤其是很多時候還不能犧牲時間,所以一般來說只能從算法本身來優化,很少有什麼套路可以套用。

在這個問題當中,要優化空間複雜度到常數級,那麼說明我們連數組都不能用。也就是說不能記錄行和列的信息,但是我們也不能用模擬的方法來進行,那麼應該怎麼辦呢?

干想是很難想出來的, 但是我們換個思路,問題就完全不一樣了。上面的算法時間複雜度是最優的,空間複雜度不太行,那麼有沒有辦法既使用同樣的算法,又能節省空間呢?看起來似乎不可能,但是其實可以,方法說穿了也並不值錢,就是將數據想辦法存在已有的地方,而不是另外開闢空間。在這個問題當中,已有的地方當然就只有一個就是原數組。也就是說我們要把每一行和列是否為0的信息記錄在原數組當中,比如我們可以把第0行和第0列用來做這個事情。

但這樣又會帶來另外一個問題,如果第0行和第0列本身當中也有0出現該怎麼辦?沒辦法,只能特判了。我們單獨用變量來記錄第0行和第0列是否被置為0,這樣我們就最大化地利用了空間,將空間複雜度降低到了常數級。

代碼邏輯和上面一脈相承,只是多了一點騷操作。

class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """  Do not return anything, modify matrix in-place instead.  """
        n = len(matrix)
        if n == 0:
            return
        
        m = len(matrix[0])
        
        row, col = False, False
        
        # 特判0,0的位置
        if matrix[0][0] == 0:
            row, col = True, True
        
        # 特判第0列是否含0
        for i in range(n):
            if matrix[i][0] == 0:
                col = True
                break
                
        # 特判第0行是否含0
        for i in range(m):
            if matrix[0][i] == 0:
                row = True
                break
                
        # 將i行,j列是否為0的信息存入matrix當中
        for i in range(0, n):
            for j in range(0, m):
                if matrix[i][j] == 0:
                    matrix[i][0] = 0
                    matrix[0][j] = 0
                    
        for i in range(1, n):
            for j in range(1, m):
                # 根據第0行與第0列數據還原
                if matrix[i][0] == 0 or matrix[0][j] == 0:
                    matrix[i][j] = 0
                  
        # 最後處理第0行與第0列
        if row:
            for i in range(m):
                matrix[0][i] = 0
                
        if col:
            for i in range(n):
                matrix[i][0] = 0

總結

到這裏,這道題就算是分享完了,它的題意簡單,但是解法挺多的,我個人感覺也許還存在更好的解法也不一定。

我個人做完這題最大的感受不是這題的思路如何,也不是它涉及的算法如何,而是想到了很多和算法題無關的事情。比如我們生活當中有沒有這樣看似簡單,但是做起來發現一點也不簡單的事情?有沒有眼看着目標就在眼前,卻發現選擇的路一開始就是錯的呢?

帶着這樣的思路來做題,會發現題目也變得有意思多了。

今天的內容就是這些,如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

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

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

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