C# 實作共用變數 by Singleton – 以交易系統登入連線為例

問題

股票期貨交易系統的登入驗證連線,必須隨時保持連線狀態 (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
思考
  1. 誰知道交易系統登入連線的狀態? 登入連線物件本身。
  2. 交易系統的登入連線有幾條?單一條,且必須保持 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

文章導覽

   

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *