ASP.NET可互動式位元影像表單設計(5)

來源:互聯網
上載者:User
asp.net|互動|設計 維護兩個列表
    因為我們要改變對象的填充顏色以實現 Change fill to hot pink 按鈕,因此維護了兩個可繪製對象列表:一個列表是全部對象,另一個列表是可填充對象。我們為這兩個列表都使用了 ArrayList 類。ArrayList 對象包含一組 Object 引用 -- 這樣一個 ArrayList 可以包含系統中任何類型的混合。
  
    這實際上並沒有什麼協助 -- 我們希望 ArrayList 僅僅包括可繪製/可填充對象。為此,我們將 ArrayList 對象設為私人;然後將向列表添加對象的過程設為一個方法,該方法只接受一個 DShape。
  
    當使用 Add 方法向列表中添加對象時,我們將所有對象添加到 wholeList 中,然後檢查對象是否還應添加到 filledList 集合中。
  
    請記住,Add 方法(以及列表)具有型別安全特性:它只接受 DShape(或者從 DShape 派生的類型,例如我們在上面建立的所有類型)。您不能將整數或字串添加到列表中,這樣我們便可以知道這個列表只包含可繪製對象。能夠確知這一點是很方便的!
  
    繪製項
  
    我們還有一個 DrawList 方法,用於在它作為參數傳遞的 Graphics 對象上繪製列表中的對象。此方法具有兩種情況:如果列表為空白,它繪製一個字串,說明列表為空白。如果列表不為空白,它使用一個 for each 建構函式遍曆該列表,並在每個對象上調用 Draw。實際的遍曆和繪圖代碼再簡單不過了,如下面的 Visual Basic 所示。
  
  
    Visual Basic
  .NET Dim d As DShape
  For Each d In wholeList
  d.Draw(g)
  Next
  
  
    C# 代碼幾乎完全相同(當然,其行數更少)。
  
  
    C#
  foreach (DShape d in wholeList)
  d.Draw(g);
  
  
    由於列表是封裝的,我們知道它具有型別安全特性,因此可以僅調用 Draw 方法而不必檢查對象的類型。
  
    返回可填充列表
    最後,我們的 Change fills to hot pink(將填充色更改為粉紅)按鈕需要一個對所有可填充對象的引用數組,以便更改其 FillBrushColor 屬性。雖然可以編寫一個方法遍曆列表並將顏色更改為傳入的值,但這一次 Dr. GUI 選擇了返回一個對象引用數組。幸運的是,ArrayList 類具有一個 ToArray 方法,利用它可以建立一個傳遞數組。該方法擷取我們需要的數組元素類型 -- 從而可以傳遞迴所需的類型 -- IFillable 數組。
  
  
    C#
  
  public IFillable[] GetFilledList() {
  return (IFillable[])filledList.ToArray(typeof(IFillable));
  }
  
    Visual Basic
  
  .NET Public Function GetFilledList() As IFillable()
  Return filledList.ToArray(GetType(IFillable))
  End Function
  
  
    在兩種語言中,我們都使用了一個內建運算子擷取給定類型的 Type 對象 -- 在 C# 中,是 typeof(IFillable);在 Visual Basic 中,是 GetType(IFillable)。
  
    調用程式使用此數組在可填充對象引用數組中遍曆。例如,將填充顏色更改為粉紅的 Visual Basic 代碼如下所示:
  
  
  Dim filledList As IFillable() = drawingList.GetFilledList()
  Dim i As IFillable
  For Each i In filledList
  i.FillBrushColor = Color.HotPink
  Next
  
    用於分解出公用代碼的 Helper 方法和類
    您可能注意到,Draw 和 Fill 方法有很多共同的代碼。確切地說,每個類中建立筆或畫筆的代碼、建立 Try/Finally 塊的代碼以及清理筆或畫筆的代碼都是相同的 -- 唯一的區別是進行繪圖或填充時調用的實際方法。(由於 C# 中 using 文法非常簡潔,因而多餘代碼的數量並不明顯。)在 Visual Basic .NET 中,每五行代碼中可能有一行特殊的代碼在所有實現中都是相同的。
  
    總之,如果存在大量重複代碼,就需要尋求分解出公用的代碼,以便形成為所有類所共用的公用子常式。這類方法有很多,Dr. GUI 非常高興為您展示其中的兩種。第一種方法僅用於類,第二種方法可用於類或介面,在本例中只用於介面。
  
    方法 1:公用進入點調用虛擬方法
    在第一個方法中,我們利用了類(不同於介面)可以包含代碼這一事實。所以我們提供了一個用於建立筆的 Draw 方法的實現,以及一個例外處理常式和 Dispose,然後調用實際進行繪圖的 abstract/MustOverride 方法。確切地說,我們更改了 DShapes 類以適應新的 Draw 方法,然後聲明了新的 JustDraw 方法:
  
  
  Public MustInherit Class DShape
  ' Draw 不是虛擬,這似乎有些不尋常……
  ' Draw 本應是抽象的 (MustOverride)。
  ' 但此方法是繪圖的架構,而不是繪圖代碼本身,
  ' 繪圖代碼在 JustDraw 中完成。
  ' 還請注意,這意味著同原版本相比,這些類具有
  ' 不同的介面,雖然它們完成的工作相同。
  Public Sub Draw(ByVal g As Graphics)
  Dim p = New Pen(penColor)
  Try
  JustDraw(g, p)
  Finally
  p.Dispose()
  End Try
  End Sub
  ' 這裡是需要成為多態的部分 -- 因此是抽象的
  Protected MustOverride Sub JustDraw(ByVal g As Graphics, _
  ByVal p As Pen)
  Protected bounding As Rectangle
  Protected penColor As Color ' 還應具有屬性
  ' 還應具有移動、調整大小等方法。
  End Class
  
    一個值得注意的有趣的地方:Draw 方法並不是 virtual/Overridable。因為所有衍生類別都將以相同的方式完成這部分繪圖(如果在 Graphics 上繪圖 [如本例中的定義],則必須指派並清理筆),因此它不需要是 virtual/Overridable。
  
    實際上,Dr. GUI 認為在本例中,Draw 不應該是 virtual/Overridable。如果確實要覆蓋 Draw 的行為(而不僅是 JustDraw 的行為),則可以將它設定為 virtual/Overridable。但在本例中,沒有理由覆蓋 Draw 的行為,如果鼓勵程式員進行覆蓋還會帶來隱患 -- 他們可能不會正確處理筆,或者使用其他方法繪製對象而不是調用 JustDraw,這就違反了我們內建到類中的假設。因此,將 Draw 設定為非虛擬(順便說一下,在 Brand J 中沒有這個選項)可能會降低代碼的靈活性,但會更加可靠 -- Dr. GUI 認為在本例中,這樣做非常值得。
  
    JustDraw 的典型實現如下所示:
  
  
  Protected Overrides Sub JustDraw(ByVal g As Graphics, ByVal p As Pen)
  g.DrawEllipse(p, bounding)
  End Sub
  
    如您所見,我們獲得了所希望的簡潔的衍生類別實現。(可填充類中的實現只是略微複雜一些 -- 稍後會看到。)
  
    請注意,我們在介面中添加了一個額外的公開方法 JustDraw,除了要繪製的 Graphics 對象外,該方法還引用我們在 Draw 中建立的 Pen 對象。因為該方法需要是 abstract/MustOverride,因此必須是公開的。
  
    這並不是一個大問題,但它確實更改了類的公開介面。所以即使這個分解出公用代碼的方法非常簡單方便,也應當儘可能選擇其他方法以避免更改公開介面。
  
    方法 2:虛擬方法調用公用 helper 方法,使用回調
    在實現介面的 Fill 方法時,代碼的複雜程度也很類似:每六行代碼中可能有一行特殊的代碼在所有實現中都是相同的。但是我們不能將公用的實現放到介面中,因為介面只是聲明,它們不包含代碼或資料。此外,上面列出的方法是不能接受的,因為它會更改介面 -- 我們可能並不希望這樣,或者因為是其他人建立的介面,我們根本不可能更改!
  
    所以,我們需要編寫一個 helper 方法以設定並回調我們的類,以便進行實際的填充。對於本例,Dr. GUI 將代碼放在一個單獨的類中,這樣任何類都可以使用該代碼。(如果採用該方法來實現 Draw,則可以將 helper 方法作為抽象基類中的私人方法實現。)
  
    暫時不進一步展開,以下是我們建立的類:
  
  
  ' 請注意,該 delegate 提供的協助仍然具有多態行為。
  Class FillHelper
  Public Delegate Sub Filler(ByVal g As Graphics, ByVal b As Brush)
  Shared Sub SafeFill(ByVal i As IFillable, ByVal g As Graphics, _
  ByVal f As Filler)
  Dim b = New SolidBrush(i.FillBrushColor)
  Try
  f(g, b)
  Finally
  b.dispose()
  End Try
  End Sub
  End Class
  
    我們的 helper 方法調用了 SafeFill,該方法接受一個可填充對象(請注意,這裡我們使用了 IFillable 介面類型,而不是 DShape,從而只能傳遞可填充對象)、一個要在其上進行繪圖的 Graphics 和一個稱為 delegate 的私人變數。我們可以將 delegate 視為一個對方法(而不是對象)的引用 -- 如果您經常使用 C 或 C++ 編程,則可以將其視為具有型別安全特性的函數指標。可以將 delegate 設定為指向任何具有相同參數類型和傳回值的方法,無論是執行個體方法還是 static/Shared 方法。將 delegate 設定為指向相應的方法後(例如在調用 SafeFill 時),我們可以通過 delegate 間接調用該方法。(順便說一下,Brand J 中沒有 delegate,這時如果使用此方法,會非常困難並且很不靈活)。
  
    delegate 類型 Filler 的聲明位於類聲明之上 -- 它被聲明為一個不返回任何內容(在 Visual Basic .NET 中是一個 Sub)並且將 Graphics 和 Brush 作為參數傳遞的方法。我們會在將來的專欄中深入討論 delegate。
  
    SafeFill 的操作非常簡單:它指派畫筆並將 Try/Finally 和 Dispose 設定為公用代碼。它通過調用我們作為參數接收的 delegate 所引用的方法進行各種操作:f(g, b)。
  
    要使用這個類,需要向可填充對象類中添加一個可以通過 delegate 調用的方法,並確保將該方法的引用(地址)傳遞到 SafeFill,我們將在介面的 Fill 實現中調用 SafeFill。以下是 DFilledCircle 的代碼:
  
  
  Public Sub Fill(ByVal g As Graphics) Implements IFillable.Fill
  FillHelper.SafeFill(Me, g, AddressOf JustFill)
  End Sub
  Private Sub JustFill(ByVal g As Graphics, ByVal b As Brush)
  g.FillEllipse(b, bounding)
  End Sub
  
    這樣,當需要填充對象時,便在該對象上調用 IFillable.Fill。它將調用我們的 Fill 方法,而 Fill 方法調用 FillHelper.SafeFill,後者傳遞一個對我們的可填充對象的引用、所傳遞的要在其上進行繪圖的 Graphics 對象以及一個對實際完成填充的方法的引用 -- 在本例中,該方法是私人的 JustFill 方法。
  
    然後,SafeFill 通過 delegate -- JustFill 方法來設定畫筆和調用,JustFill 方法通過調用 Graphics.FillEllipse 進行填充並傳回值。SafeFill 將清理畫筆並返回到 Fill,Fill 再返回到調用者。
  
    最後是 JustDraw,它和原始版本中的 Draw 很類似,因為我們都調用了 Fill,並調用了基類的 Draw 方法(這是我們以前所做的)。以下是相關代碼:
  
  
  Protected Overrides Sub JustDraw(ByVal g As Graphics, ByVal p As Pen)
  Fill(g)
  MyBase.JustDraw(g, p)
  End Sub
  
    請記住,指派畫筆和筆的複雜之處在於它在 helper 函數中的處理 -- 在 Draw 中,它位於基類中;在 Fill 中,它位於 helper 類中。
  
    如果您認為這比以前複雜了,那麼確實如此。如果您認為由於額外的調用和需要處理 delegate,速度比以前緩慢了,也確實如此。在生活中總是有很多東西需要進行權衡。
  
    那麼,這樣做值得嗎?也許值得。這取決於公用代碼的複雜程度,以及該代碼需要重複的次數。也就是說,需要權衡。如果我們決定刪除 Try/Finally,而只在完成繪圖後清理筆和畫筆,代碼便會非常簡單,這些方法也就用不上。並且在 C# 中,using 語句非常簡潔,我們也不必費神使用這些方法。Dr. GUI 認為,在 Visual Basic 中使用 Try/Finally 時,可以使用、也可以不使用這些方法,這裡旨在向大家展示這些方法,以便在遇到具有大量公用代碼的情況時使用。 
  


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.