確認以 GT 為名! realme GT 高通 S888 新旗艦將於 3/4 14:00 正式發表

昨(17)日我們才報導過 realme 副總裁徐起在微博暗示接下來 realme 的高通 Snapdragon 888 新旗艦將命名為 GT 系列的暗示,想不到在今(18)日稍早官方證實了此消息,同時預告 realme GT 的新機發表會時間就訂在 3 月 4 日。徐起也暗示 realme GT 不僅擁有 Snapdragon 888 的澎湃性能,還擁有持久續航和和高螢幕更新率。

▲圖片來源:徐起Chase(微博)

確認以 GT 為名! realme GT 高通 S888 新旗艦將於 3/4 14:00 正式發表

經過前幾天 realme 副總裁徐起在微博暗示 realme 這款內部代號「Race」的 Snapdragon 888 旗艦為 realme GT,稍早官方正式公開證實此消息,同時也預告 3 月 4 日下午 2 點舉行 realme GT 新機發表會。

▲圖片來源:徐起Chase(微博)

徐起在微博的貼文也提及 realme GT 將追求極致、挑戰「120% 的自己」,不過目前還無法肯定 120 的暗示是指 120Hz 螢幕更新率或 120W 快速充電。不過在 realme GT 首張官方宣傳海報左下角有三個圖示暗示 realme GT 的三大特色,從圖示推測由左至右依序為處理器、螢幕更新率和充電。根據過去的傳聞, realme GT 將搭載 Qualcomm Snapdragon 888 旗艦行動平台、 120Hz 螢幕更新率和 125W UltraDart 超級閃充。

▲圖片來源:徐起Chase(微博)

去年 7 月 16 亮相自家的 125W UltraDart 超級閃充技術 ,標榜只要充電 3 分鐘就能為 4000mAh 電池容量的手機充電達到 33% 電量。不過至今 125W UltraDart 超級閃充還未在 realme 旗下手機搭載,目前還是以 65W SuperDart 閃充為主流,不排除有機會在 realme GT 首次搭載。

▲圖片來源:realme真我手機(微博)

稍早數碼閒聊站在微博透露, realmeGT 將推出素皮和玻璃兩種版本,並提及在設計上有新東西:

▲圖片來源:數碼閒聊站(微博)

其實大約兩週前, realme GT 機身外觀就已經在中國工信部資料庫曝光,螢幕正面採用左上角採單挖孔的平面全螢幕設計,同時也採用光學螢幕下指紋辨識。在 realme GT 的機身背面下緣加入 GT 字樣。至於相機規格目前還無法得知,不過據過去傳聞將配備四鏡頭主相機設計。

▲圖片來源:TENAA

接下來在 3 月中旬前已經有多款新機即將陸續推出,目前可確認的依序為 2 月 22 日發表華為 Mate X2 、2 月 25 日發表紅米 Redmi K40 系列,以及 3 月 4 日將發表紅米 Redmi Note 10 系列和 realme GT 。另外,稍早 ROG 遊戲手機官方微博也確認新一代 ROG Phone 遊戲手機命名為 ROG Phone 5 ,在中國市場也是連續第三年和騰訊遊戲合作,預計也會在 3 月正式亮相。

消息來源:徐起Chase(微博)

延伸閱讀:
Redmi Note 10 系列確定將於 3/4 全球發表,相機與螢幕為此次升級重點

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

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

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

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

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

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

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

Google 雲端硬碟教育方案將不再享有免費無上限容量,2022 年 7 月生效

G Suite 教育版在過去因為提供無上限的空間容量受到許多教育單位的愛用,許多相關資源都能放在雲端共享,近日 Google 旗下的 G Suite 教育版更名為 Google Workspace for Education,緊接著來的就是跟隨整個 Google 雲端硬碟政策的腳步改變儲存空間規則而即將進行的大異動。

Google 雲端硬碟教育方案將不再享有免費無上限容量,2022 年 7 月生效

在去年 11 月時,Google 宣布相簿、文件等無限免費空間將在 2021 年 6 月後取消,現在連 Google 雲端硬碟教育版的規則也跟著動起來,在明年將失去無限儲存空間的福利。Google 表示,在新的雲端硬碟政策中推出了全新的空間模式,基本上為各級學校和大學提供 100TB 的集區儲存空間(也就是單位中所有人的共同空間,可存放帳戶中包括但不限於 Google 雲端硬碟、Gmail 與 Google 相簿的所有檔案),讓所有關聯用戶可以共同使用,這個新模式所提供的空間足以存放 1 億份文件、800 萬份簡報甚至是總時數達 40 萬小時的影片,也就是說如果你的學校單位有巨量資料在雲端硬碟中,超過 100TB 的部分可能要移至他處,或者付費購買後續擴充方案繼續使用。

這項新規定將適用於 Google Workspace for Education 所有版本用戶,對現有用戶的生效時間從 2022 年 7 月起,至於在 2022 年申請服務的新用戶也同樣適用該政策。因應新規則,Google 說明了整體的計算方式,用戶可以在規則生效後使用多少儲存空間主要取決於學校機構所使用的版本,並且進行計算:

  • Google Workspace for Education Fundamentals ( 舊稱 G Suite 教育版) 
    符合資格的機構可獲得至少 100 TB 的集區雲端儲存空間,供機構共同使用。所有檔案都會計入這個額度,包括 Google 雲端硬碟和相簿檔案以及 Gmail。

  • Google Workspace for Education Plus (舊稱 G Suite Enterprise 教育版)
    每購買一份 Google Workspace for Education Plus 授權,共用集區就會另外獲得 20 GB 的儲存空間。如果是繼續使用 G Suite Enterprise 教育版 (舊版) 授權的客戶,只要每購買一份付費員工授權,共用集區就會另外獲得 100 GB 的儲存空間。

    舉例來說:如果你的單位購買了 5000 份教育版授權,那你們總共擁有 100TB(基本空間) + (5000 份授權 x 100GB),總數為 600TB 可用空間。

如果你的單位有大量的資料儲存需求,Google 另外列出了建議的執行步驟,幫助用戶瞭解、規劃並整理可用空間,藉以釋出或獲得更多容量來存放資料,大家可前往官方說明中查看詳情並及早應變。

◎資料來源:Google

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

歷久彌新的慢時尚「萬秀的洗衣店」在 Today at Apple 分享穿搭美照創作心得

在崇尚「快時尚」的人眼裡,洗衣店可能是種不被需要的存在。因疫情而有更多時間與經營「萬秀的洗衣店」的阿公阿嬤相處的張瑞夫(Reef Chang),觀察到隨時間流逝,被遺忘的不僅是店中近十年未被領走的珍貴衣服,與一手拉拔自己長大親人的關係,乃至於阿公阿嬤對於自己專業職人的認同感,可能才是更被深埋的存在。

突然有了這樣感觸的 Reef,選擇將自己與阿公阿嬤加上老洗衣店所構築而成的慢時尚經典創作搬上社群平台。現在,他們希望將這種富含療癒生活溫度與惜物觀念的創作精神,傳遞給更多人一起尋找那被遺忘的美好。繼續閱讀歷久彌新的慢時尚「萬秀的洗衣店」在 Today at Apple 分享穿搭美照創作心得報導內文。

歷久彌新的慢時尚「萬秀的洗衣店」在 Today at Apple 分享穿搭美照創作心得

Reef 在今天「萬秀的洗衣店」於 Today at Apple 課程預覽中,分享了很多拍攝阿公阿嬤充滿「慢時尚感」的人像拍攝心得與教學 — 報名要快啊!推薦聽聽他的心得分享,跟作品一樣充滿療癒感與感動!

技巧的部分其實真的並不難 — 他會習慣以較為方正的構圖帶來更專業的感覺,所以會建議大家可以啟動 iPhone 拍攝設定內的「格線」功能,並適時啟動人像拍攝模式來營造更好的淺景深效果。

課程分享中,他常常利用 iPhone XR 開始提供的 1x 廣角人像模式來創作;新世代 iPhone 12 Pro 系列搭載的廣角人像「夜間」模式也讓 Reef 感到十分驚艷,即便是在洗衣店掛衣區的昏暗光線下,依然可以拍到運算式攝影加持後畫質足夠好的人像照片。

共感溝通,細膩觀察

Reef 分享的拍攝技巧真的不艱澀難懂,甚至來到 Pose 與穿搭的環節,他還認為很多時候根本要歸功於阿公阿嬤自己的發揮(是真的很可愛!)。但這中間的關鍵其實是在於與被攝者的溝通與引導,更與細膩的生活觀察脫離不了關係,才能拍攝出他心中追求「先感動自己,再讓別人感受生活中的溫暖」的「最基礎卻最真實」的影像。

▲圖:Reef 現場示範平常創作拍攝時的溝通與拍攝技巧。

之所以會在 Instagram 開始萬秀洗衣店的系列影像創作。是因為阿公阿嬤面對近在身邊卻有溝通隔閡的家人,淡淡說出的一句「我已經沒用了(台語)」。

這讓 Reef 驚覺養育自己長大、彼此之間曾經可以活潑共處的阿公阿嬤,好像隨著 70 多年歷史生意漸漸平淡的洗衣店一起失去活力。彷彿與掛在店深處近十年、洗得一塵不染卻沒人領回的衣物一樣,成了被遺忘的美好。

為了幫帶著自己長大的阿公阿嬤找回以往的活力,拾回身為職人的尊重感。Reef 認為自己應該嘗試扭轉以往兩個世代彼此間偏單向的溝通方式,改為嘗試向彼此解釋互相在意的事物,想要透過「共感」的方式為生活帶來改變。

以隨手可得的題材,像送洗近十年未取的老衣服,透過一起「玩」攝影的方式,讓萬秀洗衣店 WANT SHOW as young — 透過影像秀出八十多歲的萬吉與秀娥內心的那個時尚靈魂,不因為有年紀了就不做年輕時敢於實踐的事。

IG 上的「萬秀的洗衣店」的簡介,寫的是孫子不忍心看阿公阿嬤每天無聊發呆的產物。但從照片中看到的,其實更多是 Reef 對於阿公阿嬤與自己的生活溫暖瞬間的細膩觀察。Reef:「要做的,其實是用他們能懂得語言和他們對話,讓時代縮短距離。」

他會詢問像是「你以前是怎麼看小姐的?」這類有趣的問題,引導阿公與阿嬤擺出有趣的互動表情與姿勢。也懂得觀察他們平常的生活姿態,透過時尚的衣服穿搭、攝影構圖與適當的引導,拍攝出專業感十足但又充滿可愛細節的人像作品。更藉此有機會聽到更多以往沒有聽過,屬於他們倆的小故事。

▲圖右:翻出阿公 24 歲當兵時,兩人曬恩愛讓人閃瞎照片信物的阿嬤。

為了不無聊出發的「萬秀的洗衣店」,不僅成為連總統都 Feat. 的 IG 網紅。更大的重點在於,現在阿公阿嬤也更願意秀出彼此的愛,更樂於與家人分享過去的照片與穿搭,證明自己以前其實也是非常時尚的,甚至還會提早為 Reef 的拍攝構思企劃(!),提供接下來的創作靈感,這些都是很大的改變。

▲圖:現場示範衣服上的洗標,證明了他們的確曾經也是某些人的「故事」。不過 Reef 也強調,這些衣服真的是放了 8、9 年以上都未取(基本上把別人的店當衣櫃也不付錢了…XD),所以不用擔心洗衣店會沒事就拿客人送洗的衣服來拍照 — 倒是有人看到他們的新聞,才想到衣服放了一年還沒拿(咦)。

這些當初啟動「萬秀的洗衣店」創作拍攝時從沒想過的收穫。也讓 Reef 認為既然受到了這麼大的關注,似乎也應該藉此傳達更多理念。他們在嘖嘖上發起的「重新定衣計畫,建立洗衣店「被遺忘衣服」的循環平台」就是一個源自於更惜物、有慈善意義、更永續環保的計畫。

人類對於速度的追求永無止盡,暖心溫度的凝聚卻是永遠快不起來也勉強不來的存在。透過「萬秀的洗衣店」對於時尚詮釋引起的更多共感,也許可以成為你我利用手邊的創作工具,與親友有更深溝通交流的契機。

如果你喜歡他們的作品的話,可以報名 Today at Apple 課程學習如何利用手上的 iPhone,拍出充滿「萬秀的洗衣店」風格的溫暖照片。

          在 Instagram 查看這則貼文                      

萬秀的洗衣店|WANT SHOW as young(@wantshowasyoung)分享的貼文

          在 Instagram 查看這則貼文                      

萬秀的洗衣店|WANT SHOW as young(@wantshowasyoung)分享的貼文

          在 Instagram 查看這則貼文                      

萬秀的洗衣店|WANT SHOW as young(@wantshowasyoung)分享的貼文

          在 Instagram 查看這則貼文                      

萬秀的洗衣店|WANT SHOW as young(@wantshowasyoung)分享的貼文

更多「萬秀的洗衣店」作品,請至 Instagram 觀看。或動手開始自己拍出更時尚的親友吧!

延伸閱讀:

iPhone 12 Pro Max 深度實拍體驗:ProRAW 讓 iPhone 踏入專業

M1 MacBook Air 開箱體驗:最驚喜的不變

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

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

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

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

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

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

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

※回頭車貨運收費標準

iOS 14.5 Beta 2 暗示將推出 MagSafe 充電配件,磁吸無線充電行動電源即將來臨?

Apple 針對 iPhone 推出的聰穎電池護殼,一直是有著「原廠控」的果粉們選擇為 iPhone 補給電力會列入考慮的配件選項。但隨著 iPhone 12 系列支持 MagSafe 磁吸無線充電後,至今仍 Apple 就未推出聰穎電池護殼這樣內建電池的保護殼配件。然而,日前在針對開發者釋出的 iOS 14.5 Beta 2 預覽版本,除了新增多達 217 組的全新表情符號,從中似乎也找到些暗示未來即將推出對應 MagSafe 介面的行動電源配件。

▲圖片來源:Steve Moser (Twitter/ @SteveMoser)

iOS 14.5 Beta 2 暗示將推出「MagSafe 充電配件」,磁吸無線充電行動電源即將來臨?

▲圖片來源:MacRumors

據相關消息表示, Apple 將在未來 iOS 更新加入「行動充電模式(Mobile Charge Mode)」的全新電力補給方式,這預期能讓像是 iPhone 12 系列支持 MagSafe 的手機能讓電力維持在 90% 以上,以應付一整天的使用需求。
除了連接行動電源之外,過去若在外想讓 iPhone 隨時保持在充足電量,就得考慮「聰穎電池護殼」這類將行動電源設計與手機背蓋結合的充電保護殼配件。
然而這樣的配件補充電力相當方便,但也伴隨著需要隨時讓手機多了一個笨重、厚重的電池背在手機上,這也是除了價格考量外讓許多消費者卻步的原因。

▲圖片來源:Steve Moser (Twitter/ @SteveMoser)

不過,隨著 iPhone 12 系列在機身背面加入 MagSafe 介面,讓未來能透過支持 MagSafe 的行動電源或相關 MagSafe 無線充電配件,預期能直接透過無線充電方式補充電力。這項改變有別於過去聰穎電池護殼需要長時間都安裝在手機上,而是只在用戶需要為 iPhone 12 系列充電時再吸上即可。
而 MagSafe 無線充電帶來的另一項好處,則是手機只需要裸機或使用支持 MagSafe 的保護殼即可讓充電配件固定在最佳的充電位置,確保無線充電過程順暢。此外,除了 Apple 原廠的 MagSafe 配件,近期也陸續有更多第三方配件廠商陸續推出支持 Apple MFM (Made for MagSafe)認證的配件。
近期隨著傳聞 Apple 將在未來加入行動充電模式後,網路上也有許多人陸續合成 MagSafe 充電配件的概念圖:

▲圖片來源:Antonio Adrian Chavez (Twitter/ @antonio07341176)

此外,在 iOS 14.5 Beta 2 預覽版本中加入 217 組全新表情符號:

▲圖片來源:MacRumors

有趣的是耳機的表情符號也改為 Apple 自家的主動式降噪頭戴式耳機 AirPods Max :
▲圖片來源:MacRumors

消息來源:MacRumors|Steve Moser (Twitter/ @SteveMoser)

延伸閱讀:
傳聞 Apple 將於 3/16 舉行春季新品發表會,或將推出全新 iPad mini 和 AirTags 等新品

iPhone 12 mini DXOMARK 相機評測成績揭曉:總分 122 與 iPhone 12 同分

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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

iPhone 13 傳聞將搭載的 AOD 顯示,概念影片帶來多種可能性

日前傳聞 Apple 將在 iPhone 13 系列首次為 iPhone 帶來 AOD(Always-On Display)顯示的消息,而究竟 Apple 是會單純地「複製」 Android 手機在 AOD 顯示的方式,或延續 Apple Watch Series 5/Series 6 的方式讓螢幕在處於休眠狀態下能持續顯示豐富的內容,最近也有 YouTube 頻道就製作概念影片帶來多種可能方案。

▲圖片來源:A Better Computer (YouTube)

iPhone 13 傳聞將搭載的 AOD 顯示,概念影片帶來多種可能性

根據 EverythingApplePro 之前的爆料, iPhone 13 系列除了 iPhone 13 Pro 和 iPhone 13 Pro Max 螢幕將搭載 120Hz 螢幕更新率的 LTPO OLED 顯示器,同時也可能首次在 iPhone 加入 AOD 顯示。即便 Always On Display(AOD)息屏顯示在 Android 手機已經行之有年,不過目前在 Apple 旗下產品中也只有 Apple Watch Series 5 和 Apple Watch Series 6 有支援「永遠顯示」這項功能。
未來若真的在 iPhone 13 系列加入 AOD 顯示,即便用戶未抬起手機喚醒螢幕,時間顯示和其他資訊都能一目了然,用戶也能自定義 AOD 的顯示效果設定。

▲圖片來源:EverythingApplePro EAP(YouTube)

隨著 iPhone 13 搭載 AOD 顯示的傳聞曝光後,也有 YouTube 頻道「A Better Computer 」就製作了一段影片分析若 iPhone 真的搭載這項功能,會以哪幾種方式呈現的可能。
首先,如果最基本的可行方案就是比照目前有支持 AOD 功能的 Android 系統手機,在開啟這項功能後於螢幕休眠狀態下以單色方式顯示時間、天氣和有新通知的應用程式圖示。不過依照 Apple 過去的慣例,不太可能只單純使用這樣的方案。

▲圖片來源:A Better Computer (YouTube)

若參考目前已經有採用 AOD 顯示功能的 Apple Watch Series 5 、 Apple Watch Series 6 ,在螢幕顯示轉換為 AOD 顯示狀態時,依然保有基本的顯示內容,僅有亮度、對比度和色彩的有較明顯的差異,使用者依然能保有原本的使用體驗。

▲圖片來源:A Better Computer (YouTube)

不過如果也只是將 iOS 主畫面亮度、彩度、對比度都調低的顯示方式,考量自從 iOS 14 起加入桌面小工具讓內容更豐富的全新 iOS 主畫面,如果直接降低畫面亮度、彩度和對比度的做法,看來似乎也不太可行:

▲圖片來源:A Better Computer (YouTube)

倘若 Apple 以較保守的方式,可能會選擇將以深色顯示鎖定畫面桌布搭配基本時間、天氣顯示作為 AOD 顯示的畫面:

▲圖片來源:A Better Computer (YouTube)

也不排除將鎖定畫面下方的手電筒和相機快速啟動按鈕一併在 AOD 模式顯示:

▲圖片來源:A Better Computer (YouTube)

最終目前看來較有可能的方向則是在 iPhone 轉換為 AOD 顯示狀態時,除了以暗色畫面呈現保留基本的時間、天氣資訊外,在此模式下以深色模式呈現幾種自訂的 iOS 桌面小工具:

▲圖片來源:A Better Computer (YouTube)

相較於螢幕喚醒狀態,讓用戶在 AOD 模式下自訂有限、常用的桌面小工具,如此一來不僅能達到與 Android 系統之間的差異,也能將 iOS 小工具帶來更多應用的可能性:

▲圖片來源:A Better Computer (YouTube)

完整影片

 

消息來源:A Better Computer (YouTube)

延伸閱讀:
Jon Prosser 爆料全新 iPad Pro 與 AirTags 將於 3 月發表

小米11 Lite 最新概念渲染圖曝光!主打輕薄、平面挖孔螢幕的小米11 平價版

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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

【asp.net core 系列】5 布局頁和靜態資源

0. 前言

在之前的4篇的內容里,我們較為詳細的介紹了路由以及控制器還有視圖之間的關係。也就是說,系統如何從用戶的HTTP請求解析到控制器里,然後在控制器里處理數據,並返回給視圖,在視圖中显示出來。這一篇我將為大家介紹基礎的最後一部分,布局頁和靜態資源引入。

1. 布局頁

在控制器和視圖那一篇,我們了解到_ViewStart 里設置了一個Layout屬性的值,這個值正是用來設置布局頁的。所謂的布局頁,就是視圖的公用代碼。在實際開發中,布局頁通常存放我們為整個系統定義的頁面框架,視圖裡寫每個視圖的頁面。

回顧一下,默認的_ViewStart里的內容是:

@{
    Layout = "_Layout";
}

默認的布局頁指定的是名為_Layout的布局頁,在本系列第三篇中,我們得知這個視圖應當在Shared文件夾下,那我們進去看一下這個視圖有什麼內容:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - MvcWeb</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">MvcWeb</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2020 - MvcWeb - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
        </div>
    </footer>
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
    @RenderSection("Scripts", required: false)
</body>
</html>

這是默認的布局頁內容,看着挺多的,但是除了一些html代碼,裏面還有一些關鍵的地方需要注意。

1.1 RenderSection

RenderSection 分部渲染,在頁面中創建一個標記,表示這個頁面塊將在子視圖(或者是路由的實際渲染視圖)中添加內容。

來,我們看一下微軟官方給的註釋:

In layout pages, renders the content of the section named name.

意思就是在布局頁中,渲染名稱為name的分部內容。

新創建一個分佈頁,名稱為_Layout1

<html>
    <head>
        <title>Render 測試</title>
    </head>
    <body>
        @RenderSection("SectionDemo")
    </body>
</html>

這個布局頁里什麼都沒有,只有一個RenderSection。現在我們新建一個控制器:

using Microsoft.AspNetCore.Mvc;

namespace MvcWeb.Controllers
{
    public class RenderTestController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
    }
}

創建對應的視圖:

Views / RenderTest/Index.cshtml

先設置布局頁為_Layout1

@{
    Layout = "_Layout1";
}

先試試啟動應用,訪問:

http://localhost:5006/RenderTest/Index

正常情況下,你應該能看到這個頁面:

仔細看一下信息,意思是在 RenderTest/Index.cshtml 視圖中沒有找到 SectionDemo 的分部內容。

那麼,如何在視圖中設置分部內容呢?

@{
    Layout = "_Layout1";
}
@section SectionDemo{
    <h1>你好</h1>

}

使用 @section <Section 名稱> 後面跟一對大括號,在大括號中間的內容就是這個section(分部)的內容。

重啟應用,然後刷新頁面,你能看到這樣的頁面:

如果不做特殊要求的話,定義在布局頁中的分部塊,視圖必須實現。當然,RenderSection還有一個參數,可以用來設置分部不是必須的:

public HtmlString RenderSection(string name, bool required);

1.2 RenderBody

先看下微軟給的官方註釋:

In a Razor layout page, renders the portion of a content page that is not within a named section.

簡單講,如果在布局頁中設置了@RenderBody,那麼在使用了這個布局頁的視圖裡所有沒被分部塊包裹的代碼都會渲染到布局頁中聲明了@RenderBody的地方。

修改_Layout1.cshtml:

<html>
    <head>
        <title>Render 測試</title>
    </head>
    <body>
        <h1>RenderBody 測試 -之前</h1>
        @RenderBody()
        <h1>RenderBody 測試 -之後</h1>
    </body>
</html>

修改RenderTest/Index.cshtml

@{
    Layout = "_Layout1";
}

RenderBody測試
<h1>我是視圖的內容!</h1>

重啟應用,刷新剛剛訪問的頁面:

可以看出,RenderBody渲染的位置。

2. 靜態資源引入

通常情況下,靜態資源的引入與HTML引用js和css等資源是一致的,但是對於我們在編寫系統時自己創建的腳本和樣式表,asp.net core提供了不同的處理方式。那就是服務器端壓縮功能。

asp.net core 3.0 的mvc 默認項目是不啟動這個功能的,需要我們額外的開啟支持。

2.1 開啟支持

先引入 BuildBundleMinifier

cd MvcWeb # 切換目錄到MvcWeb項目下
dotnet add package BuildBundleMinifier

創建 bundleconfig.json

[
    {
        "outputFileName": "wwwroot/css/site.min.css",
        "inputFiles": [
            "wwwroot/css/site.css"
        ]
    },
  	{
        "outputFileName": "wwwroot/js/site.min.js",
        "inputFiles": [
          "wwwroot/js/site.js"
        ],
        "minify": {
          "enabled": true,
          "renameLocals": true
        },
        "sourceMap": false
      }
]

每個節點允許設置項:

  • outputFileName 生成的捆綁壓縮文件,通常路徑攜帶wwwroot
  • inputFiles 數組,包含要壓縮到此次輸出文件的文件路徑,會按照添加的順序依次加入
  • minify 輸出類型的縮小選項,可選。 默認是 enabled: true
  • sourceMap 表示是否為捆綁的文件生成源映射的標記
  • sourceMapRootPath 源映射文件的路徑

2.2 使用

正常情況下在布局頁中,把壓縮后的文件路徑引入即可。不過在開發中,通常按照以下方式引用:

<environment exclude="Development">
    <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
<environment include="Development">
    <link rel="stylesheet" href="~/css/site.css" />
</environment>

注: asp-append-version 表示在引用路徑追加一個版本號,這是針對html靜態資源緩存的問題的一個解決方案,這一步是由程序決定的。

environment表示環境,現在大家知道這個寫法就行,在接下來的篇幅會講。

3. 靜態資源目錄

我們知道到目前為止,我們的靜態資源都是在wwwroot目錄下。那麼我們是否可以修改或者添加別的目錄作為靜態資源目錄呢?

在Startup.cs文件內的Configure方法下有這樣一行代碼:

app.UseStaticFiles();

這行代碼的意思就是啟用靜態文件,程序自動從 wwwroot尋找資源。那麼,我們就可以從這個方法入手,設置我們自己的靜態資源:

public static IApplicationBuilder UseStaticFiles(this IApplicationBuilder app, StaticFileOptions options);

我們找到了這個方法的另一個重載版本,裏面有一個參數類:

public class StaticFileOptions : SharedOptionsBase
{
    public StaticFileOptions();
    public StaticFileOptions(SharedOptions sharedOptions);
    public IContentTypeProvider ContentTypeProvider { get; set; }
    public string DefaultContentType { get; set; }
    public HttpsCompressionMode HttpsCompression { get; set; }
    public Action<StaticFileResponseContext> OnPrepareResponse { get; set; }
    public bool ServeUnknownFileTypes { get; set; }
}

並沒有發現我們想要的,先別慌,它還有個父類。我們再去它的父類里看看:

public abstract class SharedOptionsBase
{
    protected SharedOptionsBase(SharedOptions sharedOptions);
    public IFileProvider FileProvider { get; set; }
    public PathString RequestPath { get; set; }
    protected SharedOptions SharedOptions { get; }
}

這下就比較明了了,需要我們提供一個文件提供器,那麼我們來找一個合適的IFileProvider實現類吧:

public class PhysicalFileProvider : IFileProvider, IDisposable

這個類可以滿足我們的要求,它位於命名空間:

namespace Microsoft.Extensions.FileProviders

那麼,添加一組我們自己的配置吧:

using Microsoft.Extensions.FileProviders;

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // 省略其他代碼,僅添加以下代碼
    app.UseStaticFiles(new StaticFileOptions
    {
        FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(),"OtherStatic")),
    });
}

在項目的根目錄創建名為OtherStatic的文件夾,然後在裏面創建個文件夾,例如: files,並在這個文件夾里隨便添加一個文件。

然後啟動應用訪問:

http://localhost:5006/files/<你添加的文件(包括後綴名)>

然後能在瀏覽器中看到這個文件被正確響應。

當然,這裏存在一個問題,如果在 OtherStatic中的文件在wwwroot也有相同目錄結構的文件存在,這樣訪問就會出現問題。這時候,可以為我們新加的這個配置設置一個請求前綴:

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(),"OtherStatic")),
    RequestPath = "/other"
});

重啟程序,然後訪問:

http://localhost:5006/other/files/<你添加的文件(包括後綴名)>

然後就能看到剛才響應的文件,重新訪問之前的路徑,發現瀏覽器提示404。

4. 總結

在這一篇,我們講解了布局頁的內容,靜態資源的壓縮綁定以及添加一個新的靜態資源目錄。通過這幾篇內容,讓我們對asp.net core mvc有了一個基本的認知。下一篇,我們將重新創建一個項目,並結合之前的內容,以實戰為背景,帶領大家完成一個功能完備的web系統。

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

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

【其他文章推薦】

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

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

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

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

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

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

[OpenGL](翻譯+補充)投影矩陣的推導

簡介

基本是翻譯和補充 http://www.songho.ca/opengl/gl_projectionmatrix.html

計算機显示器是一個2D的平面,一個3D的場景要被OpenGL渲染必須被投影到2D平面上以生成2D的圖像。在OpenGL中,GL_PROJECTION矩陣可以用來進行投影變換。首先,它將所有的頂點數據從相機坐標系(eye coordinates)轉換到裁剪坐標系(clip coordinates),然後通過除以裁剪空間坐標的w值,將裁剪空間坐標系轉換到歸一化設備坐標系(normalized device coordinates,NDC)

我們需要注意的一點就是,裁剪和NDC變換都通過GL_PROJECTION矩陣來完成。之後的文章,將會利用6個參數來構建投影矩陣,這六個參數是:left,right,bottom,top,near,far,分別為近裁剪面的左右下上邊界,近裁剪面,遠裁剪面。

視錐體剔除是在裁剪坐標下進行的,在轉換到NDC坐標系之前。已經變換到裁剪坐標系的坐標\(x_c,y_c,z_c\)會和\(w_c\)進行比較,如果裁剪坐標大於\(w_c\)或小於\(-w_c\),則頂點會被剔除,OpenGL會重建多邊形的邊。
ps.解釋一下為什麼要和\(w_c\)進行比較。因為NDC坐標的範圍是\([-1,1]\),而裁剪坐標和NDC坐標之間的關係是\(x_c/w_c = x_n\),所以\(x_c\)必須得在\([-w_c,w_c]\)之間才可見,其他兩個軸同理。不是在NDC坐標階段進行裁剪,是因為不可見的頂點,沒有必要在對其進行運算,會消耗資源。在作用完投影矩陣后,得到的是齊次坐標,OpenGL會自動除以\(w_c\),以得到笛卡爾坐標,OpenGL應該是在除以\(w_c\)之前進行視錐體剔除工作。

透視投影

在透視投影中,1個3D的點在一個像被切了一刀的金字塔的視錐體中,此時的坐標系是相機坐標系,這個坐標系會被映射正方體的NDC坐標系中。

  • \(x:[l,r]->[-1,1]\)
  • \(y:[b,t]->[-1,1]\)
  • \(z:[-n,-f]->[-1,1]\)

相機坐標系定義在右手坐標系,NDC是左手坐標系,所以相機朝着-Z的方向看去,而NDC朝着+Z的方向看去。因為glFrustum()裁剪面的參數必須為正數,所以在創建投影矩陣的時候,我們要對其進行去取反。
ps.glFrustum是opengl類庫中的函數,它是將當前矩陣與一個透視矩陣相乘,把當前矩陣轉變成透視矩陣,在使用它之前,通常會先調用glMatrixMode(GL_PROJECTION).
void glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble nearVal, GLdouble farVal),left,right指明相對於垂直平面的左右坐標位置,bottom,top指明相對於水平剪切面的下上位置,nearVal,farVal指明相對於深度剪切面的遠近的距離,兩個必須為正數。

在OpenGL中,1個3D的點將會被投影到近裁剪平面上,下圖展示了點\((x_e,y_e,z_e)\)如何投影到\((x_p,y_p,z_p)\)

在視錐體的頂視圖,我們可以利用相似三角形計算\(x_p\)的值

\[\frac{x_p}{x_e} = \frac{-n}{z_e}\\ x_p = \frac{-nx_e}{z_e}=\frac{nx_e}{-z_e} \]

同理,在側視圖中,利用相似三角形計算\(y_p\)的值

\[\frac{y_p}{y_e} = \frac{-n}{z_e}\\ y_p = \frac{-ny_e}{z_e}=\frac{ny_e}{-z_e} \]

我們觀察到\(x_p,y_p\)都依賴於\(z_e\),他們都除以\(z_e\),這是第一個線索,來幫助我們構建透視投影矩陣。當相機坐標系經過透視投影矩陣變換后,得到的是裁剪坐標系的齊次坐標,最後通過除以齊次坐標的\(w_c\),來得到NDC

\[\begin{bmatrix} x_c\\ y_c\\ z_c\\ w_c \end{bmatrix} =M_{projection} \begin{bmatrix} x_e\\ y_e\\ z_e\\ w_e \end{bmatrix} , \begin{bmatrix} x_n\\ y_n\\ z_n\\ \end{bmatrix} = \begin{bmatrix} x_c/w_c\\ y_c/w_c\\ z_c/w_c\\ \end{bmatrix} \]

因此我們可以設置\(w_c\)的值為\(-z_e\),現在投影矩陣看起來是

\[\begin{bmatrix} x_c\\ y_c\\ z_c\\ w_c \end{bmatrix} = \begin{bmatrix} .&.&.&.\\ .&.&.&.\\ .&.&.&.\\ 0&0&-1&0\\ \end{bmatrix} \begin{bmatrix} x_e\\ y_e\\ z_e\\ w_e \end{bmatrix} \]

接着,我們需要將\(x_p,y_p\)映射到\(x_n,y_n\)\([l,r]->[-1,1],[b,t]->[-1,1]\)
相當於是給定l,我要得到-1,給定r,我要得到1,這不就是給定二維平面上的兩個點,求其直線方程的問題。

\[令x_n = kx_p+\beta,求其斜率為\frac{1-(-1)}{r-l}=\frac{2}{r-l}\\ 帶入點(r,1),1 = \frac{2r}{r-l}+\beta\\ 化簡求得\beta=-\frac{r+l}{r-l}\\ 最終得x_n = \frac{2x_p}{r-l}-\frac{r+l}{r-l} \]

\[令y_n = ky_p+\beta,求其斜率為\frac{1-(-1)}{t-b}=\frac{2}{t-b}\\ 帶入點(t,1),1 = \frac{2t}{t-b}+\beta\\ 化簡求得\beta=-\frac{t+b}{t-b}\\ 最終得y_n = \frac{2y_p}{t-b}-\frac{t+b}{t-b} \]

現在有了從\(x_e,y_e\)\(x_p,y_p\)和從\(x_p,y_p\)\(x_n,y_n\),現在聯立一下就可以得到從\(x_e,y_e\)\(x_n,y_n\)的關係表達式。

\[x_n = \frac{2x_p}{r-l}-\frac{r+l}{r-l}\\ x_p = \frac{-nx_e}{z_e}=\frac{nx_e}{-z_e}\\ 最終可以化簡為(\underbrace{\frac{2n}{r-l}x_e+\frac{r+l}{r-l}z_e}_{x_c})/-z_e \]

同理

\[y_n = \frac{2y_p}{t-b}-\frac{t+b}{t-b}\\ y_p = \frac{-ny_e}{z_e}=\frac{ny_e}{-z_e}\\ 最終可以化簡為(\underbrace{\frac{2n}{t-b}y_e+\frac{t+b}{t-b}z_e}_{y_c})/-z_e \]

現在我們的透視矩陣現在是這個樣子

\[\begin{bmatrix} x_c\\ y_c\\ z_c\\ w_c \end{bmatrix} = \begin{bmatrix} \frac{2n}{r-l}&0&\frac{r+l}{r-l}&0\\ 0&\frac{2n}{t-b}&\frac{t+b}{t-b}&0\\ .&.&.&.\\ 0&0&-1&0\\ \end{bmatrix} \begin{bmatrix} x_e\\ y_e\\ z_e\\ w_e \end{bmatrix} \]

現在還剩下矩陣的第三行。\(z_n\)和其他兩個軸的坐標稍有不同,因為\(z_e\)總是投影到-n的近裁剪面,但是我們需要不同的z值來進行裁剪和深度測試,另外我們應該可以進行逆操作(逆變換)。因為我們知道z的值不依賴於x,y,我們借用w的值來尋找\(z_n,z_e\)之間的關係,因此我們指定第三行矩陣為

\[\begin{bmatrix} x_c\\ y_c\\ z_c\\ w_c \end{bmatrix} = \begin{bmatrix} \frac{2n}{r-l}&0&\frac{r+l}{r-l}&0\\ 0&\frac{2n}{t-b}&\frac{t+b}{t-b}&0\\ 0&0&A&B\\ 0&0&-1&0\\ \end{bmatrix} \begin{bmatrix} x_e\\ y_e\\ z_e\\ w_e \end{bmatrix} \]

\[z_n = z_c/w_c = \frac{Az_e+Bw_e}{-z_e} \]

在相機坐標系中,\(w_e\)的值是1,因此有\(z_n = \frac{Az_e+B}{-z_e}\),為了獲得A和B的值,我們使用\((z_e,z_n)\)的關係,\((-n,-1),(-f,1)\),然後將他們代入表達式。

\[\frac{-An+B}{n}=-1\\ \frac{-Af+B}{f}=1 \]

聯立,這是一個簡單二元一次方程組,容易求得

\[A = -\frac{f+n}{f-n}\\ B = -\frac{2fn}{f-n} \]

所以最終得到

\[z_n = \frac{-\frac{f+n}{f-n}z_e–\frac{2fn}{f-n}}{-z_e} \]

最終整個投影矩陣的表達式為

