分類
發燒車訊

WinUI 3 試玩報告

1. 什麼是 WinUI 3

在微軟 Build 2020 開發者大會上,WinUI 團隊宣布可公開預覽的 WinUI 3 Preview 1,它讓開發人員可以在 Win32 中使用 WinUI。WinUI 3 Preview 1 包含新的 VisualStudio 項目模板,可以創建面向 .NET 5 的 C# 和 C++/Win32 項目。從技術上講,WinUI 3 將 UWP 的 XAML、Composition 和 Input 層分離,並通過NuGet將它們獨立分發給針對Windows 10 版本 1803 及更高版本的 Win32 應用。

WinUI 3 適用於 Win32 和 UWP,這篇文章主要討論 Win32 的情況。

2. 理解 WinUI 3

以前我們總是抱怨 WPF 多年都不提供新的主題,不提供新的控件,性能又沒提升。現在微軟索性把什麼都是新的 WinUI 3 提供給桌面開發,沒 WPF 什麼事了。

簡單來說,UWP 的開發體驗不好(關於這個話題真是一言難盡),而且出了 Bug 還必須等待下半年的 Windows 更新進行修復,但微軟的開發人員專心給 UWP 的 UI 層加各種功能;.NET Core 更新很快,但很少人有興趣有動力給陳舊的 WPF 的 UI 層進行大幅度的改進。於是 WinUI 將 UWP 的 UI 層從 Windows SDK 的其它部分分離,並將從 Windows 轉移到 Nuget。現在建一個 C++ 或 C#(.NET 5) 程序,再從 Nuget 上裝個 WinUI 3 的包套個 UI 層,一個基於 Fluent Design,觸摸友好,性能無與倫比的應用程序就誕生了。

上圖列舉了 WinUI 3 和其他平台對比的部分特性,除此之外 WinUI 3 還有很多好處,例如開源、更新更快、更新不與系統版本綁定等,更詳細的內容還是看微軟自己怎麼宣傳吧:

WinUI – The modern native UI platform of Windows.

不過要用上 WinUI 3 還要等一年半載。下面是微軟給出的發布路線圖,目前我們也只能用 Preview 版嘗嘗鮮。

3. 試玩WinUI 3

要試玩 WinUI 3 首先要有 Windows 10 1803 以上版本的電腦(WinUI 3 最低支持1803),然後還需要使用 Visual Studio 2019 16.7 以上版本(目前只能安裝預覽版)。安裝 Visual Studio 時要把以下工作負載全都選上:

  • .NET 桌面開發
  • 通用 Windows 平台開發
  • 使用 C++ 的桌面開發
  • 適用於通用 Windows 平台負載的 C++(V142) 通用 Windows 平台工具可選組件

當然 .NET 5.0 也要裝上。

然後在 https://aka.ms/winui3/previewdownload 下載並安裝 WinUI 3 Project Templates 擴展,這樣才可以在 Visual Studio 創建 WinUI 的項目。

可選 C++ 或 C# ,這裏我選擇了 C# 的“Blank App, Packaged
(WinUI in Desktop)”項目,並選擇了對應的 Windows 平台:

項目創建后 Visual Studio 生成了兩個項目。第一個包含應用的代碼,代碼結構基本和 UWP 一樣,只是少了用於打包應用的 Package.appxmanifest 和一些圖片。從依賴項里可以看到項目已經安裝了 Microsoft.WinUI 3 的包。從項目屬性里可以看到這就是個 .NET 5 的項目。

Visual Studio 生成的第二個項目是一個 Windows 應用程序打包項目,該項目經配置后可將應用生成為適合部署的 MSIX 程序包。 也就是說 UWP 項目中用於打包的部分被獨立出來了。這個項目還應該是解決方案的啟動項目。運行這個項目后創建的應用會添加到開始菜單中,這點也和UWP一樣。

到這裏為止都和預期的一樣,我之後還嘗試了將 UWP 應用移植到 WinUI ,基本上只需要將 Windows.UI 命名空間改為 Microsoft.UI就可以了,XAML 和 C# 代碼完全不用變。只可惜目前 WinUI 還很簡陋,Win2D、Community Toolkit 等微軟自己發布的 UWP 包都還沒有 WinUI 版本。而且沒有設計視圖,XAML 視圖也沒有智能感知,現在想要用 WinUI做些什麼有趣的項目會很困難。不過從目前的移植難度上來看,將來正式發布后應該可以完整地將 UWP 的 UI 的開發經驗運用在 WinUI 上。

4. 和 WPF 及 UWP 進行對比

既然 WinUI 3 開發模式和 WPF 及 UWP 都很像,我當然對它們之間的對比很感興趣。

命名

首先說說命名,“WinUI” 光這個名字就 Win 了。 “UWP” 太高雅,我敢打賭國內有些 UWP 的開發(例如我)都不能好好地把 UWP 的全稱拼出來;“WPF” 好些,但 WPF 的含義也讓人很疑惑。而 Windows UI 簡稱 WinUI ,意義和發音都很清晰明確。不過這三個都比很多人都不會讀的 “Xamarin” 強多了。

可是有了 WinUI 3 ,就會有人問“那 WinUI 2 呢?”WinUI 2是一個 UWP 的控件庫,當然的只能用在 UWP 上。這就很尷尬了,WinUI 的 3 和 2 根本不是同一個概念,實在很容易讓人混淆,說不定以後會把後綴的 3 去掉(我這篇文章就常常懶得理寫這個3)。而且 UWP 中代碼的命名空間以 Windows.UI 開頭,在 WinUI 3 中則 Microsoft.UI ,按着 Office 365 改名為 Microsoft 365、Bind Ads 改名為 Microsoft Advertising 這些經驗,該不會以後 WinUI 可能改名為 Microsoft UI ,簡稱 MiUI 吧?

權限

權限方面是 WinUI 的一個亮點,因為它本質上就是個 Win32 程序,可以放開手腳隨便來。相對的 UWP 有很嚴格的權限限制,開發 UWP 時常常會感到綁手綁腳。例如下面這段代碼,大部分 WPF 開發者都難以想象只是最小化 UWP 程序而已,它就不能好好運行了:

int count = 0;
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += (s, e) =>
  {
      myButton.Content = count++;
  };
timer.Start();

UWP 的生命周期如上圖,當 UWP 處於 background 運行或 suspended 狀態時應用基本處於暫停狀態,也也不會處理UI功能。我明白這是 UWP 為了省電、安全等原因才這樣設計,但對開發人員來說真的太不方便。而 WinUI 應用基本上就是個 Win32 應用,目前看來不會有這些坑。

開發體驗

說起開發體驗,WPF 好歹還算正常,Visual Studio 的設計視圖運行正常,編譯起來也快。UWP 編譯很慢,設計視圖經常出問題,Blend 也時好時壞把設計師都氣跑了。就算完全按着官方的文檔完成一個 UWP App,甚至一行代碼都不改,發布到商店后還是有可能崩潰。而對於應用商店,真是千言萬語彙聚成一個草花頭。

現在 WinUI 的 XAML 視圖連智能感知都沒有,也沒有設計視圖,實在沒法談開發體驗。很難猜測正式發布的時候會怎麼樣,希望至少和WPF保持一致吧。

性能

WPF 總是給人“慢”的印象,除了因為在它剛出來的時候(10年前)電腦性能不夠導致留下了刻板印象,還有一個主要原因是:它真的很慢。

UWP 的 XAML 有很優秀的性能表現,除此之外為了照顧已經不存在的 Windows Phone 的貧弱性能,很多控件模版都經過精心設計並大幅簡化。

為了驗證 WinUI 的性能我寫了下面這些代碼,然後分別移植到 WPF .Net Framework 4.8、WPF .NET 5、UWP、WinUI(WPF 和 UWP/WinUI 的代碼稍微有一點不同):

for (int i = 0; i < 50; i++)
{
    var rectangle = new Rectangle
    {
        Height = 500,
        Width = 500,
        Opacity = ((double)i + 40) / 100d,
        RadiusX = 108,
        RadiusY = 98,
        StrokeThickness = 3,
        Stroke = new SolidColorBrush(Color.FromArgb(255, 75, 75, (byte)(i * 250d / 50d))),
        RenderTransformOrigin = new Point(0.5, 0.5)
    };
    Root.Children.Add(rectangle);
    var angle = i * 360d / 50d;
    var transform = new RotateTransform
    {
        Angle = angle
    };

    rectangle.RenderTransform = transform;

    var storyboard = new Storyboard();
    storyboard.Children.Add(new DoubleAnimation { Duration = TimeSpan.FromSeconds(1), From = angle, To = angle + 360 });
    Storyboard.SetTarget(storyboard, transform);
    Storyboard.SetTargetProperty(storyboard, "Angle");
    storyboard.RepeatBehavior = RepeatBehavior.Forever;
    storyboard.Begin();
}

上面這段代碼是讓50個矩形旋轉,十分考驗 WPF 的性能。結果可以說出乎意料。

CPU 內存 GPU
WPF .NET Framework 4.8 12 60 76
WPF .NET 5.0 12 85 72
UWP 3 28 36
WinUI 5 65 95

我的環境是 i7-6820HQ 及集成顯卡。WPF 平台佔用 70 多%的 GPU,這我大致能猜到。UWP 十分流暢,GPU 只佔用 WPF 的一半,CPU 和 內存都有出色表現,不過我還以為會更低的。

WinUI 這個濃眉大眼的我真的萬萬沒想到,不僅掉幀明顯,還佔用了幾乎 100% GPU,也就是說它連這麼簡單的代碼都跑不起來。()順便一提,將測試代碼中旋轉的矩形減少為10個,WPF 的程序佔用 32% GPU,而 WinUI 佔用 70 多%。)

從上面的數據基本可以說明,WinUI 離設計目標還十分遙遠,畢竟是預覽版,還有一年半載可以慢慢優化。

5. 結語

總的來說微軟雄心勃勃,可是現在拿出來的 WinUI 預覽版還差得太遠,功能未完善,性能不及預期。我覺得大致方向沒錯,WinUI 對 C++、WPF、UWP 開發者都是個新的工具新的機遇,可以關注一下。

6. Q & A

Windows 7 怎麼辦?

按微軟公布的路線圖,再包括跳票等因素,等 WinUI 真正可用時 Windows 7 已停止更新很久,到時 Windows 7 的佔有率可能已經下降到開發者不會關心的程度。

基於 .NET Core 的 Wpf 還是 WinUI?

假使不想花精力將現有項目遷移到 WinUI,或者對來自 UWP 的 WinUI 沒信心,又或者舍不得 Windows 7 的用戶,並且對觸摸沒需求,當然可以繼續選用 WPF,基於 .NET Core 的 WPF 會是個很好的選擇。

MAUI 還是 WinUI ?

MAUI 還在很遙遠的將來(2021年11月),我沒試玩過,所以不好評價。如果有跨平台需求當然只能選 MAUI,如果 WinUI 團隊技高一籌實現了 MAUI 難以企及的超高性能,那就選 WinUI。不過 MAUI 這個名字太過普通/普遍,可能會被逼着改名吧。

那 UWP 呢?

權限受限的 UWP 可以說是人畜無害,對用戶來說可能也是個不錯的選擇。而且 UWP 還支持 Xbox 和 Hololens 等平台,目前看來還是有它的市場。

Winforms 呢?

人只有忘卻了過去,才能好好活着。

WinUI 有未來嗎?

我做了好多年 Silverlight 開發,買了5、6部 Windows Phone 手機,寫了幾十篇 UWP 文章,根據我豐富的經驗,我可以肯定 WinUI 是有未來的。

8. 參考

WinUI – The modern native UI platform of Windows.

Introducing WinUI 3 Preview 1 – Windows Developer Blog

Get started with WinUI 3 for desktop apps Microsoft Docs

GitHub – microsoft_microsoft-ui-xaml

Windows UI Library Roadmap

WinUI 3.0_ The future of Windows controls

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

【其他文章推薦】

※回頭車貨運收費標準

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

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

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

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

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

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

分類
發燒車訊

發動機不磨合?機油燒死你!

還說買了性能車,直接來一發暴力磨合。然而事實是,出廠前就磨合好的車只是佔少數,同時暴力磨合也是一種不合理的磨合方式。所以,作為普通車主,還是不要搞什麼幺蛾子了。另外,磨合的時候多跑高速也是有特定條件的。

大家常聽老司機說新車要磨合,有所謂的磨合期,那麼磨合到底是什麼鬼?

發動機是磨合過程中最核心的部分,為了讓內部的活塞和發動機缸體達到完美的活塞運動,這時候就要在它們之間放置活塞環和缸套。

而磨合的作用,是要讓活塞環與缸套的內壁完美地磨合密封。

但是因為活塞環有彈性,如果金屬內壁不夠平滑,那就很容易出現各種磨損,最明顯的一個傷害就是燒機油。所以,所謂磨合期,就是要慢慢的讓發動機進行活塞運動,然後讓零件間的接合面打磨得更加貼合,達到最佳狀態。

不過長期以來,各種鍵盤車神和老司機間都流傳着一些以訛傳訛的說法,比如說新車早就在廠里磨合好,買回來就儘管飆。還說買了性能車,直接來一發暴力磨合。

然而事實是,出廠前就磨合好的車只是佔少數,同時暴力磨合也是一種不合理的磨合方式。所以,作為普通車主,還是不要搞什麼幺蛾子了。

另外,磨合的時候多跑高速也是有特定條件的。在跑的過程中多切換不同的擋位和油門深淺,同是不要貪新鮮開個幾百米就算,這樣各類油溫沒起來,會對車輛造成一定損害。

不過說了那麼多,千言萬語合萬一句,就是要多看看車主手冊。看完這期節目,大家記得要等着看下一期,什麼是燒機油!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※回頭車貨運收費標準

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

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

分類
發燒車訊

10萬隻能買山寨保時捷?現在能買真X5!

0T,163馬力)與手動變速箱的組合,又根據外觀的區別分文紅標藍標。而F6S又定位於緊湊級SUV,有多少因為H6型號太多,不知道怎麼選,最後放棄的朋友。報個到。4。女司機賓利無人駕駛俗話說防火防盜防女司機,就在昨天,一位賓利女司機的事故又在朋友圈瘋傳,從視頻上來看她是想去地庫,結果一不小心衝過卡,你跟我說倒車,太難了,直接就下車取卡,誰知道帶你裝逼帶你飛的賓利不帶女司機玩,自己跑了,拉都拉不住。

1. 勞斯萊斯全路況車型 Cullinan庫里南

由於全球市場SUV的熱銷,越來越多的豪車廠商紛紛下海(保時捷,瑪莎拉蒂,阿爾法羅密歐)隨着賓利添越的發布與熱賣,勞斯萊斯也公布了旗下首款非SUV的諜照,(官方一直表明這隻是全路況汽車,並非SUV)然而新車的名字Cullinan被網友戲稱為苦力男,也不是沒有道理,這車的車主腰部運動量肯定較常人多,如果非要有個正經的命名法則,應該是源於迄今為止世界上最大的鑽石。

其實早在2015年海外媒體就跟蹤報道過,當時的測試車看着就像加了尾翼底盤抬高的幻影,從目前這款車的諜照來看,勞斯萊斯Cullinan將會採用和幻影相類似的車身設計,並且極有可能搭載和幻影一致的6.8L V12發動機。

2. 東風景逸X5

眼看今年只有兩個月不到了,還在為過年回家開的車沒面子感到煩擾嗎,沒問題,現在有這麼款車,名字叫x5,德國外形設計。

極具科技感的內飾。

看到這種配方你們以為說的是眾泰嗎?too young too naïve,而是最新的東風景逸X5,官方消息表示新車將於本月17號正式上市,搭載1.6和2.0升發動機,匹配5速、6速手動變速箱和CVt自動變速箱。沒有眾泰那麼直接的借鑒加上最後十萬左右的價格(9到12萬),對過年有信心了嗎?

3. H6轎跑版哈弗F6S

就在前陣的廣州車展,哈弗推出了旗下高端品牌wey,近日工信部又表示哈弗將推兩款定位高於H6的SUV,或將命名為F6和F6S,儘管換了新名字,不過還是基於H6平台打造。

除了2.0T加上雙離合的變速箱,F6還將增加柴油發動機(2.0T,163馬力)與手動變速箱的組合,又根據外觀的區別分文紅標藍標。而F6S又定位於緊湊級SUV,有多少因為H6型號太多,不知道怎麼選,最後放棄的朋友?報個到。

4. 女司機賓利無人駕駛

俗話說防火防盜防女司機,就在昨天,一位賓利女司機的事故又在朋友圈瘋傳,從視頻上來看她是想去地庫,結果一不小心衝過卡,你跟我說倒車,太難了,直接就下車取卡,誰知道帶你裝逼帶你飛的賓利不帶女司機玩,自己跑了,拉都拉不住。

你以為這樣就完了嗎?被撞那堵牆的另一側,更是一度上演死神來了的劇本,好在那位大叔瞬間使出凌波微步,僥倖避開事故,有人說女司機真沒素質,又是在車尾裝流氓燈,又是連p擋都不會掛,也有人說賓利這麼一個豪華品牌居然沒有開門自動掛p擋的功能,差評,不管怎樣,這位女司機又給女同胞順利掛上了一個女司機的負面標籤。

5.J.D power 年度中國最靠譜汽車品牌前兩名為路虎和mini

近日,J.D. power 發布了2016年中國車輛可靠性研究SM(VDS),引來一片質疑,為什麼會這樣呢?因為今年被評為最可靠的品牌分別為路虎(豪華車)和MINI(主流車)。

不管路虎的越野性能有多出色,但是在以往J.D. power 的排名中幾乎是墊底的。甚至被調侃到如果發現一輛路虎沒有漏油,說明油漏完了,在公布成績后JD power也被調侃,JD(假的)嘛,肯定是假的咯,在這裏奶茶妹夫表示無故躺槍。(京東也算JD)本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

分類
發燒車訊

厲害word哥!上汽打算今年賣30萬輛車!

99萬)11月銷售1。2萬輛榮威 RX5搭載1。5T/2。0T發動機,MT/AT可選,參考價格(9。98萬至18。68萬)11月銷量2。1萬輛其中70%為互聯網版本乘着廣州車展的東風,11月上汽榮威品牌成績斐然,傳統熱銷車型榮威360和榮威350月銷售均破萬,累計銷量超1。

“國家政策添把火,國內廠商出爆款”來形容2016年的車市並不為過。近日,自主品牌一哥上汽傳來一則好消息,通過其爆款互聯網車型“RX5”的熱銷,已提前完成了2016年全年的銷售指標,劍指年銷售30萬量的目標(月2.5萬/輛),穩坐中國自主品牌領導者的地位。

榮威350

搭載1.5L發動機,MT/AT可選,

參考價格(7.87萬至9.97萬)

11月銷售1.2萬輛

榮威360

搭載1.5L/1.4T發動機,MT/AT可選,

參考價格(7.59萬至12.99萬)

11月銷售1.2萬輛

榮威 RX5

搭載1.5T/2.0T發動機,MT/AT可選,

參考價格(9.98萬至18.68萬)

11月銷量2.1萬輛

其中70%為互聯網版本

乘着廣州車展的東風,11月上汽榮威品牌成績斐然,傳統熱銷車型榮威360和榮威350月銷售均破萬,累計銷量超1.2萬輛。帶有馬雲爸爸光環的RX5更熱銷21344輛,三款熱銷車型帶動品牌增長超過173%,創上汽集團歷史新高,縱觀國內車市,消費者對自主品牌和互聯網汽車的認同感不斷提高,榮威品牌更獲得消費者的強烈認可。

自主品牌汽車與互聯網的結合,開始很多人並不看好,但上汽用一張漂亮的成績單告訴質疑者,在“陳虹爸爸”和“馬雲爸爸”的全力支持下,自主品牌也可以有越級的品質、各種黑科技,更重要的是,讓中國消費者買得起,用的爽,隨着榮威新一代產品Erx5和i6燈發布上市,必然會掀起一陣購車熱潮。

未來的車型發展中,上汽更深耕“互聯網概念汽車”和“新能源汽車”等技術,搭載更多貼合潮流和先進技術的汽車推出市面,如一發布就引起各方興趣的MG-ZS、榮威I6等,讓人拭目以待吧。

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

【其他文章推薦】

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

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

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

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

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

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

※回頭車貨運收費標準

分類
發燒車訊

12萬就能買德國旅行車 試駕大眾蔚領

這時你可以選擇切到S擋,這時你就可以激活一下蔚領的潛在運動基因,讓它活潑一些,也讓自己小小放肆一回。而蔚領的懸挂行程得到了增加,正常行駛沒有什麼問題,遇上爛路的話你只能包容一下了。作為一輛跨界屬性的車型,沒有一個體面的後備廂,是沒有說服力的。

六年時間,無論是對於人還是事,都是一段不短的時間了。如果說一輛車花了六年時間來研發,那它推出市場后的反響,直接就能定義它是否成功了。這對於一汽大眾蔚領來說,它面對的處境是很相似的。

近期一汽大眾的大新聞莫過於和奧迪間的瓜葛,但回到產品上來說,新上市的蔚領也是吸引了不少眼球。蔚領的定位其實有點模糊,長着旅行車的身版,標配沒卵用的行李架,又有摻了點跨界風。而它的外觀最大的亮點,我覺得應該是在於它的尾燈造型了。但是相對於現在套娃風來說,還算有點誠意了。

EA211 1.4T發動機+DQ2007速乾式雙離合變速箱,這套動力單元的搭載給人的感覺就和豆漿油條一樣,基於pQ34平台,一切的配方都是那麼熟悉。

所以蔚領一上手,你還是會不由自主的覺得,這是一輛大眾。D擋模式下,你要車子很快的走起來,那就得來多點油,雖然扭矩有小幅提升,但低扭輸出還是差不多。這時你可以選擇切到S擋,這時你就可以激活一下蔚領的潛在運動基因,讓它活潑一些,也讓自己小小放肆一回。

蔚領的內飾個人而言還是可以接受的,雖然都是一片硬塑料,拋開質感而言,說得過去。

