VBA遗传
我听说很多VBA缺乏继承性。我做了一些变通,现在在我看来,这正是继承的作用。我离pro=)还很远,可能遗漏了什么。所以我非常感谢你对可能的不利因素的想法 当我发现您仍然可以在接口类(而不仅仅是签名)中完全实现函数时,我感到非常惊讶,这让我想到了下面的内容。我看到一些人在构图的帮助下做了类似的事情,但他们在界面中只使用了一个签名 IBird级VBA遗传,vba,inheritance,Vba,Inheritance,我听说很多VBA缺乏继承性。我做了一些变通,现在在我看来,这正是继承的作用。我离pro=)还很远,可能遗漏了什么。所以我非常感谢你对可能的不利因素的想法 当我发现您仍然可以在接口类(而不仅仅是签名)中完全实现函数时,我感到非常惊讶,这让我想到了下面的内容。我看到一些人在构图的帮助下做了类似的事情,但他们在界面中只使用了一个签名 IBird级 Public Sub SayFromInterface() Debug.Print "Tweet from IBird" End Sub Publ
Public Sub SayFromInterface()
Debug.Print "Tweet from IBird"
End Sub
Public Sub SayFromInstance()
End Sub
乌鸦级
Implements IBird
Private pBird As IBird
Private Sub Class_Initialize()
Set pBird = New IBird
End Sub
'let you use already implemented code from "abstract class", to avoid
'duplicating your code, which is the main point of inheritance
'in my opinion
Public Sub IBird_SayFromInterface()
pBird.SayFromInterface
End Sub
'you can override the IBird function (common use)
Public Sub IBird_SayFromInstance()
Debug.Print "Tweet from Crow"
End Sub
测试模块
Sub testIBird()
Dim Bird As IBird
Set Bird = New Crow
Bird.SayFromInterface
Bird.SayFromInstance
Debug.Print TypeName(Bird)
Debug.Print TypeOf Bird Is IBird
End Sub
输出:
Tweet from IBird
Tweet from Crow
Crow
True
这是组合,而不是继承——是的,通过组合,您可以模拟继承。如果类实现了封装对象的接口,那么事情开始看起来像某种装饰器模式
除非在IBird
中没有任何实现代码。接口应该是纯粹抽象的。创建一个新的
接口实例,使类不再是接口:现在它只是另一个类,公开了一个默认接口,任何其他类都可以实现,而I
前缀变得相当混乱:
很奇怪的是,客户机代码现在需要考虑他们是想让那只鸟从实例发出啁啾声,还是从接口发出啁啾声,这些都是非常“元”的标识符,使得事情不能像继承那样工作
如果我们有一个Crow:Bird
,其中Bird
有一个IBird.Chirp的实现:
公共虚拟字符串Chirp()=>“Chirp!”;
…然后,Crow
有这样一个:
public覆盖字符串Chirp()=>“craaw!”;
然后调用哪个方法取决于运行时类型-这应该很明显:
IBird bird1=新鸟();
bird1.Chirp();//“叽叽喳喳!”
IBird bird2=新乌鸦();
bird2.Chirp();//“嘎嘎!”
但是,请想象一个接收IBird
参数的方法:
public void DoSomething(IBird bird)
{
Debug.Print(bird.Chirp());
}
如果bird
是bird
,它会打印“Chirp!”;如果bird
是一个Crow
,它将打印“craaw!”:运行的方法是最派生的重写,它不一定在最派生的类型上定义
继承将允许GiantCrow:Crow
从Crow
继承Chirp
方法,而不必重写它。这就是你不能用VBA类来模拟的:你不得不写一个等价的
public覆盖字符串Chirp()=>base.Chirp();
…这在技术上是多余的,如果您每次都要这样做只是为了让“基本”成员在默认界面上可见,那么它会变得非常重复
我们实际上不是继承基成员,而是包装对封装对象的调用。decorator模式正是这样做的,它提供了一种非侵入式的方式来扩展VBA类或接口
装饰器实现其扩展的接口,并封装该类型的私有实例。因此,基本上,对于装饰器,“crow继承层次结构”设置如下所示:
Dim bird As IBird
Set bird = Crow.Create(New BaseBird)
'@PredeclaredId
Implements IRepository
Private loggerInternal As ILogger
Private wrappedInternal As IRepository
Public Function Create(ByVal internal As IRepository, ByVal logger As ILogger) As IRepository
Dim result As LoggingRepository
Set result.Wrapped = internal
Set result.Log = logger
Set Create = result
End Function
Public Property Get Wrapped() As IRepository
Set Wrapped = wrappedInternal
End Property
Public Property Set Wrapped(ByVal value As IRepository)
If Not wrappedInternal Is Nothing Then Err.Raise 5, TypeName(Me), "Instance is already initialized."
Set wrappedInternal = value
End Property
Public Property Get Log() As ILogger
Set Log = loggerInternal
End Property
Public Property Set Log(ByVal value As ILogger)
If Not loggerInternal Is Nothing Then Err.Raise 5, TypeName(Me), "Instance is already initialized."
Set loggerInternal = value
End Property
Private Function IRepository_SelectAll() As Object
Log.Info "Starting IRepository.SelectAll"
Dim t As Double
t = Timer
Set IRepository_SelectAll = wrappedInternal.SelectAll
Log.Info "IRepository.SelectAll completed in " & Timer - t & " seconds."
End Function
Private Sub IRepository_Delete(ByVal id As Long)
Log.Info "Starting IRepository.Delete(" & id & ")"
Dim t As Double
t = Timer
wrappedInternal.Delete id
Log.Info "IRepository.Delete completed in " & Timer - t & " seconds."
End Sub
Private Sub IRepository_Save(ByVal entity As Object)
'...
wrappedInternal.Save entity
'...
End Sub
'...
更合适的装饰器模式示例可能是:
Dim repository As IRepository
Set repository = LoggingRepository.Create(BirdRepository.Create(connectionString), New DebugLogger)
其中,BirdRepository
负责抽象与一些Birds
表相关的数据库操作(BirdRepository
实现了IRepository
),而LoggingRepository
是一个也实现了IRepository
的装饰程序,但也包装了一个IRepository
实例(在本例中是一个BIRDepository
),以添加其自身的功能—可能如下所示:
Dim bird As IBird
Set bird = Crow.Create(New BaseBird)
'@PredeclaredId
Implements IRepository
Private loggerInternal As ILogger
Private wrappedInternal As IRepository
Public Function Create(ByVal internal As IRepository, ByVal logger As ILogger) As IRepository
Dim result As LoggingRepository
Set result.Wrapped = internal
Set result.Log = logger
Set Create = result
End Function
Public Property Get Wrapped() As IRepository
Set Wrapped = wrappedInternal
End Property
Public Property Set Wrapped(ByVal value As IRepository)
If Not wrappedInternal Is Nothing Then Err.Raise 5, TypeName(Me), "Instance is already initialized."
Set wrappedInternal = value
End Property
Public Property Get Log() As ILogger
Set Log = loggerInternal
End Property
Public Property Set Log(ByVal value As ILogger)
If Not loggerInternal Is Nothing Then Err.Raise 5, TypeName(Me), "Instance is already initialized."
Set loggerInternal = value
End Property
Private Function IRepository_SelectAll() As Object
Log.Info "Starting IRepository.SelectAll"
Dim t As Double
t = Timer
Set IRepository_SelectAll = wrappedInternal.SelectAll
Log.Info "IRepository.SelectAll completed in " & Timer - t & " seconds."
End Function
Private Sub IRepository_Delete(ByVal id As Long)
Log.Info "Starting IRepository.Delete(" & id & ")"
Dim t As Double
t = Timer
wrappedInternal.Delete id
Log.Info "IRepository.Delete completed in " & Timer - t & " seconds."
End Sub
Private Sub IRepository_Save(ByVal entity As Object)
'...
wrappedInternal.Save entity
'...
End Sub
'...
给定IRepository
对象的方法不能(也绝对不应该)知道它是否给定了一个普通的BirdRepository
,一个LoggingRepository
包装一个BirdRepository
,或者是一个FakeRepository
,它封装了一个集合
,而不是对数据库表的访问,而这种多态性就是整个要点
这是在不使用继承的情况下扩展类型的一种方法,VBA完全可以利用这种方法,而不会过分贬低模式。但这不是继承。为什么将声明为新IBird
只是为了立即用新Crow
擦除该引用?只需将声明为IBird
。我的问题是,播放后没有正确清理代码。谢谢你的评论。我很惊讶地看到VBA可以实现接口,但我想这是COM编程所期望的。我们现在非常熟悉C#和VB.NET的接口,以至于我们忘记了.NET Framework最初是COM编程的一个扩展,名为COM+。@ja72 VBA完全可以进行全面的OOP(,并为您提供单元测试(很快也会提供一个实际的模拟框架)以及其他现代IDE功能。免责声明:我编写了VBA+OOP战舰,并管理Rubberduck OSS项目。@ja72 Rubberduck是主机无关的(在VB6、Excel、Word等中工作)。有一个“导出活动项目”功能,可将整个项目导出到选定文件夹,是的,我们在导入对话框中启用了multiselect;-)