Как правильно бороться с различным поведением, основанным на полиморфизме - PullRequest
5 голосов
/ 24 марта 2011

Предположим, у меня есть интерфейс IFoo с классами реализации VideoFoo, AudioFoo и TextFoo.Предположим далее, что я не могу изменить ни один из этого кода.Предположим, что затем я хотел бы написать функцию, которая действует по-разному в зависимости от типа среды выполнения 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

. Я хотел бы реорганизовать это для использования перегруженных методов:*

Но единственный способ сделать что-то подобное - написать

Sub DoBar(ByVal foo as IFoo)

Затем я должен снова выполнить «If TypeOf ... Is».Как я могу реорганизовать это, чтобы воспользоваться полиморфизмом реализаций IFoo без ручной проверки типов?

(в VB.NET, хотя мой вопрос относится и к C #)

Ответы [ 3 ]

3 голосов
/ 24 марта 2011

То, о чем вы спрашиваете, это Multiple Dispatch , или языковая функция, которая позволяет разрешать перегрузки метода во время выполнения вместо компиляции.

К сожалению, C # и VB.NET оба являются языками с одной диспетчеризацией, что означает, что перегрузка метода выбирается во время компиляции. Это означает, что перегрузка для объекта IFoo всегда будет выбираться для IFoo, независимо от его типа реализации.

Однако есть способы обойти это. Одним из способов является использование шаблона проектирования Visitor для реализации двойной диспетчеризации, которая будет работать. В C # вы также можете использовать новое ключевое слово dynamic, чтобы заставить среду выполнения разрешать перегрузку во время выполнения. Я написал запись в блоге о том, как выполнять обработку столкновений, используя эту технику, но она, безусловно, применима и к тому, что вы делаете.

Я не очень знаком с VB.NET, но я считаю, что язык демонстрирует некоторые динамические поведения по умолчанию, если объекты приводятся к Object. Кто-нибудь, пожалуйста, поправьте меня, если это не так.

2 голосов
/ 24 марта 2011

Ну, один из вариантов - просто перегрузить метод Fix(), чтобы у вас была одна перегрузка для каждого типа, реализующего IFoo.Но я подозреваю, что вы хотите принять интерфейс напрямую, а не реализовывать типы.

На самом деле вы ищете многократная отправка . Обычно 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; ... }

Однако, как и в C #4, теперь мы можем использовать ключевое слово dynamic в C #, по сути, делаем то же самое (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 ) { ... }

Обратите внимание, что с помощью ключевого слова dynamicэтот способ имеет цену - он требует, чтобы сайт вызова обрабатывался во время выполнения.Он по существу раскручивает версию компилятора C # во время выполнения, обрабатывает метаданные, захваченные компилятором, выполняет анализ типов во время выполнения и выплевывает код C #.К счастью, DLR может эффективно кэшировать такие сайты вызовов после их первого использования.

Как общее правило, я нахожу оба этих шаблона запутанными, а в большинстве ситуаций избыточными. Если количество подтипов невелико и все они известны заранее, простой блок if/else может быть намного проще и понятнее.

0 голосов
/ 25 марта 2011

Если вы не можете изменить ни интерфейс, ни какой-либо из классов, то понятно, что ни один ранее написанный код не может воспользоваться этой новой функцией Fix, которую вы хотите добавить.

Я не знаю VB.net, но я не могу не задаться вопросом, почему вы просто не подклассифицируете каждый из текущих классов (и интерфейса) и не добавили новый метод Fix подклассы. Весь ваш новый код, который хочет отправить сообщение Fix, должен принимать IFixFoo вместо IFoo.

Если вы хотите вызвать Fix для объектов IFoo, которые вы не создавали, вам нужен метод, который может создать правильный IFixFoo. Используя вышесказанное, у вас есть только одно место, где вы должны выполнить If TypeOf ... Is (когда вы на самом деле конвертируете IFoo в IFixFoo.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...