ASP.NET可交互式位圖窗體設(shè)計(jì)(5)
發(fā)表時(shí)間:2024-02-10 來(lái)源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]維護(hù)兩個(gè)列表 因?yàn)槲覀円淖儗?duì)象的填充顏色以實(shí)現(xiàn) Change fill to hot pink 按鈕,因此維護(hù)了兩個(gè)可繪制對(duì)象列表:一個(gè)列表是全部對(duì)象,另一個(gè)列表是可填充對(duì)象。我們?yōu)檫@兩個(gè)列表都使用了 ArrayList 類(lèi)。ArrayList 對(duì)象包含一組 Object 引用 -- 這...
維護(hù)兩個(gè)列表
因?yàn)槲覀円淖儗?duì)象的填充顏色以實(shí)現(xiàn) Change fill to hot pink 按鈕,因此維護(hù)了兩個(gè)可繪制對(duì)象列表:一個(gè)列表是全部對(duì)象,另一個(gè)列表是可填充對(duì)象。我們?yōu)檫@兩個(gè)列表都使用了 ArrayList 類(lèi)。ArrayList 對(duì)象包含一組 Object 引用 -- 這樣一個(gè) ArrayList 可以包含系統(tǒng)中任何類(lèi)型的混合。
這實(shí)際上并沒(méi)有什么幫助 -- 我們希望 ArrayList 僅僅包括可繪制/可填充對(duì)象。為此,我們將 ArrayList 對(duì)象設(shè)為私有;然后將向列表添加對(duì)象的過(guò)程設(shè)為一個(gè)方法,該方法只接受一個(gè) DShape。
當(dāng)使用 Add 方法向列表中添加對(duì)象時(shí),我們將所有對(duì)象添加到 wholeList 中,然后檢查對(duì)象是否還應(yīng)添加到 filledList 集合中。
請(qǐng)記住,Add 方法(以及列表)具有類(lèi)型安全特性:它只接受 DShape(或者從 DShape 派生的類(lèi)型,例如我們?cè)谏厦鎰?chuàng)建的所有類(lèi)型)。您不能將整數(shù)或字符串添加到列表中,這樣我們便可以知道這個(gè)列表只包含可繪制對(duì)象。能夠確知這一點(diǎn)是很方便的!
繪制項(xiàng)
我們還有一個(gè) DrawList 方法,用于在它作為參數(shù)傳遞的 Graphics 對(duì)象上繪制列表中的對(duì)象。此方法具有兩種情況:如果列表為空,它繪制一個(gè)字符串,說(shuō)明列表為空。如果列表不為空,它使用一個(gè) for each 構(gòu)造函數(shù)遍歷該列表,并在每個(gè)對(duì)象上調(diào)用 Draw。實(shí)際的遍歷和繪圖代碼再簡(jiǎn)單不過(guò)了,如下面的 Visual Basic 所示。
Visual Basic
.NET Dim d As DShape
For Each d In wholeList
d.Draw(g)
Next
C# 代碼幾乎完全相同(當(dāng)然,其行數(shù)更少)。
C#
foreach (DShape d in wholeList)
d.Draw(g);
由于列表是封裝的,我們知道它具有類(lèi)型安全特性,因此可以?xún)H調(diào)用 Draw 方法而不必檢查對(duì)象的類(lèi)型。
返回可填充列表
最后,我們的 Change fills to hot pink(將填充色更改為粉紅)按鈕需要一個(gè)對(duì)所有可填充對(duì)象的引用數(shù)組,以便更改其 FillBrushColor 屬性。雖然可以編寫(xiě)一個(gè)方法遍歷列表并將顏色更改為傳入的值,但這一次 Dr. GUI 選擇了返回一個(gè)對(duì)象引用數(shù)組。幸運(yùn)的是,ArrayList 類(lèi)具有一個(gè) ToArray 方法,利用它可以創(chuàng)建一個(gè)傳遞數(shù)組。該方法獲取我們需要的數(shù)組元素類(lèi)型 -- 從而可以傳遞回所需的類(lèi)型 -- IFillable 數(shù)組。
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
在兩種語(yǔ)言中,我們都使用了一個(gè)內(nèi)置運(yùn)算符獲取給定類(lèi)型的 Type 對(duì)象 -- 在 C# 中,是 typeof(IFillable);在 Visual Basic 中,是 GetType(IFillable)。
調(diào)用程序使用此數(shù)組在可填充對(duì)象引用數(shù)組中遍歷。例如,將填充顏色更改為粉紅的 Visual Basic 代碼如下所示:
Dim filledList As IFillable() = drawingList.GetFilledList()
Dim i As IFillable
For Each i In filledList
i.FillBrushColor = Color.HotPink
Next
用于分解出公共代碼的 Helper 方法和類(lèi)
您可能注意到,Draw 和 Fill 方法有很多共同的代碼。確切地說(shuō),每個(gè)類(lèi)中創(chuàng)建筆或畫(huà)筆的代碼、建立 Try/Finally 塊的代碼以及清理筆或畫(huà)筆的代碼都是相同的 -- 唯一的區(qū)別是進(jìn)行繪圖或填充時(shí)調(diào)用的實(shí)際方法。(由于 C# 中 using 語(yǔ)法非常簡(jiǎn)潔,因而多余代碼的數(shù)量并不明顯。)在 Visual Basic .NET 中,每五行代碼中可能有一行特殊的代碼在所有實(shí)現(xiàn)中都是相同的。
總之,如果存在大量重復(fù)代碼,就需要尋求分解出公共的代碼,以便形成為所有類(lèi)所共享的公共子例程。這類(lèi)方法有很多,Dr. GUI 非常高興為您展示其中的兩種。第一種方法僅用于類(lèi),第二種方法可用于類(lèi)或接口,在本例中只用于接口。
方法 1:公共入口點(diǎn)調(diào)用虛擬方法
在第一個(gè)方法中,我們利用了類(lèi)(不同于接口)可以包含代碼這一事實(shí)。所以我們提供了一個(gè)用于創(chuàng)建筆的 Draw 方法的實(shí)現(xiàn),以及一個(gè)異常處理程序和 Dispose,然后調(diào)用實(shí)際進(jìn)行繪圖的 abstract/MustOverride 方法。確切地說(shuō),我們更改了 DShapes 類(lèi)以適應(yīng)新的 Draw 方法,然后聲明了新的 JustDraw 方法:
Public MustInherit Class DShape
' Draw 不是虛擬的,這似乎有些不尋!
' Draw 本應(yīng)是抽象的 (MustOverride)。
' 但此方法是繪圖的框架,而不是繪圖代碼本身,
' 繪圖代碼在 JustDraw 中完成。
' 還請(qǐng)注意,這意味著同原版本相比,這些類(lèi)具有
' 不同的接口,雖然它們完成的工作相同。
Public Sub Draw(ByVal g As Graphics)
Dim p = New Pen(penColor)
Try
JustDraw(g, p)
Finally
p.Dispose()
End Try
End Sub
' 這里是需要成為多態(tài)的部分 -- 因此是抽象的
Protected MustOverride Sub JustDraw(ByVal g As Graphics, _
ByVal p As Pen)
Protected bounding As Rectangle
Protected penColor As Color ' 還應(yīng)具有屬性
' 還應(yīng)具有移動(dòng)、調(diào)整大小等方法。
End Class
一個(gè)值得注意的有趣的地方:Draw 方法并不是 virtual/Overridable。因?yàn)樗信缮?lèi)都將以相同的方式完成這部分繪圖(如果在 Graphics 上繪圖 [如本例中的定義],則必須指派并清理筆),因此它不需要是 virtual/Overridable。
實(shí)際上,Dr. GUI 認(rèn)為在本例中,Draw 不應(yīng)該是 virtual/Overridable。如果確實(shí)要覆蓋 Draw 的行為(而不僅是 JustDraw 的行為),則可以將它設(shè)置為 virtual/Overridable。但在本例中,沒(méi)有理由覆蓋 Draw 的行為,如果鼓勵(lì)程序員進(jìn)行覆蓋還會(huì)帶來(lái)隱患 -- 他們可能不會(huì)正確處理筆,或者使用其他方法繪制對(duì)象而不是調(diào)用 JustDraw,這就違反了我們內(nèi)置到類(lèi)中的假設(shè)。因此,將 Draw 設(shè)置為非虛擬(順便說(shuō)一下,在 Brand J 中沒(méi)有這個(gè)選項(xiàng))可能會(huì)降低代碼的靈活性,但會(huì)更加可靠 -- Dr. GUI 認(rèn)為在本例中,這樣做非常值得。
JustDraw 的典型實(shí)現(xiàn)如下所示:
Protected Overrides Sub JustDraw(ByVal g As Graphics, ByVal p As Pen)
g.DrawEllipse(p, bounding)
End Sub
如您所見(jiàn),我們獲得了所希望的簡(jiǎn)潔的派生類(lèi)實(shí)現(xiàn)。(可填充類(lèi)中的實(shí)現(xiàn)只是略微復(fù)雜一些 -- 稍后會(huì)看到。)
請(qǐng)注意,我們?cè)诮涌谥刑砑恿艘粋(gè)額外的公開(kāi)方法 JustDraw,除了要繪制的 Graphics 對(duì)象外,該方法還引用我們?cè)?Draw 中創(chuàng)建的 Pen 對(duì)象。因?yàn)樵摲椒ㄐ枰?abstract/MustOverride,因此必須是公開(kāi)的。
這并不是一個(gè)大問(wèn)題,但它確實(shí)更改了類(lèi)的公開(kāi)接口。所以即使這個(gè)分解出公共代碼的方法非常簡(jiǎn)單方便,也應(yīng)當(dāng)盡可能選擇其他方法以避免更改公開(kāi)接口。
方法 2:虛擬方法調(diào)用公共 helper 方法,使用回調(diào)
在實(shí)現(xiàn)接口的 Fill 方法時(shí),代碼的復(fù)雜程度也很類(lèi)似:每六行代碼中可能有一行特殊的代碼在所有實(shí)現(xiàn)中都是相同的。但是我們不能將公共的實(shí)現(xiàn)放到接口中,因?yàn)榻涌谥皇锹暶,它們不包含代碼或數(shù)據(jù)。此外,上面列出的方法是不能接受的,因?yàn)樗鼤?huì)更改接口 -- 我們可能并不希望這樣,或者因?yàn)槭瞧渌藙?chuàng)建的接口,我們根本不可能更改!
所以,我們需要編寫(xiě)一個(gè) helper 方法以設(shè)置并回調(diào)我們的類(lèi),以便進(jìn)行實(shí)際的填充。對(duì)于本例,Dr. GUI 將代碼放在一個(gè)單獨(dú)的類(lèi)中,這樣任何類(lèi)都可以使用該代碼。(如果采用該方法來(lái)實(shí)現(xiàn) Draw,則可以將 helper 方法作為抽象基類(lèi)中的私有方法實(shí)現(xiàn)。)
暫時(shí)不進(jìn)一步展開(kāi),以下是我們創(chuàng)建的類(lèi):
' 請(qǐng)注意,該 delegate 提供的幫助仍然具有多態(tài)行為。
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 方法調(diào)用了 SafeFill,該方法接受一個(gè)可填充對(duì)象(請(qǐng)注意,這里我們使用了 IFillable 接口類(lèi)型,而不是 DShape,從而只能傳遞可填充對(duì)象)、一個(gè)要在其上進(jìn)行繪圖的 Graphics 和一個(gè)稱(chēng)為 delegate 的私有變量。我們可以將 delegate 視為一個(gè)對(duì)方法(而不是對(duì)象)的引用 -- 如果您經(jīng)常使用 C 或 C++ 編程,則可以將其視為具有類(lèi)型安全特性的函數(shù)指針?梢詫 delegate 設(shè)置為指向任何具有相同參數(shù)類(lèi)型和返回值的方法,無(wú)論是實(shí)例方法還是 static/Shared 方法。將 delegate 設(shè)置為指向相應(yīng)的方法后(例如在調(diào)用 SafeFill 時(shí)),我們可以通過(guò) delegate 間接調(diào)用該方法。(順便說(shuō)一下,Brand J 中沒(méi)有 delegate,這時(shí)如果使用此方法,會(huì)非常困難并且很不靈活)。
delegate 類(lèi)型 Filler 的聲明位于類(lèi)聲明之上 -- 它被聲明為一個(gè)不返回任何內(nèi)容(在 Visual Basic .NET 中是一個(gè) Sub)并且將 Graphics 和 Brush 作為參數(shù)傳遞的方法。我們會(huì)在將來(lái)的專(zhuān)欄中深入討論 delegate。
SafeFill 的操作非常簡(jiǎn)單:它指派畫(huà)筆并將 Try/Finally 和 Dispose 設(shè)置為公共代碼。它通過(guò)調(diào)用我們作為參數(shù)接收的 delegate 所引用的方法進(jìn)行各種操作:f(g, b)。
要使用這個(gè)類(lèi),需要向可填充對(duì)象類(lèi)中添加一個(gè)可以通過(guò) delegate 調(diào)用的方法,并確保將該方法的引用(地址)傳遞到 SafeFill,我們將在接口的 Fill 實(shí)現(xiàn)中調(diào)用 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
這樣,當(dāng)需要填充對(duì)象時(shí),便在該對(duì)象上調(diào)用 IFillable.Fill。它將調(diào)用我們的 Fill 方法,而 Fill 方法調(diào)用 FillHelper.SafeFill,后者傳遞一個(gè)對(duì)我們的可填充對(duì)象的引用、所傳遞的要在其上進(jìn)行繪圖的 Graphics 對(duì)象以及一個(gè)對(duì)實(shí)際完成填充的方法的引用 -- 在本例中,該方法是私有的 JustFill 方法。
然后,SafeFill 通過(guò) delegate -- JustFill 方法來(lái)設(shè)置畫(huà)筆和調(diào)用,JustFill 方法通過(guò)調(diào)用 Graphics.FillEllipse 進(jìn)行填充并返回值。SafeFill 將清理畫(huà)筆并返回到 Fill,F(xiàn)ill 再返回到調(diào)用者。
最后是 JustDraw,它和原始版本中的 Draw 很類(lèi)似,因?yàn)槲覀兌颊{(diào)用了 Fill,并調(diào)用了基類(lèi)的 Draw 方法(這是我們以前所做的)。以下是相關(guān)代碼:
Protected Overrides Sub JustDraw(ByVal g As Graphics, ByVal p As Pen)
Fill(g)
MyBase.JustDraw(g, p)
End Sub
請(qǐng)記住,指派畫(huà)筆和筆的復(fù)雜之處在于它在 helper 函數(shù)中的處理 -- 在 Draw 中,它位于基類(lèi)中;在 Fill 中,它位于 helper 類(lèi)中。
如果您認(rèn)為這比以前復(fù)雜了,那么確實(shí)如此。如果您認(rèn)為由于額外的調(diào)用和需要處理 delegate,速度比以前緩慢了,也確實(shí)如此。在生活中總是有很多東西需要進(jìn)行權(quán)衡。
那么,這樣做值得嗎?也許值得。這取決于公共代碼的復(fù)雜程度,以及該代碼需要重復(fù)的次數(shù)。也就是說(shuō),需要權(quán)衡。如果我們決定刪除 Try/Finally,而只在完成繪圖后清理筆和畫(huà)筆,代碼便會(huì)非常簡(jiǎn)單,這些方法也就用不上。并且在 C# 中,using 語(yǔ)句非常簡(jiǎn)潔,我們也不必費(fèi)神使用這些方法。Dr. GUI 認(rèn)為,在 Visual Basic 中使用 Try/Finally 時(shí),可以使用、也可以不使用這些方法,這里旨在向大家展示這些方法,以便在遇到具有大量公共代碼的情況時(shí)使用。