\[\begin{bmatrix} x_c\\ y_c\\ z_c\\ w_c \end{bmatrix} = \begin{bmatrix} \frac{2n}{r-l}&0&\frac{r+l}{r-l}&0\\ 0&\frac{2n}{t-b}&\frac{t+b}{t-b}&0\\ 0&0&-\frac{f+n}{f-n}&-\frac{2fn}{f-n}\\ 0&0&-1&0\\ \end{bmatrix} \begin{bmatrix} x_e\\ y_e\\ z_e\\ w_e \end{bmatrix} \]

這個投影矩陣是一般的視錐體,如果是對稱的話,有\(r=-l,t=-b\),那麼有

\[r+l=0,r-l=2r(width)\\ t+b=0,t-b=2t(height) \]

最後矩陣可以簡單的化為

\[\begin{bmatrix} \frac{n}{r}&0&0&0\\ 0&\frac{n}{t}&0&0\\ 0&0&-\frac{f+n}{f-n}&-\frac{2fn}{f-n}\\ 0&0&-1&0\\ \end{bmatrix} \]

注意觀察\(z_e,z_n\)的關係式,這是一個非線性的反比例函數,這意味着,在近裁剪平面的是很好,精度很高,而在遠裁剪面的時候,精度很低。當\([-n,-f]\)很大時,可能導致深度精度問題(z-fighting),一個較小的\(z_e\)的變化,在遠裁剪面可能不會影響\(z_n\)的值,n和f之間的距離應該短一些,從而最小化這個問題。
ps.因為浮點數會存在精度問題,畢竟計算機的存儲是離散的。

正交投影

正交投影的要比透視投影簡單許多,\(x_e,y_e,z_e\)相機坐標系將會線性映射到NDC坐標系。我們僅需要將長方體變為正方體,然後移動至原點。

\[x_n = \frac{1-(-1)}{r-l}x_e+\beta\\ 代入(r,1),最終可得\\ x_n = \frac{2}{r-l}x_e-\frac{r+l}{r-l} \]

同理

\[y_n = \frac{1-(-1)}{t-b}y_e+\beta\\ 代入(t,1),最終可得\\ y_n = \frac{2}{t-b}y_e-\frac{t+b}{t-b} \]

同理

\[z_n = \frac{1-(-1)}{-f-(-n)}z_e+\beta\\ 代入(-f,1),最終可得\\ z_n = \frac{-2}{f-n}z_e-\frac{f+n}{f-n} \]

因為w的值在正交投影中不必要,所以我們設置為1,因此正交投影矩陣為

\[ \begin{bmatrix} \frac{2}{r-l}&0&0&-\frac{r+l}{r-l}\\ 0&\frac{2}{t-b}&0&-\frac{t+b}{t-b}\\ 0&0&-\frac{2}{f-n}&-\frac{f+n}{f-n}\\ 0&0&0&1\\ \end{bmatrix} \]

同透視投影一樣,如果是對稱的話,那麼就可以矩陣就可以變簡單

\[ \begin{bmatrix} \frac{1}{r}&0&0&0\\ 0&\frac{1}{t}&0&0\\ 0&0&-\frac{2}{f-n}&-\frac{f+n}{f-n}\\ 0&0&0&1\\ \end{bmatrix} \]

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

【其他文章推薦】

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

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

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

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

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

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

08_提升方法_AdaBoost算法

  今天是2020年2月24日星期一。一個又一個意外因素串連起2020這不平凡的一年,多麼希望時間能夠倒退。曾經覺得電視上科比的畫面多麼熟悉,現在全成了陌生和追憶。

GitHub:https://github.com/wangzycloud/statistical-learning-method

提升方法

引入

  提升方法是一種常用的統計學習方法,還是比較容易理解的。在分類問題中,通過改變訓練樣本的權重,學習多個分類器,並將這些分類器進行線性組合,從而提高分類的性能。其實說白了,就是一個人干不好的活,我讓兩個人干;兩個人干不好,那就三個人四個人都來干。但是人多了不能像三個和尚那樣沒水喝,都不幹活。包工頭要採取一些措施,措施一:一個人幹活的時候,哪裡乾的不好?就讓第二個人補充在這個地方;兩個人幹活的時候,哪裡乾的不好?就讓第三個人補充在這個地方。措施二:這三四個人在同一個地方幹活,怎樣確定這個活的結果乾的好不好呢?有的人幹活細緻認真,自然對結果增益大;幹活粗糙的,對結果增益不多,因此需要有一種組合策略進行判斷。通過幾個人共同努力,就解決了一個人干不好的事情。

  接下來的內容,將按照書中順序,先介紹提升方法的思路和代表性的提升算法AdaBoost;再從前向分步加法模型角度解釋AdaBoost;最後看一個具體實例—提升樹。

提升方法AdaBoost算法

  提升方法基於這樣一種思想:對於一個複雜的任務來說,將多個專家的判斷進行適當的綜合所得出的判斷,要比其中任何一個專家單獨的判斷好。實際上,就是“三個臭皮匠頂個諸葛亮“的道理。書中上來提到了兩個稍晦澀的概念“強可學習”、“弱可學習”,下文沒有繼續闡述這兩個概念,這裏簡單的複述一下。在概率近似正確學習的框架中(不懂),一個概念,如果存在一個多項式的學習算法能夠學習它,並且正確率很高,那麼就稱這個概念是強可學習的;一個概念,如果存在一個多項式的學習算法能夠學習它,學習的正確率僅比隨機猜測略好,那麼就稱這個概念是弱可學習的。這兩個概念之間的相互關係,被證明是等價的,也就是說在一定的學習框架下,一個概念是強可學習的充分必要條件是這個概念是弱可學習的。(模型效果好==》強可學習;模型效果差==》弱可學習;兩者有聯繫)

  這樣一來,在學習的過程中如果已經發現了“弱學習算法”,那麼能否將它提升為“強學習算法”。我們知道,發現弱學習算法通常要比發現強學習算法容易得多,如何具體實施提升,是我們開發提升方法時所要解決的問題。

  對於分類問題而言,給定一個訓練數據樣本集,求比較粗糙的分類規則(弱分類器)要比求精確的分類規則容易得多。提升方法就是從弱學習算法出發,反覆學習,得到一系列弱分類器(又稱為基本分類器),然後組合這些弱分類器,構成一個強分類器。並且大多數的提升方法都是改變訓練數據的概率分佈(訓練數據的權值分佈),針對不同的訓練數據分佈調用弱學習算法學習一系列弱分類器。可以理解為難分的數據加大權重,讓下一個弱分類器重點學習該數據。

  這樣,對於提升方法來說,有兩個問題需要解決:一是在每一輪如何改變訓練數據的權值或概率分佈;二是如何將弱分類器組合成一個強分類器。我們接下來要學習的AdaBoost算法,針對第一個問題,提高那些被前一輪分類器錯誤分類樣本的權值,而降低那些被正確分類樣本的權值。這樣一來,那些沒有得到正確分類的數據,由於其權重的加大而受到后一輪弱分類器的更多關注。於是,分類問題被一系列的弱分類器“分而治之”。針對第二個問題,AdaBoost採取加權多數表決的方法。具體的,加大分類誤差率小的弱分類器的權值,使其在表決中起較大的作用;減小分類誤差率大的弱分類器的權值,使其在表決中起到較小的作用。

AdaBoost算法

  這裏先簡單的羅列AdaBoost算法過程,先趕完整體進度,後期編寫代碼時再整理細節。

  假設給定一個二類分類的訓練數據集

AdaBoost算法各個步驟的說明:

  步驟(1)假設訓練數據集具有均勻的權值分佈,即每個訓練樣本在基本分類器的學習中作用相同,這一假設保證第一步能夠在原始數據上學習基本分類器G1(x)。

  步驟(2)AdaBoost反覆學習基本分類器,在每一輪m=1,2,…,M順次執行四步操作:

  第a步,使用當前分佈Dm加權的訓練數據集,學習基本分類器Gm(x);

  第b步,計算基本分類器Gm(x)在加權訓練數據集上的分類誤差率。實際上,第m個分類器的分類誤差率,就是被分類器Gm(x)誤分類樣本的權值之和,這表明權重大的樣本起的作用更大一些。如果將其誤分類,則誤差率大大增加,從而進一步影響Gm(x)的係數;

  第c步,從公式8.8中,可以看出em≤1/2時,αm≥0,並且αm隨着em的減小而增大,所以分類誤差率越小的基本分類器在最終分類器中的作用越大;

  第d步,更新訓練數據的權值分佈為下一輪做準備。可以看到,誤分類樣本在下一輪學習中起更大的作用,不改變所給的訓練數據,而不斷改變訓練數據權值的分佈,使得訓練數據在基本分類器的學習中起不同的作用。

  步驟(3)線性組合f(x)實現M個基本分類器的加權表決。係數αm表示了基本分類器Gm(x)的重要性,這裏所有αm之和並不為1。f(x)的符號決定實例x的類,f(x)的絕對值表示分類的確信度。

AdaBoost的例子

AdaBoost算法的訓練誤差分析

  該訓練誤差分析部分,用證明出來的定理形式,表明AdaBoost算法的最基本性質就是能在學習過程中不斷減少訓練誤差。也就是“該算法能夠降低在訓練數據集的分類誤差率”這件事用數學方式證明了。分別是AdaBoost訓練誤差上界定理8.1、二分類問題的AdaBoost訓練誤差上界定理8.2和推論8.1。

  該定理說明,可以在每一輪選取適當的Gm使得Zm最小,從而使訓練誤差下降的最快。對於二分類問題,有定理8.2。

  該推論表明,在此條件下,AdaBoost的訓練誤差是以指數速率下降的(看着像,不懂)。

AdaBoost算法的解釋

  本節內容從另外一個已經驗證的模型來分析AdaBoost算法,並得到該算法的另外解釋(我的理解是:AdaBoost算法是一般化的加法模型的特例,現在要從加法模型的角度分析)。可以認為AdaBoost算法是模型為加法模型、損失函數為指數函數、學習算法為前向分佈算法時的二類分類學習方法。接下來的內容,先對加法模型及前向分佈算法做簡單介紹,通過對損失函數的分析,得到與AdaBoost算法等價的參數表示。

  考慮加法模型,b(x)函數相當於基分類器:

  在給定訓練數據及損失函數L(y,f(x))的條件下,學習加法模型f(x)成為經驗風險極小化及損失函數極小化問題:

  一般來說,加法模型f(x)中的M次基函數相加模型是一個複雜的優化問題,利用前向分佈算法可以求解該優化問題。前向分佈算法的思想是:因為學習的是加法模型,如果能夠從前向後,每一步只學習一個基函數及其係數,逐步逼近優化目標8.14,那麼就可以簡化優化的複雜度。具體的,每一步只需優化以下損失函數(相當於每一步優化一個基函數,優化一個前進一個):

  那麼,前向分佈算法如下:

  本來公式8.14中的同時求解從m=1到M所有參數β、γ的優化問題,通過前向分佈算法簡化成了逐次求解各個β、γ的優化問題。

  那麼,前向分佈算法與AdaBoost算法有什麼聯繫呢?我們可以用定理的形式敘述之間的聯繫,也就是我們可以由前向分佈算法推導出AdaBoost。定理如下:

  以上內容直接截圖了。本是作為筆記進行整理,數學推導插不上解釋的嘴。要是以後文章被看到的多了,手推一下,再把內容整理上來。

提升樹

  我們知道,提升方法實際上是採用加法模型(即基函數的線性組合)與前向分步算法。本節提升樹是以決策樹作為基函數的提升方法,對分類問題決策樹是二叉分類樹,對回歸問題決策樹是二叉回歸樹。提升樹被認為是統計學習中性能最好的方法之一。

  在例8.1中看到的基本分類器x<v或x>v,可以看作是一個根節點直接連接兩個恭弘=叶 恭弘節點的簡單決策樹,即所謂的決策樹樁。提升樹模型可以表示為決策樹的加法模型:

  即使數據中的輸入與輸出之間的關係很複雜,樹的線性組合都可以很好地擬合訓練數據。分類問題與回歸問題的區別主要在於使用的損失函數不同,回歸問題中一般使用平方誤差損失函數,分類問題一般使用指數損失函數。對於二分類問題,提升樹算法只需要將AdaBoost算法8.1中的基本分類器限製為二類分類即可。下面看一下用於回歸問題的提升樹:

  R是當前模型擬合數據的殘差,所以對回歸問題的提升樹算法來說,只需要簡單的擬合當前模型的殘差即可。具體算法如下:

  具體例子8.2:

  算法8.1代碼已上傳至github,先理解決策樹章節的算法5.5,該提升樹會非常好理解~

代碼效果

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

RocketMQ系列(二)環境搭建

RocketMQ的基本概念在上一篇中給大家介紹了,這一節將給大家介紹環境搭建。RocketMQ中最基礎的就是NameServer,我們先來看看它是怎麼搭建的。

NameServer

RocketMQ要求的環境是JDK8以上,我們先檢查一下環境,

[root@centOS-1 ~]# java -version
openjdk version "11.0.3" 2019-04-16 LTS
OpenJDK Runtime Environment 18.9 (build 11.0.3+7-LTS)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.3+7-LTS, mixed mode, sharing)

我的這個機器並沒有刻意的安裝JDK,而是系統自帶的OpenJDK 11,這應該也是沒有問題的。然後我們從RocketMQ官網下載最新的安裝包,並且上傳到/opt目錄下,

[root@centOS-1 opt]# ll
-rw-r--r--.  1 root  root 13838456 6月   3 08:49 rocketmq-all-4.7.0-bin-release.zip

然後我們解壓這個zip包,

[root@centOS-1 opt]# unzip rocketmq-all-4.7.0-bin-release.zip

這裏使用的是unzip命令,如果你的機器里沒有這個命令,可以使用yum install安裝一個。解壓以後,進入到RocketMQ的主目錄,並且啟動一下NameServer。

[root@centOS-1 opt]# cd rocketmq-all-4.7.0-bin-release
[root@centOS-1 rocketmq-all-4.7.0-bin-release]# ./bin/mqnamesrv
OpenJDK 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release.
Unrecognized VM option 'UseCMSCompactAtFullCollection'
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

這裏出了一個錯誤Error: Could not create the Java Virtual Machine,這是由於RocketMQ的啟動文件都是按照JDK8配置的,而我們這裏使用的是OpenJDK11,有很多命令參數不支持導致的,如果小夥伴們使用的是JDK8,正常啟動是沒有問題的。

在這裏我們改一下RocketMQ的啟動文件,

[root@centOS-1 rocketmq-all-4.7.0-bin-release]# vim bin/runserver.sh 
export JAVA_HOME
export JAVA="$JAVA_HOME/bin/java"
export BASE_DIR=$(dirname $0)/..
#在CLASSPATH中添加RocketMQ的lib目錄
#export CLASSPATH=.:${BASE_DIR}/conf:${CLASSPATH}
export CLASSPATH=.:${BASE_DIR}/lib/*:${BASE_DIR}/conf:${CLASSPATH}

修改的地方我們增加了註釋,在ClassPath里添加了lib目錄,然後在這個文件的末尾,註釋掉升級JDK后不支持的幾個參數,

JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
#JAVA_OPT="${JAVA_OPT} -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8  -XX:-UseParNewGC"
JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log -XX:+PrintGCDetails"
#JAVA_OPT="${JAVA_OPT} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m"
JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow"
JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages"
#JAVA_OPT="${JAVA_OPT} -Djava.ext.dirs=${JAVA_HOME}/jre/lib/ext:${BASE_DIR}/lib"
#JAVA_OPT="${JAVA_OPT} -Xdebug -Xrunjdwp:transport=dt_socket,address=9555,server=y,suspend=n"
JAVA_OPT="${JAVA_OPT} ${JAVA_OPT_EXT}"
JAVA_OPT="${JAVA_OPT} -cp ${CLASSPATH}"

好了,修改完以後,我們保存退出,再次啟動,這次我們在後台啟動NameServer,

[root@centOS-1 rocketmq-all-4.7.0-bin-release]# nohup ./bin/mqnamesrv &

[root@centOS-1 rocketmq-all-4.7.0-bin-release]# tail -500f ~/logs/rocketmqlogs/namesrv.log 

然後查看一下日誌,在日誌中看到main - The Name Server boot success. serializeType=JSON,說明NameServer啟動成功了。

單點的NameServer肯定是不能滿足我們的要求的,怎麼也要做個集群吧。NameServer是一個無狀態的服務,節點之間沒有任何數據往來,所以NameServer的集群搭建不需要任何的配置,只需要啟動多個NameServer服務就可以了,它不像Zookeeper集群搭建那樣,需要配置各個節點。在這裏我們就啟動3個NameServer節點吧,對應我們的3台機器,192.168.73.130,192.168.73.131,192.168.73.132

Broker

NameServer集群搭建完成,下面就搭建Broker了,Broker呢,我們要搭建一個兩主兩從結構的,主從之間異步備份,保存磁盤也是使用異步的方式。如果你對主從同步和保存磁盤的方式還不了解,看看上一節的內容吧。異步兩主兩從這種結構的配置,在RocketMQ中已經有例子了,我們先一下配置文件。

[root@centOS-1 rocketmq-all-4.7.0-bin-release]# vim conf/2m-2s-async/broker-a.properties 

這個配置文件是broker-a“主”的配置文件,

brokerClusterName=RocketMQ-Cluster
brokerName=broker-a
brokerId=0
deleteWhen=04
fileReservedTime=48
brokerRole=ASYNC_MASTER
flushDiskType=ASYNC_FLUSH

其中,

  • brokerClusterName是MQ集群的名稱,我們改為RocketMQ-Cluster。
  • brokerName是隊列的名字,配置為broker-a。
  • brokerId是隊列的id,0代表是“主”,其他正整數代表着“從”。
  • deleteWhen=04 代表着commitLog過期了,就會被刪除。
  • fileReservedTime是commitLog的過期時間,單位是小時,這裏配置的是48小時。
  • brokerRole,隊列的角色,ASYNC_MASTER是異步主。
  • flushDiskType,保存磁盤的方式,異步保存。

再看看broker-a的從配置,

brokerClusterName=RocketMQ-Cluster
brokerName=broker-a
brokerId=1
deleteWhen=04
fileReservedTime=48
brokerRole=SLAVE
flushDiskType=ASYNC_FLUSH

其中,集群的名字一樣,隊列的名字一樣,只是brokerId和brokerRole不一樣,這裏的配置代表着它是隊列broker-a的“從”。broker-b的配置和broker-a是一樣的,只是brokerName不一樣而已,在這裏就不貼出來了。

兩主兩從的配置文件都已經配置好了,我們來規劃一下,我們的NameServer是3台192.168.73.130,192.168.73.131,192.168.73.132,broker按照如下部署:

  • broker-a(主):192.168.73.130
  • broker-a(從):192.168.73.131
  • broker-b(主):192.168.73.131
  • broker-b(從):192.168.73.130

接下來,我們啟動broker,在192.168.73.130上啟動 broker-a(主)和broker-b(從)。和NameServer一樣,我們需要修改一下啟動的腳本,否則也會報錯誤。我們修改的是runbroker.sh這個文件,修改的內容和前面是一樣的,這裏就不贅述了。在啟動文件中,內存大小配置的是8g,如果機器的內存不夠,可以適當減少一下內存。

這裏還要做個說明,由於我們在一台機器上啟動了兩個broker實例,監聽端口和日誌存儲的路徑都會有衝突。那麼我們在192.168.73.130的broker-b(從)的配置文件中,增加配置,如下:

brokerClusterName=RocketMQ-Cluster
brokerName=broker-b
brokerId=1
deleteWhen=04
fileReservedTime=48
brokerRole=SLAVE
flushDiskType=ASYNC_FLUSH

listenPort=11911
storePathRootDir=~/store-b                       

broker-b(從)的端口改為11911,區別默認的10911;storePathRootDir改為~/store-b,區分默認的~/store

同樣在192.168.73.131的broker-a(從)也要做修改,如下:

brokerClusterName=RocketMQ-Cluster
brokerName=broker-a
brokerId=1
deleteWhen=04
fileReservedTime=48
brokerRole=SLAVE
flushDiskType=ASYNC_FLUSH

listenPort=11911
storePathRootDir=~/store-a

然後,我們在192.168.73.130上啟動,如下,

nohup ./bin/mqbroker -c conf/2m-2s-async/broker-a.properties -n '192.168.73.130:9876;192.168.73.131:9876;192.168.73.132:9876' &

nohup ./bin/mqbroker -c conf/2m-2s-async/broker-b-s.properties -n '192.168.73.130:9876;192.168.73.131:9876;192.168.73.132:9876' &

  • -c 指定的是配置文件,分別指定的是broker-a(主)和broker-b(從)。
  • -n 指定的是NameServer的地址,指定了3個,用,隔開。

再在192.168.73.131上啟動,如下,

nohup ./bin/mqbroker -c conf/2m-2s-async/broker-b.properties -n '192.168.73.130:9876;192.168.73.131:9876;192.168.73.132:9876' &

nohup ./bin/mqbroker -c conf/2m-2s-async/broker-a-s.properties -n '192.168.73.130:9876;192.168.73.131:9876;192.168.73.132:9876' &

好,如果沒有出現錯誤,到這裏,集群就搭建成功了。這裏邊有個小坑,大家一定要注意,就是-n後面的地址一定要用”括起來,並且地址之間要用;,否則,我們在查看集群列表時,是看不到的。

mqadmin

集群已經搭建好了,我們可以查看一下集群的狀態,查看集群的狀態,我們可以使用mqadmin,命令如下:

./bin/mqadmin clusterlist -n '192.168.73.130:9876;192.168.73.131:9876;192.168.73.132:9876'
  • clusterlist 是查看集群的命令
  • -n 後面是NameServer的地址,注意這裏也要用”括起來,並且地址之間要用;隔開

執行結果如下:

#Cluster Name     #Broker Name            #BID  #Addr                  #Version                #InTPS(LOAD)       #OutTPS(LOAD) #PCWait(ms) #Hour #SPACE
RocketMQ-Cluster  broker-a                0     192.168.73.130:10911   V4_7_0                   0.00(0,0ms)         0.00(0,0ms)          0 442039.47 -1.0000
RocketMQ-Cluster  broker-a                1     192.168.73.131:11911   V4_7_0                   0.00(0,0ms)         0.00(0,0ms)          0 442039.47 0.2956
RocketMQ-Cluster  broker-b                0     192.168.73.131:10911   V4_7_0                   0.00(0,0ms)         0.00(0,0ms)          0 442039.47 0.2956
RocketMQ-Cluster  broker-b                1     192.168.73.130:11911   V4_7_0                   0.00(0,0ms)         0.00(0,0ms)          0 442039.47 -1.0000

我們可以看到在這個NameServer中心中,只有一個broker集群RocketMQ-Cluster,有兩個broker,broker-abroker-b,而且每一個broker都有主從,broker的ip我們也可以看到。

好了~ 到這裏RocketMQ的集群就搭建好了,有問題評論區留言哦~~

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

【其他文章推薦】

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

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

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

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

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

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

※回頭車貨運收費標準

不知道怎麼提高代碼質量?來看看這幾種設計模式吧!

提高代碼質量的目的

程序猿的本職工作就是寫代碼,寫出高質量的代碼應該是我們的追求和對自己的要求,因為:

  1. 高質量的代碼往往意味着更少的BUG,更好的模塊化,是我們擴展性,復用性的基礎
  2. 高質量的代碼也意味着更好的書寫,更好的命名,有利於我們的維護

什麼代碼算好的質量

怎樣來定義代碼質量的”好”,業界有很多標準,本文認為好的代碼應該有以下特點:

  1. 代碼整潔,比如縮進之類的,現在有很多工具可以自動解決這個問題,比如eslint。
  2. 結構規整,沒有漫長的結構,函數拆分合理,不會來一個幾千行的函數,也不會有幾十個if...else。這要求寫代碼的人有一些優化的經驗,本文會介紹幾種模式來優化這些情況。
  3. 閱讀起來好理解,不會出現一堆a,b,c這種命名,而是應該盡量語義化,變量名和函數名都盡量有意義,最好是代碼即註釋,讓別人看你的代碼就知道你在幹嘛。

本文介紹的設計模式主要有策略/狀態模式外觀模式迭代器模式備忘錄模式

策略/狀態模式

策略模式基本結構

假如我們需要做一個計算器,需要支持加減乘除,為了判斷用戶具體需要進行哪個操作,我們需要4個if...else來進行判斷,如果支持更多操作,那if...else會更長,不利於閱讀,看着也不優雅。所以我們可以用策略模式優化如下:

function calculator(type, a, b) {
  const strategy = {
    add: function(a, b) {
      return a + b;
    },
    minus: function(a, b) {
      return a - b;
    },
    division: function(a, b) {
      return a / b;
    },
    times: function(a, b) {
      return a * b;
    }
  }
  
  return strategy[type](a, b);
}

// 使用時
calculator('add', 1, 1);

上述代碼我們用一個對象取代了多個if...else,我們需要的操作都對應這個對象裏面的一個屬性,這個屬性名字對應我們傳入的type,我們直接用這個屬性名字就可以獲取對應的操作。

狀態模式基本結構

狀態模式和策略模式很像,也是有一個對象存儲一些策略,但是還有一個變量來存儲當前的狀態,我們根據當前狀態來獲取具體的操作:

function stateFactor(state) {
  const stateObj = {
    status: '',
    state: {
      state1: function(){},
      state2: function(){},
    },
    run: function() {
      return this.state[this.status];
    }
  }
  
  stateObj.status = state;
  return stateObj;
}

// 使用時
stateFactor('state1').run();

if...else其實是根據不同的條件來改變代碼的行為,而策略模式和狀態模式都可以根據傳入的策略或者狀態的不同來改變行為,所有我們可以用這兩種模式來替代if...else

實例:訪問權限

這個例子的需求是我們的頁面需要根據不同的角色來渲染不同的內容,如果我們用if...else寫就是這樣:

// 有三個模塊需要显示,不同角色看到的模塊應該不同
function showPart1() {}
function showPart2() {}
function showPart3() {}

// 獲取當前用戶的角色,然後決定显示哪些部分
axios.get('xxx').then((role) => {
  if(role === 'boss'){
    showPart1();
    showPart2();
    showPart3();
  } else if(role === 'manager') {
    showPart1();
    showPart2();
  } else if(role === 'staff') {
    showPart3();
  }
});

上述代碼中我們通過API請求獲得了當前用戶的角色,然後一堆if...else去判斷應該显示哪些模塊,如果角色很多,這裏的if...else就可能很長,我們可以嘗試用狀態模式優化下:

// 先把各種角色都包裝到一個ShowController類裏面
function ShowController() {
  this.role = '';
  this.roleMap = {
    boss: function() {
      showPart1();
      showPart2();
      showPart3();
    },
    manager: function() {
      showPart1();
    	showPart2();
    },
    staff: function() {
      showPart3();
    }
  }
}

// ShowController上添加一個實例方法show,用來根據角色展示不同的內容
ShowController.prototype.show = function() {
  axios.get('xxx').then((role) => {
    this.role = role;
    this.roleMap[this.role]();
  });
}

// 使用時
new ShowController().show();

上述代碼我們通過一個狀態模式改寫了訪問權限模塊,去掉了if...else,而且不同角色的展示都封裝到了roleMap裏面,後面要增加或者減少都會方便很多。

實例:複合運動

這個例子的需求是我們現在有一個小球,我們需要控制他移動,他移動的方向可以是上下左右,還可以是左上,右下之類的複合運動。如果我們也用if...else來寫,這頭都會寫大:

// 先來四個方向的基本運動
function moveUp() {}
function moveDown() {}
function moveLeft() {}
function moveRight() {}

// 具體移動的方法,可以接收一個或兩個參數,一個就是基本操作,兩個參數就是左上,右下這類操作
function move(...args) {
  if(args.length === 1) {
    if(args[0] === 'up') {
      moveUp();
    } else if(args[0] === 'down') {
      moveDown();        
    } else if(args[0] === 'left') {
      moveLeft();        
    } else if(args[0] === 'right') {
      moveRight();        
    }
  } else {
    if(args[0] === 'left' && args[1] === 'up') {
      moveLeft();
      moveUp();
    } else if(args[0] === 'right' && args[1] === 'down') {
      moveRight();
      moveDown();
    }
    // 後面還有很多if...
  }
}

可以看到這裏if...else看得我們頭都大了,還是用策略模式來優化下吧:

// 建一個移動控制類
function MoveController() {
  this.status = [];
  this.moveHanders = {
    // 寫上每個指令對應的方法
    up: moveUp,
    dowm: moveDown,
    left: moveLeft,
    right: moveRight
  }
}

// MoveController添加一個實例方法來觸發運動
MoveController.prototype.run = function(...args) {
  this.status = args;
  this.status.forEach((move) => {
    this.moveHanders[move]();
  });
}

// 使用時
new MoveController().run('left', 'up')

上述代碼我們也是將所有的策略都封裝到了moveHanders裏面,然後通過實例方法run傳入的方法來執行具體的策略。

外觀模式

基本結構

當我們設計一個模塊時,裏面的方法可以會設計得比較細,但是暴露給外面使用的時候,不一定非得直接暴露這些細小的接口,外部使用者需要的可能是組合部分接口來實現某個功能,我們暴露的時候其實就可以將這個組織好。這就像餐廳裏面的菜單,有很多菜,用戶可以一個一個菜去點,也可以直接點一個套餐,外觀模式提供的就類似於這樣一個組織好的套餐:

function model1() {}

function model2() {}

// 可以提供一個更高階的接口,組合好了model1和model2給外部使用
function use() {
  model2(model1());
}

實例:常見的接口封裝

外觀模式說起來其實非常常見,很多模塊內部都很複雜,但是對外的接口可能都是一兩個,我們無需知道複雜的內部細節,只需要調用統一的高級接口就行,比如下面的選項卡模塊:

// 一個選項卡類,他內部可能有多個子模塊
function Tab() {}

Tab.prototype.renderHTML = function() {}    // 渲染頁面的子模塊
Tab.prototype.bindEvent = function() {}    // 綁定事件的子模塊
Tab.prototype.loadCss = function() {}    // 加載樣式的子模塊

// 對外不需要暴露上面那些具體的子模塊,只需要一個高級接口就行
Tab.prototype.init = function(config) {
  this.loadCss();
  this.renderHTML();
  this.bindEvent();
}

上述代碼這種封裝模式非常常見,其實也是用到了外觀模式,他當然也可以暴露具體的renderHTMLbindEventloadCss這些子模塊,但是外部使用者可能並不關心這些細節,只需要給一個統一的高級接口就行,就相當於改變了外觀暴露出來,所以叫外觀模式

實例:方法的封裝

這個例子也很常見,就是把一些類似的功能封裝成一個方法,而不是每個地方去寫一遍。在以前還是IE主導天下的時候,我們需要做很多兼容的工作,僅僅是一個綁定事件就有addEventListenerattachEvent,onclick等,為了避免每次都進行這些檢測,我們可以將他們封裝成一個方法:

function addEvent(dom, type, fn) {
  if(dom.addEventListener) {
    return dom.addEventListener(type, fn, false);
  } else if(dom.attachEvent) {
    return dom.attachEvent("on" + type, fn);
  } else {
    dom["on" + type] = fn;
  }
}

然後將addEvent暴露出去給外面使用,其實我們在實際編碼時經常這樣封裝方法,只是我們自己可能沒意識到這個是外觀模式。

迭代器模式

基本結構

迭代器模式模式在JS裏面很常見了,數組自帶的forEach就是迭代器模式的一個應用,我們也可以實現一個類似的功能:

function Iterator(items) {
  this.items = items;
}

Iterator.prototype.dealEach = function(fn) {
  for(let i = 0; i < this.items.length; i++) {
    fn(this.items[i], i);
  }
}

上述代碼我們新建了一個迭代器類,構造函數接收一個數組,實例方法dealEach可以接收一個回調,對實例上的items每一項都執行這個回調。

實例:數據迭代器

其實JS數組很多原生方法都用了迭代器模式,比如findfind接收一個測試函數,返回符合這個測試函數的第一個數據。這個例子要做的是擴展這個功能,返回所有符合這個測試函數的數據項,而且也可以接收兩個參數,第一個參數是屬性名,第二個參數是值,同樣返回所有該屬性與值匹配的項:

// 外層用一個工廠模式封裝下,調用時不用寫new
function iteratorFactory(data) {
  function Iterator(data) {
    this.data = data;
  }
  
  Iterator.prototype.findAll = function(handler, value) {
    const result = [];
    let handlerFn;
    // 處理參數,如果第一個參數是函數,直接拿來用
    // 如果不是函數,就是屬性名,給一個對比的默認函數
    if(typeof handler === 'function') {
      handlerFn = handler;
    } else {
      handlerFn = function(item) {
        if(item[handler] === value) {
          return true;
        }
        
        return false;
      }
    }
    
    // 循環數據裏面的每一項,將符合結果的塞入結果數組
    for(let i = 0; i < this.data.length; i++) {
      const item = this.data[i];
      const res = handlerFn(item);
      if(res) {
        result.push(item)
      }
    }
    
    return result;
  }
  
  return new Iterator(data);
}

// 寫個數據測試下
const data = [{num: 1}, {num: 2}, {num: 3}];
iteratorFactory(data).findAll('num', 2);    // [{num: 2}]
iteratorFactory(data).findAll(item => item.num >= 2); // [{num: 2}, {num: 3}]

上述代碼封裝了一個類似數組find的迭代器,擴展了他的功能,這種迭代器非常適合用來處理API返回的大量結構相似的數據。

備忘錄模式

基本結構

備忘錄模式類似於JS經常使用的緩存函數,內部記錄一個狀態,也就是緩存,當我們再次訪問的時候可以直接拿緩存數據:

function memo() {
  const cache = {};
  
  return function(arg) {
    if(cache[arg]) {
      return cache[arg];
    } else {
      // 沒緩存的時候先執行方法,得到結果res
      // 然後將res寫入緩存
      cache[arg] = res;
      return res;
    }
}

實例:文章緩存

這個例子在實際項目中也比較常見,用戶每次點進一個新文章都需要從API請求數據,如果他下次再點進同一篇文章,我們可能希望直接用上次請求的數據,而不再次請求,這時候就可以用到我們的備忘錄模式了,直接拿上面的結構來用就行了:

function pageCache(pageId) {
  const cache = {};
  
  return function(pageId) {
    // 為了保持返回類型一致,我們都返回一個Promise
    if(cache[pageId]) {
      return Promise.resolve(cache[pageId]);
    } else {
      return axios.get(pageId).then((data) => {
        cache[pageId] = data;
        return data;
      })
    }
  }
}

上述代碼用了備忘錄模式來解決這個問題,但是代碼比較簡單,實際項目中可能需求會更加複雜一些,但是這個思路還是可以參考的。

實例:前進後退功能

這個例子的需求是,我們需要做一個可以移動的DIV,用戶把這個DIV隨意移動,但是他有時候可能誤操作或者反悔了,想把這個DIV移動回去,也就是將狀態回退到上一次,有了回退狀態的需求,當然還有配對的前進狀態的需求。這種類似的需求我們就可以用備忘錄模式實現:

function moveDiv() {
  this.states = [];       // 一個數組記錄所有狀態
  this.currentState = 0;  // 一個變量記錄當前狀態位置
}

// 移動方法,每次移動記錄狀態
moveDiv.prototype.move = function(type, num) {
  changeDiv(type, num);       // 偽代碼,移動DIV的具體操作,這裏並未實現
  
  // 記錄本次操作到states裏面去
  this.states.push({type,num});
  this.currentState = this.states.length - 1;   // 改變當前狀態指針
}

// 前進方法,取出狀態執行
moveDiv.prototype.forward = function() {
  // 如果當前不是最後一個狀態
  if(this.currentState < this.states.length - 1) {
    // 取出前進的狀態
    this.currentState++;
    const state = this.states[this.currentState];
    
    // 執行該狀態位置
    changeDiv(state.type, state.num);
  }
}

// 後退方法是類似的
moveDiv.prototype.back = function() {
  // 如果當前不是第一個狀態
  if(this.currentState > 0) {
    // 取出後退的狀態
    this.currentState--;
    const state = this.states[this.currentState];
    
    // 執行該狀態位置
    changeDiv(state.type, state.num);
  }
}

上述代碼通過一個數組將用戶所有操作過的狀態都記錄下來了,用戶可以隨時在狀態間進行前進和後退。

總結

本文講的這幾種設計模式策略/狀態模式外觀模式迭代器模式備忘錄模式都很好理解,而且在實際工作中也非常常見,熟練使用他們可以有效減少冗餘代碼,提高我們的代碼質量。

  1. 策略模式通過將我們的if條件改寫為一條條的策略減少了if...else的數量,看起來更清爽,擴展起來也更方便。狀態模式策略模式很像,只是還多了一個狀態,可以根據這個狀態來選取具體的策略。
  2. 外觀模式可能我們已經在無意間使用了,就是將模塊一些內部邏輯封裝在一個更高級的接口內部,或者將一些類似操作封裝在一個方法內部,從而讓外部調用更加方便。
  3. 迭代器模式在JS數組上有很多實現,我們也可以模仿他們做一下數據處理的工作,特別適合處理從API拿來的大量結構相似的數據。
  4. 備忘錄模式就是加一個緩存對象,用來記錄之前獲取過的數據或者操作的狀態,後面可以用來加快訪問速度或者進行狀態回滾。
  5. 還是那句話,設計模式的重點在於理解思想,實現方式可以多種多樣。

本文是講設計模式的最後一篇文章,前面三篇是:

(500+贊!)不知道怎麼封裝代碼?看看這幾種設計模式吧!

(100+贊!)框架源碼中用來提高擴展性的設計模式

不知道怎麼提高代碼復用性?看看這幾種設計模式吧

文章的最後,感謝你花費寶貴的時間閱讀本文,如果本文給了你一點點幫助或者啟發,請不要吝嗇你的贊和GitHub小星星,你的支持是作者持續創作的動力。

本文素材來自於網易高級前端開發工程師微專業唐磊老師的設計模式課程。

作者博文GitHub項目地址: https://github.com/dennis-jiang/Front-End-Knowledges

作者掘金文章匯總:https://juejin.im/post/5e3ffc85518825494e2772fd

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

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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