Reflecting Enumeration

摘要:Reflecting Enumeration

本篇主要以如何生成 Template Strategy 的實作類別為情境, 來介紹 Reflecting Enumeration 的使用.

Strategy 常常用來使程式結構更符合 IOC 原則, 以一個工作的例子來看, 先抽象化出工作的執行動作, 這一般是個介面. 我假設有兩種工作分別是 View 和 Query, 並由兩個類別實作工作介面; 當然 Template 也很方便用來處理各實作類別的相同部份, 僅把需要實作的方法遺留給子類別. 接下來是指定描述工作或工作實體生成, 常使用 Enum 定義工作項目. 程式碼如下:


' 抽象化工作介面來描述一件工作的特性
Public Interface IWork
    Sub start()
    Function getWorkName() As String
End Interface

' 為實作工作介面的類別增加指定型態
Public Enum WorkType
    View
    Query
End Enum

' 工作的抽象基底類別, 為所有的子類別實作 IWork 中的 getWorkName
Public MustInherit Class WorkBase
    Implements IWork

    Protected mType As WorkType

    Public Sub New(ByVal wt As WorkType)
        mType = wt
    End Sub

    Public Function getWorkName() As String Implements IWork.getWorkName
        Return mType.Name()
    End Function

    Public MustOverride Sub start() Implements IWork.start
End Class

' View 工作實作類別, 其 Template 為 WorkBase
Public Class ViewWork
    Inherits WorkBase

    Public Sub New()
        MyBase.New(WorkType.View)
    End Sub

    Public Overrides Sub start()
        MsgBox("start to view")
    End Sub
End Class

' Query 工作實作類別, 其 Template 為 WorkBase
Public Class QueryWork
    Inherits WorkBase

    Public Sub New()
        MyBase.New(WorkType.Query)
    End Sub

    Public Overrides Sub start()
        MsgBox("start to query")
    End Sub
End Class

' Strategy 的包裝類別
Public Class Worker
    Private mMyWork As IWork

    Public Sub New(ByVal work As IWork)
        mMyWork = work
    End Sub

    Public Sub doWork()
        mMyWork.start()
    End Sub
End Class

一個 Worker 會做一件份內的工作, 在生成 Worker 時指定工作, 指定的方式為指定工作介面. 這裡符合了 IOC, Worker 不會依靠在任何實作的工作類別, 而是依靠抽象!! 那麼抽象不變, 指定的工作變更, 那工人就能完成不同的工作, 而 Worker 不會因為新增或刪減工作類別而變更程式碼.

在 Strategy 中的參與角色是 IWork, ViewWork, QueryWork 和 Worker.
在 Template 中的參與角色是 WorkBase, ViewWork 和 QueryWork.

比較特別的是 WorkType Enum, 其實它主要的功能不是用來實作 getWorkName (雖然它有能力), 而是用來較具體地描述和生成工作實體類別, 因為要使用這些東西, 必須要生成物件!!

一般的生成方法就是依某個變數狀態來決定要產生什麼物件, Enum 是很常使用的方式, 當然也能使用設定檔或資源. 這邊採用最容易被使用到的 Simple Factory (也可稱作靜態工廠), 即使用一個靜態函式來幫助產生我們需要的物件!


' 一個靜態工廠
Public Class WorkFactory
    Public Shared Function create(ByVal wt As WorkType) As IWork
        Select Case wt
            Case WorkType.View
                Return New ViewWork
            Case WorkType.Query
                Return New QueryWork
            Case Else
                Return Nothing
        End Select
    End Function
End Class


只要傳入描述工作類型的 WorkType 給 WorkFactory 中的 create 方法, 就能拿到工作物件.


' 使用方式
Public Sub Test
    Dim work As IWork = WorkFactory.create(WorkType.View)
    Dim neil As New Worker(work)
    neil.doWork()
End Sub


在 Test 函式中, 我要一位叫 Neil 的工人幫我做 ViewWork 這件事; 使用了 Factory 後, 我不需要知道怎麼生成 ViewWork, 甚至也不需要知道 ViewWork 是什麼東西, 因為上層(使用者操作或在 Test 中)皆取決於抽象或介面.

到這邊情境就講完了, 那本篇的重點呢?

你覺得還有什麼地方可以修改的更好?如果你對 Java 熟的話, 應該知道 Simple Factory 常配合用 Reflecting Enumeration 去生成物件, 而不是如上述的方式採用 select (也就是C++, C#, 和 Java 中的 switch), 如果 WorkType 有超多種, 那不就GG了. 所以採用反射相對是個好方法, 雖然會比較慢一點, 但效率不是唯一的考慮!!

我原本就拿 WorkType 來描述工作的類型, 自然也是利用 WorkType 來自動反射出對應的物件. 不過這邊有個問題, WorkType 是 Enum, .NET 中的 Enum 不像 Java 的可以隨意設定其值, 所以得要另外找個地方儲存反射所需要的資料, 這部份可參考另一篇 Enumeration with Class State. 修改後的程式碼如下:


Public Class WorkFactory
    Shared Sub New()
        WorkType.View.setIndexValue(GetType(ViewWork))
        WorkType.Query.setIndexValue(GetType(QueryWork))
    End Sub

    Public Shared Function create(ByVal wt As WorkType) As IWork
        Return Activator.CreateInstance(wt.IndexValue)
    End Function
End Class


和修改前的 WorkFactory 相比, create 乾淨很多, 就只是傳回反射後的物件; 另外還多了靜態建構子的部份, 我需要在一個地方設置 WorkType 與反射出實際物件所需資料的對應, 這一般是類別的 Type. 設置的 Type 會存放在其 Enum 中的 Class State. 另外一提, 也有人使用 attribute 的方式來放置反射資料, 這種方式會固定在 Enum 上, 算是方便且更改也只要動到 Enum. 在這篇文章的範例是在靜態工廠中設定反射資料, 一旦更動就需要修變 Enum 和 WorkFactory, 不過這就是彈性上的取捨, 視設計者需求而定.

其實這種間接為 Enum 設置反射型態的方式, 比傳統 Enum String 更有彈性, 因為不同的 Factory 就能有不同的反射型態設定.

看完這篇, 是不是覺得很簡單呢? 那快來自己寫個 Reflecting Enumeration 吧!

如果不熟 Reflection 可以參考下面連結.

Reflection:
http://msdn.microsoft.com/en-us/library/cxz4wk15(v=vs.95).aspx

Reflection Object Creation, Assembly & Activator:
http://msdn.microsoft.com/zh-tw/library/system.activator(v=VS.90).aspx
http://msdn.microsoft.com/zh-tw/library/system.reflection.assembly.createinstance(v=VS.90).aspx