問題
股票期貨交易系統的登入驗證連線,必須隨時保持連線狀態 (connection state),也就是必須設計為 Stateful (有狀態),並於盤中需隨時得知連線情形,若斷線則系統應該要能通知用戶做重新登入的動作。
如果有多個表單 (View) /多個類別需在執行期間 (run-time)可以察知共享登入的連線狀態,該如何實現?
解決方案
共用變數類別
最簡單直覺的方法,設計一個共用變數 (global variables)類別,並保存需要在多個應用程式/類別之間共享的狀態/值。
可以參考此篇的作法:Global variables class。
/// <summary> /// Contains global variables for project. /// </summary> public static class GlobalVar { /// <summary> /// Global variable that is constant. /// </summary> public const string GlobalString = "Important Text"; /// <summary> /// Static value protected by access routine. /// </summary> static int _globalValue; /// <summary> /// Access routine for global variable. /// </summary> public static int GlobalValue { get { return _globalValue; } set { _globalValue = value; } } /// <summary> /// Global static field. /// </summary> public static bool GlobalBoolean; } |
將共用變數利用靜態類別/方法 (static class/method)實作,如此任一用戶端均可以存取該共用類別內的屬性 (properties),存取方式如:
//Client side GlobalVar.GlobalValue GlobalVar.GlobalBoolean |
.NET/Java 採用 Static Class/Method 來實現傳統程序導向語言的共用變數作法,但其實這樣的作法很不理想,問題多多、毫無安全性可言。再則,共用變數類別只儲存了如登入連線的狀態屬性,但並沒有實際監測實際連線的情形 (需再實作一個 Monitor 類型的物件來負責監測連線狀態),很可能會造成連線狀態的不一致性。
Singleton Pattern
思考
- 誰知道交易系統登入連線的狀態? 登入連線物件本身。
- 交易系統的登入連線有幾條?單一條,且必須保持 Stateful。
其實答案就呼之欲出了:採以 Singleton 設計模式是不錯的解決方案,它特別適用於 Logging, 通訊 (Communication), 交易系統登入/報價連線 ...等機制的連線設計。
群益API 登入連線物件實作
群益API 提供了期權金融商品的報價、下單的應用程式連線介面。它其實是採用 COM 傳統較底層的機制所撰寫的,網頁上各類程式語言的範本其實也都只是提供如何呼叫 COM API 的寫法而已。
當然還是要參考他們所提供的 C#.NET 連線範本,但個人準備重新改寫過,披上較有彈性的 C#.NET Wrapper 介面/物件外衣,讓交易系統其它模組 (線圖、策略)不與 登入/報價/下單等連線物件直接耦合 (coupling)。
底下即是關於群益API 登入連線的 C#.NET Wrapper 實作:
ILogin.cs
public interface ILogin { void Login(string id, string password); LoginStateEnum LoginState { get; } } |
LoginStateEnum.cs
public enum LoginStateEnum { SUCCESS, ERROR, UNKNOWN } |
CapitalLoginAdapter.cs
// 群益 登入Adapter by Singleton public class CapitalLoginAdapter : ILogin { static CapitalLoginAdapter _instance; public static CapitalLoginAdapter Instance { get { return _instance ?? (_instance = new CapitalLoginAdapter()); } } private CapitalLoginAdapter() { skCenter = new SKCenterLib(); LoginState = LoginStateEnum.UNKNOWN; } private SKCenterLib skCenter; public void Login(string id, string password) { int status_code = skCenter.SKCenterLib_Login(id, password); // 登入成功 or 重複登入 if (status_code == 0 || status_code == 2003) LoginState = LoginStateEnum.SUCCESS; else LoginState = LoginStateEnum.ERROR; } public void Logout() { LoginState = LoginStateEnum.UNKNOWN; skCenter = null; // 很不得已的做法,否則須實作 COM 物件釋放資源的方法 GC.Collect(); GC.WaitForPendingFinalizers(); } public LoginStateEnum LoginState { get; private set; } } |
另關於 CapitalLoginAdapter.cs 的單元測試程式 (unit test):
CapitalLoginAdapterTest.cs
[TestClass] public class CapitalLoginAdapterTest { private CapitalLoginAdapter adapter; [TestInitialize] public void Setup() { adapter = CapitalLoginAdapter.Instance; } [TestMethod] public void TestLogin() { LoginStateEnum expected = LoginStateEnum.SUCCESS; LoginStateEnum actual; adapter.Login("username", "password"); actual = adapter.LoginState; Assert.AreEqual(expected, actual); } [TestCleanup] public void Cleanup() { adapter.Logout(); adapter = null; } } |
簡單結論
上述登入 (Login) 的連線是採 Singleton Pattern 實作方式,藉以讓其它 Client 用戶端可以共享該連線物件,以達成查詢登入狀態的目的。
另外關於報價連線 (Quote Connection) 的實作方式也是一樣,不過它的實作相對繁雜許多,還需要實現 Real time 的事件處理 (event handling),這點另篇再行討論。
共用變數的實作當然不限於上述兩種方式,參考該篇:Alternatives to global variables and passing the same value over a long chain of calls。
因應不同的場合與應用狀況,另外也有兩種作法可以參考:
延伸參考:
o Global Variable。
o Singleton Pattern。