蔚領提供了1.4T和1.6L兩款發動機型號可選,目前的售價區間為12.59-16.29萬元。能接受兩廂車造型,憧憬一家大小在周末出外休閑的朋友,現在就多了一個選擇了。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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

分類
發燒車訊

豪華新標杆 這台又新又帥的合資SUV讓奔馳寶馬震驚了

看起來漂亮些嗎。我告訴他,是性能。性能是一台豪車最內在的地方,而豪車之間差距最大的則在於動力系統和科技水平,比如說XT5的2。0T發動機動力強大198KW/400牛米,這樣的強大動力推動XT5動力綽綽有餘,而同級別的奧迪Q5僅為169kw,奔馳GLC更是僅有135kw。

目前的中國豪車市場上,奧迪Q5和寶馬X3車型偏老,遲遲未換代的尷尬一直存在,老舊的設計以及技術平台使得它們的性能表現差強人意,而GLC雖然新上市,但是加價過高,性價比比較低。

但是XT5的出現打破了這種局面,XT5一經上市便引爆市場,銷量超過了德系老將寶馬X3,XT5的到來為這個市場吹進了一股新風,而XT5出現之後,其他的產品似乎變得黯淡無光,而在小編看來XT5也確實是這個價位最值得推薦的SUV車型。為什麼呢?

龐大尺寸 有容乃大

外觀設計是豪華SUV的消費者十分注重的一個方面,一個氣派的造型設計很大程度上決定了它的成功與否,而XT5的外觀設計十分有特點,氣派不失時尚。4812*1903*1685也全面碾壓BBA車型。4813mm的長度是什麼概念呢?百萬級SUV奔馳GLE長度也不過是4813mm而已。花一台GLC的錢買一台GLE級別的SUV,你說划不划算呢?

而大尺寸帶來的最直接的好處就是大空間,XT5的空間表現十分驚人。相比之下奧迪Q5/奔馳GLC表現就相形見絀了。得益於2857mm的超長軸距,XT5的內部乘坐空間十分寬敞,每個乘客擁有的都不止是一席之地。

而且XT5的裝載空間也十分巨大,XT5在常規狀態下行李箱容積便已經達到了584升,而放倒後排座椅之後容積更是高達1634升,相當於一部廂式小貨車的空間了,不過還不止如此。XT5有着多種車內空間的組合方式,支持4/6比例放倒的後排座椅可以靈活兼顧坐人和拉貨的需求,空間靈活性做得十分優秀。

反觀對手奧迪Q5和奔馳GLC的裝載空間,奧迪Q5的行李箱容積為540升,軸距也僅為2807mm,而奔馳GLC甚至都沒有公開它的尾箱容積數據。

澎湃動力 智能四驅

有人問我豪車和普通車型差別到底在哪?看起來漂亮些嗎?我告訴他,是性能。

性能是一台豪車最內在的地方,而豪車之間差距最大的則在於動力系統和科技水平,比如說XT5的2.0T發動機動力強大198KW/400牛米,這樣的強大動力推動XT5動力綽綽有餘,而同級別的奧迪Q5僅為169kw,奔馳GLC更是僅有135kw。在動力上的差距就尤其明顯了。

再比如XT5的智能雙離合適時四驅系統使得車輛的動力能夠在前後軸之間實現0~100%的扭矩分配,在運動和經濟以及通過性上取得完美均衡。為什麼前後軸的0~100%扭矩分配這麼重要呢?車輛在不同狀態下前後軸需要的動力是不一樣的,比如在山路就需要把更多動力分配給後輪,提高操控靈活性;在雪地就需要更多的實現前後50:50的動力分配來保證穩定性,而在高速則可以變成前驅車達到節油目的,XT5的四驅系統就能夠實現0~100%的扭矩分配。

而奔馳GLC的4matci四驅系統和奧迪Q5的quattro四驅系統屬於全時四驅,不管在什麼狀態下都是四驅,而我們日常使用實際上是用不到四輪驅動的,而四驅會帶來高油耗,因此奔馳/奧迪的全時四驅車型能夠省油嗎?

如果你說你只看四驅系統實力不在乎油耗,奧迪能打敗XT5嗎?不能!奧迪Q5的quattro四驅系統最多只能將85%的動力傳遞給後輪,前輪最多也只能接受70%的動力,相比XT5來說,弱爆了~

總結:

為什麼XT5的性價比/性能會如此突出呢?眾所周知BBA在中國的價格一直虛高,花了40萬隻能買到30萬的品質,但是許多人對BBA盲目追捧,即使是X3這樣的老產品也能有春天,而這些價格虛高的產品也一定程度上凸顯了XT5的性價比,因而一直保持高裝備水平的XT5的也就更加顯得划算了。

XT5的出現是必然,豪華SUV市場就應該是百花齊放的,而配置高動力強勁內外裝備也足夠奢華的XT5獲得成功是必然,XT5的出現為豪華SUV市場樹立了一個新的標杆,使用最具性價比的價格打造一檯面面俱到的優秀SUV,到目前也只有凱迪拉克XT5做到了。動力性和空間上的優勢是德系豪車無法相比的,想要追上XT5,德系還需要再換一次代才行!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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

分類
發燒車訊

你真的了解EF嗎?關於EntityFramework的優化

接上一篇文章。現在寫程序,做項目不是說功能做完就完事了,在平常的開發過程中對於性能的考慮也是極其重要的。

關於ef的那些事,今天就來說說吧。首先必須得知道.net ef在程序中的五種狀態變化過程與原理。

主要來說說查詢部分的性能優化,在所有查詢中,客戶端查詢出來的數據一般來說是不需要進行跟蹤的。也就是說查詢只是給用戶看,不做其他任何操作。對於基於B/S模式的項目網站開發,應該是無狀態的,也就是ef中的遊離態(Unchanged)(個人理解)。如果是C/S可能還需要進行其他連接的狀態。通俗的說,在開發過程不用去檢測某一個屬性或者類是否被修改或者刪除。

例如:

AsNoTracking:它的作用就是在查詢的過程中不被緩存,也就是不被保留,查出來就完事了,這樣的狀態就變成了Detached遊離態

//AsNoTracking:它的性能比ToList快大約4.8倍,不被緩存的數據。
var item=db.Book.AsNoTracking().First(m=>m.Id==1);
Console.WriteLine(db.Entry(item).State); //輸出Detached(遊離態)

去掉AsNoTracking

var item=db.Book.First(m=>m.Id==1);
Console.WriteLine(db.Entry(item).State); //輸出UnChanged(持久態)

也就是加上AsNoTracking做出來的查詢操作,就和數據庫斷絕了關係(個人理解)這樣對性能也是一個極好的優化。

然後來說說在項目中對ef整體框架優化的使用。

C#是一門面向對象的語言無外乎就是封裝、繼承、多態。。。類可以繼承類,同樣接口也可以繼承接口。面向對象的思想就是每張表都應該有一個父類,只寫一遍,其他類繼承就好了不用重複去寫。

建立一個空白的解決方案,添加一個名為BaseEntity的類,你可以把它看做是所有類的基礎類(父類)。

重點來說說這個BaseEntity為什麼要作為基礎類,它的用意何在。

先看看裏面的寫了些什麼吧!

以前用int或者long作為主鍵自增長的數據類型,這樣後期數據非常龐大的時候可能會無法預估難免可能會出現重複的數據,這個時候對於整個系統來說就無法保障了。

Guid給我做出了一個計算,它是32位的数字加字母的組合,而且是特別長的不會重複的自增數,可以這樣去理解。

DateTime就是每條數據的創建時間,IsRemove就是作為數據邏輯刪除的標識,也就是說,每個類(表)都會存在這三個字段屬性。你可以通過DateTime對沒張表進行排序的操作。

using System;
namespace Book.Models
{
    public class BaseEntity
    {
        public Guid Id { get; set; }=Guid.NewGuid(); //計算32位,字母加数字,特別長的,不重複的,自增數
        public DateTime DateTime { get; set; }=DateTime.Now;    //每條數據的創建時間
        public bool IsRemove { get; set; }  //偽刪除的標識
    }
}

創建BookType類

using System.ComponentModel.DataAnnotations;

namespace Book.Models
{
    public class BookType:BaseEntity
    {
        [StringLength(20)]
        public string Name { get; set; }
    }
}

創建Book類

using System.ComponentModel.DataAnnotations;

namespace Book.Models
{
    public class Book:BaseEntity
    {
        [StringLength(50),Required]
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
}

這兩個類都會繼承BaseEntity

在App.Config裏面寫上你自己的數據庫連接字符串

這樣的目的就是去生成數據庫,只做生成數據庫的操作。這裏只是在Models層寫了一次,在後期加上表示層還有在寫一遍!

接下來就是去通過指令遷移到數據庫,操作有三步:

  1. 啟動遷移:enable-migrations
  2. 添加遷移:add-migration ‘參數’
  3. 更新數據庫:update-database(如果你需要添加或者修改某個字段屬性,只需要進行第二步和第三步的操作即可!)

記住默認項目要選擇你的Models,如果是多個項目(我這裡是只有一個)就必須要把Models設為啟動項目,不然遷移指令可能不會起到作用

這樣就表示你的數據庫遷移的工作已經完成了,你可以在數據庫進行查看

 接下來的工作就是準備分層的工作,什麼是分層?怎麼分?好處是什麼?

我的理解:聽某一個大佬說,代碼是一層一層寫的不是一行一行寫的。個人覺得拿捏了。原諒我沒去百度。。。

一直覺得對於面向對象的思想,一直覺得自己才疏學淺,學的只是皮毛,真是非常的慚愧。

剛好借這個機會來通過分層一起來學習學習。三層,在之前的學習中對於三層也只是找到表示層(UI)、業務邏輯層(BLL)、數據訪問層(DAL)。

個人覺得,這樣確實是解耦了,但是,並沒有將業務邏輯層的“業務邏輯”這一詞的功能發揮到極致,就只是簡單的從UI層get一下BLL層,BLL層簡單的get一下DAL層。。。(個人覺得並沒有解什麼耦。。。來自菜鳥的思考)

那我們可不可以封一個接口層,通過面向對象的繼承來去調整一下呢?

首先得明確接口是可以繼承接口的!

創建一個數據接口層IDAL,結構如下:引用Models層

