原定 #4 就提到的 "抽像化",竟然被我連拖兩期,拖到 #6 才提到它... 人老了果然比較囉唆... 在前面的幾篇,重點都在如何 "具體" 的描述 "生命遊戲" 裡的細胞。不過現在要把這程式擴大到能容納各種不同的生物,先作好抽像化的工作是必要的...。
一般物件導向所指的 "抽像化",是指你對某些事物的一般概念。比如有人問你:
"你會開車嗎?"
你腦袋裡想的應該是一般印像中的車子,有方向盤,排檔打下去,油門踩了就會前進,煞車踩了就會停下來...,這就是你對 "車子" 的抽像化。你不會去管車子是什麼牌的,什麼顏色,是二門跑車,或是休旅車之類的細節... 而你 "會" 開的車,也不會因為這些細節,有太大的不同。
這樣的抽像化概念,套用到考駕照這件事來說,你只要知道方向盤,油門等等的用法,同時也練習過,能正確的控制教練車,通過測驗,監理所就會發張駕照給你,證明你會開一般的車子。就算是在你學會開車後十年才上市發表的新車也是一樣。
看起來沒什麼了不起的描述,在電腦的世界裡可不是這麼一回事。Microsoft Word 1.0 想要順利開啟 Microsoft Word 6.0 的檔案,大概想都不用想,因為 1.0 版設計之初,有太多 6.0 版的變化是無法事先預料的,自然無法設計出能正確操作的程式,這現像在電腦的世界很正常。不過如果你兩年前考到的駕照,碰到兩年後的新車你就不會開了,甚至監理所還要求你重考張新的架照... 那這駕照等於一點用都沒有。中間的差別,就在於駕駛者對於開車的認知,跟實際的車子,中間是隔著一層 "抽像化" 的概念,而只要能掌握這抽像化的定義,就能順利操作未來的車種。
因此物件技術不斷的想要模擬這樣的關係,就發展出繼承這樣的方式,來表達這個概念。先用一個類別 (base class) 或是介面 (interface) 來表達這個 "抽像化" 的概念,而不表達細節。其它要跟它互動的程式,只能透過這個抽像化型別來溝通,而其它的細節或實作,則被藏在裡面,或是衍生類別。中間的故事我就不再多說了,再說我就直接去寫 OOP 的書好了 =_=,有興趣可以參考這本經典 [世紀末軟體革命],有復刻版喔。套用到我們的 "生命遊戲" 裡,要定義的就是 "世界" 如何跟 "生命" 互動? 之間的關係是什麼? 另外就是 "生命" 有各種不同的型態,所有的 "生命" 型態是否都能順利的在同一個 "世界" 裡生存?
先試著用簡單文字來描述吧。在我們的定義裡,世界是個 M * N 的棋盤,每一格都能放一個生物。每個生物有自己的狀態 (生/死),也會隨著時間與環境的不同,讓生物的狀態產生變化。畫成 UML 的 class diagram, 大概就像這樣 (手邊沒工具,用 power point 大概畫一下… Orz):
我們在撰寫程式時,就必需思考題目中講到的生物各種特性,那些是所有的生命共有的特色? 這部份要把它定義在 Life … 另一部份是某種細胞特有的,則要放在衍生類別 Cell 裡。而世界必需要能跟生命作適當的互動,讓生命的進行能繼續下去。這樣的架構好處是,未來如果有第二種 Cell 或是其它的生物,只要是從 Life 繼承下來,都能很順利的在 World 裡活著,因為物件導向技術的 "抽像化" 概念,保證這樣程式的可行性。
好,我們就以需要跟 World 接觸跟互動的部份為主,把原程式的 Cell 抽離出來,放到它的上層類別 Life 裡。這也是物件技術裡常提到的 "generalization" (一般化),越一般的特性要越往上層類別移動,而越往下就是 "specialization" (特殊化),底層的類別要去實作特殊的部份,或是特有的細節。
先把原程式作好調整吧。原 Cell 的程式碼,部份被搬移到 Life, 同時這兩個類別有了繼承關係,如下:
Life 的部份,定義了所有 Life 都該表達出來的特性,也就是我們對於 Life 的認知,都應該描述在裡面,像是 Life 活在 World (CurrentWorld) 裡,會有它的座標 (PosX, PosY), 也會有它在這個棋盤內顯示的方式 (DisplayText) 等。而跟 World 互動的方面,Life 則透過 GetNextWorldTask( ) 來讓 World 來讓 Life 驅動它生命的進行。
在 World 的這邊,不管是那種 Life 衍生類別的物件,一律都當成 Life 的 "抽像概念" 來操作。這樣的優點,在還不曉得未來這世界到底還有多少種不同的 Life 會在裡面生活時,主要程式就能開發了。未來 Life 可以一直擴充,衍生出多種不同的 Life 子類別,而 Life / World 之間的互動及規範,則可以完全不用修正。
接下來就要讓這遊戲的規則,變的更真實一點了。實際的情況下,應該是我們已經知道會有那些不同的生物,經過歸納 (一般化及特殊化) 之後,可以設計出我們需要的類別架構。不過實際寫起程式來可沒這麼好命 (就像 USER 永遠不會一次給你完整確定的需求一樣),很多時後你得去 "猜" 或是 "假設",因此跟本沒有 "一般化" 這回事,你得預先去猜測未來要應付什麼問題,而在細節都還不清楚時就先定義出上層類別。
我們開始來試看看,我們定義的夠不夠抽像吧! 如果助教看你這麼快就把生命遊戲的作業交出來,覺的很沒面子,想把題目變難一點,加上有病毒感染的情況。於是原題目的四條規則追加一條,變成這樣:
public bool IsInfected { get { return this.InfectedCount > 0; } } private int InfectedCount = 0; public override string DisplayText { get { if (this.IsAlive == true) return "●"; else if (this.IsInfected == true) return "◎"; else return "○"; } } protected override IEnumerable<TimeSpan> WholeLife() { yield return TimeSpan.FromMilliseconds(_rnd.Next(800, 1200)); for (int index = 0; index < int.MaxValue; index++) { int livesCount = 0; int infectsCount = 0; foreach (Cell item in this.FindNeighbors()) { if (item.IsAlive == true) livesCount++; if (item.IsInfected == true) infectsCount++; } bool? value = _table[this.IsAlive ? 1 : 0, livesCount]; if (value.HasValue == true) { this.IsAlive = value.Value; } if (this.IsInfected == true) { this.InfectedCount--; if (this.InProbability(10) == true) this.IsAlive = false; } else { if (this.InProbability(1 + infectsCount * 5) == true) this.InfectedCount = 3; } yield return TimeSpan.FromMilliseconds(_rnd.Next(800, 1200)); } this.Dispose(); yield break; }細節我就不多介紹了。這裡的重點是經過抽像化的動作後,把 Life / Cell 之間的邏輯做明確的劃分。World 的類別程式碼完全沒有出現任何有關 Cell 的 Code, 只有出現 Life 而已。除了在主程式 GameHost 有這麼一段,明確的把 Cell 建立起來,把它放進 World:
static void Main(string[] args) { int worldSizeX = 30; int worldSizeY = 30; World realworld = new World(worldSizeX, worldSizeY); Random _rnd = new Random(); for (int x = 0; x < worldSizeX; x++) { for (int y = 0; y < worldSizeY; y++) { Cell item = new Cell(); realworld.PutOn(item, x, y); } } // ... }這樣的作法,其實已經引含了 "動態聯結" 的特性了。在開發主程式的階段 (指 World / Life 這兩個主要的 class), 都還沒有 Cell 的相關細節,而事後執行的程式碼卻可以依照 Cell 裡的邏輯來執行。這代表了我們不需要改主程式的設計,就能不斷的加入新的規責,甚至是新的生物進來一起運作。 如何? 物件技術的 "抽像化" 能力,的確很有效的解決了這樣的變化需求。下一篇會沿用一樣的架構,但是執行的範例會完全不一樣 (這次不用細胞了,直接用草原上的生態: 草、羊、虎) 的生命及規則,來套進這個框架,看看它能怎麼模擬出一個新的生態系統。 這樣的架構可以應付未來未知的變化,只要你的抽像化概念不變的前題下都沒問題。這種保留彈性,卻又不用在 design time 去多做不必要的實作,才是物件技術強大的地方。我舉個反例,很多剛入行的軟體工程師,你給他一個需求,他會想太多... 一個簡單的輸入 1 + 1 要顯示 2 的結果,他會這麼想: USER 需不需要列印啊? 我先把這需求放進去好了,然後加個 config 預設關掉它,以免以後需要我還得大改程式... 只能算 1 + 1? 如果以後 USER 要算 3 * 5 怎麼辦? 好吧,我把 + 用一個 mode 來代表好了,以後 USER 需要括充為支援 +-*/ 就不用大改程式了... ... 碰到這些狀況,我只能誇獎這位年青有為的程式設計師一句話: "你很認真... 辛苦了..." 不過我心裡會苦笑... 只不過要你寫個 1 + 1 = 2,搞這麼一大包? 多作考慮,預留未來可能需要的功能,不是件壞事。不過既然是未知的需求,你又如何保證你能夠正確的 "預知" ,然後進一步 "預留" ? 何況這些多做的需求,未來真正會用上的有多少? 用不到的話,只是開發成本的浪費,及讓你的架構複雜性提高,維護的困難增加而已。物件技術真的解決的了這種問題嗎? 下一篇的目標,我們會定在不修改 World / Life 的設計為前題,把生命遊戲的模擬內容換成草原的生態模擬。敬請期待續集 :D
最近這兩個禮拜,很扯... 我的手機時鐘越來越不準,竟然每天會晚個幾分鐘,不到兩個禮拜,竟然跟正常時間比起來已經差了廿分鐘...
天那,這什麼時代了,一個五十塊的電子錶都比我這隻五為數價位 (幾年前的價位啦 =_=) 的手機準時... 怪的是,白天再公司又是準的 !? 越看越怪,難不成這時代,連看個時鐘都得先 DEBUG 一番... 今天就花了點心思追一下問題 @@
首先,原來慢的不是我的手機,而是我家裡的 PC,因為手機插上 USB 充電 + SYNC,會順便對時,就這樣誤差越來越大了。不過 PC 不是都會上網對時嘛? 後來就再繼續追下去...
繼續追下去,家裡 PC 是跟家裡的 SERVER (domain controller) 對時的... 原來時間慢的是家裡的 SERVER... 不過問題還是一樣啊! 現在一直都連上網的 SERVER 怎麼可能會這樣?
因為每次重灌,一堆帳號就要重建,很麻煩,所以上個月重灌 SERVER時,就順手在 VM 裡裝了台 SERVER,當作 Active Directory 的 domain controller (Guest OS)… 而 SERVER 本身 (Host OS) 才是拿來做 NAT / RRAS / FILE SERVER 等服務...
這樣的架構,機車的地方就在於: Hyper-V 本身 VM 會跟 Host 做 time sync ... 而 Host 有加入 AD, Host 又會跟 Guest 同步時間,Hyper 又替 Guest 跟 Host 同步時間... 每次誤差一點,幾週下來就變這樣了 @@ 害我的手機莫名奇妙就晚了快半小時...
果然,這選項移除後,就一切正常了 @@,頂多就讓 DC 脫褲子放屁,到外面的 server 去對時吧... 嗯,這年頭,真的什麼怪事都會發生... 還好這次有抓到問題,哈哈... 這篇就給跟我一樣宅的人參考吧 :D
在繼續下去之前,先來講一下,我希望讓這個 "生命遊戲" 程式,發展到什麼程度吧。其實前面四篇都還只是基礎入門,跟準備動作而已,接下來才開始會有些有趣的。
我希望這系列文章寫完後,這個程式要能扮演一個真正可運作的 Matrix … 沒錯,就是像電影駭客任務裡的母體一樣,這個程式會變成 Matrix 的主要架構,而各式各樣的 "生物" 可以在這個虛擬世界裡生活。為了讓它真的跑的動,所以前幾篇提到的效能問題,執行緒問題,就不能不考慮。為了讓這個虛擬世界能更擬真一點,它至少要是個依時間驅動的模式,而不是像 "生命遊戲" 最早定義的回合制,因此這個問題也要在基本架構裡解決掉。
這些 "生物" 那裡來? 當然是大家來開發 :D 最終目標是要把這 Matrix 建起來,讓各位的生物可以放進來互相較勁一番... 因此先替這些程式 (生物) 抽像化,定義好它跟世界,及跟其它生物之間互動的規格 (就是下篇 "抽像化 / 多型" 要說的) 就是必要的工作之一了。替生命定義好抽像化介面之後,就可以開始衍生出各種不同的生命型態,一起加入這個虛擬世界,因此繼承、多型的技術就派上用場了。
生命是會演化的,當世界上真的演化出一種新的生命型態時,整個世界可以 "安裝" 好新的生命型態,然後全部存檔,重新啟動嗎? 當然不行... 因此如何 "動態" 的加入新的生命型態,如何不停止 GameHost 的前提下,由新的 Assembly 載入 Class (再下篇要說明的 "動態載入"),也是必需克服的技術之一。
這幾個階段及目標,就是我這一系列文章想要做到的。聽起來好像很有趣,可是卻又沒什麼實際的用途... Orz, 沒辦法,我就是喜歡寫這類要動點腦筋的程式,即使畫面一點都不炫也沒關係... 平常工作就不大有機會寫這種程式了,加上現在又只剩一張嘴...。
如果順利的發展,我倒是有個打算,這個 GameHost 成形之後,我打算定些基本的規則,比如土地上會有一定的機率及規則,長出草 (食物) 來。而這世界有各種不同的生物 (EX: 羊),需要靠這世界上的資源維持生命。到時大家可以把自己創造的 "羊" 一起放到這個世界內,看看執行了一陣子之後,誰設計的 "品種" 比較好,最後可以一代一代的繁衍下來...。
想的很美好,不過我不像 darkthread 可以替最後優勝的造物者提供獎品.. Orz.. 未來的設計藍圖就先規劃到這裡。在繼續下去之前,我把程式重新整理了一下,有興趣的人可以下載回去。這份程式碼跟 #4 的功能結構是一樣的,只不過整個架構都作過重整,變數等命名也調整過了,是為了往後說明相關物件技術時,不會被這些從 #1 ~ #4 改的支離破碎的程式碼干擾...
嗯,講了一堆廢話,結論就是: 敬請期待續集 :D 哈哈...
下載重整過的程式碼:
原本這篇不講執行緒,要直接跳到 OOP 多型的應用... 不過看一看 #3 自己寫的程式,實在有點看不下去... 30x30 的大小,程式跑起來就看到 903 條執行緒在那邊跑... 而看一下 CPU usage, 只有 5% 不到... 這實在不是很好看的實作範例,如果這是線上遊戲的 SERVER 程式,裡面的每個人,每個怪物等等都用一條專用的執行緒在控制他的行為的話,我看這遊戲不用太多人玩,SERVER 就掛掉了吧! 因此要繼續更貼近實際的生命模擬遊戲前,我們先來解決效能的問題,所以多安插了這篇進來 :D 前一篇 (#3) 的主題是把生命的進行,從被動的在固定時間被喚醒 (callback) 的作法,改成主動的在指定時間執行 (execute)。 想也知道,現實世界的生物都是 "主動" 的,後面的作法比較符合 OOP 的 "模擬世界,加以處理" 的精神。但是,一個小程式就吃掉 900 條執行緒,是有點過頭了。不知道還有沒有人記得,我騙到獎品的這個程式... 很另類的用 yield return 來解決類似問題的作法... 藉著 compiler 很雞婆的把單一流程翻成數段可以切開執行的邏輯...,正好拿來利用一下,替我們把一連串連續的邏輯切段,以便利用多執行緒來處理。我的想法是這樣,原程式是用個迴圈,作完該作的事,就休息 (sleep) 一段時間。而新的寫法,我打算用 yield return new TimeSpan(…) 來取代 Thread.Sleep(…)。每個 Cell內部的程式結構修改不大,不過對於 GameHost 就是個挑戰了... 來看看修改前及修改後的程式碼:
// 修改前 // 使用 Thread.Sleep( ) 來控制時間 public void WholeLife(object state) { int generation = (int)state; for (int index = 0; index < generation; index++) { this.OnNextStateChange(); Thread.Sleep(_rnd.Next(950, 1050)); } } // // // // 修改後 // 使用 yield return new TimeSpan( ) 來控制時間 public IEnumerable<TimeSpan> WholeLife(object state) { int generation = (int)state; for (int index = 0; index < generation; index++) { this.OnNextStateChange(); yield return TimeSpan.FromMilliseconds(_rnd.Next(950, 1050)); } yield break; }別想的太美,只改這樣,程式是不會動的... 修改過之後,麻煩的地方會在 GameHost. 因為整個 GameHost 的邏輯都反過來了。原本是 GameHost 只要放著那九百條執行緒自生自滅,它只要不斷的刷新畫面就好了。現在它則得用 foreach(…) 去詢問:
"大爺,這次您要休息多久?"接到 yield return 傳回的 TimeSpan 物件 (代表它要休息多久後,繼續下一個動作) 後,經過這段時間,GameHost 就要再去叫醒 cell, 然後再詢問一次:
"大爺,這次您要休息多久?"關鍵就在於 GameHost 如何能透過少量的 thread 來伺後這些大爺,而不是像 #3 的程式一樣,每個大爺都用一條專屬的 thread… 要共用執行緒,就要先想辦法把工作切碎,這是基本法則。如果你希望你的生命遊戲程式不只是作業的話,那麼效能跟即時回應的問題是必需要考慮的。在動手改寫 GameHost 程式之前,先來分析一下改寫的目標有那些:
目標是要達到像 #2 範例一樣的效果,但是要用更有效率的方式。目標很清楚,再來就看看有什麼手段可以用了。第一個是過量的執行緒,應該要想辦法改用執行緒集區。因為 #2 用了高達 900 條執行緒,不過整體 CPU USAGE 不到 5%,大部份的執行緒都在閒置狀態。如果能想辦法把這些運算丟到執行緒集區,由集區動態管理會有效率的多。 第二,就是把原本的 Thread.Sleep(ts) 改成 yield return ts 後,原本每個 thread 自己睡覺的機制,就要改成 cell 各自回報 game host 它想要睡多久,然後由 game host 統一在時間到時叫醒它。由於一次有多個 cell 同時在運作,因此我們需要一個簡單的排程器,作法像這樣:
public class CellToDoList { public void AddCell(Cell cell) {...} public Cell GetNextCell() {...} public Cell CheckNextCell() {...} public int Count {get;} }裡面的實作,我就不多說了。我是把它當成 QUEUE 在設計,唯一的差別是,放進 QUEUE 的東西會先經過排序,因此不見得是 "First In First Out" 這種典型的貯列,而是會以 Cell 上標示的時間為準,依序 Out …。實作起來很簡單,用現成的 SortedList 當內部的儲存方式,加上基本的 lock 機制來確保它是 thread safe 的就夠了。 好,這些雞絲都準備好之後,就可以來打造我們的新版 GameHost 了。來看看 Code:
static CellToDoList _cq; static void _YieldReturnGameHost(string[] args) { int worldSizeX = 30; int worldSizeY = 30; World realworld = new World(worldSizeX, worldSizeY); _cq = new CellToDoList(); // init threads for each cell for (int positionX = 0; positionX < worldSizeX; positionX++) { for (int positionY = 0; positionY < worldSizeY; positionY++) { Cell cell = realworld.GetCell(positionX, positionY); cell.OnNextStateChangeEx(); _cq.AddCell(cell); } } // 啟動定期更新畫面的執行緒 Thread t = new Thread(RefreshScreen); t.Start(realworld); while (_cq.Count > 0) { Cell item = _cq.GetNextCell(); if (item.NextWakeUpTime > DateTime.Now) { // 時間還沒到,發呆一下等到時間到為止 Thread.Sleep(item.NextWakeUpTime - DateTime.Now); } ThreadPool.QueueUserWorkItem(RunCellNextStateChange, item); } } private static void RunCellNextStateChange(object state) { Cell item = state as Cell; TimeSpan? ts = item.OnNextStateChangeEx(); if (ts != null) _cq.AddCell(item); } private static void RefreshScreen(object state) { while (true) { Thread.Sleep(500); (state as World).ShowMaps(""); } }GameHost 的工作很明確,一開始 (line 18 ~ 20) 就把更新畫面的動作完全交給另一個執行緒,之後就專心處理 ToDoList 內的工作了。 接著後面的 while loop (line 21 ~ 30) 則是很單純的從 ToDoList 裡取出下一個要要動作的 Cell, 如果時間還沒到就 Sleep 等一下它。執行完後會再詢問下一次是什麼時後,同時再把他加到 ToDoList 內等待下一次輪到他時繼續。 這次的程式我沒有設定停止的條件,因此你會看到程式會不斷的執行下去。程式執行起來,結果跟 #3 沒什麼不同,畫面上的每個細胞會照著題目的規則生長或死亡,不同的是 #3 的 Game Host 需要用到 903 條執行緒,而這版的 Game Host 只要 9 條執行緒...
Game Of Life 那一系列的,先暫停一期 :D,先穿插一篇不相干的內容...。這篇要講的是網站的登入部份要改用 SSL 的作法。這是很常見的問題,不過對怎麼作搞不清楚的人,仍然大有人在... 所以興起了寫這篇的念頭。
先從 "為什麼" 來說好了。實際碰到的客戶,常常會把 "SSL" 跟 "加密" 劃上等號... 以為網站加上 SSL 就固若金湯了。這樣講是沒錯啦,不過 SSL ( Secure Socket Layer。Wiki 有說明) 再安全,也只是個 "加密" 的傳輸方式,只有對外人 (竊聽者) 是加密的... 正所謂內賊難防... SSL 可以防外賊,但是防不了內賊。因此 SSL 是不等於 DRM 這類技術的...
扯遠了,會這樣講只是因為,很多客戶需要的其實是 DRM,不過客戶的 IT 卻天真的以為,網站加上 SSL 就萬無一失了... 於是老有客戶在問:
客: "你們的系統可以整個放到 HTTPS 裡嗎?"
我: "可以啊,不過效能會很糟,有什麼特別的需求,要這樣作嗎?"
客: "老闆說網站的文件怕流出去,所以要用 HTTPS"
我: "...."
很想把 DRM / DPM 的介紹貼給客戶看... 不過只能很婉轉的引導客戶的需求...
我: "HTTPS 只是防竊聽,不防把資料存下來偷帶出去的... 要防外賊,只要保互好登入過程傳輸帳號秘碼"
大部份人對 SSL 的第一個誤解,就是 SSL 只是個加密的傳輸方式,不是加密的儲存方式。就好像你用黑貓寄東西,他是用貨車載... 像 Mission Impossible 裡的阿湯哥那種身手,一下就可以把你的包裹摸出來... 如果你的包裹給保全公司送,他們用的安全規格就完全不同了... 至少是運鈔車的那種規格 (不過應該也擋不住阿湯哥就是...)。
不過,貨物送到對方手上之後,保護就不見了... 這點就是大家常常沒搞清楚的地方。
弄清楚 SSL 只保護傳輸過程後,該怎麼應用,該應用在那裡,就很清楚了。最典型的例子,就是用在網站登入 (輸入帳號密碼,輸入信用卡號碼等等) 的地方。登入成功後就沒必要繼續留在 SSL (HTTPS) 保護的範圍了,就可以切回一般網站 (HTTP)。
剩下的就跟你怎麼設計,怎麼規劃有關了。概念上來說,一般網站就像這張圖一樣:
所有資訊都在橘色 (不安全) 的部份傳輸。為了確保重要資訊不被竊聽,我們至少要改成這樣:
如果把 HTTP / HTTPS 當成兩個網站,則帳號密碼一定要在綠色(安全)部份傳輸。而兩台SERVER之間可以用HTTPS,或是其它管道傳輸,不一定要加密,不過至少可以把外面的駭客檔在門外。
接下來的作法就各憑本事了,沒有標準的解法。我的客戶因為有可能有各種不同的認證規則,這規則可能跟網站的邏輯有關,所以不大適合把認證的部份擺在 HTTPS 這端,因此我的設計是:
打個比方,這就是傳統的商店刷信用卡時,會用電話等方式,銀行先跟使用者確認資訊後,給店家一個授權碼。之後店家就只能在規範的條件下,用這授權碼取款。這授權碼是不怕被盜取的,因為它只是個代號,外人取得它完全無法做壞事,就好像上例的 TOKEN 一樣。
接下來,就來看看程式該怎麼寫了。只要使用量不大的話,A/B兩網站是可以放在同一台SERVER上,這時中間的傳輸方式就很多種簡易的選擇 (比如 local file)。考量到整個系統的 scalability, HTTPS 的部份屬 CPU BOUND,量大的話有可能影響到原網站的運作,因此實際部屬時,有可能碰到 A 網站必需跟 B 網站獨立出來,而且 B 網站也有可能因為效能問題,而再分散成多個 Web Farm 的運作方式...,這時 AB 網站間的傳輸方式就得能跨 SERVER 運作,就得挑選 DB / WCF 等等方式進行。 (當然 ASP.NET Session State Server 也可以啦,不過暫不考慮,不然就沒東西好寫了...)
整理一下最後要實作的需求,跟架構設計。都到設計階段了,Use Case 就跳過去,直接到 Sequence Diagram…
用到的技術反而沒什麼特別的。我舉幾個常見的難題,或是常被忽略掉的漏洞: