如何基于多态清晰地处理不同的行为

Pat*_*ski 5 .net vb.net oop polymorphism

假设我有一个接口IFoo与实现类VideoFoo,AudioFooTextFoo.进一步假设我无法修改任何代码.假设我想编写一个基于运行时类型的函数IFoo,例如

Public Class Bar
    Public Shared Sub Fix(ByVal Foo as IFoo)
        If TypeOf Foo Is VideoFoo Then DoBar1()
        If TypeOf Foo Is AudioFoo Then DoBar2()
        If TypeOf Foo Is TextFoo Then DoBar3()

    End Sub
End Class
Run Code Online (Sandbox Code Playgroud)

我想重构这个以使用重载方法:

Sub DoBar(ByVal foo as VideoFoo)
Sub DoBar(ByVal foo as AudioFoo)
Sub DoBar(ByVal foo as TextFoo)
Run Code Online (Sandbox Code Playgroud)

但我认为做这样的事情的唯一方法就是写作

Sub DoBar(ByVal foo as IFoo)
Run Code Online (Sandbox Code Playgroud)

然后我必须再做一次"If TypeOf ...... Is".我怎样才能重构这个以利用实现的多态性IFoo而无需手动检查类型?

(在VB.NET中,虽然我的问题也适用于C#)

LBu*_*kin 2

那么,一种选择是简单地重载该Fix()方法,以便为每种实现IFoo. 但我怀疑您想直接接受接口,而不是它的实现类型。

您实际上正在寻找的是multiple split通常,C#/VB 使用参数的类型在编译时执行重载解析,并根据调用该方法的实例的运行时类型动态调度调用。您想要的是在运行时根据参数的运行时类型执行重载解析- VB.NET 或 C# 都不直接支持此功能。

过去,我通常使用索引为的委托字典来解决此类问题System.Type

private readonly Dictionary<Type,Action<IFoo>> _dispatchDictionary;

static Bar()
{
    _dispatchDictionary.Add( typeof(TextFoo),  DoBarTextFoo );
    _dispatchDictionary.Add( typeof(AudioFoo), DoBarAudioFoo );
    _dispatchDictionary.Add( typeof(VideoFoo), DoBarVideoFoo );        
}

public void Fix( IFoo foo )
{
   Action<IFoo> barAction;
   if( _dispatchDictionary.TryGetValue( foo.GetType(), out barAction ) )
   {
      barAction( foo );
   }
   throw new NotSupportedException("No Bar exists for type" + foo.GetType());
}

private void DoBarTextFoo( IFoo foo ) { TextFoo textFoo = (TextFoo)foo; ... }
private void DoBarAudioFoo( IFoo foo ) { AudioFoo textFoo = (AudioFoo)foo; ... }
private void DoBarVideoFoo( IFoo foo ) { VideoFoo textFoo = (VideoFoo)foo; ... }
Run Code Online (Sandbox Code Playgroud)

然而,从 C# 4 开始,我们现在可以使用dynamicC# 中的关键字 do 做同样的事情(VB.NET 目前还没有这个功能):

public void Fix( IFoo foo )
{
    dynamic dynFoo = foo;
    dynamic thisBar = this;

    thisBar.DoBar( dynFoo ); // performs runtime resolution, may throw
}

private void Dobar( TextFoo foo ) { ... /* no casts needed here */ }
private void Dobar( AudioFoo foo ) { ... }
private void Dobar( VideoFoo foo ) { ... }
Run Code Online (Sandbox Code Playgroud)

请注意,以这种方式使用dynamic关键字是有代价的 - 它要求在运行时处理调用站点。它本质上是在运行时启动 C# 编译器的一个版本,处理编译器捕获的元数据,执行类型的运行时分析,并吐出 C# 代码。幸运的是,DLR 通常可以在首次使用后有效地缓存此类调用站点。

一般来说,我发现这两种模式都很令人困惑,并且在大多数情况下都太过分了。如果子类型的数量很少并且提前都知道,那么一个简单的if/else块可能会更简单、更清晰。