 為什麼寫這個接口呢?原因很簡單,就是將增刪改查等操作進行一個封裝,並且這不是普通的增刪改查,看代碼:

你可以把它看做是增刪改查的基礎接口,這個接口是一個泛型(比如你的商品表,用戶表等等…),它必須得基礎BaseEntity,並且這個T必須繼承與BaseEntity,也就是必須是BaseEntity的派生類。

關於IQueryable的好處註釋以寫!

這四個方法就是增刪改查,就算你有幾百張表,應該也只要寫一次增刪改查的操作就ok(也是來自菜鳥理解)

using System;
using System.Linq;
using Book.Models;

namespace IDAL
{
    //接口封裝增刪改查的方法它,是個泛型,並且要給一個約束,這個T必須是BaseEntity的派生類也就是子類,別的不行
    public interface IBaseService<T> where T:BaseEntity
    {
        void Add(T t);
        void Edit(T t);
        void Remove(Guid id);
        T GetOne(Guid id); //查詢某一個對象
        //IQueryable得到的是一個集合,它不會立刻給你去生成Sql語句,直到你需要拿到指定的某個結果后,再去給你生成Sql語句,比如根據條件,分頁,排序等操作獲得的集合數據
        IQueryable<T> GetAll();
    }
}

既然有了接口,那麼肯定是要實現這個接口才能調用裏面的增刪改查的四個方法吧!既然如此,那麼就再寫一個DAL類庫,去實現這個接口層。

結構如下:引用EF以及Models和IDAL層,並且寫一個BaseService類去實現IBaseService接口裡面的方法

BaseService代碼如下:

using IDAL;
using System;
using System.Linq;
using Book.Models;

namespace DAL
{
    public class BaseService<T> : IBaseService<T> where T:BaseEntity    //指明這個T是誰,就是繼承BaseEntity的類
    {
        public void Add(T t)
        {
            throw new NotImplementedException();
        }

        public void Edit(T t)
        {
            throw new NotImplementedException();
        }

        public IQueryable<T> GetAll()
        {
            throw new NotImplementedException();
        }

        public T GetOne(Guid id)
        {
            throw new NotImplementedException();
        }

        public void Remove(Guid id)
        {
            throw new NotImplementedException();
        }
    }
}

前面的 IBaseService<T>就是你實現的是哪個接口,後面的Where就是告訴這個接口要實現的T(泛型已經指定了是繼承BaseEntity的派生類)是誰。。。

可能這裏的Where作用還需要去理解一下,個人理解就是約束、條件之類的作用。

怎麼寫方法?代碼如下:

using IDAL;
using System;
using System.Data.Entity;
using System.Linq;
using Book.Models;

namespace DAL
{
    public class BaseService<T> : IBaseService<T> where T:BaseEntity,new()    //指明這個T是誰,就是繼承BaseEntity的類
    {
        private BookContext _db=new BookContext();
        public void Add(T t)
        {
            //db.Books.Add(t);
            _db.Set<T>().Add(t); //Set<>就是返回一個DbSet實例,為什麼是T ,作用就是動態的訪問,無論是Book還是BookTypes,你給我什麼我就用T接就好了
            _db.SaveChanges();
        }

        public void Edit(T t)
        {
            _db.Entry(t).State = EntityState.Modified;   //直接通過主題去作修改
            _db.SaveChanges();
        }

        public IQueryable<T> GetAll()
        {
            return _db.Set<T>().Where(m => !m.IsRemove).AsNoTracking(); //脫離持久態,變為遊離態,並且過濾掉了已被刪除的數據
        }

        public T GetOne(Guid id)
        {
            return GetAll().First(m => m.Id == id); //直接通過GetAll在通過id查到你想要的數據
        }

        public void Remove(Guid id)
        {
            var t=new T()
            {
                Id = id
            };  //這裡是偽刪除,T不能直接new,也是給個約束條件 new()即可,代表它有構造函數
            _db.Entry(t).State = EntityState.Unchanged; //也是根據狀態去刪除 
            t.IsRemove = true;
            _db.SaveChanges();
        }
    }
}

這些增刪改查方法寫完后,就要去繼承了。原理實際上和BaseEntity差不多

既然增刪改查的方法都寫好了,那麼怎麼去才能調用到它呢?這個時候為什麼還需要去寫IBook和IBookType呢?。首先我們在之前是寫了IBaseService這個接口並且寫了對應的增刪改查方法,但是具體的實現是在DAL層所有,這兩個接口只需要繼承對應的IBaseService就好了,具體實現也是在DAL層,至於DAL層怎麼去寫,請看下面的內容。

這個時候泛型 T就其作用了,我們可以去寫對應的接口繼承IBaseService就好了。。

 代碼如下:

IBookService:

namespace IDAL
{
    public interface IBook:IBaseService<Book.Models.Book>
    {
        
    }
}

IBookTypeService:

using Book.Models;

namespace IDAL
{
    public interface IBookTypeService:IBaseService<BookType>
    {
        
    }
}

DAL層結構:

為什麼還要繼承BaseService?首先BaseService具體實現了繼承IBaseService的方法,所以,我們只需要繼承一下就可以調用到BaseService的增刪改查的方法了!

BookService代碼:

using Book.Models;
using IDAL;

namespace DAL
{
    public class BookService:BaseService<Book.Models.Book>, IBookService
    {
        
    }
}

BookTypeService代碼:

using Book.Models;
using IDAL;

namespace DAL
{
    public class BookTypeService:BaseService<BookType>,IBookTypeService
    {
        
    }
}

以上操作完成后,我們只需要實例化DAL層的Service方法就可以調用在UI層增刪改查了。

寫到這裏只是最最基礎的底層,如果還要完善需要進一步的改善改善。。。

如果有錯誤的地方請提出來,本人也是學生,大家都是抱着學習的心態去記錄和鞏固自己的知識點。。。。。

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

※回頭車貨運收費標準

分類
發燒車訊

容器技術之Docker私有鏡像倉庫harbor

  前文我們聊到了docker的私有鏡像倉庫docker-distribution的搭建和簡單的使用,回顧請參考https://www.cnblogs.com/qiuhom-1874/p/13058338.html;從前文的搭建和使用過程來看,docker-distribution搭建的倉庫非常簡陋,它甚至連一個用戶認證都沒有,更別提多用戶;今天我們來介紹另外一款docker倉庫工具harbor;harbor這款工具相對docker-distribution來講功能上豐富了許多;它支持多租戶,可擴展的API和web ui ,支持跨多個harbor實例的鏡像複製,支持身份集成和基於角色的訪問控制等等特徵;接下來我們來安裝看看harbor吧;

  首先我們要去官網下載安裝器,目前最新版本是2.0;下載地址https://github.com/goharbor/harbor/releases/tag/v2.0.0;harbor的安裝器有在線和離線兩個版本,在線包通常較小,適用於網絡環境較好地環境中使用,離線包是所有的安裝文件和腳本等等打包在一起的;

  1、上傳已經下載好的安裝器到服務器

  2、解壓安裝器,並進入到解壓后的目錄中

[root@docker_node01 ~]# tar xf harbor-offline-installer-v2.0.0.tgz -C /usr/local/
[root@docker_node01 ~]# ls /usr/local/
bin  etc  games  harbor  include  lib  lib64  libexec  sbin  share  src
[root@docker_node01 ~]# cd /usr/local/harbor/
[root@docker_node01 harbor]# ls
common.sh  harbor.v2.0.0.tar.gz  harbor.yml.tmpl  install.sh  LICENSE  prepare
[root@docker_node01 harbor]# 

  3、編輯harbor.yml.tmpl文件,更改必要的配置

  提示:以上我只修改了hostname的值,後面的我都是用默認值;有關這個配置文件的說明,可參考官方文檔說明去配置;這裏需要注意一點使用https需要自己手動的去申請證書,沒有證書文件harbor是不能夠正常安裝的;

  4、把harbor.yml.tmpl重命名為harbor.yml

[root@docker_node01 harbor]# ls
common.sh  harbor.v2.0.0.tar.gz  harbor.yml.tmpl  install.sh  LICENSE  prepare
[root@docker_node01 harbor]# mv harbor.yml.tmpl harbor.yml
[root@docker_node01 harbor]#

  5、運行install.sh

  提示:如果運行install.sh腳本出現以上錯誤,我們需要先安裝好docker-compose;

  6、安裝docker-compose

[root@docker_node01 harbor]# yum install docker-compose -y
Loaded plugins: fastestmirror
base                                                                                                                                                | 3.6 kB  00:00:00     
docker-ce-stable                                                                                                                                    | 3.5 kB  00:00:00     
epel                                                                                                                                                | 4.7 kB  00:00:00     
extras                                                                                                                                              | 2.9 kB  00:00:00     
updates                                                                                                                                             | 2.9 kB  00:00:00     
(1/3): updates/7/x86_64/primary_db                                                                                                                  | 2.1 MB  00:00:00     
(2/3): epel/x86_64/updateinfo                                                                                                                       | 1.0 MB  00:00:01     
(3/3): epel/x86_64/primary_db                                                                                                                       | 6.8 MB  00:00:03     
Loading mirror speeds from cached hostfile
 * base: mirrors.aliyun.com
 * extras: mirrors.aliyun.com
 * updates: mirror.bit.edu.cn
Resolving Dependencies
--> Running transaction check
---> Package docker-compose.noarch 0:1.18.0-4.el7 will be installed
--> Processing Dependency: python36-cached_property >= 1.2.0 for package: docker-compose-1.18.0-4.el7.noarch
--> Processing Dependency: python36-docker >= 2.6.1 for package: docker-compose-1.18.0-4.el7.noarch
……省略部分內容
Installed:
  docker-compose.noarch 0:1.18.0-4.el7                                                                                                                                     

Dependency Installed:
  python36-PyYAML.x86_64 0:3.13-1.el7                 python36-cached_property.noarch 0:1.5.1-2.el7             python36-chardet.noarch 0:3.0.4-1.el7                      
  python36-docker.noarch 0:2.6.1-3.el7                python36-docker-pycreds.noarch 0:0.2.1-2.el7              python36-dockerpty.noarch 0:0.4.1-18.el7                   
  python36-docopt.noarch 0:0.6.2-8.el7                python36-idna.noarch 0:2.7-2.el7                          python36-jsonschema.noarch 0:2.5.1-4.el7                   
  python36-pysocks.noarch 0:1.6.8-7.el7               python36-requests.noarch 0:2.14.2-2.el7                   python36-six.noarch 0:1.14.0-2.el7                         
  python36-texttable.noarch 0:1.6.2-1.el7             python36-urllib3.noarch 0:1.25.6-1.el7                    python36-websocket-client.noarch 0:0.47.0-2.el7            

Complete!
[root@docker_node01 harbor]# 

  提示:docker-compose是docker容器的單機編排工具;

  7、再運行install.sh腳本

[root@docker_node01 harbor]# ./install.sh 

[Step 0]: checking if docker is installed ...

Note: docker version: 19.03.8

[Step 1]: checking docker-compose is installed ...

Note: docker-compose version: 1.18.0

[Step 2]: loading Harbor images ...
dbaf2c918102: Loading layer [==================================================>]   34.5MB/34.5MB
1f3458bb7308: Loading layer [==================================================>]  8.435MB/8.435MB
74e91bd5ca15: Loading layer [==================================================>]  6.317MB/6.317MB
82da861dccd3: Loading layer [==================================================>]  14.61MB/14.61MB
8d62f2bfdf94: Loading layer [==================================================>]  28.25MB/28.25MB
40510e398799: Loading layer [==================================================>]  22.02kB/22.02kB
6941a908d292: Loading layer [==================================================>]  49.17MB/49.17MB
Loaded image: goharbor/notary-signer-photon:v2.0.0
bd70463b9e5a: Loading layer [==================================================>]  8.441MB/8.441MB
d3927e3c53ea: Loading layer [==================================================>]  3.584kB/3.584kB
a3b2acbb8f7d: Loading layer [==================================================>]  3.072kB/3.072kB
de14f7f144ce: Loading layer [==================================================>]   9.71MB/9.71MB
94c03f31b276: Loading layer [==================================================>]  10.53MB/10.53MB
Loaded image: goharbor/clair-adapter-photon:v2.0.0
935e17d700d1: Loading layer [==================================================>]   8.44MB/8.44MB
eef8d67e9248: Loading layer [==================================================>]   42.3MB/42.3MB
a181769f3c52: Loading layer [==================================================>]  3.072kB/3.072kB
4b801e4d76d7: Loading layer [==================================================>]  3.584kB/3.584kB
7f7c81a33722: Loading layer [==================================================>]  43.12MB/43.12MB
Loaded image: goharbor/chartmuseum-photon:v2.0.0
4076b322e7f5: Loading layer [==================================================>]  49.89MB/49.89MB
da16bbe3a170: Loading layer [==================================================>]  3.584kB/3.584kB
f8967a1d9155: Loading layer [==================================================>]  3.072kB/3.072kB
6b7eaf984fde: Loading layer [==================================================>]   2.56kB/2.56kB
4406aea83cb2: Loading layer [==================================================>]  3.072kB/3.072kB
78566a971bf2: Loading layer [==================================================>]  3.584kB/3.584kB
e4e05e2ffdad: Loading layer [==================================================>]  12.29kB/12.29kB
f3bcf1de026d: Loading layer [==================================================>]  5.632kB/5.632kB
Loaded image: goharbor/harbor-log:v2.0.0
101133a0a2e6: Loading layer [==================================================>]  8.441MB/8.441MB
40eb3ab360dd: Loading layer [==================================================>]  3.584kB/3.584kB
172ace267ace: Loading layer [==================================================>]  20.94MB/20.94MB
cb361129c579: Loading layer [==================================================>]  3.072kB/3.072kB
f0221c34f9dc: Loading layer [==================================================>]  8.721MB/8.721MB
1880cedc9407: Loading layer [==================================================>]  30.48MB/30.48MB
Loaded image: goharbor/harbor-registryctl:v2.0.0
15f399ca8b42: Loading layer [==================================================>]  8.441MB/8.441MB
182251d62618: Loading layer [==================================================>]  3.584kB/3.584kB
c72ce5e8bba9: Loading layer [==================================================>]  3.072kB/3.072kB
6cb620513867: Loading layer [==================================================>]  20.94MB/20.94MB
8f68617c13e6: Loading layer [==================================================>]  21.76MB/21.76MB
Loaded image: goharbor/registry-photon:v2.0.0
464d98f962d2: Loading layer [==================================================>]  115.2MB/115.2MB
6f577ce93b49: Loading layer [==================================================>]  12.15MB/12.15MB
468b747374fb: Loading layer [==================================================>]  3.072kB/3.072kB
c7d4e40274a2: Loading layer [==================================================>]  49.15kB/49.15kB
349c2528bf8f: Loading layer [==================================================>]  3.584kB/3.584kB
50765adb1994: Loading layer [==================================================>]  13.03MB/13.03MB
Loaded image: goharbor/clair-photon:v2.0.0
f3ae9281f64f: Loading layer [==================================================>]  16.04MB/16.04MB
79de921bba64: Loading layer [==================================================>]  28.25MB/28.25MB
a4826ccd0680: Loading layer [==================================================>]  22.02kB/22.02kB
527c0492bb8a: Loading layer [==================================================>]   50.6MB/50.6MB
Loaded image: goharbor/notary-server-photon:v2.0.0
da380ff7675f: Loading layer [==================================================>]  39.44MB/39.44MB
3e72063a3c12: Loading layer [==================================================>]  3.072kB/3.072kB
87063a362784: Loading layer [==================================================>]   59.9kB/59.9kB
12042912d563: Loading layer [==================================================>]  61.95kB/61.95kB
Loaded image: goharbor/redis-photon:v2.0.0
497d39fd8ed4: Loading layer [==================================================>]  10.28MB/10.28MB
Loaded image: goharbor/nginx-photon:v2.0.0
db89bcd4a7aa: Loading layer [==================================================>]  12.22MB/12.22MB
a3c69d8e6487: Loading layer [==================================================>]  3.072kB/3.072kB
22888c961e12: Loading layer [==================================================>]   2.56kB/2.56kB
15c04c0d67b3: Loading layer [==================================================>]   46.5MB/46.5MB
5e59e5738914: Loading layer [==================================================>]  5.632kB/5.632kB
2fb21742e876: Loading layer [==================================================>]   51.2kB/51.2kB
ebe005c22385: Loading layer [==================================================>]  47.32MB/47.32MB
e91a77a1cc5d: Loading layer [==================================================>]   2.56kB/2.56kB
Loaded image: goharbor/harbor-core:v2.0.0
c9ad3414e408: Loading layer [==================================================>]  63.57MB/63.57MB
0aea7ae12d77: Loading layer [==================================================>]  60.58MB/60.58MB
c3be2cda3349: Loading layer [==================================================>]  5.632kB/5.632kB
970c1e4372ae: Loading layer [==================================================>]  2.048kB/2.048kB
51e00ddbcdac: Loading layer [==================================================>]   2.56kB/2.56kB
27d44e884cd0: Loading layer [==================================================>]   2.56kB/2.56kB
3086c2ee0489: Loading layer [==================================================>]   2.56kB/2.56kB
efd18d9ef79c: Loading layer [==================================================>]  10.24kB/10.24kB
Loaded image: goharbor/harbor-db:v2.0.0
ad0a4ed99dd0: Loading layer [==================================================>]  12.22MB/12.22MB
50121125e459: Loading layer [==================================================>]  3.072kB/3.072kB
6d05b39a8c44: Loading layer [==================================================>]   2.56kB/2.56kB
5380ddc5210f: Loading layer [==================================================>]  35.68MB/35.68MB
e8053e60aee7: Loading layer [==================================================>]   36.5MB/36.5MB
Loaded image: goharbor/harbor-jobservice:v2.0.0
9fefe33a31db: Loading layer [==================================================>]  9.741MB/9.741MB
a52a9b417697: Loading layer [==================================================>]  3.584kB/3.584kB
9b6c54642038: Loading layer [==================================================>]  3.072kB/3.072kB
6a32c528face: Loading layer [==================================================>]  20.34MB/20.34MB
526552ecb5a3: Loading layer [==================================================>]  9.317MB/9.317MB
bc3e72205f25: Loading layer [==================================================>]  30.48MB/30.48MB
Loaded image: goharbor/trivy-adapter-photon:v2.0.0
51193d3ba093: Loading layer [==================================================>]  77.29MB/77.29MB
398b7c3413c0: Loading layer [==================================================>]  48.31MB/48.31MB
cb902b44bae6: Loading layer [==================================================>]   2.56kB/2.56kB
11d3bf655c22: Loading layer [==================================================>]  1.536kB/1.536kB
3d373d988076: Loading layer [==================================================>]  18.43kB/18.43kB
755d5115a4fd: Loading layer [==================================================>]  3.751MB/3.751MB
5d456b2e2b47: Loading layer [==================================================>]  249.3kB/249.3kB
Loaded image: goharbor/prepare:v2.0.0
2128feaae029: Loading layer [==================================================>]  10.28MB/10.28MB
c1e2c6faf4a4: Loading layer [==================================================>]  8.487MB/8.487MB
8728e424e45b: Loading layer [==================================================>]  178.7kB/178.7kB
243de4b81324: Loading layer [==================================================>]  157.2kB/157.2kB
1909dd7d54dc: Loading layer [==================================================>]  33.28kB/33.28kB
e91e103cac7d: Loading layer [==================================================>]  17.41kB/17.41kB
ef43ac036ce0: Loading layer [==================================================>]  15.36kB/15.36kB
3205feaa4e7b: Loading layer [==================================================>]  3.584kB/3.584kB
Loaded image: goharbor/harbor-portal:v2.0.0


[Step 3]: preparing environment ...

[Step 4]: preparing harbor configs ...
prepare base dir is set to /usr/local/harbor
WARNING:root:WARNING: HTTP protocol is insecure. Harbor will deprecate http protocol in the future. Please make sure to upgrade to https
Clearing the configuration file: /config/log/logrotate.conf
Clearing the configuration file: /config/log/rsyslog_docker.conf
Clearing the configuration file: /config/nginx/nginx.conf
Clearing the configuration file: /config/core/env
Clearing the configuration file: /config/core/app.conf
Clearing the configuration file: /config/registry/passwd
Clearing the configuration file: /config/registry/config.yml
Clearing the configuration file: /config/registry/root.crt
Clearing the configuration file: /config/registryctl/env
Clearing the configuration file: /config/registryctl/config.yml
Clearing the configuration file: /config/db/env
Clearing the configuration file: /config/jobservice/env
Clearing the configuration file: /config/jobservice/config.yml
Generated configuration file: /config/log/logrotate.conf
Generated configuration file: /config/log/rsyslog_docker.conf
Generated configuration file: /config/nginx/nginx.conf
Generated configuration file: /config/core/env
Generated configuration file: /config/core/app.conf
Generated configuration file: /config/registry/config.yml
Generated configuration file: /config/registryctl/env
Generated configuration file: /config/registryctl/config.yml
Generated configuration file: /config/db/env
Generated configuration file: /config/jobservice/env
Creating harbor-log ... done
loaded secret from file: /data/secret/keys/secretkey
Generated configuration file: /compose_location/docker-compose.yml
Clean up the input dir

Creating harbor-db ... done
Creating harbor-core ... done
[Step 5]: starting Harbor ...
Creating nginx ... done
Creating registry ... 
Creating harbor-db ... 
Creating redis ... 
Creating harbor-portal ... 
Creating registryctl ... 
Creating harbor-core ... 
Creating harbor-jobservice ... 
Creating nginx ... 
 ----Harbor has been installed and started successfully.----
[root@docker_node01 harbor]# 

  提示:從上面的信息可以看到harbor導入了很多鏡像,然後基於各個鏡像間的關係提供配置文件,然後按照一定的依賴關係順序啟動為容器;我們用docker images 可以來看看它導入了那些鏡像

[root@docker_node01 harbor]# docker images
REPOSITORY                      TAG                 IMAGE ID            CREATED             SIZE
goharbor/chartmuseum-photon     v2.0.0              4db8d6aa63e9        3 weeks ago         127MB
goharbor/redis-photon           v2.0.0              c89ea2e53cc0        3 weeks ago         72.2MB
goharbor/trivy-adapter-photon   v2.0.0              6122c52b7e48        3 weeks ago         103MB
goharbor/clair-adapter-photon   v2.0.0              dd2210cb7f53        3 weeks ago         62MB
goharbor/clair-photon           v2.0.0              f7c7fcc52278        3 weeks ago         171MB
goharbor/notary-server-photon   v2.0.0              983ac10ed8be        3 weeks ago         143MB
goharbor/notary-signer-photon   v2.0.0              bee1b6d75e0d        3 weeks ago         140MB
goharbor/harbor-registryctl     v2.0.0              c53c32d58d04        3 weeks ago         102MB
goharbor/registry-photon        v2.0.0              afdc1b7ada36        3 weeks ago         84.5MB
goharbor/nginx-photon           v2.0.0              17892f03e56c        3 weeks ago         43.6MB
goharbor/harbor-log             v2.0.0              5f8ff08e795c        3 weeks ago         82MB
goharbor/harbor-jobservice      v2.0.0              c68a2495bf55        3 weeks ago         116MB
goharbor/harbor-core            v2.0.0              3aa3af64baf8        3 weeks ago         138MB
goharbor/harbor-portal          v2.0.0              e0b1d3c894c4        3 weeks ago         52.4MB
goharbor/harbor-db              v2.0.0              5c76f0296cec        3 weeks ago         154MB
goharbor/prepare                v2.0.0              7266d49995ed        3 weeks ago         158MB
[root@docker_node01 harbor]# docker ps -a
CONTAINER ID        IMAGE                                COMMAND                  CREATED             STATUS                   PORTS                       NAMES
909486114bab        goharbor/nginx-photon:v2.0.0         "nginx -g 'daemon of…"   2 minutes ago       Up 2 minutes (healthy)   0.0.0.0:80->8080/tcp        nginx
201af4781190        goharbor/harbor-jobservice:v2.0.0    "/harbor/entrypoint.…"   2 minutes ago       Up 2 minutes (healthy)                               harbor-jobservice
d926598a1b4b        goharbor/harbor-core:v2.0.0          "/harbor/entrypoint.…"   2 minutes ago       Up 2 minutes (healthy)                               harbor-core
b655e8bb9da3        goharbor/harbor-portal:v2.0.0        "nginx -g 'daemon of…"   2 minutes ago       Up 2 minutes (healthy)   8080/tcp                    harbor-portal
596d050acf8b        goharbor/registry-photon:v2.0.0      "/home/harbor/entryp…"   2 minutes ago       Up 2 minutes (healthy)   5000/tcp                    registry
88a6b3335d25        goharbor/harbor-registryctl:v2.0.0   "/home/harbor/start.…"   2 minutes ago       Up 2 minutes (healthy)                               registryctl
cf8db1840524        goharbor/harbor-db:v2.0.0            "/docker-entrypoint.…"   2 minutes ago       Up 2 minutes (healthy)   5432/tcp                    harbor-db
5d522f8f3c38        goharbor/redis-photon:v2.0.0         "redis-server /etc/r…"   2 minutes ago       Up 2 minutes (healthy)   6379/tcp                    redis
020fbf3571a2        goharbor/harbor-log:v2.0.0           "/bin/sh -c /usr/loc…"   2 minutes ago       Up 2 minutes (healthy)   127.0.0.1:1514->10514/tcp   harbor-log
[root@docker_node01 harbor]# 

  提示:可以看到本地倉庫中多了很多鏡像,同時也啟動了很多容器;其中名為nginx的容器把80端口暴露到數組機上了;到此harbor就安裝好了;接下來我們訪問宿主機的80端口看看是否能夠訪問到harbor

  提示:以上就是harbor的web 頁面,默認用戶名是admin密碼是Harbor12345

  登錄harbor web頁面

  提示:我們就可以基於這個web頁面來做管理了;接下來我們先創建一個用戶和項目,然後在通過docker push上傳鏡像到harbor上

  創建用戶

  提示:填寫好以上信息,點擊確定用戶就創建好了;

  創建項目

   提示:如果創建的項目是私有的,把訪問級別後面的公開對勾取消即可

  從別的docker主機上上傳鏡像到harbor

  提示:使用非https的倉庫必須要在daemon.json文件中配置insecure-registries來聲明不安全的鏡像倉庫地址;

  提示:這裏提示我們未授權;接下來我們去web管理頁面授權qiuhom是test項目的成員;

  提示:現在我們把qiuhom這個用戶設置為test這個項目的管理員,現在我們在以qiuhom的身份推鏡像到test項目中,看看是否能夠成功把進行推送到harbor上?

[root@docker_node02 ~]# docker push node01.docker-registry.io/test/nginx:1.14-alpine
The push refers to repository [node01.docker-registry.io/test/nginx]
076c58d2644f: Pushed 
b2cbae4b8c15: Pushed 
5ac9a5170bf2: Pushed 
a464c54f93a9: Pushed 
1.14-alpine: digest: sha256:a3a0c4126587884f8d3090efca87f5af075d7e7ac8308cffc09a5a082d5f4760 size: 1153
[root@docker_node02 ~]# 

  提示:這次推送鏡像沒有報錯,我們去web頁面中看看鏡像是否推送到test項目中去了?

  驗證:在harborweb界面看看是否有我們推上去的鏡像?

  用其他docker主機下載harbor上的鏡像

  提示:可以看到現在我們搭建的harbor是可以正常下載和上傳鏡像的;管理鏡像我們可以通過web頁面管理即可,我這裏就不去演示了;接下來我們再來說說在命令行用docker-compose啟動harbor和停止harbor吧

  停止harbor

  提示:用docker-compose停止harbor需要先進入到harbor目錄下,然後執行docker-compose stop 這條命令會去尋找docker-compose.yml文件,根據文件中定義的服務來停止容器;這個有點類似docker build命令,找Dockerfile文件,而docker-compose 是找docker-compose.yml;這裏還需要注意一點的是這個文件名必須是docker-compose.yml;

  啟動harbor

  提示:啟動huabor同停止harbor一樣都必須在docker-compose.yml文件所在目錄下執行docker-compose start 或docker-compose up -d ;

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

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

分類
發燒車訊

重學 Java 設計模式:實戰組合模式(營銷差異化人群發券,決策樹引擎搭建場景)

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

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

一、前言

小朋友才做選擇題,成年人我都要

頭幾年只要群里一問我該學哪個開發語言,哪個語言最好。群里肯定聊的特別火熱,有人支持PHP、有人喊號Java、也有C++和C#。但這幾年開始好像大家並不會真的刀槍棍棒、斧鉞鈎叉般討論了,大多數時候都是開玩笑的鬧一鬧。於此同時在整體的互聯網開發中很多時候是一些開發語言公用的,共同打造整體的生態圈。而大家選擇的方式也是更偏向於不同領域下選擇適合的架構,而不是一味地追求某個語言。這可以給很多初學編程的新人一些提議,不要刻意的覺得某個語言好,某個語言不好,只是在適合的場景下選擇最需要的。而你要選擇的那個語言可以參考招聘網站的需求量和薪資水平決定。

編程開發不是炫技

總會有人喜歡在整體的項目開發中用上點新特性,把自己新學的知識實踐試試。不能說這樣就是不好,甚至可以說這是一部分很熱愛學習的人,喜歡創新,喜歡實踐。但編程除了用上新特性外,還需要考慮整體的擴展性、可讀性、可維護、易擴展等方面的考慮。就像你家裡雇傭了一夥裝修師傅,有那麼一個小工喜歡炫技搞花活,在家的淋浴下安裝了馬桶。

即使是寫CRUD也應該有設計模式

往往很多大需求都是通過增刪改查堆出來的,今天要一個需求if一下,明天加個內容else擴展一下。日積月累需求也就越來越大,擴展和維護的成本也就越來越高。往往大部分研發是不具備產品思維和整體業務需求導向的,總以為寫好代碼完成功能即可。但這樣的不考慮擴展性的實現,很難讓後續的需求都快速迭代,久而久之就會被陷入惡性循環,每天都有bug要改。

二、開發環境

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

三、組合模式介紹

從上圖可以看到這有點像螺絲和螺母,通過一堆的鏈接組織出一棵結構樹。而這種通過把相似對象(也可以稱作是方法)組合成一組可被調用的結構樹對象的設計思路叫做組合模式。

這種設計方式可以讓你的服務組節點進行自由組合對外提供服務,例如你有三個原子校驗功能(A:身份證B:銀行卡C:手機號)服務並對外提供調用使用。有些調用方需要使用AB組合,有些調用方需要使用到CBA組合,還有一些可能只使用三者中的一個。那麼這個時候你就可以使用組合模式進行構建服務,對於不同類型的調用方配置不同的組織關係樹,而這個樹結構你可以配置到數據庫中也可以不斷的通過圖形界面來控制樹結構。

所以不同的設計模式用在恰當好處的場景可以讓代碼邏輯非常清晰並易於擴展,同時也可以減少團隊新增人員對項目的學習成本。

四、案例場景模擬

以上是一個非常簡化版的營銷規則決策樹,根據性別年齡來發放不同類型的優惠券,來刺激消費起到精準用戶促活的目的。

雖然一部分小夥伴可能並沒有開發過營銷場景,但你可能時時刻刻的被營銷着。比如你去經常瀏覽男性喜歡的机械鍵盤、筆記本電腦、汽車裝飾等等,那麼久給你推薦此類的優惠券刺激你消費。那麼如果你購物不多,或者錢不在自己手裡。那麼你是否打過車,有一段時間經常有小夥伴喊,為什麼同樣的距離他就10元,我就15元呢?其實這些都是被營銷的案例,一般對於不常使用軟件的小夥伴,經常會進行稍微大力度的促活,增加用戶粘性。

那麼在這裏我們就模擬一個類似的決策場景,體現出組合模式在其中起到的重要性。另外,組合模式不只是可以運用於規則決策樹,還可以做服務包裝將不同的接口進行組合配置,對外提供服務能力,減少開發成本。

五、用一坨坨代碼實現

這裏我們舉一個關於ifelse誕生的例子,介紹小姐姐與程序員‍‍之間的故事導致的事故

日期 需求 緊急程度 程序員(話外音)
星期一.早上 猿哥哥,老闆說要搞一下營銷拉拉量,給男生女生髮不同的優惠券,促活消費。 很緊急,下班就要 行吧,也不難,加下判斷就上線
星期二.下午 小哥哥,咱們上線后非常好。要讓咱們按照年輕、中年、成年,不同年齡加下判斷,準確刺激消費。 超緊急,明天就要 也不難,加就加吧
星期三.晚上 喂,小哥哥!睡了嗎!老闆說咱們這次活動很成功,可以不可以在細分下,把單身、結婚、有娃的都加上不同判斷。這樣更能刺激用戶消費。 賊緊急,最快上線。 已經意識到ifelse越來越多了
星期四.凌晨 哇!小哥哥你們太棒了,上的真快。嘻嘻!有個小請求,需要調整下年齡段,因為現在學生處對象的都比較早,有對象的更容易買某某某東西。要改下值!辛苦辛苦! 老闆,在等着呢! 一大片的值要修改,哎!這麼多ifelse
星期五.半夜 歪歪喂!巴巴,壞了,怎麼發的優惠券不對了,有客訴了,很多女生都來投訴。你快看看。老闆,他… (一頭汗),哎,值粘錯位置了! 終究還是一個人扛下了所有

1. 工程結構

itstack-demo-design-8-01
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                └── EngineController.java
  • 公司里要都是這樣的程序員絕對省下不少成本,根本不要搭建微服務,一個工程搞定所有業務!
  • 但千萬不要這麼干!酒肉穿腸過,佛祖心中留。世人若學我,如同進魔道。

2. 代碼實現

public class EngineController {

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

