维护两个列表
因为我们要改变对象的填充颜色以实现 Change fill to hot pink 按钮因此维护了两个可绘制对象列表一个列表是全部对象另一个列表是可填充对象我们为这两个列表都使用了 ArrayList 类ArrayList 对象包含一组 Object 引用 这样一个 ArrayList 可以包含系统中任何类型的混合
这实际上并没有什么帮助 我们希望 ArrayList 仅仅包括可绘制/可填充对象为此我们将 ArrayList 对象设为私有然后将向列表添加对象的过程设为一个方法该方法只接受一个 DShape
当使用 Add 方法向列表中添加对象时我们将所有对象添加到 wholeList 中然后检查对象是否还应添加到 filledList 集合中
请记住Add 方法(以及列表)具有类型安全特性它只接受 DShape(或者从 DShape 派生的类型例如我们在上面创建的所有类型)您不能将整数或字符串添加到列表中这样我们便可以知道这个列表只包含可绘制对象能够确知这一点是很方便的!
绘制项
我们还有一个 DrawList 方法用于在它作为参数传递的 Graphics 对象上绘制列表中的对象此方法具有两种情况如果列表为空它绘制一个字符串说明列表为空如果列表不为空它使用一个 for each 构造函数遍历该列表并在每个对象上调用 Draw实际的遍历和绘图代码再简单不过了
foreach (DShape d in wholeList)
dDraw(g);
由于列表是封装的我们知道它具有类型安全特性因此可以仅调用 Draw 方法而不必检查对象的类型
最后我们的 Change fills to hot pink(将填充色更改为粉红)按钮需要一个对所有可填充对象的引用数组以便更改其 FillBrushColor 属性虽然可以编写一个方法遍历列表并将颜色更改为传入的值但这一次 Dr GUI 选择了返回一个对象引用数组幸运的是ArrayList 类具有一个 ToArray 方法利用它可以创建一个传递数组该方法获取我们需要的数组元素类型 从而可以传递回所需的类型 IFillable 数组
public IFillable[] GetFilledList() {
return (IFillable[])filledListToArray(typeof(IFillable));
}
在两种语言中我们都使用了一个内置运算符获取给定类型的 Type 对象 在 C# 中是 typeof(IFillable)在 Visual Basic 中是 GetType(IFillable)
调用程序使用此数组在可填充对象引用数组中遍历例如将填充颜色更改为粉红的 Visual Basic 代码如下所示
Dim filledList As IFillable() = drawingListGetFilledList()
Dim i As IFillable
For Each i In filledList
iFillBrushColor = ColorHotPink
Next
用于分解出公共代码的 Helper 方法和类
您可能注意到Draw 和 Fill 方法有很多共同的代码确切地说每个类中创建笔或画笔的代码建立 Try/Finally 块的代码以及清理笔或画笔的代码都是相同的 唯一的区别是进行绘图或填充时调用的实际方法(由于 C# 中 using 语法非常简洁因而多余代码的数量并不明显)在 Visual Basic NET 中每五行代码中可能有一行特殊的代码在所有实现中都是相同的
总之如果存在大量重复代码就需要寻求分解出公共的代码以便形成为所有类所共享的公共子例程这类方法有很多Dr GUI 非常高兴为您展示其中的两种第一种方法仅用于类第二种方法可用于类或接口在本例中只用于接口
方法 公共入口点调用虚拟方法
在第一个方法中我们利用了类(不同于接口)可以包含代码这一事实所以我们提供了一个用于创建笔的 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
pDispose()
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)
gDrawEllipse(p bounding)
End Sub
如您所见我们获得了所希望的简洁的派生类实现(可填充类中的实现只是略微复杂一些 稍后会看到)
请注意我们在接口中添加了一个额外的公开方法 JustDraw除了要绘制的 Graphics 对象外该方法还引用我们在 Draw 中创建的 Pen 对象因为该方法需要是 abstract/MustOverride因此必须是公开的
这并不是一个大问题但它确实更改了类的公开接口所以即使这个分解出公共代码的方法非常简单方便也应当尽可能选择其他方法以避免更改公开接口
方法 虚拟方法调用公共 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(iFillBrushColor)
Try
f(g b)
Finally
bdispose()
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 IFillableFill
FillHelperSafeFill(Me g AddressOf JustFill)
End Sub
Private Sub JustFill(ByVal g As Graphics ByVal b As Brush)
gFillEllipse(b bounding)
End Sub
这样当需要填充对象时便在该对象上调用 IFillableFill它将调用我们的 Fill 方法而 Fill 方法调用 FillHelperSafeFill后者传递一个对我们的可填充对象的引用所传递的要在其上进行绘图的 Graphics 对象以及一个对实际完成填充的方法的引用 在本例中该方法是私有的 JustFill 方法
然后SafeFill 通过 delegate JustFill 方法来设置画笔和调用JustFill 方法通过调用 GraphicsFillEllipse 进行填充并返回值SafeFill 将清理画笔并返回到 FillFill 再返回到调用者
最后是 JustDraw它和原始版本中的 Draw 很类似因为我们都调用了 Fill并调用了基类的 Draw 方法(这是我们以前所做的)以下是相关代码
Protected Overrides Sub JustDraw(ByVal g As Graphics ByVal p As Pen)
Fill(g)
MyBaseJustDraw(g p)
End Sub
请记住指派画笔和笔的复杂之处在于它在 helper 函数中的处理 在 Draw 中它位于基类中在 Fill 中它位于 helper 类中
如果您认为这比以前复杂了那么确实如此如果您认为由于额外的调用和需要处理 delegate速度比以前缓慢了也确实如此在生活中总是有很多东西需要进行权衡
那么这样做值得吗?也许值得这取决于公共代码的复杂程度以及该代码需要重复的次数也就是说需要权衡如果我们决定删除 Try/Finally而只在完成绘图后清理笔和画笔代码便会非常简单这些方法也就用不上并且在 C# 中using 语句非常简洁我们也不必费神使用这些方法Dr GUI 认为在 Visual Basic 中使用 Try/Finally 时可以使用也可以不使用这些方法这里旨在向大家展示这些方法以便在遇到具有大量公共代码的情况时使用
维护两个列表
因为我们要改变对象的填充颜色以实现 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 所示
NET Dim d As DShape
For Each d In wholeList
dDraw(g)
Next
由于列表是封装的我们知道它具有类型安全特性因此可以仅调用 Draw 方法而不必检查对象的类型
返回可填充列表
最后我们的 Change fills to hot pink(将填充色更改为粉红)按钮需要一个对所有可填充对象的引用数组以便更改其 FillBrushColor 属性虽然可以编写一个方法遍历列表并将颜色更改为传入的值但这一次 Dr GUI 选择了返回一个对象引用数组幸运的是ArrayList 类具有一个 ToArray 方法利用它可以创建一个传递数组该方法获取我们需要的数组元素类型 从而可以传递回所需的类型 IFillable 数组
NET Public Function GetFilledList() As IFillable()
Return filledListToArray(GetType(IFillable))
End Function