    public String process(final String userId, final String userSex, final int userAge) {

        logger.info("ifelse實現方式判斷用戶結果。userId:{} userSex:{} userAge:{}", userId, userSex, userAge);

        if ("man".equals(userSex)) {
            if (userAge < 25) {
                return "果實A";
            }

            if (userAge >= 25) {
                return "果實B";
            }
        }

        if ("woman".equals(userSex)) {
            if (userAge < 25) {
                return "果實C";
            }

            if (userAge >= 25) {
                return "果實D";
            }
        }

        return null;

    }

}
  • 除了我們說的擴展性和每次的維護以外,這樣的代碼實現起來是最快的。而且從樣子來看也很適合新人理解。
  • 但是我勸你別寫,寫這樣代碼不是被扣績效就是被開除。

3. 測試驗證

3.1 編寫測試類

@Test
public void test_EngineController() {
    EngineController engineController = new EngineController();
    String process = engineController.process("Oli09pLkdjh", "man", 29);
    logger.info("測試結果:{}", process);
}
  • 這裏我們模擬了一個用戶ID,並傳輸性別:man、年齡:29,我們的預期結果是:果實B。實際對應業務就是給頭禿的程序員發一張枸杞優惠券

3.2 測試結果

22:10:12.891 [main] INFO  o.i.demo.design.EngineController - ifelse實現方式判斷用戶結果。userId:Oli09pLkdjh userSex:man userAge:29
22:10:12.898 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:果實B

Process finished with exit code 0
  • 從測試結果上看我們的程序運行正常並且符合預期,只不過實現上並不是我們推薦的。接下來我們會採用組合模式來優化這部分代碼。

六、組合模式重構代碼

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

接下來的重構部分代碼改動量相對來說會比較大一些,為了讓我們可以把不同類型的決策節點和最終的果實組裝成一棵可被運行的決策樹,需要做適配設計和工廠方法調用,具體會體現在定義接口以及抽象類和初始化配置決策節點(性別年齡)上。建議這部分代碼多閱讀幾次,最好實踐下。

1. 工程結構

itstack-demo-design-8-02
└── src
    ├── main
    │   └── java
    │      └── org.itstack.demo.design.domain
    │          ├── model
    │          │   ├── aggregates
    │          │   │   └── TreeRich.java
    │          │   └── vo
    │          │       ├── EngineResult.java
    │          │       ├── TreeNode.java
    │          │       ├── TreeNodeLink.java    
    │          │       └── TreeRoot.java	
    │          └── service
    │              ├── engine
    │              │   ├── impl	
    │              │   │   └── TreeEngineHandle.java	   
    │              │   ├── EngineBase.java 
    │              │   ├── EngineConfig.java       
    │              │   └── IEngine.java	
    │              └── logic
    │                  ├── impl	
    │                  │   ├── LogicFilter.java	 
    │                  │   └── LogicFilter.java	    
    │                  └── LogicFilter.java	
    └── test
         └── java
             └── org.itstack.demo.design.test
                 └── ApiTest.java

組合模式模型結構

  • 首先可以看下黑色框框的模擬指導樹結構;11112111112121122,這是一組樹結構的ID,並由節點串聯組合出一棵關係樹樹。

  • 接下來是類圖部分,左側是從LogicFilter開始定義適配的決策過濾器,BaseLogic是對接口的實現,提供最基本的通用方法。UserAgeFilterUserGenerFilter,是兩個具體的實現類用於判斷年齡性別

  • 最後則是對這顆可以被組織出來的決策樹,進行執行的引擎。同樣定義了引擎接口和基礎的配置,在配置裏面設定了需要的模式決策節點。

    • static {
           logicFilterMap = new ConcurrentHashMap<>();
           logicFilterMap.put("userAge", new UserAgeFilter());
           logicFilterMap.put("userGender", new UserGenderFilter());
      }
      
  • 接下來會對每一個類進行細緻的講解,如果感覺沒有讀懂一定是我作者的表述不夠清晰,可以添加我的微信(fustack)與我交流。

2. 代碼實現

2.1 基礎對象

包路徑 介紹
model.aggregates TreeRich 聚合對象,包含組織樹信息
model.vo EngineResult 決策返回對象信息
model.vo TreeNode 樹節點;子恭弘=叶 恭弘節點、果實節點
model.vo TreeNodeLink 樹節點鏈接鏈路
model.vo TreeRoot 樹根信息
  • 以上這部分簡單介紹,不包含邏輯只是各項必要屬性的get/set,整個源代碼可以通過關注微信公眾號:bugstack蟲洞棧,回復源碼下載打開鏈接獲取。

2.2 樹節點邏輯過濾器接口

public interface LogicFilter {

    /**
     * 邏輯決策器
     *
     * @param matterValue          決策值
     * @param treeNodeLineInfoList 決策節點
     * @return 下一個節點Id
     */
    Long filter(String matterValue, List<TreeNodeLink> treeNodeLineInfoList);

    /**
     * 獲取決策值
     *
     * @param decisionMatter 決策物料
     * @return 決策值
     */
    String matterValue(Long treeId, String userId, Map<String, String> decisionMatter);

}
  • 這一部分定義了適配的通用接口,邏輯決策器、獲取決策值,讓每一個提供決策能力的節點都必須實現此接口,保證統一性。

2.3 決策抽象類提供基礎服務

public abstract class BaseLogic implements LogicFilter {

    @Override
    public Long filter(String matterValue, List<TreeNodeLink> treeNodeLinkList) {
        for (TreeNodeLink nodeLine : treeNodeLinkList) {
            if (decisionLogic(matterValue, nodeLine)) return nodeLine.getNodeIdTo();
        }
        return 0L;
    }

    @Override
    public abstract String matterValue(Long treeId, String userId, Map<String, String> decisionMatter);

    private boolean decisionLogic(String matterValue, TreeNodeLink nodeLink) {
        switch (nodeLink.getRuleLimitType()) {
            case 1:
                return matterValue.equals(nodeLink.getRuleLimitValue());
            case 2:
                return Double.parseDouble(matterValue) > Double.parseDouble(nodeLink.getRuleLimitValue());
            case 3:
                return Double.parseDouble(matterValue) < Double.parseDouble(nodeLink.getRuleLimitValue());
            case 4:
                return Double.parseDouble(matterValue) <= Double.parseDouble(nodeLink.getRuleLimitValue());
            case 5:
                return Double.parseDouble(matterValue) >= Double.parseDouble(nodeLink.getRuleLimitValue());
            default:
                return false;
        }
    }

}
  • 在抽象方法中實現了接口方法,同時定義了基本的決策方法;1、2、3、4、5等於、小於、大於、小於等於、大於等於的判斷邏輯。
  • 同時定義了抽象方法,讓每一個實現接口的類都必須按照規則提供決策值,這個決策值用於做邏輯比對。

2.4 樹節點邏輯實現類

年齡節點

public class UserAgeFilter extends BaseLogic {

    @Override
    public String matterValue(Long treeId, String userId, Map<String, String> decisionMatter) {
        return decisionMatter.get("age");
    }

}

性別節點

public class UserGenderFilter extends BaseLogic {

    @Override
    public String matterValue(Long treeId, String userId, Map<String, String> decisionMatter) {
        return decisionMatter.get("gender");
    }

}
  • 以上兩個決策邏輯的節點獲取值的方式都非常簡單,只是獲取用戶的入參即可。實際的業務開發可以從數據庫、RPC接口、緩存運算等各種方式獲取。

2.5 決策引擎接口定義

public interface IEngine {

    EngineResult process(final Long treeId, final String userId, TreeRich treeRich, final Map<String, String> decisionMatter);

}
  • 對於使用方來說也同樣需要定義統一的接口操作,這樣的好處非常方便後續拓展出不同類型的決策引擎,也就是可以建造不同的決策工廠。

2.6 決策節點配置

public class EngineConfig {

    static Map<String, LogicFilter> logicFilterMap;

    static {
        logicFilterMap = new ConcurrentHashMap<>();
        logicFilterMap.put("userAge", new UserAgeFilter());
        logicFilterMap.put("userGender", new UserGenderFilter());
    }

    public Map<String, LogicFilter> getLogicFilterMap() {
        return logicFilterMap;
    }

    public void setLogicFilterMap(Map<String, LogicFilter> logicFilterMap) {
        this.logicFilterMap = logicFilterMap;
    }

}
  • 在這裏將可提供服務的決策節點配置到map結構中,對於這樣的map結構可以抽取到數據庫中,那麼就可以非常方便的管理。

2.7 基礎決策引擎功能

public abstract class EngineBase extends EngineConfig implements IEngine {

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

    @Override
    public abstract EngineResult process(Long treeId, String userId, TreeRich treeRich, Map<String, String> decisionMatter);

    protected TreeNode engineDecisionMaker(TreeRich treeRich, Long treeId, String userId, Map<String, String> decisionMatter) {
        TreeRoot treeRoot = treeRich.getTreeRoot();
        Map<Long, TreeNode> treeNodeMap = treeRich.getTreeNodeMap();
        // 規則樹根ID
        Long rootNodeId = treeRoot.getTreeRootNodeId();
        TreeNode treeNodeInfo = treeNodeMap.get(rootNodeId);
        //節點類型[NodeType];1子恭弘=叶 恭弘、2果實
        while (treeNodeInfo.getNodeType().equals(1)) {
            String ruleKey = treeNodeInfo.getRuleKey();
            LogicFilter logicFilter = logicFilterMap.get(ruleKey);
            String matterValue = logicFilter.matterValue(treeId, userId, decisionMatter);
            Long nextNode = logicFilter.filter(matterValue, treeNodeInfo.getTreeNodeLinkList());
            treeNodeInfo = treeNodeMap.get(nextNode);
            logger.info("決策樹引擎=>{} userId:{} treeId:{} treeNode:{} ruleKey:{} matterValue:{}", treeRoot.getTreeName(), userId, treeId, treeNodeInfo.getTreeNodeId(), ruleKey, matterValue);
        }
        return treeNodeInfo;
    }

}
  • 這裏主要提供決策樹流程的處理過程,有點像通過鏈路的關係(性別年齡)在二叉樹中尋找果實節點的過程。
  • 同時提供一個抽象方法,執行決策流程的方法供外部去做具體的實現。

2.8 決策引擎的實現

public class TreeEngineHandle extends EngineBase {

    @Override
    public EngineResult process(Long treeId, String userId, TreeRich treeRich, Map<String, String> decisionMatter) {
        // 決策流程
        TreeNode treeNode = engineDecisionMaker(treeRich, treeId, userId, decisionMatter);
        // 決策結果
        return new EngineResult(userId, treeId, treeNode.getTreeNodeId(), treeNode.getNodeValue());
    }

}
  • 這裏對於決策引擎的實現就非常簡單了,通過傳遞進來的必要信息;決策樹信息、決策物料值,來做具體的樹形結構決策。

3. 測試驗證

3.1 組裝樹關係

@Before
public void init() {
    // 節點:1
    TreeNode treeNode_01 = new TreeNode();
    treeNode_01.setTreeId(10001L);
    treeNode_01.setTreeNodeId(1L);
    treeNode_01.setNodeType(1);
    treeNode_01.setNodeValue(null);
    treeNode_01.setRuleKey("userGender");
    treeNode_01.setRuleDesc("用戶性別[男/女]");
    // 鏈接:1->11
    TreeNodeLink treeNodeLink_11 = new TreeNodeLink();
    treeNodeLink_11.setNodeIdFrom(1L);
    treeNodeLink_11.setNodeIdTo(11L);
    treeNodeLink_11.setRuleLimitType(1);
    treeNodeLink_11.setRuleLimitValue("man");
    // 鏈接:1->12
    TreeNodeLink treeNodeLink_12 = new TreeNodeLink();
    treeNodeLink_12.setNodeIdTo(1L);
    treeNodeLink_12.setNodeIdTo(12L);
    treeNodeLink_12.setRuleLimitType(1);
    treeNodeLink_12.setRuleLimitValue("woman");
    List<TreeNodeLink> treeNodeLinkList_1 = new ArrayList<>();
    treeNodeLinkList_1.add(treeNodeLink_11);
    treeNodeLinkList_1.add(treeNodeLink_12);
    treeNode_01.setTreeNodeLinkList(treeNodeLinkList_1);
    // 節點:11
    TreeNode treeNode_11 = new TreeNode();
    treeNode_11.setTreeId(10001L);
    treeNode_11.setTreeNodeId(11L);
    treeNode_11.setNodeType(1);
    treeNode_11.setNodeValue(null);
    treeNode_11.setRuleKey("userAge");
    treeNode_11.setRuleDesc("用戶年齡");
    // 鏈接:11->111
    TreeNodeLink treeNodeLink_111 = new TreeNodeLink();
    treeNodeLink_111.setNodeIdFrom(11L);
    treeNodeLink_111.setNodeIdTo(111L);
    treeNodeLink_111.setRuleLimitType(3);
    treeNodeLink_111.setRuleLimitValue("25");
    // 鏈接:11->112
    TreeNodeLink treeNodeLink_112 = new TreeNodeLink();
    treeNodeLink_112.setNodeIdFrom(11L);
    treeNodeLink_112.setNodeIdTo(112L);
    treeNodeLink_112.setRuleLimitType(5);
    treeNodeLink_112.setRuleLimitValue("25");
    List<TreeNodeLink> treeNodeLinkList_11 = new ArrayList<>();
    treeNodeLinkList_11.add(treeNodeLink_111);
    treeNodeLinkList_11.add(treeNodeLink_112);
    treeNode_11.setTreeNodeLinkList(treeNodeLinkList_11);
    // 節點:12
    TreeNode treeNode_12 = new TreeNode();
    treeNode_12.setTreeId(10001L);
    treeNode_12.setTreeNodeId(12L);
    treeNode_12.setNodeType(1);
    treeNode_12.setNodeValue(null);
    treeNode_12.setRuleKey("userAge");
    treeNode_12.setRuleDesc("用戶年齡");
    // 鏈接:12->121
    TreeNodeLink treeNodeLink_121 = new TreeNodeLink();
    treeNodeLink_121.setNodeIdFrom(12L);
    treeNodeLink_121.setNodeIdTo(121L);
    treeNodeLink_121.setRuleLimitType(3);
    treeNodeLink_121.setRuleLimitValue("25");
    // 鏈接:12->122
    TreeNodeLink treeNodeLink_122 = new TreeNodeLink();
    treeNodeLink_122.setNodeIdFrom(12L);
    treeNodeLink_122.setNodeIdTo(122L);
    treeNodeLink_122.setRuleLimitType(5);
    treeNodeLink_122.setRuleLimitValue("25");
    List<TreeNodeLink> treeNodeLinkList_12 = new ArrayList<>();
    treeNodeLinkList_12.add(treeNodeLink_121);
    treeNodeLinkList_12.add(treeNodeLink_122);
    treeNode_12.setTreeNodeLinkList(treeNodeLinkList_12);
    // 節點:111
    TreeNode treeNode_111 = new TreeNode();
    treeNode_111.setTreeId(10001L);
    treeNode_111.setTreeNodeId(111L);
    treeNode_111.setNodeType(2);
    treeNode_111.setNodeValue("果實A");
    // 節點:112
    TreeNode treeNode_112 = new TreeNode();
    treeNode_112.setTreeId(10001L);
    treeNode_112.setTreeNodeId(112L);
    treeNode_112.setNodeType(2);
    treeNode_112.setNodeValue("果實B");
    // 節點:121
    TreeNode treeNode_121 = new TreeNode();
    treeNode_121.setTreeId(10001L);
    treeNode_121.setTreeNodeId(121L);
    treeNode_121.setNodeType(2);
    treeNode_121.setNodeValue("果實C");
    // 節點:122
    TreeNode treeNode_122 = new TreeNode();
    treeNode_122.setTreeId(10001L);
    treeNode_122.setTreeNodeId(122L);
    treeNode_122.setNodeType(2);
    treeNode_122.setNodeValue("果實D");
    // 樹根
    TreeRoot treeRoot = new TreeRoot();
    treeRoot.setTreeId(10001L);
    treeRoot.setTreeRootNodeId(1L);
    treeRoot.setTreeName("規則決策樹");
    Map<Long, TreeNode> treeNodeMap = new HashMap<>();
    treeNodeMap.put(1L, treeNode_01);
    treeNodeMap.put(11L, treeNode_11);
    treeNodeMap.put(12L, treeNode_12);
    treeNodeMap.put(111L, treeNode_111);
    treeNodeMap.put(112L, treeNode_112);
    treeNodeMap.put(121L, treeNode_121);
    treeNodeMap.put(122L, treeNode_122);
    treeRich = new TreeRich(treeRoot, treeNodeMap);
}

  • 重要,這一部分是組合模式非常重要的使用,在我們已經建造好的決策樹關係下,可以創建出樹的各個節點,以及對節點間使用鏈路進行串聯。
  • 及時後續你需要做任何業務的擴展都可以在裏面添加相應的節點,並做動態化的配置。
  • 關於這部分手動組合的方式可以提取到數據庫中,那麼也就可以擴展到圖形界面的進行配置操作。

3.2 編寫測試類

@Test
public void test_tree() {
    logger.info("決策樹組合結構信息:\r\n" + JSON.toJSONString(treeRich));
    
    IEngine treeEngineHandle = new TreeEngineHandle();
    Map<String, String> decisionMatter = new HashMap<>();
    decisionMatter.put("gender", "man");
    decisionMatter.put("age", "29");
    
    EngineResult result = treeEngineHandle.process(10001L, "Oli09pLkdjh", treeRich, decisionMatter);
    
    logger.info("測試結果:{}", JSON.toJSONString(result));
}
  • 在這裏提供了調用的通過組織模式創建出來的流程決策樹,調用的時候傳入了決策樹的ID,那麼如果是業務開發中就可以方便的解耦決策樹與業務的綁定關係,按需傳入決策樹ID即可。
  • 此外入參我們還提供了需要處理;(man)、年齡(29歲),的參數信息。

3.3 測試結果

23:35:05.711 [main] INFO  o.i.d.d.d.service.engine.EngineBase - 決策樹引擎=>規則決策樹 userId:Oli09pLkdjh treeId:10001 treeNode:11 ruleKey:userGender matterValue:man
23:35:05.712 [main] INFO  o.i.d.d.d.service.engine.EngineBase - 決策樹引擎=>規則決策樹 userId:Oli09pLkdjh treeId:10001 treeNode:112 ruleKey:userAge matterValue:29
23:35:05.715 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:{"nodeId":112,"nodeValue":"果實B","success":true,"treeId":10001,"userId":"Oli09pLkdjh"}

Process finished with exit code 0
  • 從測試結果上看這與我們使用ifelse是一樣的,但是目前這與的組合模式設計下,就非常方便後續的拓展和修改。
  • 整體的組織關係框架以及調用決策流程已經搭建完成,如果閱讀到此沒有完全理解,可以下載代碼觀察結構並運行調試。

七、總結

  • 從以上的決策樹場景來看,組合模式的主要解決的是一系列簡單邏輯節點或者擴展的複雜邏輯節點在不同結構的組織下,對於外部的調用是仍然可以非常簡單的。
  • 這部分設計模式保證了開閉原則,無需更改模型結構你就可以提供新的邏輯節點的使用並配合組織出新的關係樹。但如果是一些功能差異化非常大的接口進行包裝就會變得比較困難,但也不是不能很好的處理,只不過需要做一些適配和特定化的開發。
  • 很多時候因為你的極致追求和稍有倔強的工匠精神,即使在面對同樣的業務需求,你能完成出最好的代碼結構和最易於擴展的技術架構。不要被遠不能給你指導提升能力的影響到放棄自己的追求!

八、推薦閱讀

  • 1. 重學 Java 設計模式:實戰工廠方法模式(多種類型商品發獎場景)
  • 2. 重學 Java 設計模式:實戰抽象工廠模式(替換Redis雙集群升級場景)
  • 3. 重學 Java 設計模式:實戰建造者模式(裝修物料組合套餐選配場景)
  • 4. 重學 Java 設計模式:實戰原型模式(多套試每人題目和答案亂序場景)
  • 5. 重學 Java 設計模式:實戰橋接模式(多支付渠道「微信、支付寶」與多支付模式「刷臉、指紋」場景)

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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

分類
發燒車訊

.NET開發者省份分佈排名

 

什麼叫.NET開發者省份分佈排名呢? 顧名思義,這幾個詞大家都認識,.NET開發者都集中在城市,涵蓋一線城市到五線城市。排名的方法非常簡單粗暴,就是根據本公眾號(dotnet跨平台)的省份訂閱讀者數量排名的微信大數據分析。

本號從2015年初的三位數訂閱到現在五位數的訂閱,目前總數6.2w,增長一直平緩從未有過暴增,這显示了傳播和反饋的自主選擇,目前每天還在增長。同時我注意到一個現象:由於公眾號內容都是.NET Core相關的,對.NET 不感興趣的人,壓根就讀不下去。

從訂閱年齡看,高達99%的人落在18歲到60歲的區間且分佈正態,這正是我國勞動人口的年齡, 25歲以下只有20%,所以訂閱並不是以大學生為主,這也反映了現在高校中.NET 的教學比較少或者還是以.NET Framework的老舊內容;60歲以上極少,而所謂的“大專家”群體落在這個區間。

從地域分佈看,訂閱讀者分佈在300多個地級市,幾乎完整覆蓋全國。我的微信好友還不到5000個,遠遠達不到這個廣度,因此傳播是自發形成的。

排名中也提供了海外訂閱的比例。我們從中可以看到海外華人佔比3.22%,按人口比例還是很突出的,有大量的.NET開發到北美打拚,那邊的.NET環境要比國內好很多

這些數據都是藉助於微信的大數據,其實後台是根據註冊IP判斷地址的,會有少量遷移但不影響結果。這裏的6萬樣本相對於程序員群體來說,聚焦於.NET開發領域這個數據根據統計學原理,差異的顯著性已經足夠,具體我不展開了。

下面我們來看下主要省份排名:

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

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